Skip to content

Commit

Permalink
Merge pull request #12 from bmcfee/tf2
Browse files Browse the repository at this point in the history
updated for keras 2.2+, tf2
  • Loading branch information
bmcfee authored Apr 21, 2022
2 parents 4c3e172 + a8fc7d2 commit 1abbee9
Show file tree
Hide file tree
Showing 19 changed files with 497 additions and 82 deletions.
12 changes: 12 additions & 0 deletions .github/environment-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: test
channels:
- conda-forge
- defaults
dependencies:
# required
- keras>=2.2
- tensorflow>=2.0
- numpy >= 1.17
- pytest-cov
- pytest-faulthandler
- pytest
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI

on:
push:
branches:
- main
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: True

jobs:
test:
name: "Python ${{ matrix.python-version }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
python-version: "3.7"
channel-priority: "strict"
envfile: ".github/environment-ci.yml"

- os: ubuntu-latest
python-version: "3.8"
channel-priority: "strict"
envfile: ".github/environment-ci.yml"

- os: ubuntu-latest
python-version: "3.9"
channel-priority: "strict"
envfile: ".github/environment-ci.yml"

steps:
- uses: actions/checkout@v2
- name: Cache conda
uses: actions/cache@v2
env:
# Increase this value to reset cache if etc/example-environment.yml has not changed
CACHE_NUMBER: 1
with:
path: ~/conda_pkgs_dir
key: ${{ runner.os }}-${{ matrix.python-version }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles( matrix.envfile ) }}

- name: Install Conda environment
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
python-version: ${{ matrix.python-version }}
add-pip-as-python-dependency: true
auto-activate-base: false
activate-environment: test
channel-priority: ${{ matrix.channel-priority }}
environment-file: ${{ matrix.envfile }}
use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly!

- name: Conda info
shell: bash -l {0}
run: |
conda info -a
conda list
- name: Install autopool
shell: bash -l {0}
run: python -m pip install --upgrade-strategy only-if-needed -e .[tests]

- name: Run pytest
shell: bash -l {0}
run: pytest -v

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
directory: ./coverage/reports/
flags: unittests
env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: true
verbose: true
37 changes: 37 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI

on:
release:
types: [created]

jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@master
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7

- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Publish distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_API_TOKEN }}
File renamed without changes.
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Adaptive pooling operators for Multiple Instance Learning ([documentation](http:

[![PyPI](https://img.shields.io/pypi/v/autopool.svg)](https://pypi.python.org/pypi/autopool)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT)
[![Documentation Status](https://readthedocs.org/projects/autopool/badge/?version=latest)](http://scaper.readthedocs.io/en/latest/?badge=latest)
[![Documentation Status](https://readthedocs.org/projects/autopool/badge/?version=latest)](http://autopool.readthedocs.io/en/latest/?badge=latest)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/autopool.svg)]()


Expand All @@ -19,8 +19,13 @@ IEEE Transactions on Audio, Speech and Language Processing, In press, 2018.
Installation
------------

To install the keras-based implementation:
```
pip install autopool
python -m pip install autopool[keras]
```
For the tensorflow implementation:
```
python -m pip install autopool[tf]
```

Definition
Expand All @@ -37,7 +42,8 @@ Usage
AutoPool is implemented as a [keras](https://keras.io/) layer, so using it is as straightforward as using any standard Keras pooling layer, for example:

```
bag_pred = AutoPool(axis=1)(instance_pred)
from autpool.keras import AutoPool1D
bag_pred = AutoPool1D(axis=1)(instance_pred)
```

Further details and examples are provided in the [documentation](http://autopool.readthedocs.io/).
Expand All @@ -48,23 +54,29 @@ Constrained and Regularized AutoPool
In the [paper](http://www.justinsalamon.com/uploads/4/3/9/4/4394963/mcfee_autopool_taslp_2018.pdf) we show there can be benefits to either constraining the range α can take, or, alternatively, applying l2 regularization on α; this results in constrained AutoPool (CAP) and regularized AutoPool (RAP) respectively. Since AutoPool is implemented as a [keras](https://keras.io/) layer, CAP and RAP can be can be achieved through the layer's optional arugments:

CAP with non-negative α:
```
bag_pred = AutoPool(axis=1, kernel_constraint=keras.constraints.non_neg())(instance_pred)
```python
bag_pred = AutoPool1D(axis=1, kernel_constraint=keras.constraints.non_neg())(instance_pred)
```

CAP with α norm-constrained to some value `alpha_max`:
```
bag_pred = AutoPool(axis=1, kernel_constraint=keras.constraints.max_norm(alpha_max, axis=0))(instance_pred)
```python
bag_pred = AutoPool1D(axis=1, kernel_constraint=keras.constraints.max_norm(alpha_max, axis=0))(instance_pred)
```
Heuristics for determining sensible values of `alpha_max` are given in the paper (section III.E).

RAP with l2 regularized α:
```
bag_pred = AutoPool(axis=1, kernel_regularizer=keras.regularizers.l2(l=1e-4))(instance_pred)
```python
bag_pred = AutoPool1D(axis=1, kernel_regularizer=keras.regularizers.l2(l=1e-4))(instance_pred)
```

CAP and RAP can be combined, of course, by applying both a kernel constraint and a kernel regularizer.

If using the tensorflow-based implementation, all of the above will also work, except that `keras` should be replaced by `tensorflow.keras` (or `tf.keras`), e.g.:
```python
import tensorflow as tf
from autopool.tf import AutoPool1D
bag_pred = AutoPool1D(axis=1, kernel_regularizer=tf.keras.regularizers.l2(l=1e-4))(instance_pred)
```

Multi-label
-----------
Expand Down
1 change: 0 additions & 1 deletion autopool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
'''AutoPool: adaptive pooling operators for multiple instance learning'''

from .version import version as __version__
from .autopool import *
13 changes: 4 additions & 9 deletions autopool/autopool.py → autopool/keras.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
'''Autopool: Adaptive pooling operators for multiple instance learning'''

from keras import backend as K
from keras.engine.topology import Layer, InputSpec
from keras.layers import Layer, InputSpec
from keras import initializers
from keras import constraints
from keras import regularizers


class AutoPool1D(Layer):
'''Automatically tuned soft-max pooling.
'''Automatically tuned soft-max pooling. (Keras implementation)
This layer automatically adapts the pooling behavior to interpolate
between mean- and max-pooling for each dimension.
Expand Down Expand Up @@ -76,10 +76,7 @@ def get_config(self):
return dict(list(base_config.items()) + list(config.items()))

def call(self, x, mask=None):
scaled = self.kernel * x
max_val = K.max(scaled, axis=self.axis, keepdims=True)
softmax = K.exp(scaled - max_val)
weights = softmax / K.sum(softmax, axis=self.axis, keepdims=True)
weights = K.softmax(self.kernel * x, axis=self.axis)
return K.sum(x * weights, axis=self.axis, keepdims=False)


Expand Down Expand Up @@ -111,9 +108,7 @@ def compute_output_shape(self, input_shape):
return self.get_output_shape_for(input_shape)

def call(self, x, mask=None):
max_val = K.max(x, axis=self.axis, keepdims=True)
softmax = K.exp((x - max_val))
weights = softmax / K.sum(softmax, axis=self.axis, keepdims=True)
weights = K.softmax(x, axis=self.axis)
return K.sum(x * weights, axis=self.axis, keepdims=False)

def get_config(self):
Expand Down
117 changes: 117 additions & 0 deletions autopool/tf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env python
'''Autopool: Adaptive pooling operators for multiple instance learning'''

from tensorflow.keras import backend as K
from tensorflow.keras.layers import Layer, InputSpec
from tensorflow.keras import initializers
from tensorflow.keras import constraints
from tensorflow.keras import regularizers


class AutoPool1D(Layer):
'''Automatically tuned soft-max pooling. (tensorflow.keras implementation)
This layer automatically adapts the pooling behavior to interpolate
between mean- and max-pooling for each dimension.
'''
def __init__(self, axis=0,
kernel_initializer='zeros',
kernel_constraint=None,
kernel_regularizer=None,
**kwargs):
'''
Parameters
----------
axis : int
Axis along which to perform the pooling. By default 0 (should be time).
kernel_initializer: Initializer for the weights matrix
kernel_regularizer: Regularizer function applied to the weights matrix
kernel_constraint: Constraint function applied to the weights matrix
kwargs
'''

if 'input_shape' not in kwargs and 'input_dim' in kwargs:
kwargs['input_shape'] = (kwargs.pop('input_dim'), )

super(AutoPool1D, self).__init__(**kwargs)

self.axis = axis
self.kernel_initializer = initializers.get(kernel_initializer)
self.kernel_constraint = constraints.get(kernel_constraint)
self.kernel_regularizer = regularizers.get(kernel_regularizer)
self.input_spec = InputSpec(min_ndim=3)
self.supports_masking = True

def build(self, input_shape):
assert len(input_shape) >= 3
input_dim = input_shape[-1]

self.kernel = self.add_weight(shape=(1, input_dim),
initializer=self.kernel_initializer,
name='kernel',
regularizer=self.kernel_regularizer,
constraint=self.kernel_constraint)
self.input_spec = InputSpec(min_ndim=2, axes={-1: input_dim})
self.built = True

def compute_output_shape(self, input_shape):
return self.get_output_shape_for(input_shape)

def get_output_shape_for(self, input_shape):
shape = list(input_shape)
del shape[self.axis]
return tuple(shape)

def get_config(self):
config = {'kernel_initializer': initializers.serialize(self.kernel_initializer),
'kernel_constraint': constraints.serialize(self.kernel_constraint),
'kernel_regularizer': regularizers.serialize(self.kernel_regularizer),
'axis': self.axis}

base_config = super(AutoPool1D, self).get_config()
return dict(list(base_config.items()) + list(config.items()))

def call(self, x, mask=None):
weights = K.softmax(self.kernel * x, axis=self.axis)
return K.sum(x * weights, axis=self.axis, keepdims=False)


class SoftMaxPool1D(Layer):
'''
Tensorflow-keras softmax pooling layer.
'''

def __init__(self, axis=0, **kwargs):
'''
Parameters
----------
axis : int
Axis along which to perform the pooling. By default 0
(should be time).
kwargs
'''
super(SoftMaxPool1D, self).__init__(**kwargs)

self.axis = axis

def get_output_shape_for(self, input_shape):
shape = list(input_shape)
del shape[self.axis]
return tuple(shape)

def compute_output_shape(self, input_shape):
return self.get_output_shape_for(input_shape)

def call(self, x, mask=None):
weights = K.softmax(x, axis=self.axis)
return K.sum(x * weights, axis=self.axis, keepdims=False)

def get_config(self):
config = {'axis': self.axis}
base_config = super(SoftMaxPool1D, self).get_config()
return dict(list(base_config.items()) + list(config.items()))
4 changes: 2 additions & 2 deletions autopool/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# -*- coding: utf-8 -*-
"""Version info"""

short_version = '0.1.0'
version = '0.1.0'
short_version = '0.2.0'
version = '0.2.0'
Loading

0 comments on commit 1abbee9

Please sign in to comment.