From 794758bfe420c981c0b4512d668d33570943e905 Mon Sep 17 00:00:00 2001 From: keisen Date: Thu, 3 Jun 2021 19:58:37 +0900 Subject: [PATCH 1/3] Deprecate and rename Rotate to Rotate2D, and some improvements. Fixes #58 --- .../activation_maximization_test.py | 70 ++++++++++++++++--- tests/tf-keras-vis/saliency_test.py | 4 +- .../activation_maximization/__init__.py | 7 +- tf_keras_vis/utils/input_modifiers.py | 25 +++++-- tf_keras_vis/utils/test.py | 4 +- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/tests/tf-keras-vis/activation_maximization/activation_maximization_test.py b/tests/tf-keras-vis/activation_maximization/activation_maximization_test.py index d3e07b2..7f98b9f 100644 --- a/tests/tf-keras-vis/activation_maximization/activation_maximization_test.py +++ b/tests/tf-keras-vis/activation_maximization/activation_maximization_test.py @@ -4,12 +4,11 @@ from tensorflow.keras.models import load_model from tf_keras_vis.activation_maximization import ActivationMaximization -from tf_keras_vis.utils.input_modifiers import Jitter, Rotate -from tf_keras_vis.utils.test import (MockCallback, MockListOfScore, MockScore, - MockTupleOfScore, does_not_raise, - dummy_sample, mock_conv_model, - mock_conv_model_with_flot32_output, - mock_multiple_io_model) +from tf_keras_vis.utils.input_modifiers import Jitter, Rotate2D +from tf_keras_vis.utils.regularizers import Norm, TotalVariation2D +from tf_keras_vis.utils.test import (MockCallback, MockListOfScore, MockScore, MockTupleOfScore, + does_not_raise, dummy_sample, mock_conv_model, + mock_conv_model_with_flot32_output, mock_multiple_io_model) if version(tf.version.VERSION) >= version("2.4.0"): from tensorflow.keras.mixed_precision import set_global_policy @@ -166,8 +165,9 @@ def test__call__if_seed_input_is_(self, seed_inputs, expectation, multiple_io_mo def test__call__with_inputs_modifiers(self, multiple_io_model): activation_maximization = ActivationMaximization(multiple_io_model) result = activation_maximization( - MockScore(), steps=3, input_modifiers={'input-1': [Jitter(jitter=8), - Rotate(degree=3)]}) + MockScore(), + steps=3, + input_modifiers={'input-1': [Jitter(jitter=8), Rotate2D(degree=3)]}) assert result[0].shape == (1, 8, 8, 3) assert result[1].shape == (1, 10, 10, 3) @@ -227,3 +227,57 @@ def test__call__when_reuse_optimizer(self): with pytest.raises(ValueError): result = activation_maximization(MockScore(), steps=3, optimizer=optimizer) assert result.shape == (1, 8, 8, 3) + + +class TestActivationMaximizationWithDenseModel(): + @pytest.mark.parametrize("scores,expectation", [ + (None, pytest.raises(ValueError)), + (MockScore(), does_not_raise()), + (MockTupleOfScore(), does_not_raise()), + (MockListOfScore(), does_not_raise()), + ([MockScore()], does_not_raise()), + ]) + def test__call__if_score_is_(self, scores, expectation, dense_model): + activation_maximization = ActivationMaximization(dense_model) + with expectation: + result = activation_maximization(scores, + input_modifiers=[], + regularizers=[Norm(10.)], + steps=3) + assert result.shape == (1, 8) + + @pytest.mark.parametrize("seed_input,expected", [ + ([dummy_sample((8, ))], [(1, 8)]), + (dummy_sample((1, 8)), (1, 8)), + ([dummy_sample((1, 8))], [(1, 8)]), + ]) + def test__call__if_seed_input_is_(self, seed_input, expected, dense_model): + activation_maximization = ActivationMaximization(dense_model) + result = activation_maximization(MockScore(), + seed_input=seed_input, + input_modifiers=[], + regularizers=[Norm(10.)], + steps=3) + if type(expected) is list: + assert type(result) == list + result = result[0] + expected = expected[0] + assert result.shape == expected + + @pytest.mark.parametrize("input_modifiers,regularizers,expectation", [ + ([Jitter(), Rotate2D()], [TotalVariation2D(), Norm()], pytest.raises(ValueError)), + ([Jitter()], [], pytest.raises(ValueError)), + ([Rotate2D()], [], pytest.raises(ValueError)), + ([], [TotalVariation2D()], pytest.raises(ValueError)), + ([], [Norm()], does_not_raise()), + ([], [], does_not_raise()), + ]) + def test__call__if_input_modifiers_or_regurarizers_are(self, input_modifiers, regularizers, + expectation, dense_model): + activation_maximization = ActivationMaximization(dense_model) + with expectation: + result = activation_maximization(MockScore(), + input_modifiers=input_modifiers, + regularizers=regularizers, + steps=3) + assert result.shape == (1, 8) diff --git a/tests/tf-keras-vis/saliency_test.py b/tests/tf-keras-vis/saliency_test.py index 69c43f9..ca3f3a0 100644 --- a/tests/tf-keras-vis/saliency_test.py +++ b/tests/tf-keras-vis/saliency_test.py @@ -65,8 +65,8 @@ def test__call__if_smoothing_is_active(self, smooth_samples, conv_model): def test__call__if_model_has_only_dense_layers(self, dense_model): saliency = Saliency(dense_model) - result = saliency(MockScore(), dummy_sample((3, )), keepdims=True) - assert result.shape == (1, 3) + result = saliency(MockScore(), dummy_sample((8, )), keepdims=True) + assert result.shape == (1, 8) @pytest.mark.parametrize("score_class,modefier_enabled,clone_enabled," "batch_size,expectation", [ diff --git a/tf_keras_vis/activation_maximization/__init__.py b/tf_keras_vis/activation_maximization/__init__.py index 613dcb9..136e0f4 100644 --- a/tf_keras_vis/activation_maximization/__init__.py +++ b/tf_keras_vis/activation_maximization/__init__.py @@ -7,8 +7,9 @@ from packaging.version import parse as version from tf_keras_vis import ModelVisualization -from tf_keras_vis.utils import (check_steps, is_mixed_precision, listify, lower_precision_dtype) -from tf_keras_vis.utils.input_modifiers import Jitter, Rotate +from tf_keras_vis.utils import (check_steps, is_mixed_precision, listify, + lower_precision_dtype) +from tf_keras_vis.utils.input_modifiers import Jitter, Rotate2D from tf_keras_vis.utils.regularizers import Norm, TotalVariation2D if version(tf.version.VERSION) >= version("2.4.0"): @@ -21,7 +22,7 @@ def __call__( score, seed_input=None, input_range=(0, 255), - input_modifiers=[Jitter(jitter=8), Rotate(degree=3)], + input_modifiers=[Jitter(jitter=8), Rotate2D(degree=3)], regularizers=[TotalVariation2D(weight=1.), Norm(weight=1., p=2)], steps=200, diff --git a/tf_keras_vis/utils/input_modifiers.py b/tf_keras_vis/utils/input_modifiers.py index 5cc0941..22e719d 100644 --- a/tf_keras_vis/utils/input_modifiers.py +++ b/tf_keras_vis/utils/input_modifiers.py @@ -2,6 +2,7 @@ import numpy as np import tensorflow as tf +from deprecated import deprecated from scipy.ndimage import rotate @@ -32,32 +33,46 @@ def __init__(self, jitter=8): def __call__(self, seed_input): ndim = len(seed_input.shape) + if ndim < 3: + raise ValueError("The dimensions of seed_input must be 3 or more " + f"(batch_size, ..., channels), but was {ndim}") seed_input = tf.roll(seed_input, shift=tuple(np.random.randint(-self.jitter, self.jitter, ndim - 2)), axis=tuple(range(ndim)[1:-1])) return seed_input -class Rotate(InputModifier): - def __init__(self, degree=3.): +class Rotate2D(InputModifier): + def __init__(self, degree=3.0): """Implements an input modifier that introduces random rotation. Rotate has been shown to produce crisper activation maximization images. # Arguments: degree: Integer or float. The amount of rotation to apply. """ - self.rg = float(degree) + self.degree = float(degree) def __call__(self, seed_input): + ndim = len(seed_input.shape) + if ndim != 4: + raise ValueError("seed_input shape must be (batch_size, height, width, channels)," + f" but was {seed_input.shape}") if tf.is_tensor(seed_input): seed_input = seed_input.numpy() if seed_input.dtype == np.float16: seed_input = seed_input.astype(np.float32) seed_input = rotate(seed_input, - np.random.uniform(-self.rg, self.rg), + np.random.uniform(-self.degree, self.degree), axes=tuple(range(len(seed_input.shape))[1:-1]), reshape=False, mode='nearest', order=1, prefilter=True) - return tf.constant(seed_input) + seed_input = tf.constant(seed_input) + return seed_input + + +@deprecated(version='0.6.2', reason="Please use Rotate2D class instead of Rotate class.") +class Rotate(Rotate2D): + def __init__(self, degree=3.0): + super().__init__(degree=3.0) # pragma: no cover diff --git a/tf_keras_vis/utils/test.py b/tf_keras_vis/utils/test.py index 882402e..b9d858f 100644 --- a/tf_keras_vis/utils/test.py +++ b/tf_keras_vis/utils/test.py @@ -14,8 +14,8 @@ def mock_dense_model(): - inputs = Input((3, ), name='input-1') - x = Dense(5, activation='relu', name='dense-1')(inputs) + inputs = Input((8, ), name='input-1') + x = Dense(6, activation='relu', name='dense-1')(inputs) x = Dense(2, activation='softmax', name='dense-2')(x) return Model(inputs=inputs, outputs=x) From 0a31cc3b600b5e7758041a15e71aa7df7b7d49d5 Mon Sep 17 00:00:00 2001 From: keisen Date: Thu, 3 Jun 2021 19:58:58 +0900 Subject: [PATCH 2/3] Update setup.py --- setup.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f6d782b..6feac97 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="tf-keras-vis", - version="0.6.1", + version="0.6.2", author="keisen", author_email="k.keisen@gmail.com", description="Neural network visualization toolkit for tf.keras", @@ -14,9 +14,26 @@ url="https://github.com/keisen/tf-keras-vis", packages=find_packages(), classifiers=[ - "Programming Language :: Python :: 3", + "Environment :: GPU :: NVIDIA CUDA", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Education", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Image Recognition", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", ], python_requires='>=3.6, <3.10', install_requires=['scipy', 'pillow', 'deprecated', 'imageio', 'packaging'], From a7416d5e016cd5bc86bb0e184826c5ef54c51e07 Mon Sep 17 00:00:00 2001 From: keisen Date: Thu, 3 Jun 2021 19:59:16 +0900 Subject: [PATCH 3/3] Update python-package.yml --- .github/workflows/python-package.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1a808c0..3a853b8 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -17,28 +17,42 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: python-version: [3.6, 3.7, 3.8] tensorflow: [2.0.4, 2.1.3, 2.2.2, 2.3.2, 2.4.1, 2.5.0] - include: - - python-version: 3.9 - tensorflow: 2.5.0 + extra-require: ["develop,examples", "develop,examples,tfa"] exclude: - python-version: 3.8 tensorflow: 2.0.4 - python-version: 3.8 tensorflow: 2.1.3 + - tensorflow: 2.0.4 + extra-require: "develop,examples,tfa" + - tensorflow: 2.1.3 + extra-require: "develop,examples,tfa" + - tensorflow: 2.2.2 + extra-require: "develop,examples,tfa" + include: + - python-version: 3.9 + tensorflow: 2.5.0 + extra-require: "develop,examples" + - python-version: 3.9 + tensorflow: 2.5.0 + extra-require: "develop,examples,tfa" steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Update packaging tools run: | python -m pip install --no-cache-dir --upgrade pip python -m pip install --no-cache-dir --upgrade setuptools - python -m pip install --no-cache-dir -e .[develop,examples] tensorflow==${{ matrix.tensorflow }} + - name: Install dependencies + run: | + python -m pip install --no-cache-dir -e .[${{ matrix.extra-require }}] tensorflow==${{ matrix.tensorflow }} - name: Test with pytest run: | PYTHONPATH=$PWD:$PYTHONPATH py.test