{
 "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": [
    "## Image classification"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "### Introduction to ConvNets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import keras\n",
    "from keras import layers\n",
    "\n",
    "inputs = keras.Input(shape=(28, 28, 1))\n",
    "x = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(inputs)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.GlobalAveragePooling2D()(x)\n",
    "outputs = layers.Dense(10, activation=\"softmax\")(x)\n",
    "model = keras.Model(inputs=inputs, outputs=outputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "model.summary(line_length=80)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "from keras.datasets import mnist\n",
    "\n",
    "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\n",
    "train_images = train_images.reshape((60000, 28, 28, 1))\n",
    "train_images = train_images.astype(\"float32\") / 255\n",
    "test_images = test_images.reshape((10000, 28, 28, 1))\n",
    "test_images = test_images.astype(\"float32\") / 255\n",
    "model.compile(\n",
    "    optimizer=\"adam\",\n",
    "    loss=\"sparse_categorical_crossentropy\",\n",
    "    metrics=[\"accuracy\"],\n",
    ")\n",
    "model.fit(train_images, train_labels, epochs=5, batch_size=64)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "test_loss, test_acc = model.evaluate(test_images, test_labels)\n",
    "print(f\"Test accuracy: {test_acc:.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### The convolution operation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "##### Understanding border effects and padding"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "##### Understanding convolution strides"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### The max-pooling operation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "inputs = keras.Input(shape=(28, 28, 1))\n",
    "x = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(inputs)\n",
    "x = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.GlobalAveragePooling2D()(x)\n",
    "outputs = layers.Dense(10, activation=\"softmax\")(x)\n",
    "model_no_max_pool = keras.Model(inputs=inputs, outputs=outputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "model_no_max_pool.summary(line_length=80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "### Training a ConvNet from scratch on a small dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### The relevance of deep learning for small-data problems"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### Downloading the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import kagglehub\n",
    "\n",
    "kagglehub.login()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "download_path = kagglehub.competition_download(\"dogs-vs-cats\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import zipfile\n",
    "\n",
    "with zipfile.ZipFile(download_path + \"/train.zip\", \"r\") as zip_ref:\n",
    "    zip_ref.extractall(\".\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import os, shutil, pathlib\n",
    "\n",
    "original_dir = pathlib.Path(\"train\")\n",
    "new_base_dir = pathlib.Path(\"dogs_vs_cats_small\")\n",
    "\n",
    "def make_subset(subset_name, start_index, end_index):\n",
    "    for category in (\"cat\", \"dog\"):\n",
    "        dir = new_base_dir / subset_name / category\n",
    "        os.makedirs(dir)\n",
    "        fnames = [f\"{category}.{i}.jpg\" for i in range(start_index, end_index)]\n",
    "        for fname in fnames:\n",
    "            shutil.copyfile(src=original_dir / fname, dst=dir / fname)\n",
    "\n",
    "make_subset(\"train\", start_index=0, end_index=1000)\n",
    "make_subset(\"validation\", start_index=1000, end_index=1500)\n",
    "make_subset(\"test\", start_index=1500, end_index=2500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### Building your model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import keras\n",
    "from keras import layers\n",
    "\n",
    "inputs = keras.Input(shape=(180, 180, 3))\n",
    "x = layers.Rescaling(1.0 / 255)(inputs)\n",
    "x = layers.Conv2D(filters=32, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=512, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.GlobalAveragePooling2D()(x)\n",
    "outputs = layers.Dense(1, activation=\"sigmoid\")(x)\n",
    "model = keras.Model(inputs=inputs, outputs=outputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "model.summary(line_length=80)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "model.compile(\n",
    "    loss=\"binary_crossentropy\",\n",
    "    optimizer=\"adam\",\n",
    "    metrics=[\"accuracy\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### Data preprocessing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "from keras.utils import image_dataset_from_directory\n",
    "\n",
    "batch_size = 64\n",
    "image_size = (180, 180)\n",
    "train_dataset = image_dataset_from_directory(\n",
    "    new_base_dir / \"train\", image_size=image_size, batch_size=batch_size\n",
    ")\n",
    "validation_dataset = image_dataset_from_directory(\n",
    "    new_base_dir / \"validation\", image_size=image_size, batch_size=batch_size\n",
    ")\n",
    "test_dataset = image_dataset_from_directory(\n",
    "    new_base_dir / \"test\", image_size=image_size, batch_size=batch_size\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "##### Understanding TensorFlow Dataset objects"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "random_numbers = np.random.normal(size=(1000, 16))\n",
    "dataset = tf.data.Dataset.from_tensor_slices(random_numbers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "for i, element in enumerate(dataset):\n",
    "    print(element.shape)\n",
    "    if i >= 2:\n",
    "        break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "batched_dataset = dataset.batch(32)\n",
    "for i, element in enumerate(batched_dataset):\n",
    "    print(element.shape)\n",
    "    if i >= 2:\n",
    "        break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "reshaped_dataset = dataset.map(\n",
    "    lambda x: tf.reshape(x, (4, 4)),\n",
    "    num_parallel_calls=8)\n",
    "for i, element in enumerate(reshaped_dataset):\n",
    "    print(element.shape)\n",
    "    if i >= 2:\n",
    "        break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "##### Fitting the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "for data_batch, labels_batch in train_dataset:\n",
    "    print(\"data batch shape:\", data_batch.shape)\n",
    "    print(\"labels batch shape:\", labels_batch.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "callbacks = [\n",
    "    keras.callbacks.ModelCheckpoint(\n",
    "        filepath=\"convnet_from_scratch.keras\",\n",
    "        save_best_only=True,\n",
    "        monitor=\"val_loss\",\n",
    "    )\n",
    "]\n",
    "history = model.fit(\n",
    "    train_dataset,\n",
    "    epochs=50,\n",
    "    validation_data=validation_dataset,\n",
    "    callbacks=callbacks,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "accuracy = history.history[\"accuracy\"]\n",
    "val_accuracy = history.history[\"val_accuracy\"]\n",
    "loss = history.history[\"loss\"]\n",
    "val_loss = history.history[\"val_loss\"]\n",
    "epochs = range(1, len(accuracy) + 1)\n",
    "\n",
    "plt.plot(epochs, accuracy, \"r--\", label=\"Training accuracy\")\n",
    "plt.plot(epochs, val_accuracy, \"b\", label=\"Validation accuracy\")\n",
    "plt.title(\"Training and validation accuracy\")\n",
    "plt.legend()\n",
    "plt.figure()\n",
    "\n",
    "plt.plot(epochs, loss, \"r--\", label=\"Training loss\")\n",
    "plt.plot(epochs, val_loss, \"b\", label=\"Validation loss\")\n",
    "plt.title(\"Training and validation loss\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "test_model = keras.models.load_model(\"convnet_from_scratch.keras\")\n",
    "test_loss, test_acc = test_model.evaluate(test_dataset)\n",
    "print(f\"Test accuracy: {test_acc:.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### Using data augmentation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "data_augmentation_layers = [\n",
    "    layers.RandomFlip(\"horizontal\"),\n",
    "    layers.RandomRotation(0.1),\n",
    "    layers.RandomZoom(0.2),\n",
    "]\n",
    "\n",
    "def data_augmentation(images, targets):\n",
    "    for layer in data_augmentation_layers:\n",
    "        images = layer(images)\n",
    "    return images, targets\n",
    "\n",
    "augmented_train_dataset = train_dataset.map(\n",
    "    data_augmentation, num_parallel_calls=8\n",
    ")\n",
    "augmented_train_dataset = augmented_train_dataset.prefetch(tf.data.AUTOTUNE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 10))\n",
    "for image_batch, _ in train_dataset.take(1):\n",
    "    image = image_batch[0]\n",
    "    for i in range(9):\n",
    "        ax = plt.subplot(3, 3, i + 1)\n",
    "        augmented_image, _ = data_augmentation(image, None)\n",
    "        augmented_image = keras.ops.convert_to_numpy(augmented_image)\n",
    "        plt.imshow(augmented_image.astype(\"uint8\"))\n",
    "        plt.axis(\"off\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "inputs = keras.Input(shape=(180, 180, 3))\n",
    "x = layers.Rescaling(1.0 / 255)(inputs)\n",
    "x = layers.Conv2D(filters=32, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=64, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=128, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=256, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.MaxPooling2D(pool_size=2)(x)\n",
    "x = layers.Conv2D(filters=512, kernel_size=3, activation=\"relu\")(x)\n",
    "x = layers.GlobalAveragePooling2D()(x)\n",
    "x = layers.Dropout(0.25)(x)\n",
    "outputs = layers.Dense(1, activation=\"sigmoid\")(x)\n",
    "model = keras.Model(inputs=inputs, outputs=outputs)\n",
    "\n",
    "model.compile(\n",
    "    loss=\"binary_crossentropy\",\n",
    "    optimizer=\"adam\",\n",
    "    metrics=[\"accuracy\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "callbacks = [\n",
    "    keras.callbacks.ModelCheckpoint(\n",
    "        filepath=\"convnet_from_scratch_with_augmentation.keras\",\n",
    "        save_best_only=True,\n",
    "        monitor=\"val_loss\",\n",
    "    )\n",
    "]\n",
    "history = model.fit(\n",
    "    augmented_train_dataset,\n",
    "    epochs=100,\n",
    "    validation_data=validation_dataset,\n",
    "    callbacks=callbacks,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "test_model = keras.models.load_model(\n",
    "    \"convnet_from_scratch_with_augmentation.keras\"\n",
    ")\n",
    "test_loss, test_acc = test_model.evaluate(test_dataset)\n",
    "print(f\"Test accuracy: {test_acc:.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "### Using a pretrained model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### Feature extraction with a pretrained model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import keras_hub\n",
    "\n",
    "conv_base = keras_hub.models.Backbone.from_preset(\"xception_41_imagenet\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "preprocessor = keras_hub.layers.ImageConverter.from_preset(\n",
    "    \"xception_41_imagenet\",\n",
    "    image_size=(180, 180),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "##### Fast feature extraction without data augmentation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "def get_features_and_labels(dataset):\n",
    "    all_features = []\n",
    "    all_labels = []\n",
    "    for images, labels in dataset:\n",
    "        preprocessed_images = preprocessor(images)\n",
    "        features = conv_base.predict(preprocessed_images, verbose=0)\n",
    "        all_features.append(features)\n",
    "        all_labels.append(labels)\n",
    "    return np.concatenate(all_features), np.concatenate(all_labels)\n",
    "\n",
    "train_features, train_labels = get_features_and_labels(train_dataset)\n",
    "val_features, val_labels = get_features_and_labels(validation_dataset)\n",
    "test_features, test_labels = get_features_and_labels(test_dataset)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "train_features.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "inputs = keras.Input(shape=(6, 6, 2048))\n",
    "x = layers.GlobalAveragePooling2D()(inputs)\n",
    "x = layers.Dense(256, activation=\"relu\")(x)\n",
    "x = layers.Dropout(0.25)(x)\n",
    "outputs = layers.Dense(1, activation=\"sigmoid\")(x)\n",
    "model = keras.Model(inputs, outputs)\n",
    "model.compile(\n",
    "    loss=\"binary_crossentropy\",\n",
    "    optimizer=\"adam\",\n",
    "    metrics=[\"accuracy\"],\n",
    ")\n",
    "\n",
    "callbacks = [\n",
    "    keras.callbacks.ModelCheckpoint(\n",
    "        filepath=\"feature_extraction.keras\",\n",
    "        save_best_only=True,\n",
    "        monitor=\"val_loss\",\n",
    "    )\n",
    "]\n",
    "history = model.fit(\n",
    "    train_features,\n",
    "    train_labels,\n",
    "    epochs=10,\n",
    "    validation_data=(val_features, val_labels),\n",
    "    callbacks=callbacks,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "acc = history.history[\"accuracy\"]\n",
    "val_acc = history.history[\"val_accuracy\"]\n",
    "loss = history.history[\"loss\"]\n",
    "val_loss = history.history[\"val_loss\"]\n",
    "epochs = range(1, len(acc) + 1)\n",
    "plt.plot(epochs, acc, \"r--\", label=\"Training accuracy\")\n",
    "plt.plot(epochs, val_acc, \"b\", label=\"Validation accuracy\")\n",
    "plt.title(\"Training and validation accuracy\")\n",
    "plt.legend()\n",
    "plt.figure()\n",
    "plt.plot(epochs, loss, \"r--\", label=\"Training loss\")\n",
    "plt.plot(epochs, val_loss, \"b\", label=\"Validation loss\")\n",
    "plt.title(\"Training and validation loss\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "test_model = keras.models.load_model(\"feature_extraction.keras\")\n",
    "test_loss, test_acc = test_model.evaluate(test_features, test_labels)\n",
    "print(f\"Test accuracy: {test_acc:.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "##### Feature extraction together with data augmentation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "import keras_hub\n",
    "\n",
    "conv_base = keras_hub.models.Backbone.from_preset(\n",
    "    \"xception_41_imagenet\",\n",
    "    trainable=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "conv_base.trainable = True\n",
    "len(conv_base.trainable_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "conv_base.trainable = False\n",
    "len(conv_base.trainable_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "inputs = keras.Input(shape=(180, 180, 3))\n",
    "x = preprocessor(inputs)\n",
    "x = conv_base(x)\n",
    "x = layers.GlobalAveragePooling2D()(x)\n",
    "x = layers.Dense(256)(x)\n",
    "x = layers.Dropout(0.25)(x)\n",
    "outputs = layers.Dense(1, activation=\"sigmoid\")(x)\n",
    "model = keras.Model(inputs, outputs)\n",
    "model.compile(\n",
    "    loss=\"binary_crossentropy\",\n",
    "    optimizer=\"adam\",\n",
    "    metrics=[\"accuracy\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "callbacks = [\n",
    "    keras.callbacks.ModelCheckpoint(\n",
    "        filepath=\"feature_extraction_with_data_augmentation.keras\",\n",
    "        save_best_only=True,\n",
    "        monitor=\"val_loss\",\n",
    "    )\n",
    "]\n",
    "history = model.fit(\n",
    "    augmented_train_dataset,\n",
    "    epochs=30,\n",
    "    validation_data=validation_dataset,\n",
    "    callbacks=callbacks,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "test_model = keras.models.load_model(\n",
    "    \"feature_extraction_with_data_augmentation.keras\"\n",
    ")\n",
    "test_loss, test_acc = test_model.evaluate(test_dataset)\n",
    "print(f\"Test accuracy: {test_acc:.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text"
   },
   "source": [
    "#### Fine-tuning a pretrained model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "model.compile(\n",
    "    loss=\"binary_crossentropy\",\n",
    "    optimizer=keras.optimizers.Adam(learning_rate=1e-5),\n",
    "    metrics=[\"accuracy\"],\n",
    ")\n",
    "\n",
    "callbacks = [\n",
    "    keras.callbacks.ModelCheckpoint(\n",
    "        filepath=\"fine_tuning.keras\",\n",
    "        save_best_only=True,\n",
    "        monitor=\"val_loss\",\n",
    "    )\n",
    "]\n",
    "history = model.fit(\n",
    "    augmented_train_dataset,\n",
    "    epochs=30,\n",
    "    validation_data=validation_dataset,\n",
    "    callbacks=callbacks,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab_type": "code"
   },
   "outputs": [],
   "source": [
    "model = keras.models.load_model(\"fine_tuning.keras\")\n",
    "test_loss, test_acc = model.evaluate(test_dataset)\n",
    "print(f\"Test accuracy: {test_acc:.3f}\")"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [],
   "name": "chapter08_image-classification",
   "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
}