diff --git a/keras_visualize_activations/.gitignore b/keras_visualize_activations/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b9d6d01b6f198cfe96bae995139f4e6d13966973 --- /dev/null +++ b/keras_visualize_activations/.gitignore @@ -0,0 +1,103 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +.DS_Store +.idea/ +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/keras_visualize_activations/LICENSE b/keras_visualize_activations/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8dada3edaf50dbc082c9a125058f25def75e625a --- /dev/null +++ b/keras_visualize_activations/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/keras_visualize_activations/README.md b/keras_visualize_activations/README.md new file mode 100644 index 0000000000000000000000000000000000000000..db3ead1bb98663fa32d7bc31a62a4d3124f76f11 --- /dev/null +++ b/keras_visualize_activations/README.md @@ -0,0 +1,82 @@ +# Extract the activation maps of your Keras models +[](https://github.com/philipperemy/keras-attention-mechanism/blob/master/LICENSE) [](https://www.tensorflow.org/) [](https://keras.io/) + +*Short code and useful examples to show how to get the activations for each layer for Keras.* + +**-> Works for any kind of model (recurrent, convolutional, residuals...). Not only for images!** + +## Example of MNIST + +Shapes of the activations (one sample) on Keras CNN MNIST: +``` +----- activations ----- +(1, 26, 26, 32) +(1, 24, 24, 64) +(1, 12, 12, 64) +(1, 12, 12, 64) +(1, 9216) +(1, 128) +(1, 128) +(1, 10) # softmax output! +``` + +Shapes of the activations (batch of 200 samples) on Keras CNN MNIST: +``` +----- activations ----- +(200, 26, 26, 32) +(200, 24, 24, 64) +(200, 12, 12, 64) +(200, 12, 12, 64) +(200, 9216) +(200, 128) +(200, 128) +(200, 10) +``` + +<p align="center"> + <img src="assets/0.png" width="50"> + <br><i>A random seven from MNIST</i> +</p> + + +<p align="center"> + <img src="assets/1.png"> + <br><i>Activation map of CONV1 of LeNet</i> +</p> + +<p align="center"> + <img src="assets/2.png" width="200"> + <br><i>Activation map of FC1 of LeNet</i> +</p> + + +<p align="center"> + <img src="assets/3.png" width="300"> + <br><i>Activation map of Softmax of LeNet. <b>Yes it's a seven!</b></i> +</p> + +<hr/> + +The function for visualizing the activations is in the script [read_activations.py](https://github.com/philipperemy/keras-visualize-activations/blob/master/read_activations.py) + +Inputs: +- `model`: Keras model +- `model_inputs`: Model inputs for which we want to get the activations (for example 200 MNIST images) +- `print_shape_only`: If set to True, will print the entire activations arrays (might be very verbose!) +- `layer_name`: Will retrieve the activations of a specific layer, if the name matches one of the existing layers of the model. + +Outputs: +- returns a list of each layer (by order of definition) and its corresponding activations. + +I provide a simple example to see how it works with the MNIST model. I separated the training and the visualizations because if the two were to be done sequentially, we would have to re-train the model every time we would like to visualize the activations! Not very practical! Here are the main steps: + +Running `python model_train.py` will do: + +- define the model +- if no checkpoints are detected: + - train the model + - save the best model in checkpoints/ +- load the model from the best checkpoint +- read the activations + +`model_multi_inputs_train.py` contains very simple examples to visualize activations with multi inputs models. diff --git a/keras_visualize_activations/assets/0.png b/keras_visualize_activations/assets/0.png new file mode 100644 index 0000000000000000000000000000000000000000..bb63ae85c5e043c1e637304e220c0bff8d1e1ea3 Binary files /dev/null and b/keras_visualize_activations/assets/0.png differ diff --git a/keras_visualize_activations/assets/1.png b/keras_visualize_activations/assets/1.png new file mode 100644 index 0000000000000000000000000000000000000000..b42ea9bfe6f93884ba9566ab2914952eef4bbf07 Binary files /dev/null and b/keras_visualize_activations/assets/1.png differ diff --git a/keras_visualize_activations/assets/2.png b/keras_visualize_activations/assets/2.png new file mode 100644 index 0000000000000000000000000000000000000000..9d8650e740c2c9dca5e73a2cc99c8bf1ad879f34 Binary files /dev/null and b/keras_visualize_activations/assets/2.png differ diff --git a/keras_visualize_activations/assets/3.png b/keras_visualize_activations/assets/3.png new file mode 100644 index 0000000000000000000000000000000000000000..d13d209a8b436b336ce4ec8e66b325344f520172 Binary files /dev/null and b/keras_visualize_activations/assets/3.png differ diff --git a/keras_visualize_activations/checkpoints/model_09_0.990.h5 b/keras_visualize_activations/checkpoints/model_09_0.990.h5 new file mode 100644 index 0000000000000000000000000000000000000000..0ba2c701786c2e104b2a736d48f22adadc8dbbae Binary files /dev/null and b/keras_visualize_activations/checkpoints/model_09_0.990.h5 differ diff --git a/keras_visualize_activations/data.py b/keras_visualize_activations/data.py new file mode 100644 index 0000000000000000000000000000000000000000..76d5e78e52d465ad89bb420600f5da8e14873389 --- /dev/null +++ b/keras_visualize_activations/data.py @@ -0,0 +1,30 @@ +import keras +import keras.backend as K +from keras.datasets import mnist + +# input image dimensions +img_rows, img_cols = 28, 28 +input_shape = (img_rows, img_cols, 1) +num_classes = 10 + + +def get_mnist_data(): + + # the data, shuffled and split between train and test sets + (x_train, y_train), (x_test, y_test) = mnist.load_data() + + x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1) + x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1) + + x_train = x_train.astype('float32') + x_test = x_test.astype('float32') + x_train /= 255 + x_test /= 255 + print('x_train shape:', x_train.shape) + print(x_train.shape[0], 'train samples') + print(x_test.shape[0], 'test samples') + + # convert class vectors to binary class matrices + y_train = keras.utils.to_categorical(y_train, num_classes) + y_test = keras.utils.to_categorical(y_test, num_classes) + return x_train, y_train, x_test, y_test \ No newline at end of file diff --git a/keras_visualize_activations/model_multi_inputs_train.py b/keras_visualize_activations/model_multi_inputs_train.py new file mode 100644 index 0000000000000000000000000000000000000000..d47c2ae537c2831cd6c4086da4b50ad2950fd930 --- /dev/null +++ b/keras_visualize_activations/model_multi_inputs_train.py @@ -0,0 +1,43 @@ +import keras.backend as K +import numpy as np +from keras.layers import merge, Dense +from keras.models import Input, Model, Sequential + + +def get_multi_inputs_model(): + a = Input(shape=(10,)) + b = Input(shape=(10,)) + c = merge([a, b], mode='mul') + c = Dense(1, activation='sigmoid', name='only_this_layer')(c) + m_multi = Model(inputs=[a, b], outputs=c) + return m_multi + + +def get_single_inputs_model(): + m_single = Sequential() + m_single.add(Dense(1, activation='sigmoid', input_shape=(10,))) + return m_single + + +if __name__ == '__main__': + + m = get_multi_inputs_model() + m.compile(optimizer='adam', + loss='binary_crossentropy') + + inp_a = np.random.uniform(size=(100, 10)) + inp_b = np.random.uniform(size=(100, 10)) + inp_o = np.random.randint(low=0, high=2, size=(100, 1)) + m.fit([inp_a, inp_b], inp_o) + + from read_activations import * + + get_activations(m, [inp_a[0:1], inp_b[0:1]], print_shape_only=True) + get_activations(m, [inp_a[0:1], inp_b[0:1]], print_shape_only=True, layer_name='only_this_layer') + + m2 = get_single_inputs_model() + m2.compile(optimizer='adam', + loss='binary_crossentropy') + m2.fit([inp_a], inp_o) + + get_activations(m2, [inp_a[0]], print_shape_only=True) diff --git a/keras_visualize_activations/model_train.py b/keras_visualize_activations/model_train.py new file mode 100644 index 0000000000000000000000000000000000000000..62454722d2e75320501f3342fda6404ee77fc84e --- /dev/null +++ b/keras_visualize_activations/model_train.py @@ -0,0 +1,96 @@ +from __future__ import print_function + +from glob import glob + +import keras +from keras.callbacks import ModelCheckpoint +from keras.layers import Conv2D, MaxPooling2D +from keras.layers import Dense, Dropout, Flatten +from keras.models import Sequential + +from data import get_mnist_data, num_classes, input_shape +from read_activations import get_activations, display_activations + +if __name__ == '__main__': + + checkpoints = glob('checkpoints/*.h5') + # pip3 install natsort + from natsort import natsorted + + from keras.models import load_model + + if len(checkpoints) > 0: + + checkpoints = natsorted(checkpoints) + assert len(checkpoints) != 0, 'No checkpoints found.' + checkpoint_file = checkpoints[-1] + print('Loading [{}]'.format(checkpoint_file)) + model = load_model(checkpoint_file) + + model.compile(optimizer='adam', + loss='categorical_crossentropy', + metrics=['accuracy']) + + print(model.summary()) + + x_train, y_train, x_test, y_test = get_mnist_data() + + # checking that the accuracy is the same as before 99% at the first epoch. + test_loss, test_acc = model.evaluate(x_test, y_test, verbose=1, batch_size=128) + print('') + assert test_acc > 0.98 + + a = get_activations(model, x_test[0:1], print_shape_only=True) # with just one sample. + display_activations(a) + + get_activations(model, x_test[0:200], print_shape_only=True) # with 200 samples. + + # import numpy as np + # import matplotlib.pyplot as plt + # plt.imshow(np.squeeze(x_test[0:1]), interpolation='None', cmap='gray') + else: + x_train, y_train, x_test, y_test = get_mnist_data() + + model = Sequential() + model.add(Conv2D(32, kernel_size=(3, 3), + activation='relu', + input_shape=input_shape)) + model.add(Conv2D(64, (3, 3), activation='relu')) + model.add(MaxPooling2D(pool_size=(2, 2))) + model.add(Dropout(0.25)) + model.add(Flatten()) + model.add(Dense(128, activation='relu')) + model.add(Dropout(0.5)) + model.add(Dense(num_classes, activation='softmax')) + + model.compile(loss=keras.losses.categorical_crossentropy, + optimizer=keras.optimizers.Adadelta(), + metrics=['accuracy']) + + # Change starts here + import shutil + import os + + # delete folder and its content and creates a new one. + try: + shutil.rmtree('checkpoints') + except: + pass + os.mkdir('checkpoints') + + checkpoint = ModelCheckpoint(monitor='val_acc', + filepath='checkpoints/model_{epoch:02d}_{val_acc:.3f}.h5', + save_best_only=True) + + model.fit(x_train, y_train, + batch_size=128, + epochs=12, + verbose=1, + validation_data=(x_test, y_test), + callbacks=[checkpoint]) + + # Change finishes here + + score = model.evaluate(x_test, y_test, verbose=0) + print('Test loss:', score[0]) + print('Test accuracy:', score[1]) diff --git a/keras_visualize_activations/read_activations.py b/keras_visualize_activations/read_activations.py new file mode 100644 index 0000000000000000000000000000000000000000..0e4641ac32bfcd48aa42cefb676d021e27697310 --- /dev/null +++ b/keras_visualize_activations/read_activations.py @@ -0,0 +1,72 @@ +import keras.backend as K + + +def get_activations(model, model_inputs, print_shape_only=False, layer_name=None): + print('----- activations -----') + activations = [] + inp = model.input + + model_multi_inputs_cond = True + if not isinstance(inp, list): + # only one input! let's wrap it in a list. + inp = [inp] + model_multi_inputs_cond = False + + outputs = [layer.output for layer in model.layers if + layer.name == layer_name or layer_name is None] # all layer outputs + + funcs = [K.function(inp + [K.learning_phase()], [out]) for out in outputs] # evaluation functions + + if model_multi_inputs_cond: + list_inputs = [] + list_inputs.extend(model_inputs) + list_inputs.append(0.) + else: + list_inputs = [model_inputs, 0.] + + # Learning phase. 0 = Test mode (no dropout or batch normalization) + # layer_outputs = [func([model_inputs, 0.])[0] for func in funcs] + layer_outputs = [func(list_inputs)[0] for func in funcs] + for layer_activations in layer_outputs: + activations.append(layer_activations) + if print_shape_only: + print(layer_activations.shape) + else: + print(layer_activations) + return activations + + +def display_activations(activation_maps): + import numpy as np + import matplotlib.pyplot as plt + """ + (1, 26, 26, 32) + (1, 24, 24, 64) + (1, 12, 12, 64) + (1, 12, 12, 64) + (1, 9216) + (1, 128) + (1, 128) + (1, 10) + """ + batch_size = activation_maps[0].shape[0] + assert batch_size == 1, 'One image at a time to visualize.' + for i, activation_map in enumerate(activation_maps): + print('Displaying activation map {}'.format(i)) + shape = activation_map.shape + if len(shape) == 4: + activations = np.hstack(np.transpose(activation_map[0], (2, 0, 1))) + elif len(shape) == 2: + # try to make it square as much as possible. we can skip some activations. + activations = activation_map[0] + num_activations = len(activations) + if num_activations > 1024: # too hard to display it on the screen. + square_param = int(np.floor(np.sqrt(num_activations))) + activations = activations[0: square_param * square_param] + activations = np.reshape(activations, (square_param, square_param)) + else: + activations = np.expand_dims(activations, axis=0) + else: + raise Exception('len(shape) = 3 has not been implemented.') + plt.imshow(activations, interpolation='None', cmap='jet') + plt.show() diff --git a/keras_visualize_activations/requirements.txt b/keras_visualize_activations/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..7fdf5d210f59e02ad3a757c0c7416be9c7e3dfcd --- /dev/null +++ b/keras_visualize_activations/requirements.txt @@ -0,0 +1,4 @@ +Keras==2.0.2 +natsort==5.0.2 +numpy==1.12.1 +h5py \ No newline at end of file