{ "cells": [ { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "This is a companion notebook for the book [Deep Learning with Python, Third Edition](https://www.manning.com/books/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.\n\n**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.**\n\nThe book's contents are available online at [deeplearningwithpython.io](https://deeplearningwithpython.io)." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "!pip install keras keras-hub --upgrade -q" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import os\n", "os.environ[\"KERAS_BACKEND\"] = \"jax\"" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "cellView": "form", "colab_type": "code" }, "outputs": [], "source": [ "# @title\n", "import os\n", "from IPython.core.magic import register_cell_magic\n", "\n", "@register_cell_magic\n", "def backend(line, cell):\n", " current, required = os.environ.get(\"KERAS_BACKEND\", \"\"), line.split()[-1]\n", " if current == required:\n", " get_ipython().run_cell(cell)\n", " else:\n", " print(\n", " f\"This cell requires the {required} backend. To run it, change KERAS_BACKEND to \"\n", " f\"\\\"{required}\\\" at the top of the notebook, restart the runtime, and rerun the notebook.\"\n", " )" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "## Interpreting what ConvNets learn" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "### Visualizing intermediate activations" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "from google.colab import files\n", "\n", "# You can use this to load the file\n", "# \"convnet_from_scratch_with_augmentation.keras\"\n", "# you obtained in the last chapter.\n", "files.upload()" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import keras\n", "model = keras.models.load_model(\n", " \"convnet_from_scratch_with_augmentation.keras\"\n", ")\n", "model.summary(line_length=80)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import keras\n", "import numpy as np\n", "\n", "img_path = keras.utils.get_file(\n", " fname=\"cat.jpg\", origin=\"https://img-datasets.s3.amazonaws.com/cat.jpg\"\n", ")\n", "\n", "def get_img_array(img_path, target_size):\n", " img = keras.utils.load_img(img_path, target_size=target_size)\n", " array = keras.utils.img_to_array(img)\n", " array = np.expand_dims(array, axis=0)\n", " return array\n", "\n", "img_tensor = get_img_array(img_path, target_size=(180, 180))" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.axis(\"off\")\n", "plt.imshow(img_tensor[0].astype(\"uint8\"))\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "from keras import layers\n", "\n", "layer_outputs = []\n", "layer_names = []\n", "for layer in model.layers:\n", " if isinstance(layer, (layers.Conv2D, layers.MaxPooling2D)):\n", " layer_outputs.append(layer.output)\n", " layer_names.append(layer.name)\n", "activation_model = keras.Model(inputs=model.input, outputs=layer_outputs)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "activations = activation_model.predict(img_tensor)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "first_layer_activation = activations[0]\n", "print(first_layer_activation.shape)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.matshow(first_layer_activation[0, :, :, 5], cmap=\"viridis\")" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "images_per_row = 16\n", "for layer_name, layer_activation in zip(layer_names, activations):\n", " n_features = layer_activation.shape[-1]\n", " size = layer_activation.shape[1]\n", " n_cols = n_features // images_per_row\n", " display_grid = np.zeros(\n", " ((size + 1) * n_cols - 1, images_per_row * (size + 1) - 1)\n", " )\n", " for col in range(n_cols):\n", " for row in range(images_per_row):\n", " channel_index = col * images_per_row + row\n", " channel_image = layer_activation[0, :, :, channel_index].copy()\n", " if channel_image.sum() != 0:\n", " channel_image -= channel_image.mean()\n", " channel_image /= channel_image.std()\n", " channel_image *= 64\n", " channel_image += 128\n", " channel_image = np.clip(channel_image, 0, 255).astype(\"uint8\")\n", " display_grid[\n", " col * (size + 1) : (col + 1) * size + col,\n", " row * (size + 1) : (row + 1) * size + row,\n", " ] = channel_image\n", " scale = 1.0 / size\n", " plt.figure(\n", " figsize=(scale * display_grid.shape[1], scale * display_grid.shape[0])\n", " )\n", " plt.title(layer_name)\n", " plt.grid(False)\n", " plt.axis(\"off\")\n", " plt.imshow(display_grid, aspect=\"auto\", cmap=\"viridis\")" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "### Visualizing ConvNet filters" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import keras_hub\n", "\n", "model = keras_hub.models.Backbone.from_preset(\n", " \"xception_41_imagenet\",\n", ")\n", "preprocessor = keras_hub.layers.ImageConverter.from_preset(\n", " \"xception_41_imagenet\",\n", " image_size=(180, 180),\n", ")" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "for layer in model.layers:\n", " if isinstance(layer, (keras.layers.Conv2D, keras.layers.SeparableConv2D)):\n", " print(layer.name)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "layer_name = \"block3_sepconv1\"\n", "layer = model.get_layer(name=layer_name)\n", "feature_extractor = keras.Model(inputs=model.input, outputs=layer.output)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "activation = feature_extractor(preprocessor(img_tensor))" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "from keras import ops\n", "\n", "def compute_loss(image, filter_index):\n", " activation = feature_extractor(image)\n", " filter_activation = activation[:, 2:-2, 2:-2, filter_index]\n", " return ops.mean(filter_activation)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Gradient ascent in TensorFlow" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "%%backend tensorflow\n", "import tensorflow as tf\n", "\n", "@tf.function\n", "def gradient_ascent_step(image, filter_index, learning_rate):\n", " with tf.GradientTape() as tape:\n", " tape.watch(image)\n", " loss = compute_loss(image, filter_index)\n", " grads = tape.gradient(loss, image)\n", " grads = ops.normalize(grads)\n", " image += learning_rate * grads\n", " return image" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Gradient ascent in PyTorch" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "%%backend torch\n", "import torch\n", "\n", "def gradient_ascent_step(image, filter_index, learning_rate):\n", " image = image.clone().detach().requires_grad_(True)\n", " loss = compute_loss(image, filter_index)\n", " loss.backward()\n", " grads = image.grad\n", " grads = ops.normalize(grads)\n", " image = image + learning_rate * grads\n", " return image" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Gradient ascent in JAX" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "%%backend jax\n", "import jax\n", "\n", "grad_fn = jax.grad(compute_loss)\n", "\n", "@jax.jit\n", "def gradient_ascent_step(image, filter_index, learning_rate):\n", " grads = grad_fn(image, filter_index)\n", " grads = ops.normalize(grads)\n", " image += learning_rate * grads\n", " return image" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### The filter visualization loop" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "img_width = 200\n", "img_height = 200\n", "\n", "def generate_filter_pattern(filter_index):\n", " iterations = 30\n", " learning_rate = 10.0\n", " image = keras.random.uniform(\n", " minval=0.4, maxval=0.6, shape=(1, img_width, img_height, 3)\n", " )\n", " for i in range(iterations):\n", " image = gradient_ascent_step(image, filter_index, learning_rate)\n", " return image[0]" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "def deprocess_image(image):\n", " image -= ops.mean(image)\n", " image /= ops.std(image)\n", " image *= 64\n", " image += 128\n", " image = ops.clip(image, 0, 255)\n", " image = image[25:-25, 25:-25, :]\n", " image = ops.cast(image, dtype=\"uint8\")\n", " return ops.convert_to_numpy(image)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "plt.axis(\"off\")\n", "plt.imshow(deprocess_image(generate_filter_pattern(filter_index=2)))" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "all_images = []\n", "for filter_index in range(64):\n", " print(f\"Processing filter {filter_index}\")\n", " image = deprocess_image(generate_filter_pattern(filter_index))\n", " all_images.append(image)\n", "\n", "margin = 5\n", "n = 8\n", "box_width = img_width - 25 * 2\n", "box_height = img_height - 25 * 2\n", "full_width = n * box_width + (n - 1) * margin\n", "full_height = n * box_height + (n - 1) * margin\n", "stitched_filters = np.zeros((full_width, full_height, 3))\n", "\n", "for i in range(n):\n", " for j in range(n):\n", " image = all_images[i * n + j]\n", " stitched_filters[\n", " (box_width + margin) * i : (box_width + margin) * i + box_width,\n", " (box_height + margin) * j : (box_height + margin) * j + box_height,\n", " :,\n", " ] = image\n", "\n", "keras.utils.save_img(f\"filters_for_layer_{layer_name}.png\", stitched_filters)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "### Visualizing heatmaps of class activation" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "img_path = keras.utils.get_file(\n", " fname=\"elephant.jpg\",\n", " origin=\"https://img-datasets.s3.amazonaws.com/elephant.jpg\",\n", ")\n", "img = keras.utils.load_img(img_path)\n", "img_array = np.expand_dims(img, axis=0)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "model = keras_hub.models.ImageClassifier.from_preset(\n", " \"xception_41_imagenet\",\n", " activation=\"softmax\",\n", ")\n", "preds = model.predict(img_array)\n", "preds.shape" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "keras_hub.utils.decode_imagenet_predictions(preds)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "np.argmax(preds[0])" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "img_array = model.preprocessor(img_array)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "last_conv_layer_name = \"block14_sepconv2_act\"\n", "last_conv_layer = model.backbone.get_layer(last_conv_layer_name)\n", "last_conv_layer_model = keras.Model(model.inputs, last_conv_layer.output)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "classifier_input = last_conv_layer.output\n", "x = classifier_input\n", "for layer_name in [\"pooler\", \"predictions\"]:\n", " x = model.get_layer(layer_name)(x)\n", "classifier_model = keras.Model(classifier_input, x)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Getting the gradient of the top class: TensorFlow version" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "%%backend tensorflow\n", "import tensorflow as tf\n", "\n", "def get_top_class_gradients(img_array):\n", " last_conv_layer_output = last_conv_layer_model(img_array)\n", " with tf.GradientTape() as tape:\n", " tape.watch(last_conv_layer_output)\n", " preds = classifier_model(last_conv_layer_output)\n", " top_pred_index = ops.argmax(preds[0])\n", " top_class_channel = preds[:, top_pred_index]\n", "\n", " grads = tape.gradient(top_class_channel, last_conv_layer_output)\n", " return grads, last_conv_layer_output\n", "\n", "grads, last_conv_layer_output = get_top_class_gradients(img_array)\n", "grads = ops.convert_to_numpy(grads)\n", "last_conv_layer_output = ops.convert_to_numpy(last_conv_layer_output)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Getting the gradient of the top class: PyTorch version" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "%%backend torch\n", "def get_top_class_gradients(img_array):\n", " last_conv_layer_output = last_conv_layer_model(img_array)\n", " last_conv_layer_output = (\n", " last_conv_layer_output.clone().detach().requires_grad_(True)\n", " )\n", " preds = classifier_model(last_conv_layer_output)\n", " top_pred_index = ops.argmax(preds[0])\n", " top_class_channel = preds[:, top_pred_index]\n", " top_class_channel.backward()\n", " grads = last_conv_layer_output.grad\n", " return grads, last_conv_layer_output\n", "\n", "grads, last_conv_layer_output = get_top_class_gradients(img_array)\n", "grads = ops.convert_to_numpy(grads)\n", "last_conv_layer_output = ops.convert_to_numpy(last_conv_layer_output)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Getting the gradient of the top class: JAX version" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "%%backend jax\n", "import jax\n", "\n", "def loss_fn(last_conv_layer_output):\n", " preds = classifier_model(last_conv_layer_output)\n", " top_pred_index = ops.argmax(preds[0])\n", " top_class_channel = preds[:, top_pred_index]\n", " return top_class_channel[0]\n", "\n", "grad_fn = jax.grad(loss_fn)\n", "\n", "def get_top_class_gradients(img_array):\n", " last_conv_layer_output = last_conv_layer_model(img_array)\n", " grads = grad_fn(last_conv_layer_output)\n", " return grads, last_conv_layer_output\n", "\n", "grads, last_conv_layer_output = get_top_class_gradients(img_array)\n", "grads = ops.convert_to_numpy(grads)\n", "last_conv_layer_output = ops.convert_to_numpy(last_conv_layer_output)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "#### Displaying the class activation heatmap" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "pooled_grads = np.mean(grads, axis=(0, 1, 2))\n", "last_conv_layer_output = last_conv_layer_output[0].copy()\n", "for i in range(pooled_grads.shape[-1]):\n", " last_conv_layer_output[:, :, i] *= pooled_grads[i]\n", "heatmap = np.mean(last_conv_layer_output, axis=-1)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "heatmap = np.maximum(heatmap, 0)\n", "heatmap /= np.max(heatmap)\n", "plt.matshow(heatmap)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab_type": "code" }, "outputs": [], "source": [ "import matplotlib.cm as cm\n", "\n", "img = keras.utils.load_img(img_path)\n", "img = keras.utils.img_to_array(img)\n", "\n", "heatmap = np.uint8(255 * heatmap)\n", "\n", "jet = cm.get_cmap(\"jet\")\n", "jet_colors = jet(np.arange(256))[:, :3]\n", "jet_heatmap = jet_colors[heatmap]\n", "\n", "jet_heatmap = keras.utils.array_to_img(jet_heatmap)\n", "jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))\n", "jet_heatmap = keras.utils.img_to_array(jet_heatmap)\n", "\n", "superimposed_img = jet_heatmap * 0.4 + img\n", "superimposed_img = keras.utils.array_to_img(superimposed_img)\n", "\n", "plt.imshow(superimposed_img)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text" }, "source": [ "### Visualizing the latent space of a ConvNet" ] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [], "name": "chapter10_interpreting-what-convnets-learn", "private_outputs": false, "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 0 }