Skip to content

Commit a6b3f3f

Browse files
committed
initial pass at conv layer viz
1 parent a7aba9e commit a6b3f3f

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

kviz/conv.py

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Copyright 2021 Lance Galletti
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
17+
18+
import numpy as np
19+
import matplotlib.pyplot as plt
20+
from tensorflow.keras import models
21+
22+
23+
class ConvGraph():
24+
"""
25+
Class for creating and rendering visualization of Keras
26+
Sequential Model with Convolutional Layers
27+
28+
Attributes:
29+
model : tf.keras.Model
30+
a compiled keras sequential model
31+
32+
Methods:
33+
render :
34+
Shows all the convolution activations
35+
36+
"""
37+
38+
def __init__(self, model):
39+
self.model = model
40+
41+
42+
def _snap_layer(self, display_grid, scale, filename):
43+
fig, ax = plt.subplots(figsize=(int(scale * display_grid.shape[1]), int(scale * display_grid.shape[0])))
44+
ax.grid(False)
45+
ax.imshow(display_grid, aspect='auto')
46+
fig.savefig(filename + '.png', transparent=True)
47+
plt.close()
48+
return
49+
50+
51+
def render(self, X=None, filename='conv_filters'):
52+
"""
53+
Render visualization of a Convolutional keras model
54+
55+
Parameters:
56+
X : ndarray
57+
input to a Keras model
58+
filename : str
59+
name of file to which visualization will be saved
60+
61+
Returns:
62+
None
63+
"""
64+
65+
layer_outputs = [layer.output for layer in self.model.layers]
66+
# Creates a model that will return these outputs, given the model input
67+
activation_model = models.Model(inputs=self.model.input, outputs=layer_outputs)
68+
images_per_row = 8
69+
70+
for j in range(len(X)):
71+
activations = activation_model.predict(X[j])
72+
73+
for i in range(len(activations)):
74+
# Ignore non-conv2d layers
75+
layer_name = self.model.layers[i].name
76+
if not layer_name.startswith("conv2d"):
77+
continue
78+
79+
# Number of features in the feature map
80+
n_features = activations[i].shape[-1]
81+
# The feature map has shape (1, size, size, n_features).
82+
size = activations[i].shape[1]
83+
# Tiles the activation channels in this matrix
84+
n_cols = n_features // images_per_row
85+
display_grid = np.zeros((size * n_cols, images_per_row * size))
86+
# Tiles each filter into a big horizontal grid
87+
for col in range(n_cols):
88+
for row in range(images_per_row):
89+
# Displays the grid
90+
display_grid[
91+
col * size: (col + 1) * size,
92+
row * size: (row + 1) * size] = activations[i][0, :, :, col * images_per_row + row]
93+
94+
self._snap_layer(display_grid, 1. / size, filename + "_" + str(j) + "_" + layer_name)
95+
96+
return

kviz/dense.py

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class DenseGraph():
4949
is provided, show a GIF of the activations of each
5050
Neuron based on the input provided.
5151
52+
animate_learning:
53+
Make GIF from snapshots of decision boundary at
54+
given snap_freq
55+
5256
"""
5357

5458
def __init__(self, model):

tests/test_conv.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import numpy as np
2+
from tensorflow import keras
3+
from tensorflow.keras import layers, utils
4+
from tensorflow.keras.datasets import mnist
5+
6+
from kviz.conv import ConvGraph
7+
8+
9+
def test_conv_input():
10+
(X_train, y_train), (X_test, y_test) = mnist.load_data()
11+
12+
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1)
13+
X_train = X_train.astype('float32')
14+
X_train /= 255
15+
16+
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1)
17+
X_test = X_test.astype('float32')
18+
X_test /= 255
19+
20+
number_of_classes = 10
21+
Y_train = utils.to_categorical(y_train, number_of_classes)
22+
Y_test = utils.to_categorical(y_test, number_of_classes)
23+
24+
ACTIVATION = "relu"
25+
model = keras.models.Sequential()
26+
model.add(layers.Conv2D(32, 5, input_shape=(28, 28, 1), activation=ACTIVATION))
27+
model.add(layers.MaxPooling2D())
28+
model.add(layers.Conv2D(64, 5, activation=ACTIVATION))
29+
model.add(layers.MaxPooling2D())
30+
model.add(layers.Flatten())
31+
model.add(layers.Dense(100, activation=ACTIVATION))
32+
model.add(layers.Dense(10, activation="softmax"))
33+
model.compile(loss="categorical_crossentropy", metrics=['accuracy'])
34+
35+
model.fit(X_train, Y_train, batch_size=100, epochs=5)
36+
37+
score = model.evaluate(X_test, Y_test, verbose=0)
38+
print("Test loss:", score[0])
39+
print("Test accuracy:", score[1])
40+
41+
dg = ConvGraph(model)
42+
X = []
43+
for i in range(number_of_classes):
44+
X.append(np.expand_dims(X_train[np.where(y_train == i)[0][0]], axis=0))
45+
dg.render(X, filename='test_input_mnist')

tests/test_viz.py tests/test_dense.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def test_dense_input_xor():
3535
[1, 1]])
3636
Y = np.array([x[0] ^ x[1] for x in X])
3737

38-
model.fit(X, Y, batch_size=4, epochs=1000)
38+
model.fit(X, Y, batch_size=4, epochs=100)
3939

4040
colors = np.array(['b', 'g'])
4141
fig, ax = plt.subplots()
@@ -71,7 +71,7 @@ def test_dense_input_line():
7171
X = np.array(t)
7272
Y = np.array([1 if x[0] - x[1] >= 0 else 0 for x in X])
7373

74-
model.fit(X, Y, batch_size=50, epochs=100)
74+
model.fit(X, Y, batch_size=50, epochs=10)
7575

7676
# see which nodes activate for a given class
7777
X0 = X[X[:, 0] - X[:, 1] <= 0]

0 commit comments

Comments
 (0)