Chapter 08
Interpreting what ConvNets learn
NotebookPython 351 cells
This is a companion notebook for the book Deep Learning with Python, Third Edition. For readability, it only contains runnable code blocks and section titles, and omits everything else in the book: text paragraphs, figures, and pseudocode.
If you want to be able to follow what's going on, I recommend reading the notebook side by side with your copy of the book.
The book's contents are available online at deeplearningwithpython.io.
In [0]python · cell 2
python
!pip install keras keras-hub --upgrade -qIn [0]python · cell 3
python
import os
os.environ["KERAS_BACKEND"] = "jax"In [0]python · cell 4
python
# @title
import os
from IPython.core.magic import register_cell_magic
@register_cell_magic
def backend(line, cell):
current, required = os.environ.get("KERAS_BACKEND", ""), line.split()[-1]
if current == required:
get_ipython().run_cell(cell)
else:
print(
f"This cell requires the {required} backend. To run it, change KERAS_BACKEND to "
f"\"{required}\" at the top of the notebook, restart the runtime, and rerun the notebook."
)Interpreting what ConvNets learn
Visualizing intermediate activations
In [0]python · cell 7
python
from google.colab import files
# You can use this to load the file
# "convnet_from_scratch_with_augmentation.keras"
# you obtained in the last chapter.
files.upload()In [0]python · cell 8
python
import keras
model = keras.models.load_model(
"convnet_from_scratch_with_augmentation.keras"
)
model.summary(line_length=80)In [0]python · cell 9
python
import keras
import numpy as np
img_path = keras.utils.get_file(
fname="cat.jpg", origin="https://img-datasets.s3.amazonaws.com/cat.jpg"
)
def get_img_array(img_path, target_size):
img = keras.utils.load_img(img_path, target_size=target_size)
array = keras.utils.img_to_array(img)
array = np.expand_dims(array, axis=0)
return array
img_tensor = get_img_array(img_path, target_size=(180, 180))In [0]python · cell 10
python
import matplotlib.pyplot as plt
plt.axis("off")
plt.imshow(img_tensor[0].astype("uint8"))
plt.show()In [0]python · cell 11
python
from keras import layers
layer_outputs = []
layer_names = []
for layer in model.layers:
if isinstance(layer, (layers.Conv2D, layers.MaxPooling2D)):
layer_outputs.append(layer.output)
layer_names.append(layer.name)
activation_model = keras.Model(inputs=model.input, outputs=layer_outputs)In [0]python · cell 12
python
activations = activation_model.predict(img_tensor)In [0]python · cell 13
python
first_layer_activation = activations[0]
print(first_layer_activation.shape)In [0]python · cell 14
python
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 5], cmap="viridis")In [0]python · cell 15
python
images_per_row = 16
for layer_name, layer_activation in zip(layer_names, activations):
n_features = layer_activation.shape[-1]
size = layer_activation.shape[1]
n_cols = n_features // images_per_row
display_grid = np.zeros(
((size + 1) * n_cols - 1, images_per_row * (size + 1) - 1)
)
for col in range(n_cols):
for row in range(images_per_row):
channel_index = col * images_per_row + row
channel_image = layer_activation[0, :, :, channel_index].copy()
if channel_image.sum() != 0:
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
channel_image = np.clip(channel_image, 0, 255).astype("uint8")
display_grid[
col * (size + 1) : (col + 1) * size + col,
row * (size + 1) : (row + 1) * size + row,
] = channel_image
scale = 1.0 / size
plt.figure(
figsize=(scale * display_grid.shape[1], scale * display_grid.shape[0])
)
plt.title(layer_name)
plt.grid(False)
plt.axis("off")
plt.imshow(display_grid, aspect="auto", cmap="viridis")Visualizing ConvNet filters
In [0]python · cell 17
python
import keras_hub
model = keras_hub.models.Backbone.from_preset(
"xception_41_imagenet",
)
preprocessor = keras_hub.layers.ImageConverter.from_preset(
"xception_41_imagenet",
image_size=(180, 180),
)In [0]python · cell 18
python
for layer in model.layers:
if isinstance(layer, (keras.layers.Conv2D, keras.layers.SeparableConv2D)):
print(layer.name)In [0]python · cell 19
python
layer_name = "block3_sepconv1"
layer = model.get_layer(name=layer_name)
feature_extractor = keras.Model(inputs=model.input, outputs=layer.output)In [0]python · cell 20
python
activation = feature_extractor(preprocessor(img_tensor))In [0]python · cell 21
python
from keras import ops
def compute_loss(image, filter_index):
activation = feature_extractor(image)
filter_activation = activation[:, 2:-2, 2:-2, filter_index]
return ops.mean(filter_activation)Gradient ascent in TensorFlow
In [0]python · cell 23
python
%%backend tensorflow
import tensorflow as tf
@tf.function
def gradient_ascent_step(image, filter_index, learning_rate):
with tf.GradientTape() as tape:
tape.watch(image)
loss = compute_loss(image, filter_index)
grads = tape.gradient(loss, image)
grads = ops.normalize(grads)
image += learning_rate * grads
return imageGradient ascent in PyTorch
In [0]python · cell 25
python
%%backend torch
import torch
def gradient_ascent_step(image, filter_index, learning_rate):
image = image.clone().detach().requires_grad_(True)
loss = compute_loss(image, filter_index)
loss.backward()
grads = image.grad
grads = ops.normalize(grads)
image = image + learning_rate * grads
return imageGradient ascent in JAX
In [0]python · cell 27
python
%%backend jax
import jax
grad_fn = jax.grad(compute_loss)
@jax.jit
def gradient_ascent_step(image, filter_index, learning_rate):
grads = grad_fn(image, filter_index)
grads = ops.normalize(grads)
image += learning_rate * grads
return imageThe filter visualization loop
In [0]python · cell 29
python
img_width = 200
img_height = 200
def generate_filter_pattern(filter_index):
iterations = 30
learning_rate = 10.0
image = keras.random.uniform(
minval=0.4, maxval=0.6, shape=(1, img_width, img_height, 3)
)
for i in range(iterations):
image = gradient_ascent_step(image, filter_index, learning_rate)
return image[0]In [0]python · cell 30
python
def deprocess_image(image):
image -= ops.mean(image)
image /= ops.std(image)
image *= 64
image += 128
image = ops.clip(image, 0, 255)
image = image[25:-25, 25:-25, :]
image = ops.cast(image, dtype="uint8")
return ops.convert_to_numpy(image)In [0]python · cell 31
python
plt.axis("off")
plt.imshow(deprocess_image(generate_filter_pattern(filter_index=2)))In [0]python · cell 32
python
all_images = []
for filter_index in range(64):
print(f"Processing filter {filter_index}")
image = deprocess_image(generate_filter_pattern(filter_index))
all_images.append(image)
margin = 5
n = 8
box_width = img_width - 25 * 2
box_height = img_height - 25 * 2
full_width = n * box_width + (n - 1) * margin
full_height = n * box_height + (n - 1) * margin
stitched_filters = np.zeros((full_width, full_height, 3))
for i in range(n):
for j in range(n):
image = all_images[i * n + j]
stitched_filters[
(box_width + margin) * i : (box_width + margin) * i + box_width,
(box_height + margin) * j : (box_height + margin) * j + box_height,
:,
] = image
keras.utils.save_img(f"filters_for_layer_{layer_name}.png", stitched_filters)Visualizing heatmaps of class activation
In [0]python · cell 34
python
img_path = keras.utils.get_file(
fname="elephant.jpg",
origin="https://img-datasets.s3.amazonaws.com/elephant.jpg",
)
img = keras.utils.load_img(img_path)
img_array = np.expand_dims(img, axis=0)In [0]python · cell 35
python
model = keras_hub.models.ImageClassifier.from_preset(
"xception_41_imagenet",
activation="softmax",
)
preds = model.predict(img_array)
preds.shapeIn [0]python · cell 36
python
keras_hub.utils.decode_imagenet_predictions(preds)In [0]python · cell 37
python
np.argmax(preds[0])In [0]python · cell 38
python
img_array = model.preprocessor(img_array)In [0]python · cell 39
python
last_conv_layer_name = "block14_sepconv2_act"
last_conv_layer = model.backbone.get_layer(last_conv_layer_name)
last_conv_layer_model = keras.Model(model.inputs, last_conv_layer.output)In [0]python · cell 40
python
classifier_input = last_conv_layer.output
x = classifier_input
for layer_name in ["pooler", "predictions"]:
x = model.get_layer(layer_name)(x)
classifier_model = keras.Model(classifier_input, x)Getting the gradient of the top class: TensorFlow version
In [0]python · cell 42
python
%%backend tensorflow
import tensorflow as tf
def get_top_class_gradients(img_array):
last_conv_layer_output = last_conv_layer_model(img_array)
with tf.GradientTape() as tape:
tape.watch(last_conv_layer_output)
preds = classifier_model(last_conv_layer_output)
top_pred_index = ops.argmax(preds[0])
top_class_channel = preds[:, top_pred_index]
grads = tape.gradient(top_class_channel, last_conv_layer_output)
return grads, last_conv_layer_output
grads, last_conv_layer_output = get_top_class_gradients(img_array)
grads = ops.convert_to_numpy(grads)
last_conv_layer_output = ops.convert_to_numpy(last_conv_layer_output)Getting the gradient of the top class: PyTorch version
In [0]python · cell 44
python
%%backend torch
def get_top_class_gradients(img_array):
last_conv_layer_output = last_conv_layer_model(img_array)
last_conv_layer_output = (
last_conv_layer_output.clone().detach().requires_grad_(True)
)
preds = classifier_model(last_conv_layer_output)
top_pred_index = ops.argmax(preds[0])
top_class_channel = preds[:, top_pred_index]
top_class_channel.backward()
grads = last_conv_layer_output.grad
return grads, last_conv_layer_output
grads, last_conv_layer_output = get_top_class_gradients(img_array)
grads = ops.convert_to_numpy(grads)
last_conv_layer_output = ops.convert_to_numpy(last_conv_layer_output)Getting the gradient of the top class: JAX version
In [0]python · cell 46
python
%%backend jax
import jax
def loss_fn(last_conv_layer_output):
preds = classifier_model(last_conv_layer_output)
top_pred_index = ops.argmax(preds[0])
top_class_channel = preds[:, top_pred_index]
return top_class_channel[0]
grad_fn = jax.grad(loss_fn)
def get_top_class_gradients(img_array):
last_conv_layer_output = last_conv_layer_model(img_array)
grads = grad_fn(last_conv_layer_output)
return grads, last_conv_layer_output
grads, last_conv_layer_output = get_top_class_gradients(img_array)
grads = ops.convert_to_numpy(grads)
last_conv_layer_output = ops.convert_to_numpy(last_conv_layer_output)Displaying the class activation heatmap
In [0]python · cell 48
python
pooled_grads = np.mean(grads, axis=(0, 1, 2))
last_conv_layer_output = last_conv_layer_output[0].copy()
for i in range(pooled_grads.shape[-1]):
last_conv_layer_output[:, :, i] *= pooled_grads[i]
heatmap = np.mean(last_conv_layer_output, axis=-1)In [0]python · cell 49
python
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)In [0]python · cell 50
python
import matplotlib.cm as cm
img = keras.utils.load_img(img_path)
img = keras.utils.img_to_array(img)
heatmap = np.uint8(255 * heatmap)
jet = cm.get_cmap("jet")
jet_colors = jet(np.arange(256))[:, :3]
jet_heatmap = jet_colors[heatmap]
jet_heatmap = keras.utils.array_to_img(jet_heatmap)
jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))
jet_heatmap = keras.utils.img_to_array(jet_heatmap)
superimposed_img = jet_heatmap * 0.4 + img
superimposed_img = keras.utils.array_to_img(superimposed_img)
plt.imshow(superimposed_img)