{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "colab": {
      "provenance": [],
      "gpuType": "T4",
      "include_colab_link": true
    },
    "accelerator": "GPU",
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/kuhyx/ARAI/blob/main/TWM_KerasIntro.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "c9tFwxET8YY5"
      },
      "source": [
        "%matplotlib inline"
      ],
      "execution_count": 1,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "M9PoJm628YY_"
      },
      "source": [
        "# Introduction to Deep Learning with Keras and TensorFlow\n",
        "\n",
        "Based on excelent work by **[Daniel Moser (UT Southwestern Medical Center)](https://github.com/AviatorMoser/keras-mnist-tutorial)**, Resources: **[Xavier Snelgrove](https://github.com/wxs/keras-mnist-tutorial), [Yash Katariya](https://github.com/yashk2810/MNIST-Keras)**"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lh0dsQg08YZA"
      },
      "source": [
        "To help you understand the fundamentals of deep learning, this demo will walk through the basic steps of building two toy models for classifying handwritten numbers with accuracies surpassing 95%. The first model will be a basic fully-connected neural network, and the second model will be a deeper network that introduces the concepts of convolution and pooling."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LJVXVJ6_8YZB"
      },
      "source": [
        "## The Task for the AI\n",
        "\n",
        "Our goal is to construct and train an artificial neural network on thousands of images of handwritten digits so that it may successfully identify others when presented. The data that will be incorporated is the MNIST database which contains 60,000 images for training and 10,000 test images. We will use the Keras Python API with TensorFlow as the backend."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3eVu55U98YZC"
      },
      "source": [
        "<img src=\"https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/mnist.png\" >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ms_a4hzY8YZC"
      },
      "source": [
        "## Prerequisite Python Modules\n",
        "\n",
        "First, some software needs to be loaded into the Python environment."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "D7Bns2Lo8YZD",
        "outputId": "4037ddf7-5ac4-4f77-f482-f0a02108c74c",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "import numpy as np                   # advanced math library\n",
        "import matplotlib.pyplot as plt      # MATLAB like plotting routines\n",
        "import random                        # for generating random numbers\n",
        "\n",
        "import tensorflow as tf\n",
        "\n",
        "from keras.datasets import mnist     # MNIST dataset is included in Keras%\n",
        "from keras.models import Sequential  # Model type to be used\n",
        "\n",
        "from keras.layers import Dense, Dropout, Activation # Types of layers to be used in our model\n",
        "from keras.utils import to_categorical                         # NumPy related tools\n",
        "\n",
        "from keras import optimizers\n",
        "\n",
        "\n",
        "from sklearn.metrics import confusion_matrix\n",
        "import itertools\n",
        "\n",
        "print(tf.__version__)"
      ],
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "2.15.0\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QygDQ7Ch8YZG"
      },
      "source": [
        "## Loading Training Data\n",
        "\n",
        "The MNIST dataset is conveniently bundled within Keras, and we can easily analyze some of its features in Python."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "SdZZph6i8YZG",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "ee9e11b8-6b44-4fa0-f974-e83d7999e930"
      },
      "source": [
        "# The MNIST data is split between 60,000 28 x 28 pixel training images and 10,000 28 x 28 pixel images\n",
        "(X_train, y_train), (X_test, y_test) = mnist.load_data()\n",
        "\n",
        "print(\"X_train shape\", X_train.shape)\n",
        "print(\"y_train shape\", y_train.shape)\n",
        "print(\"X_test shape\", X_test.shape)\n",
        "print(\"y_test shape\", y_test.shape)"
      ],
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz\n",
            "11490434/11490434 [==============================] - 2s 0us/step\n",
            "X_train shape (60000, 28, 28)\n",
            "y_train shape (60000,)\n",
            "X_test shape (10000, 28, 28)\n",
            "y_test shape (10000,)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9v5sjP468YZJ"
      },
      "source": [
        "Using matplotlib, we can plot some sample images from the training set directly into this Jupyter Notebook. We can egzamine the interclass variability - how many different ways of writing the same digit there are!"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Ov1T_A6X8YZJ",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 811
        },
        "outputId": "51c0509e-a421-4161-99c3-a44b346222bd"
      },
      "source": [
        "plt.rcParams['figure.figsize'] = (9,9) # Make the figures a bit bigger\n",
        "\n",
        "def visualize_classes(X, y):\n",
        "  for i in range(0, 10):\n",
        "    img_batch = X[y == i][0:10]\n",
        "    img_batch = np.reshape(img_batch, (img_batch.shape[0]*img_batch.shape[1], img_batch.shape[2]))\n",
        "    if i > 0:\n",
        "      img = np.concatenate([img, img_batch], axis = 1)\n",
        "    else:\n",
        "      img = img_batch\n",
        "  plt.figure(figsize=(10,20))\n",
        "  plt.axis('off')\n",
        "  plt.imshow(img, cmap='gray')\n",
        "\n",
        "\n",
        "visualize_classes(X_train, y_train)\n"
      ],
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1000x2000 with 1 Axes>"
            ],
            "image/png": "[encoded data, 339736 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uGjX5ZD68YZM"
      },
      "source": [
        "Let's examine a single digit a little closer, and print out the array representing the last digit."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "wW_rrJfj8YZN",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "5b74571c-eab3-455f-fa2b-aa7d6f1f28f3"
      },
      "source": [
        "# just a little function for pretty printing a matrix\n",
        "def matprint(mat, fmt=\"g\"):\n",
        "    col_maxes = [max([len((\"{:\"+fmt+\"}\").format(x)) for x in col]) for col in mat.T]\n",
        "    for x in mat:\n",
        "        for i, y in enumerate(x):\n",
        "            print((\"{:\"+str(col_maxes[i])+fmt+\"}\").format(y), end=\"  \")\n",
        "        print(\"\")\n",
        "\n",
        "# now print!\n",
        "matprint(X_train[-1])"
      ],
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0   38   48   48   22    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0   62   97  198  243  254  254  212   27    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0   67  172  254  254  225  218  218  237  248   40    0   21  164  187  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0   89  219  254   97   67   14    0    0   92  231  122   23  203  236   59  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0   25  217  242   92    4    0    0    0    0    4  147  253  240  232   92    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0  101  255   92    0    0    0    0    0    0  105  254  254  177   11    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0  167  244   41    0    0    0    7   76  199  238  239   94   10    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0  192  121    0    0    2   63  180  254  233  126    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0  190  196   14    2   97  254  252  146   52    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0  130  225   71  180  232  181   60    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0  130  254  254  230   46    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    6   77  244  254  162    4    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0  110  254  218  254  116    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0  131  254  154   28  213   86    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0   66  209  153   19   19  233   60    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0  142  254  165    0   14  216  167    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0   90  254  175    0   18  229   92    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0   26  229  249  176  222  244   44    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0   73  193  197  134    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n",
            "0  0  0  0  0  0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0  0  0  \n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "s3koPAgMdzoG"
      },
      "source": [],
      "execution_count": 5,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vQPPE2Bq8YZP"
      },
      "source": [
        "Each pixel is an 8-bit integer from 0-255. 0 is full black, while 255 is full white. This what we call a single-channel pixel. It's called monochrome.\n",
        "\n",
        "*Fun-fact! Your computer screen has three channels for each pixel: red, green, blue. Each of these channels also likely takes an 8-bit integer. 3 channels -- 24 bits total -- 16,777,216 possible colors!*"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "z09IKy7PR9M9"
      },
      "source": [
        "## More input data nalysis\n",
        "\n",
        "\n",
        "Visualizing high-dimensional data by projecting it into a low-dimensional space is a classic operation that anyone working with data has probably done at least once in their life. There are a huge variety of methods for reducing dimensionality, but one very popular method is t-SNE, a method proposed by Geoffry Hinton’s group back in 2008. [ _more..._ ](https://mlexplained.com/2018/09/14/paper-dissected-visualizing-data-using-t-sne-explained/)\n",
        "\n",
        "Code by [Zaid Alyafeai](https://github.com/zaidalyafeai/Notebooks) [MIT license]"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "59m4-pjCSBgM"
      },
      "source": [
        "from sklearn.manifold import TSNE\n",
        "import matplotlib.patheffects as PathEffects\n",
        "import seaborn as sns\n",
        "\n",
        "RS=19238\n",
        "\n",
        "class_names = [ str(clid) for clid in range(10) ]\n",
        "\n",
        "def scatter(x, colors):\n",
        "    # We choose a color palette with seaborn.\n",
        "    palette = np.array(sns.color_palette(\"hls\", 10))\n",
        "\n",
        "    # We create a scatter plot.\n",
        "    f = plt.figure(figsize=(8, 8))\n",
        "    ax = plt.subplot(aspect='equal')\n",
        "    sc = ax.scatter(x[:,0], x[:,1], lw=0, s=40,\n",
        "                    c=palette[colors.astype(\"int\")])\n",
        "    plt.xlim(-25, 25)\n",
        "    plt.ylim(-25, 25)\n",
        "    ax.axis('off')\n",
        "    ax.axis('tight')\n",
        "\n",
        "    # We add the labels for each digit.\n",
        "    txts = []\n",
        "    for i in range(10):\n",
        "        # Position of each label.\n",
        "        xtext, ytext = np.median(x[colors == i, :], axis=0)\n",
        "        txt = ax.text(xtext, ytext, class_names[i], fontsize=15)\n",
        "        txt.set_path_effects([\n",
        "            PathEffects.Stroke(linewidth=5, foreground=\"w\"),\n",
        "            PathEffects.Normal()])\n",
        "        txts.append(txt)\n",
        "\n",
        "def plot_tsne(X, y):\n",
        "  print('calculating tsne ...')\n",
        "  proj = TSNE(random_state=RS, learning_rate=200, init='random').fit_transform(X)\n",
        "  scatter(proj, y)"
      ],
      "execution_count": 6,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1_gbsFQ3Srw_",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 670
        },
        "outputId": "f20b5dea-b04d-4f61-857b-5785251c616e"
      },
      "source": [
        "X = np.reshape(X_train, (X_train.shape[0], 28 * 28))[0:2000]\n",
        "y = y_train[0:2000]\n",
        "plot_tsne(X, y)"
      ],
      "execution_count": 7,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "calculating tsne ...\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 800x800 with 1 Axes>"
            ],
            "image/png": "[encoded data, 200860 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UFWr5nQo8YZQ"
      },
      "source": [
        "## Formatting the input data layer\n",
        "\n",
        "Instead of a 28 x 28 matrix, we build our network to accept a 784-length vector.\n",
        "\n",
        "Each image needs to be then reshaped (or flattened) into a vector. We'll also normalize the inputs to be in the range [0-1] rather than [0-255]. Normalizing inputs is generally recommended, so that any additional dimensions (for other network architectures) are of the same scale."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B-b0XGKd8YZQ"
      },
      "source": [
        "<img src='https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/flatten.png' >"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0ge5s-pT8YZR",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "2e430092-404d-4abf-f2c0-5cbf97adb29a"
      },
      "source": [
        "X_train = X_train.reshape(60000, 784) # reshape 60,000 28 x 28 matrices into 60,000 784-length vectors.\n",
        "X_test = X_test.reshape(10000, 784)   # reshape 10,000 28 x 28 matrices into 10,000 784-length vectors.\n",
        "\n",
        "X_train = X_train.astype('float32')   # change integers to 32-bit floating point numbers\n",
        "X_test = X_test.astype('float32')\n",
        "\n",
        "X_train /= 255                        # normalize each value for each pixel for the entire vector for each input\n",
        "X_test /= 255\n",
        "\n",
        "print(\"Training matrix shape\", X_train.shape)\n",
        "print(\"Testing matrix shape\", X_test.shape)"
      ],
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Training matrix shape (60000, 784)\n",
            "Testing matrix shape (10000, 784)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sS8jHU6V8YZT"
      },
      "source": [
        "We then modify our classes (unique digits) to be in the one-hot (categorical) format, i.e.\n",
        "\n",
        "```\n",
        "0 -> [1, 0, 0, 0, 0, 0, 0, 0, 0]\n",
        "1 -> [0, 1, 0, 0, 0, 0, 0, 0, 0]\n",
        "2 -> [0, 0, 1, 0, 0, 0, 0, 0, 0]\n",
        "etc.\n",
        "```\n",
        "\n",
        "If the final output of our network is very close to one of these classes, then it is most likely that class. For example, if the final output is:\n",
        "\n",
        "```\n",
        "[0, 0.94, 0, 0, 0, 0, 0.06, 0, 0]\n",
        "```\n",
        "then it is most probable that the image is that of the digit `1`."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "AFvPkCFw8YZU"
      },
      "source": [
        "nb_classes = 10 # number of unique digits\n",
        "\n",
        "Y_train = to_categorical(y_train, nb_classes)\n",
        "Y_test = to_categorical(y_test, nb_classes)"
      ],
      "execution_count": 9,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "q_B8ZH5G8YZX"
      },
      "source": [
        "# Building a 3-layer fully connected network\n",
        "\n",
        "<img src=\"https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/figure.png\" />"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "M-uOWlRv8YZX"
      },
      "source": [
        "# The Sequential model is a linear stack of layers and is very common.\n",
        "\n",
        "model = Sequential()"
      ],
      "execution_count": 10,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iyoUd9WE8YZa"
      },
      "source": [
        "## The first hidden layer"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "IdgP4lai8YZb"
      },
      "source": [
        "# The first hidden layer is a set of 512 nodes (artificial neurons).\n",
        "# Each node will receive an element from each input vector and apply some weight and bias to it.\n",
        "\n",
        "model.add(Dense(512, input_shape=(784,))) #(784,) is not a typo -- that represents a 784 length vector!"
      ],
      "execution_count": 11,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "c1MiY3u68YZd"
      },
      "source": [
        "# An \"activation\" is a non-linear function applied to the output of the layer above.\n",
        "# It checks the new value of the node, and decides whether that artifical neuron has fired.\n",
        "# The Rectified Linear Unit (ReLU) converts all negative inputs to nodes in the next layer to be zero.\n",
        "# Those inputs are then not considered to be fired.\n",
        "# Positive values of a node are unchanged.\n",
        "\n",
        "model.add(Activation('relu'))"
      ],
      "execution_count": 12,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tQCRmiMI8YZg"
      },
      "source": [
        "$$f(x) = max (0,x)$$\n",
        "<img src = 'https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/relu.jpg' >"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kudeX_kN8YZg"
      },
      "source": [
        "# Dropout zeroes a selection of random outputs (i.e., disables their activation)\n",
        "# Dropout helps protect the model from memorizing or \"overfitting\" the training data.\n",
        "model.add(Dropout(0.2))"
      ],
      "execution_count": 13,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PY5XiXYc8YZj"
      },
      "source": [
        "## Adding the second hidden layer"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Tnz-mpIS8YZj"
      },
      "source": [
        "# The second hidden layer appears identical to our first layer.\n",
        "# However, instead of each of the 512-node receiving 784-inputs from the input image data,\n",
        "# they receive 512 inputs from the output of the first 512-node layer.\n",
        "\n",
        "model.add(Dense(512))\n",
        "model.add(Activation('relu'))\n",
        "model.add(Dropout(0.2))"
      ],
      "execution_count": 14,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vkeHSS0h8YZm"
      },
      "source": [
        "## The Final Output Layer"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "cndhIgBK8YZm"
      },
      "source": [
        "# The final layer of 10 neurons in fully-connected to the previous 512-node layer.\n",
        "# The final layer of a FCN should be equal to the number of desired classes (10 in this case).\n",
        "model.add(Dense(10))"
      ],
      "execution_count": 15,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "02Zk2VWL8YZp"
      },
      "source": [
        "# The \"softmax\" activation represents a probability distribution over K different possible outcomes.\n",
        "# Its values are all non-negative and sum to 1.\n",
        "\n",
        "model.add(Activation('softmax'))"
      ],
      "execution_count": 16,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "YReJLqWL8YZr",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "acf9807b-a564-41d7-fbb7-a212242469e5"
      },
      "source": [
        "# Summarize the built model\n",
        "\n",
        "model.summary()"
      ],
      "execution_count": 17,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model: \"sequential\"\n",
            "_________________________________________________________________\n",
            " Layer (type)                Output Shape              Param #   \n",
            "=================================================================\n",
            " dense (Dense)               (None, 512)               401920    \n",
            "                                                                 \n",
            " activation (Activation)     (None, 512)               0         \n",
            "                                                                 \n",
            " dropout (Dropout)           (None, 512)               0         \n",
            "                                                                 \n",
            " dense_1 (Dense)             (None, 512)               262656    \n",
            "                                                                 \n",
            " activation_1 (Activation)   (None, 512)               0         \n",
            "                                                                 \n",
            " dropout_1 (Dropout)         (None, 512)               0         \n",
            "                                                                 \n",
            " dense_2 (Dense)             (None, 10)                5130      \n",
            "                                                                 \n",
            " activation_2 (Activation)   (None, 10)                0         \n",
            "                                                                 \n",
            "=================================================================\n",
            "Total params: 669706 (2.55 MB)\n",
            "Trainable params: 669706 (2.55 MB)\n",
            "Non-trainable params: 0 (0.00 Byte)\n",
            "_________________________________________________________________\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UcndsYyk8YZv"
      },
      "source": [
        "## Compiling the model\n",
        "\n",
        "Keras is built on top of Theano and TensorFlow. Both packages allow you to define a *computation graph* in Python, which then compiles and runs efficiently on the CPU or GPU without the overhead of the Python interpreter.\n",
        "\n",
        "When compiing a model, Keras asks you to specify your **loss function** and your **optimizer**. The loss function we'll use here is called *categorical cross-entropy*, and is a loss function well-suited to comparing two probability distributions.\n",
        "\n",
        "Our predictions are probability distributions across the ten different digits (e.g. \"we're 80% confident this image is a 3, 10% sure it's an 8, 5% it's a 2, etc.\"), and the target is a probability distribution with 100% for the correct category, and 0 for everything else. The cross-entropy is a measure of how different your predicted distribution is from the target distribution. [More detail at Wikipedia](https://en.wikipedia.org/wiki/Cross_entropy)\n",
        "\n",
        "The optimizer helps determine how quickly the model learns through **gradient descent**. The rate at which descends a gradient is called the **learning rate**."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Wsv1Z9LC8YZw"
      },
      "source": [
        "<img src = \"https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/gradient_descent.png\" >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vZWeBx-58YZx"
      },
      "source": [
        "<img src = \"https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/learning_rate.png\" >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ptVA6aSg8YZy"
      },
      "source": [
        "So are smaller learning rates better? Not quite! It's important for an optimizer not to get stuck in local minima while neglecting the global minimum of the loss function. Sometimes that means trying a larger learning rate to jump out of a local minimum."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zSGiajLT8YZ1"
      },
      "source": [
        "<img src = 'https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/complicated_loss_function.png' >"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7RUmQxfp8YZ2"
      },
      "source": [
        "# Let's use the Adam optimizer for learning\n",
        "adam = tf.optimizers.Adam(learning_rate=0.001)\n",
        "model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])"
      ],
      "execution_count": 18,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d-u8EqW48YZ3"
      },
      "source": [
        "## Train the model!\n",
        "This is the fun part!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rTynqX-H8YZ4"
      },
      "source": [
        "The batch size determines over how much data per step is used to compute the loss function, gradients, and back propagation. Large batch sizes allow the network to complete it's training faster; however, there are other factors beyond training speed to consider.\n",
        "\n",
        "Too large of a batch size smoothes the local minima of the loss function, causing the optimizer to settle in one because it thinks it found the global minimum.\n",
        "\n",
        "Too small of a batch size creates a very noisy loss function, and the optimizer may never find the global minimum.\n",
        "\n",
        "So a good batch size may take some trial and error to find!"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "MxhTSlBI8YZ5",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "88c04566-8028-4fd6-fea8-0a2c728eaa64"
      },
      "source": [
        "model.fit(X_train, Y_train,\n",
        "          batch_size=128, epochs=5,\n",
        "          verbose=1)"
      ],
      "execution_count": 19,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch 1/5\n",
            "469/469 [==============================] - 5s 4ms/step - loss: 0.2438 - accuracy: 0.9265\n",
            "Epoch 2/5\n",
            "469/469 [==============================] - 2s 3ms/step - loss: 0.1011 - accuracy: 0.9684\n",
            "Epoch 3/5\n",
            "469/469 [==============================] - 2s 5ms/step - loss: 0.0716 - accuracy: 0.9775\n",
            "Epoch 4/5\n",
            "469/469 [==============================] - 2s 5ms/step - loss: 0.0564 - accuracy: 0.9816\n",
            "Epoch 5/5\n",
            "469/469 [==============================] - 2s 3ms/step - loss: 0.0450 - accuracy: 0.9852\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<keras.src.callbacks.History at 0x7f0d88148e20>"
            ]
          },
          "metadata": {},
          "execution_count": 19
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YufwyCC38YZ6"
      },
      "source": [
        "The two numbers, in order, represent the value of the loss function of the network on the training set, and the overall accuracy of the network on the training data. But how does it do on data it did not train on?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wQORnbe08YZ7"
      },
      "source": [
        "## Evaluate Model's Accuracy on Test Data"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "SX3n6p108YZ7",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "989db8c3-3559-47bd-b32d-a84a3855f339"
      },
      "source": [
        "score = model.evaluate(X_test, Y_test)\n",
        "print('Test score:', score[0])\n",
        "print('Test accuracy:', score[1])"
      ],
      "execution_count": 20,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "313/313 [==============================] - 1s 3ms/step - loss: 0.0637 - accuracy: 0.9812\n",
            "Test score: 0.0636596530675888\n",
            "Test accuracy: 0.9811999797821045\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NYD0_NjusHRL"
      },
      "source": [
        "def plot_confusion_matrix(cm, classes,\n",
        "                          normalize=False,\n",
        "                          title='Confusion matrix',\n",
        "                          cmap=plt.cm.Blues):\n",
        "    \"\"\"\n",
        "    This function prints and plots the confusion matrix.\n",
        "    Normalization can be applied by setting `normalize=True`.\n",
        "    \"\"\"\n",
        "    if normalize:\n",
        "        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n",
        "        print(\"Normalized confusion matrix\")\n",
        "    else:\n",
        "        print('Confusion matrix, without normalization')\n",
        "\n",
        "    print(cm)\n",
        "\n",
        "\n",
        "    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n",
        "    plt.title(title)\n",
        "    plt.colorbar()\n",
        "    tick_marks = np.arange(len(classes))\n",
        "    plt.xticks(tick_marks, classes, rotation=45)\n",
        "    plt.yticks(tick_marks, classes)\n",
        "\n",
        "    fmt = '.2f' if normalize else 'd'\n",
        "    thresh = cm.max() / 2.\n",
        "    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n",
        "        plt.text(j, i, format(cm[i, j], fmt),\n",
        "                 horizontalalignment=\"center\",\n",
        "                 color=\"white\" if cm[i, j] > thresh else \"black\")\n",
        "\n",
        "    plt.ylabel('True label')\n",
        "    plt.xlabel('Predicted label')\n",
        "    plt.tight_layout()"
      ],
      "execution_count": 21,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yWaT13H78YZ9"
      },
      "source": [
        "### Inspecting the output\n",
        "\n",
        "It's always a good idea to inspect the output and make sure everything looks sane. Here we'll look at some examples it gets right, and some examples it gets wrong."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "LFECu0nE8YZ-",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "outputId": "f534eaf7-27ee-4861-8df4-aa8d9c03b489"
      },
      "source": [
        "# The predict_classes function outputs the highest probability class\n",
        "# according to the trained classifier for each input example.\n",
        "predict = model.predict(X_test)\n",
        "predicted_classes = np.argmax(predict,axis=1)\n",
        "\n",
        "# Check which items we got right / wrong\n",
        "correct_indices = np.nonzero(predicted_classes == y_test)[0]\n",
        "incorrect_indices = np.nonzero(predicted_classes != y_test)[0]\n",
        "cnf_matrix = confusion_matrix(y_test, predicted_classes)\n",
        "\n",
        "class_names = [str(i) for i in range(10)]\n",
        "\n",
        "# Plot non-normalized confusion matrix\n",
        "plt.figure()\n",
        "plot_confusion_matrix(cnf_matrix, classes=class_names,\n",
        "                      title='Confusion matrix, without normalization')\n",
        "\n",
        "plt.show()\n"
      ],
      "execution_count": 22,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "313/313 [==============================] - 1s 2ms/step\n",
            "Confusion matrix, without normalization\n",
            "[[ 974    1    0    0    0    0    0    2    2    1]\n",
            " [   0 1124    3    0    0    0    2    1    5    0]\n",
            " [   2    1 1012    3    3    0    2    6    2    1]\n",
            " [   0    1    2  992    0    5    0    4    1    5]\n",
            " [   0    0    4    0  952    0    4    1    0   21]\n",
            " [   2    0    0    6    1  872    2    2    2    5]\n",
            " [   4    2    0    0    7    4  941    0    0    0]\n",
            " [   0    4    5    3    0    0    0 1012    0    4]\n",
            " [   1    0    6    3    4    5    2    6  940    7]\n",
            " [   1    2    0    3    3    0    0    7    0  993]]\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 2 Axes>"
            ],
            "image/png": "[encoded data, 61532 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "d4tL55qnBfAA"
      },
      "execution_count": 22,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ct14A0rsD-Ja"
      },
      "source": [
        "**Correct predictions**"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ew6oWSoY8YaA",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 906
        },
        "outputId": "040cbfff-02a8-431e-da94-8a67510fa803"
      },
      "source": [
        "def show_samples(indices, preds, images, labels, count=3, names = []):\n",
        "    plt.figure()\n",
        "    for i, sample in enumerate(indices[:count**2]):\n",
        "        pred_id = int(np.argmax(preds[sample]))\n",
        "        real_id = int(labels[sample])\n",
        "        pred_score = preds[sample][pred_id]\n",
        "        real_score = preds[sample][real_id]\n",
        "        plt.subplot(count,count,i+1)\n",
        "        plt.imshow(images[sample].reshape(28,28), cmap='gray', interpolation='none')\n",
        "        plt.axis('off')\n",
        "        if len(names) > 0:\n",
        "          plt.title(\"P: {} ({:.2f})\\nE: {} ({:.2f})\".format(names[pred_id], pred_score, names[real_id], real_score))\n",
        "        else:\n",
        "          plt.title(\"P: {} ({:.2f})\\nE: {} ({:.2f})\".format(pred_id, pred_score, real_id, real_score))\n",
        "\n",
        "    plt.tight_layout()\n",
        "\n",
        "show_samples(correct_indices, predict, X_test, y_test, 5)"
      ],
      "execution_count": 23,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 25 Axes>"
            ],
            "image/png": "[encoded data, 78808 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fhZp92ZxEGn4"
      },
      "source": [
        "**Wrong predictions**"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "FrAIwG1CEFxe",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 906
        },
        "outputId": "4717e081-20c2-410b-8cf9-c8161eed590d"
      },
      "source": [
        "show_samples(incorrect_indices, predict, X_test, y_test, 5)"
      ],
      "execution_count": 24,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 25 Axes>"
            ],
            "image/png": "[encoded data, 100132 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6ZzALozi8YaD"
      },
      "source": [
        "# Try experimenting with the batch size!\n",
        "\n",
        "   * How does increasing the batch size to 10,000 affect the training time and test accuracy?\n",
        "   * How about a batch size of 32?\n",
        "   * Is there any difference in results between the students? If so - why?\n",
        "   * Experiment with the learning rate in the optimizer"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uPUgO1BY8YaD"
      },
      "source": [
        "# Introducing Convolution! What is it?"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vs75ypBn8YaE"
      },
      "source": [
        "Before, we built a network that accepts the normalized pixel values of each value and operates soley on those values. What if we could instead feed different features (e.g. **curvature, edges**) of each image into a network, and have the network learn which features are important for classifying an image?\n",
        "\n",
        "This possible through convolution! Convolution applies **kernels** (filters) that traverse through each image and generate **feature maps**."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eVIKBobD8YaF"
      },
      "source": [
        "<img src = 'https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/convolution.gif' >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gYJDfK4p8YaG"
      },
      "source": [
        "In the above example, the image is a 5 x 5 matrix and the kernel going over it is a 3 x 3 matrix. A dot product operation takes place between the image and the kernel and the convolved feature is generated. Each kernel in a CNN learns a different characteristic of an image.\n",
        "\n",
        "Kernels are often used in photoediting software to apply blurring, edge detection, sharpening, etc."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qm9CKpnr8YaH"
      },
      "source": [
        "<img src = 'https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/kernels.png' >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QdHIoa-E8YaH"
      },
      "source": [
        "Kernels in deep learning networks are used in similar ways, i.e. highlighting some feature. Combined with a system called **max pooling**, the non-highlighted elements are discarded from each feature map, leaving only the features of interest, reducing the number of learned parameters, and decreasing the computational cost (e.g. system memory)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ceCEMpHf8YaI"
      },
      "source": [
        "<img src = 'https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/max_pooling.png' >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "poGu943u8YaJ"
      },
      "source": [
        "We can also take convolutions of convolutions -- we can stack as many convolutions as we want, as long as there are enough pixels to fit a kernel.\n",
        "\n",
        "*Warning: What you may find down there in those deep convolutions may not appear recognizable to you.*"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "u7KmEU3m8YaJ"
      },
      "source": [
        "<img src = 'https://github.com/wut-mpg/keras-mnist-tutorial/raw/master/go_deeper.jpg' >"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h88R-Bsa8YaK"
      },
      "source": [
        "## Building a \"Deep\" Convolutional Neural Network"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qoXIbzAG8YaL"
      },
      "source": [
        "# import some additional tools\n",
        "\n",
        "from keras.preprocessing.image import ImageDataGenerator\n",
        "from keras.layers import Conv2D, MaxPooling2D, ZeroPadding2D, GlobalAveragePooling2D, Flatten\n",
        "from keras.layers import BatchNormalization"
      ],
      "execution_count": 25,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "w0MBTlCL8YaM"
      },
      "source": [
        "# Reload the MNIST data\n",
        "(X_train, y_train), (X_test, y_test) = mnist.load_data()"
      ],
      "execution_count": 26,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TbBjjLil8YaO",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "5273e5ab-be9b-4dd9-c807-e28ccd847a5f"
      },
      "source": [
        "# Again, do some formatting\n",
        "# Except we do not flatten each image into a 784-length vector because we want to perform convolutions first\n",
        "\n",
        "X_train = X_train.reshape(60000, 28, 28, 1) #add an additional dimension to represent the single-channel\n",
        "X_test = X_test.reshape(10000, 28, 28, 1)\n",
        "\n",
        "X_train = X_train.astype('float32')         # change integers to 32-bit floating point numbers\n",
        "X_test = X_test.astype('float32')\n",
        "\n",
        "X_train /= 255                              # normalize each value for each pixel for the entire vector for each input\n",
        "X_test /= 255\n",
        "\n",
        "print(\"Training matrix shape\", X_train.shape)\n",
        "print(\"Testing matrix shape\", X_test.shape)"
      ],
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Training matrix shape (60000, 28, 28, 1)\n",
            "Testing matrix shape (10000, 28, 28, 1)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "aNNglEKV8YaR"
      },
      "source": [
        "# one-hot format classes\n",
        "\n",
        "nb_classes = 10 # number of unique digits\n",
        "\n",
        "Y_train = to_categorical(y_train, nb_classes)\n",
        "Y_test = to_categorical(y_test, nb_classes)"
      ],
      "execution_count": 28,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "buX5gLwP8YaT"
      },
      "source": [
        "model = Sequential()                                 # Linear stacking of layers\n",
        "\n",
        "# Convolution Layer 1\n",
        "model.add(Conv2D(16, (5, 5), input_shape=(28,28,1))) # 16 different 5x5 kernels -- so 16 feature maps\n",
        "model.add(Activation('relu') )                       # activation\n",
        "model.add(MaxPooling2D(pool_size=(2,2)))             # Pool the max values over a 2x2 kernel\n",
        "\n",
        "# Convolution Layer 2\n",
        "model.add(Conv2D(32, (5, 5)))                        # 32 different 5x5 kernels -- so 32 feature maps\n",
        "model.add(Activation('relu'))                        # activation\n",
        "model.add(MaxPooling2D(pool_size=(2,2)))             # Pool the max values over a 2x2 kernel\n",
        "\n",
        "model.add(Flatten())                                 # Flatten final output matrix into a vector\n",
        "\n",
        "# Fully Connected Layer\n",
        "model.add(Dense(128))                                # 128 FC nodes\n",
        "model.add(Activation('relu'))                        # activation\n",
        "\n",
        "# Fully Connected Layer\n",
        "model.add(Dense(10))                                 # final 10 FC nodes\n",
        "model.add(Activation('softmax'))                     # softmax activation"
      ],
      "execution_count": 29,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "srtd-OZV8YaV",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "93630a9d-2080-4624-9586-3ad2820774de"
      },
      "source": [
        "model.summary()"
      ],
      "execution_count": 30,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model: \"sequential_1\"\n",
            "_________________________________________________________________\n",
            " Layer (type)                Output Shape              Param #   \n",
            "=================================================================\n",
            " conv2d (Conv2D)             (None, 24, 24, 16)        416       \n",
            "                                                                 \n",
            " activation_3 (Activation)   (None, 24, 24, 16)        0         \n",
            "                                                                 \n",
            " max_pooling2d (MaxPooling2  (None, 12, 12, 16)        0         \n",
            " D)                                                              \n",
            "                                                                 \n",
            " conv2d_1 (Conv2D)           (None, 8, 8, 32)          12832     \n",
            "                                                                 \n",
            " activation_4 (Activation)   (None, 8, 8, 32)          0         \n",
            "                                                                 \n",
            " max_pooling2d_1 (MaxPoolin  (None, 4, 4, 32)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " flatten (Flatten)           (None, 512)               0         \n",
            "                                                                 \n",
            " dense_3 (Dense)             (None, 128)               65664     \n",
            "                                                                 \n",
            " activation_5 (Activation)   (None, 128)               0         \n",
            "                                                                 \n",
            " dense_4 (Dense)             (None, 10)                1290      \n",
            "                                                                 \n",
            " activation_6 (Activation)   (None, 10)                0         \n",
            "                                                                 \n",
            "=================================================================\n",
            "Total params: 80202 (313.29 KB)\n",
            "Trainable params: 80202 (313.29 KB)\n",
            "Non-trainable params: 0 (0.00 Byte)\n",
            "_________________________________________________________________\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TJ5vnJet8YaX"
      },
      "source": [
        "# we'll use the same optimizer\n",
        "adam = tf.optimizers.Adam(learning_rate=0.001)\n",
        "model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])"
      ],
      "execution_count": 31,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "WShqV9h28Yaa"
      },
      "source": [
        "# data augmentation prevents overfitting by slightly changing the data randomly\n",
        "# Keras has a great built-in feature to do automatic augmentation\n",
        "\n",
        "gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3,\n",
        "                         height_shift_range=0.08, zoom_range=0.08, validation_split=0.2)\n",
        "\n",
        "test_gen = ImageDataGenerator()"
      ],
      "execution_count": 32,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "VsGRzqqd8Yab"
      },
      "source": [
        "# We can then feed our augmented data in batches\n",
        "# Besides loss function considerations as before, this method actually results in significant memory savings\n",
        "# because we are actually LOADING the data into the network in batches before processing each batch\n",
        "\n",
        "# Before the data was all loaded into memory, but then processed in batches.\n",
        "\n",
        "train_generator = gen.flow(X_train, Y_train, batch_size=128, subset='training')\n",
        "valid_generator = gen.flow(X_train, Y_train, batch_size=128, subset='validation')\n",
        "test_generator = test_gen.flow(X_test, Y_test, batch_size=128)"
      ],
      "execution_count": 33,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_DXSGa-z8Yae",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "b898cc08-08ba-4466-83d2-bb9427cd790a"
      },
      "source": [
        "# We can now train our model which is fed data by our batch loader\n",
        "# Steps per epoch should always be total size of the set divided by the batch size\n",
        "\n",
        "# SIGNIFICANT MEMORY SAVINGS (important for larger, deeper networks)\n",
        "\n",
        "model.fit(train_generator, steps_per_epoch=48000//128, epochs=5, verbose=1, validation_data=valid_generator, validation_steps = 12000 // 128)"
      ],
      "execution_count": 34,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch 1/5\n",
            "375/375 [==============================] - 24s 52ms/step - loss: 0.4303 - accuracy: 0.8682 - val_loss: 0.1679 - val_accuracy: 0.9474\n",
            "Epoch 2/5\n",
            "375/375 [==============================] - 19s 52ms/step - loss: 0.1356 - accuracy: 0.9595 - val_loss: 0.1257 - val_accuracy: 0.9621\n",
            "Epoch 3/5\n",
            "375/375 [==============================] - 18s 47ms/step - loss: 0.0981 - accuracy: 0.9704 - val_loss: 0.0902 - val_accuracy: 0.9724\n",
            "Epoch 4/5\n",
            "375/375 [==============================] - 17s 46ms/step - loss: 0.0783 - accuracy: 0.9763 - val_loss: 0.0735 - val_accuracy: 0.9785\n",
            "Epoch 5/5\n",
            "375/375 [==============================] - 19s 52ms/step - loss: 0.0666 - accuracy: 0.9791 - val_loss: 0.0713 - val_accuracy: 0.9780\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<keras.src.callbacks.History at 0x7f0cf03883a0>"
            ]
          },
          "metadata": {},
          "execution_count": 34
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6YZaV3U-8Yah",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "d83dfad8-59c1-454c-9808-87122e10f625"
      },
      "source": [
        "score = model.evaluate(X_test, Y_test)\n",
        "print('Test score:', score[0])\n",
        "print('Test accuracy:', score[1])"
      ],
      "execution_count": 35,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "313/313 [==============================] - 1s 3ms/step - loss: 0.0291 - accuracy: 0.9902\n",
            "Test score: 0.0290591549128294\n",
            "Test accuracy: 0.9901999831199646\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "WqvF3eS2pnLp",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "outputId": "cee59a44-ec0c-480e-9247-e60c74c92660"
      },
      "source": [
        "# The predict_classes function outputs the highest probability class\n",
        "# according to the trained classifier for each input example.\n",
        "predict = model.predict(X_test)\n",
        "predicted_classes = np.argmax(predict,axis=1)\n",
        "\n",
        "# Check which items we got right / wrong\n",
        "correct_indices = np.nonzero(predicted_classes == y_test)[0]\n",
        "\n",
        "incorrect_indices = np.nonzero(predicted_classes != y_test)[0]\n",
        "\n",
        "\n",
        "cnf_matrix = confusion_matrix(y_test, predicted_classes)\n",
        "\n",
        "class_names = [str(i) for i in range(10)]\n",
        "\n",
        "# Plot non-normalized confusion matrix\n",
        "plt.figure()\n",
        "plot_confusion_matrix(cnf_matrix, classes=class_names,\n",
        "                      title='Confusion matrix, without normalization')\n",
        "\n",
        "plt.show()"
      ],
      "execution_count": 36,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "313/313 [==============================] - 1s 2ms/step\n",
            "Confusion matrix, without normalization\n",
            "[[ 976    0    1    0    0    0    0    1    2    0]\n",
            " [   0 1122    1    1    1    0    2    2    5    1]\n",
            " [   1    1 1017    0    0    0    0   11    2    0]\n",
            " [   0    0    1 1004    0    0    0    2    2    1]\n",
            " [   0    0    0    0  973    0    1    0    0    8]\n",
            " [   0    0    0    4    0  884    2    1    0    1]\n",
            " [   1    2    1    0    1    5  944    0    4    0]\n",
            " [   0    1    2    1    3    0    0 1014    0    7]\n",
            " [   0    0    1    0    0    1    0    1  965    6]\n",
            " [   0    0    0    1    5    0    0    0    0 1003]]\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 2 Axes>"
            ],
            "image/png": "[encoded data, 58212 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Wn7FDt7DqSKB",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 906
        },
        "outputId": "8598c5ba-17e3-4da4-a20e-c2c9cfc4b060"
      },
      "source": [
        "show_samples(correct_indices, predict, X_test, y_test, 5)"
      ],
      "execution_count": 37,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 25 Axes>"
            ],
            "image/png": "[encoded data, 79548 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "r0m_gom9qL3o",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 906
        },
        "outputId": "c03db89b-056c-4412-e1f0-687c03007376"
      },
      "source": [
        "show_samples(incorrect_indices, predict, X_test, y_test, 5)"
      ],
      "execution_count": 38,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 25 Axes>"
            ],
            "image/png": "[encoded data, 98328 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RsY5uPgU8Yak"
      },
      "source": [
        "## Great results!\n",
        "\n",
        "But wouldn't it be nice if we could visualize those convolutions so that we can see what the model is seeing?"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "BgbunToX8Yal"
      },
      "source": [
        "def visualize2(model, layer, img, r, c):\n",
        "  from keras import Model\n",
        "  # expand dimensions so that it represents a single 'sample'\n",
        "  img = np.expand_dims(img, axis=0)\n",
        "  outputs = [layer.output]\n",
        "  model = Model(inputs=model.inputs, outputs=outputs)\n",
        "  # get feature map for first hidden layer\n",
        "  feature_maps = model.predict(img)\n",
        "  for fmap in feature_maps:\n",
        "  # plot all 64 maps in an 8x8 squares\n",
        "    ix = 1\n",
        "    for _ in range(r):\n",
        "      for _ in range(c):\n",
        "        # specify subplot and turn of axis\n",
        "        ax = plt.subplot(r, c, ix)\n",
        "        ax.set_xticks([])\n",
        "        ax.set_yticks([])\n",
        "        # plot filter channel in grayscale\n",
        "        plt.imshow(fmap[:, :, ix-1], cmap='gray')\n",
        "        ix += 1"
      ],
      "execution_count": 39,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ZiDemlgp8Yan",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 770
        },
        "outputId": "1fd81fd0-2e08-40de-fcc2-2f8d1cccb533"
      },
      "source": [
        "plt.figure()\n",
        "img = X_test[11]\n",
        "plt.imshow(img[:,:,0], cmap='gray', interpolation='none')"
      ],
      "execution_count": 40,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<matplotlib.image.AxesImage at 0x7f0d78ec5a20>"
            ]
          },
          "metadata": {},
          "execution_count": 40
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 1 Axes>"
            ],
            "image/png": "[encoded data, 13120 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "UklZv03C8Yao",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 747
        },
        "outputId": "a31a7e23-e735-46c1-a323-3c49d430527a"
      },
      "source": [
        "visualize2(model, model.layers[1], img, 4, 4)"
      ],
      "execution_count": 41,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1/1 [==============================] - 0s 82ms/step\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 16 Axes>"
            ],
            "image/png": "[encoded data, 32776 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "gt8S9bzR8Yar",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 670
        },
        "outputId": "97bb3961-0315-449e-8200-3637b42ce121"
      },
      "source": [
        "visualize2(model, model.layers[4], img, 4, 8)"
      ],
      "execution_count": 42,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1/1 [==============================] - 0s 169ms/step\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 32 Axes>"
            ],
            "image/png": "[encoded data, 14528 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "okUztZOd79by"
      },
      "source": [
        "# Transfer learning"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8MRwLZgGhK-K"
      },
      "source": [
        "## Funkcje pomocnicze"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1AIiBfX78luv"
      },
      "source": [
        "# Loading the specified subset of digits from the MNIST dataset\n",
        "def get_mnist(digits):\n",
        "  # Reload the MNIST data\n",
        "  (X_train, y_train), (X_test, y_test) = mnist.load_data()\n",
        "\n",
        "  train_ids = np.in1d(y_train, digits)\n",
        "  test_ids = np.in1d(y_test, digits)\n",
        "  X_train = X_train[train_ids]\n",
        "  X_test = X_test[test_ids]\n",
        "  y_train = y_train[train_ids]\n",
        "  y_test = y_test[test_ids]\n",
        "\n",
        "  X_train = np.expand_dims(X_train, axis=-1)\n",
        "  X_test = np.expand_dims(X_test, axis=-1)\n",
        "\n",
        "  X_train = X_train.astype('float32')         # change integers to 32-bit floating point numbers\n",
        "  X_test = X_test.astype('float32')\n",
        "\n",
        "  X_train /= 255                              # normalize each value for each pixel for the entire vector for each input\n",
        "  X_test /= 255\n",
        "\n",
        "  return X_train, y_train, X_test, y_test"
      ],
      "execution_count": 43,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Zf0T6tdPBC70"
      },
      "source": [
        "# Preparing the base model\n",
        "def prepare_model(input_shape, class_count):\n",
        "  model = Sequential()                                 # Linear stacking of layers\n",
        "\n",
        "  # Convolution Layer 1\n",
        "  model.add(Conv2D(16, (3, 3), padding=\"same\", input_shape=input_shape))\n",
        "  model.add(Activation('relu') )\n",
        "  model.add(MaxPooling2D(pool_size=(2,2)))\n",
        "\n",
        "  # Convolution Layer 2\n",
        "  model.add(Conv2D(32, (3, 3), padding=\"same\"))\n",
        "  model.add(Activation('relu'))\n",
        "  model.add(MaxPooling2D(pool_size=(2,2)))\n",
        "\n",
        "  # Convolution Layer 3\n",
        "  model.add(Conv2D(64, (3, 3), padding=\"same\"))\n",
        "  model.add(Activation('relu'))\n",
        "  model.add(MaxPooling2D(pool_size=(2,2)))\n",
        "\n",
        "  model.add(Flatten())\n",
        "\n",
        "  # Fully Connected Layer\n",
        "  model.add(Dense(64))\n",
        "  model.add(Activation('relu'))\n",
        "\n",
        "  # Fully Connected Layer\n",
        "  model.add(Dense(class_count))\n",
        "  model.add(Activation('softmax'))\n",
        "\n",
        "  return model"
      ],
      "execution_count": 44,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "EX6bXUPBGDyx"
      },
      "source": [
        "# One-hot encoding of non-consecutive labels\n",
        "def one_hot(labels):\n",
        "  from sklearn.preprocessing import OneHotEncoder\n",
        "  encoder = OneHotEncoder(categories='auto')\n",
        "  l = labels.reshape(-1, 1)\n",
        "  output = encoder.fit_transform(l)\n",
        "  return output.toarray()"
      ],
      "execution_count": 45,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-VP_9oFFiE6a"
      },
      "source": [
        "def test_model(model, X_test, Y_test, y_test, digits):\n",
        "  score = model.evaluate(X_test, Y_test)\n",
        "  print('Test score:', score[0])\n",
        "  print('Test accuracy:', score[1])\n",
        "\n",
        "  # The predict_classes function outputs the highest probability class\n",
        "  # according to the trained classifier for each input example.\n",
        "  predicted = model.predict(X_test)\n",
        "  predicted_classes = np.argmax(predicted, axis=1)\n",
        "\n",
        "  actual_classes = [digits[x] for x in predicted_classes]\n",
        "\n",
        "  # Check which items we got right / wrong\n",
        "  correct_indices = np.nonzero(actual_classes == y_test)[0]\n",
        "\n",
        "  incorrect_indices = np.nonzero(actual_classes != y_test)[0]\n",
        "\n",
        "\n",
        "  cnf_matrix = confusion_matrix(y_test, actual_classes)\n",
        "\n",
        "  class_names = [str(i) for i in digits]\n",
        "\n",
        "  # Plot non-normalized confusion matrix\n",
        "  plt.figure()\n",
        "  plot_confusion_matrix(cnf_matrix, classes=class_names,\n",
        "                        title='Confusion matrix, without normalization')\n",
        "\n",
        "  plt.show()"
      ],
      "execution_count": 46,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Fi7BwsX8h5Dm"
      },
      "source": [
        "## Trening modelu bazowego: cyfry 0..4"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ZyAj3Kk4Bw4d",
        "outputId": "079eade4-dd17-4e2f-e7df-e68c2fc499a7"
      },
      "source": [
        "digits = [0, 1, 2, 3, 4]\n",
        "\n",
        "X_train, y_train, X_test, y_test = get_mnist(digits)\n",
        "\n",
        "Y_train = one_hot(y_train)\n",
        "Y_test = one_hot(y_test)\n",
        "\n",
        "model = prepare_model( (28, 28, 1), 5)\n",
        "\n",
        "adam = tf.optimizers.Adam(learning_rate=0.001)\n",
        "model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])\n",
        "\n",
        "model.summary()\n",
        "\n"
      ],
      "execution_count": 47,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model: \"sequential_2\"\n",
            "_________________________________________________________________\n",
            " Layer (type)                Output Shape              Param #   \n",
            "=================================================================\n",
            " conv2d_2 (Conv2D)           (None, 28, 28, 16)        160       \n",
            "                                                                 \n",
            " activation_7 (Activation)   (None, 28, 28, 16)        0         \n",
            "                                                                 \n",
            " max_pooling2d_2 (MaxPoolin  (None, 14, 14, 16)        0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " conv2d_3 (Conv2D)           (None, 14, 14, 32)        4640      \n",
            "                                                                 \n",
            " activation_8 (Activation)   (None, 14, 14, 32)        0         \n",
            "                                                                 \n",
            " max_pooling2d_3 (MaxPoolin  (None, 7, 7, 32)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " conv2d_4 (Conv2D)           (None, 7, 7, 64)          18496     \n",
            "                                                                 \n",
            " activation_9 (Activation)   (None, 7, 7, 64)          0         \n",
            "                                                                 \n",
            " max_pooling2d_4 (MaxPoolin  (None, 3, 3, 64)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " flatten_1 (Flatten)         (None, 576)               0         \n",
            "                                                                 \n",
            " dense_5 (Dense)             (None, 64)                36928     \n",
            "                                                                 \n",
            " activation_10 (Activation)  (None, 64)                0         \n",
            "                                                                 \n",
            " dense_6 (Dense)             (None, 5)                 325       \n",
            "                                                                 \n",
            " activation_11 (Activation)  (None, 5)                 0         \n",
            "                                                                 \n",
            "=================================================================\n",
            "Total params: 60549 (236.52 KB)\n",
            "Trainable params: 60549 (236.52 KB)\n",
            "Non-trainable params: 0 (0.00 Byte)\n",
            "_________________________________________________________________\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "BjEZRMnvChne"
      },
      "source": [
        "gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3,\n",
        "                         height_shift_range=0.08, zoom_range=0.08, validation_split=0.2)\n",
        "\n",
        "test_gen = ImageDataGenerator()\n",
        "\n",
        "train_generator = gen.flow(X_train, Y_train, batch_size=128, subset='training')\n",
        "valid_generator = gen.flow(X_train, Y_train, batch_size=128, subset='validation')\n",
        "test_generator = test_gen.flow(X_test, Y_test, batch_size=128)"
      ],
      "execution_count": 48,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "byGKttIXDfE3",
        "outputId": "0f8b64ce-1306-4187-b53a-5255a36b2711"
      },
      "source": [
        "model.fit_generator(train_generator, steps_per_epoch=25000//128, epochs=5, verbose=1, validation_data=valid_generator, validation_steps = 5000 // 128)"
      ],
      "execution_count": 49,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch 1/5\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "<ipython-input-49-1bedfa515cd7>:1: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.\n",
            "  model.fit_generator(train_generator, steps_per_epoch=25000//128, epochs=5, verbose=1, validation_data=valid_generator, validation_steps = 5000 // 128)\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "191/195 [============================>.] - ETA: 0s - loss: 0.2532 - accuracy: 0.9235"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:tensorflow:Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches (in this case, 975 batches). You may need to use the repeat() function when building your dataset.\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r195/195 [==============================] - 11s 43ms/step - loss: 0.2527 - accuracy: 0.9238 - val_loss: 0.0744 - val_accuracy: 0.9784\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<keras.src.callbacks.History at 0x7f0d801654e0>"
            ]
          },
          "metadata": {},
          "execution_count": 49
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "3sUGcowEMLHk",
        "outputId": "c94d3f21-d4d1-46b8-841d-e918e87c8217"
      },
      "source": [
        "test_model(model, X_test, Y_test, y_test, digits)"
      ],
      "execution_count": 50,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "161/161 [==============================] - 1s 5ms/step - loss: 0.0275 - accuracy: 0.9930\n",
            "Test score: 0.02751018851995468\n",
            "Test accuracy: 0.9929947257041931\n",
            "161/161 [==============================] - 1s 3ms/step\n",
            "Confusion matrix, without normalization\n",
            "[[ 977    0    2    0    1]\n",
            " [   0 1125    4    4    2]\n",
            " [   5    0 1015    9    3]\n",
            " [   0    0    3 1007    0]\n",
            " [   1    0    1    1  979]]\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 2 Axes>"
            ],
            "image/png": "[encoded data, 41728 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0fkbvsjbiIi5"
      },
      "source": [
        "## Transfer wiedzy do nowego modelu: cyfry 5..9"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "MfJ3UmNrMwfC"
      },
      "source": [
        "# Extract specified range of layers to a new sequential model\n",
        "def extract_layers(main_model, starting_layer_ix, ending_layer_ix):\n",
        "  # create an empty model\n",
        "  new_model = Sequential()\n",
        "  for ix in range(starting_layer_ix, ending_layer_ix + 1):\n",
        "    curr_layer = main_model.get_layer(index=ix)\n",
        "    # copy this layer over to the new model\n",
        "    new_model.add(curr_layer)\n",
        "  return new_model"
      ],
      "execution_count": 51,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aAY_aLnOiTct"
      },
      "source": [
        "Nowy model stworzony jest na bazie wytrenowanego modelu bazowego. Część odpowiedzialna za detekcję cech w obrazie (warstwy splotowe) są wykorzystywane jako podstawa dla nowej sieci. Część klasyfikacyjna jest dodawana na nowo, z losowo zainicjowanymi wagami.\n",
        "\n",
        "Warstwy splotowe były wstępnie wytrenowane w modelu bazowym, więc są zamrażane. Treningowi podlega tylko część klasyfikacyjna."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "qzDfXKIMNXMK",
        "outputId": "21ec04f6-f91c-47c2-f616-989d5da80821"
      },
      "source": [
        "new_model = extract_layers(model, 0, 9)\n",
        "\n",
        "# Fully Connected Layer\n",
        "new_model.add(Dense(64))\n",
        "new_model.add(Activation('relu'))\n",
        "\n",
        "# Fully Connected Layer\n",
        "new_model.add(Dense(5))\n",
        "new_model.add(Activation('softmax'))\n",
        "\n",
        "for ix in range(0, 9+1):\n",
        "  new_model.get_layer(index=ix).trainable=False\n",
        "\n",
        "new_digits = [5, 6, 7, 8, 9]\n",
        "\n",
        "X_train, y_train, X_test, y_test = get_mnist(new_digits)\n",
        "\n",
        "Y_train = one_hot(y_train)\n",
        "Y_test = one_hot(y_test)\n",
        "\n",
        "new_model.compile(loss='categorical_crossentropy', optimizer=tf.keras.optimizers.legacy.Adam(), metrics=['accuracy'])\n",
        "\n",
        "new_model.summary()"
      ],
      "execution_count": 52,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model: \"sequential_3\"\n",
            "_________________________________________________________________\n",
            " Layer (type)                Output Shape              Param #   \n",
            "=================================================================\n",
            " conv2d_2 (Conv2D)           (None, 28, 28, 16)        160       \n",
            "                                                                 \n",
            " activation_7 (Activation)   (None, 28, 28, 16)        0         \n",
            "                                                                 \n",
            " max_pooling2d_2 (MaxPoolin  (None, 14, 14, 16)        0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " conv2d_3 (Conv2D)           (None, 14, 14, 32)        4640      \n",
            "                                                                 \n",
            " activation_8 (Activation)   (None, 14, 14, 32)        0         \n",
            "                                                                 \n",
            " max_pooling2d_3 (MaxPoolin  (None, 7, 7, 32)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " conv2d_4 (Conv2D)           (None, 7, 7, 64)          18496     \n",
            "                                                                 \n",
            " activation_9 (Activation)   (None, 7, 7, 64)          0         \n",
            "                                                                 \n",
            " max_pooling2d_4 (MaxPoolin  (None, 3, 3, 64)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " flatten_1 (Flatten)         (None, 576)               0         \n",
            "                                                                 \n",
            " dense_7 (Dense)             (None, 64)                36928     \n",
            "                                                                 \n",
            " activation_12 (Activation)  (None, 64)                0         \n",
            "                                                                 \n",
            " dense_8 (Dense)             (None, 5)                 325       \n",
            "                                                                 \n",
            " activation_13 (Activation)  (None, 5)                 0         \n",
            "                                                                 \n",
            "=================================================================\n",
            "Total params: 60549 (236.52 KB)\n",
            "Trainable params: 37253 (145.52 KB)\n",
            "Non-trainable params: 23296 (91.00 KB)\n",
            "_________________________________________________________________\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "quqhC141PBTO"
      },
      "source": [
        "gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3,\n",
        "                         height_shift_range=0.08, zoom_range=0.08, validation_split=0.2)\n",
        "\n",
        "test_gen = ImageDataGenerator()\n",
        "\n",
        "train_generator = gen.flow(X_train, Y_train, batch_size=128, subset='training')\n",
        "valid_generator = gen.flow(X_train, Y_train, batch_size=128, subset='validation')\n",
        "test_generator = test_gen.flow(X_test, Y_test, batch_size=128)"
      ],
      "execution_count": 53,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oqy-_NxblqDy"
      },
      "source": [
        "Trening nowego modelu"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "l0H5XRxWPKvC",
        "outputId": "4ea974a3-fee9-4bfa-a584-7f40b0cd5d2f"
      },
      "source": [
        "new_model.fit_generator(train_generator, steps_per_epoch=24000//128, epochs=5, verbose=1, validation_data=valid_generator, validation_steps = 6000 // 128)"
      ],
      "execution_count": 54,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch 1/5\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "<ipython-input-54-05ca407e5c2c>:1: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.\n",
            "  new_model.fit_generator(train_generator, steps_per_epoch=24000//128, epochs=5, verbose=1, validation_data=valid_generator, validation_steps = 6000 // 128)\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "183/187 [============================>.] - ETA: 0s - loss: 0.4288 - accuracy: 0.8646"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:tensorflow:Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches (in this case, 935 batches). You may need to use the repeat() function when building your dataset.\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r187/187 [==============================] - 11s 51ms/step - loss: 0.4275 - accuracy: 0.8651 - val_loss: 0.2110 - val_accuracy: 0.9355\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<keras.src.callbacks.History at 0x7f0cfa1a8b80>"
            ]
          },
          "metadata": {},
          "execution_count": 54
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "FRuh3MN-PeDr",
        "outputId": "c8a9d191-d9d8-4d96-8202-f20f6d69580c"
      },
      "source": [
        "test_model(new_model, X_test, Y_test, y_test, new_digits)"
      ],
      "execution_count": 55,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "152/152 [==============================] - 1s 4ms/step - loss: 0.1271 - accuracy: 0.9636\n",
            "Test score: 0.1270837038755417\n",
            "Test accuracy: 0.963587760925293\n",
            "152/152 [==============================] - 0s 3ms/step\n",
            "Confusion matrix, without normalization\n",
            "[[865   3   3  14   7]\n",
            " [  3 946   0   8   1]\n",
            " [  0   1 968   8  51]\n",
            " [  8   2   9 930  25]\n",
            " [ 10   5  11   8 975]]\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 2 Axes>"
            ],
            "image/png": "[encoded data, 43736 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2zgQd-nJjU2F"
      },
      "source": [
        "## Fine-tuning"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7kctU-jel8k-"
      },
      "source": [
        "Po wytrenowaniu części klasyfikacyjnej można ponownie odmrozić wszystkie warstwy i przeprowadzić dotrenowanie pełnej sieci na nowych danych, bez ryzyka związanego z dużymi gradientami w pierwszych fazach uczenia."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "yu4ks06pR1aO",
        "outputId": "36451d32-572f-4ca4-f1ee-8478e600ab68"
      },
      "source": [
        "for ix in range(0, 9+1):\n",
        "  new_model.get_layer(index=ix).trainable=True\n",
        "\n",
        "new_model.compile(loss='categorical_crossentropy', optimizer=tf.keras.optimizers.legacy.Adam(), metrics=['accuracy'])\n",
        "new_model.summary()"
      ],
      "execution_count": 56,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model: \"sequential_3\"\n",
            "_________________________________________________________________\n",
            " Layer (type)                Output Shape              Param #   \n",
            "=================================================================\n",
            " conv2d_2 (Conv2D)           (None, 28, 28, 16)        160       \n",
            "                                                                 \n",
            " activation_7 (Activation)   (None, 28, 28, 16)        0         \n",
            "                                                                 \n",
            " max_pooling2d_2 (MaxPoolin  (None, 14, 14, 16)        0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " conv2d_3 (Conv2D)           (None, 14, 14, 32)        4640      \n",
            "                                                                 \n",
            " activation_8 (Activation)   (None, 14, 14, 32)        0         \n",
            "                                                                 \n",
            " max_pooling2d_3 (MaxPoolin  (None, 7, 7, 32)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " conv2d_4 (Conv2D)           (None, 7, 7, 64)          18496     \n",
            "                                                                 \n",
            " activation_9 (Activation)   (None, 7, 7, 64)          0         \n",
            "                                                                 \n",
            " max_pooling2d_4 (MaxPoolin  (None, 3, 3, 64)          0         \n",
            " g2D)                                                            \n",
            "                                                                 \n",
            " flatten_1 (Flatten)         (None, 576)               0         \n",
            "                                                                 \n",
            " dense_7 (Dense)             (None, 64)                36928     \n",
            "                                                                 \n",
            " activation_12 (Activation)  (None, 64)                0         \n",
            "                                                                 \n",
            " dense_8 (Dense)             (None, 5)                 325       \n",
            "                                                                 \n",
            " activation_13 (Activation)  (None, 5)                 0         \n",
            "                                                                 \n",
            "=================================================================\n",
            "Total params: 60549 (236.52 KB)\n",
            "Trainable params: 60549 (236.52 KB)\n",
            "Non-trainable params: 0 (0.00 Byte)\n",
            "_________________________________________________________________\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "g9x5tXRzS9yE",
        "outputId": "4b9b67da-1fad-4b25-e63d-f489278bb706"
      },
      "source": [
        "new_model.fit_generator(train_generator, steps_per_epoch=24000//128, epochs=2, verbose=1, validation_data=valid_generator, validation_steps = 6000 // 128)"
      ],
      "execution_count": 57,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch 1/2\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "<ipython-input-57-5ed88bbb5f18>:1: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.\n",
            "  new_model.fit_generator(train_generator, steps_per_epoch=24000//128, epochs=2, verbose=1, validation_data=valid_generator, validation_steps = 6000 // 128)\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "184/187 [============================>.] - ETA: 0s - loss: 0.1569 - accuracy: 0.9506"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:tensorflow:Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches (in this case, 374 batches). You may need to use the repeat() function when building your dataset.\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r187/187 [==============================] - 9s 44ms/step - loss: 0.1569 - accuracy: 0.9506 - val_loss: 0.0890 - val_accuracy: 0.9716\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<keras.src.callbacks.History at 0x7f0d7834a3e0>"
            ]
          },
          "metadata": {},
          "execution_count": 57
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "pbfKwpJ9TI-e",
        "outputId": "cd1cef33-22c7-42f1-e65e-56abdf5c6610"
      },
      "source": [
        "test_model(new_model, X_test, Y_test, y_test, new_digits)"
      ],
      "execution_count": 58,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "152/152 [==============================] - 0s 3ms/step - loss: 0.0513 - accuracy: 0.9842\n",
            "Test score: 0.051340196281671524\n",
            "Test accuracy: 0.9841596484184265\n",
            "152/152 [==============================] - 0s 2ms/step\n",
            "Confusion matrix, without normalization\n",
            "[[ 880    1    2    6    3]\n",
            " [   5  946    0    6    1]\n",
            " [   1    0 1012    5   10]\n",
            " [   9    0    4  957    4]\n",
            " [   7    2    6    5  989]]\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 900x900 with 2 Axes>"
            ],
            "image/png": "[encoded data, 43932 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e6ssF6eHeHro"
      },
      "source": [
        "# Test on different dataset"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "s00LerSteLv5",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "9906bb92-ad9d-4521-9433-6a5a46257042"
      },
      "source": [
        "!wget https://raw.githubusercontent.com/googlecreativelab/quickdraw-dataset/master/categories.txt"
      ],
      "execution_count": 59,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "--2024-04-19 14:32:03--  https://raw.githubusercontent.com/googlecreativelab/quickdraw-dataset/master/categories.txt\n",
            "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n",
            "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n",
            "HTTP request sent, awaiting response... 200 OK\n",
            "Length: 2791 (2.7K) [text/plain]\n",
            "Saving to: ‘categories.txt’\n",
            "\n",
            "categories.txt      100%[===================>]   2.73K  --.-KB/s    in 0s      \n",
            "\n",
            "2024-04-19 14:32:04 (41.9 MB/s) - ‘categories.txt’ saved [2791/2791]\n",
            "\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "H4d-lVtqgzgC"
      },
      "source": [
        "import os\n",
        "import urllib\n",
        "\n",
        "def download_and_load(class_names, test_split = 0.2, max_items_per_class = 10000):\n",
        "  root = 'data'\n",
        "  if not os.path.exists(root):\n",
        "    os.makedirs(root)\n",
        "\n",
        "  print('downloading ...')\n",
        "  base = 'https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/'\n",
        "  for c in class_names:\n",
        "    cc = c.replace('_', '%20')\n",
        "    path = base+cc+'.npy'\n",
        "    print(path)\n",
        "    if not os.path.exists(f'{root}/{cc}.npy'):\n",
        "      urllib.request.urlretrieve(path, f'{root}/{cc}.npy')\n",
        "    else:\n",
        "      print(\"Already downloaded\")\n",
        "  print('loading ...')\n",
        "\n",
        "  #initialize variables\n",
        "  x = np.empty([0, 784])\n",
        "  y = np.empty([0])\n",
        "\n",
        "  #load each data file\n",
        "  for idx, file in enumerate(class_names):\n",
        "      file = file.replace('_', '%20')\n",
        "      data = np.load(f'{root}/{file}.npy')\n",
        "      data = data[0: max_items_per_class, :]\n",
        "      labels = np.full(data.shape[0], idx)\n",
        "\n",
        "      x = np.concatenate((x, data), axis=0)\n",
        "      y = np.append(y, labels)\n",
        "\n",
        "  data = None\n",
        "  labels = None\n",
        "\n",
        "  #randomize the dataset\n",
        "  permutation = np.random.permutation(y.shape[0])\n",
        "  x = x[permutation, :]\n",
        "  y = y[permutation]\n",
        "\n",
        "  #reshape and inverse the colors\n",
        "  x = 255 - np.reshape(x, (x.shape[0], 28, 28))\n",
        "\n",
        "  #separate into training and testing\n",
        "  test_size  = int(x.shape[0]/100*(test_split*100))\n",
        "\n",
        "  x_test = x[0:test_size, :]\n",
        "  y_test = y[0:test_size]\n",
        "\n",
        "  x_train = x[test_size:x.shape[0], :]\n",
        "  y_train = y[test_size:y.shape[0]]\n",
        "\n",
        "  print('Training Data : ', x_train.shape[0])\n",
        "  print('Testing  Data : ', x_test.shape[0])\n",
        "  return x_train, y_train, x_test, y_test"
      ],
      "execution_count": 60,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kAiJYOJBgXup"
      },
      "source": [
        "# set your random seed value, put any number here\n",
        "RANDOM_SEED = 1234"
      ],
      "execution_count": 61,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "CsfWTt6NeUEJ",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "outputId": "8f5a3e43-397a-4069-8076-cb4ac25a702f"
      },
      "source": [
        "import random\n",
        "\n",
        "with open('categories.txt') as f:\n",
        "    all_class_names = f.readlines()\n",
        "\n",
        "\n",
        "random.seed(RANDOM_SEED)\n",
        "\n",
        "all_class_names = [x.strip().replace(' ', '_') for x in all_class_names]\n",
        "\n",
        "nb_classes = 10\n",
        "\n",
        "# select random 10 classes\n",
        "class_names = random.sample(all_class_names, nb_classes)\n",
        "\n",
        "# you can also try those \"hard\" classes instead\n",
        "#class_names = ['wheel', 'pizza', 'smiley_face', 'apple', 'potato', 'basketball', 'soccer_ball', 'brain', 'clock', 'circle']\n",
        "\n",
        "print(class_names)\n",
        "\n",
        "X_train, y_train, X_test, y_test = download_and_load(class_names)\n",
        "\n",
        "X_train = X_train.reshape(X_train.shape[0], 28, 28, 1) #add an additional dimension to represent the single-channel\n",
        "X_test = X_test.reshape(X_test.shape[0], 28, 28, 1)\n",
        "\n",
        "X_train = X_train.astype('float32')         # change integers to 32-bit floating point numbers\n",
        "X_test = X_test.astype('float32')\n",
        "\n",
        "X_train /= 255                              # normalize each value for each pixel for the entire vector for each input\n",
        "X_test /= 255\n",
        "\n",
        "Y_train = to_categorical(y_train, nb_classes)\n",
        "Y_test = to_categorical(y_test, nb_classes)\n",
        "\n",
        "visualize_classes(X_train, y_train)"
      ],
      "execution_count": 62,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "['pizza', 'cannon', 'ambulance', 'bulldozer', 'swing_set', 'baseball_bat', 'zebra', 'bridge', 'cactus', 'matches']\n",
            "downloading ...\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/pizza.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/cannon.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/ambulance.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/bulldozer.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/swing%20set.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/baseball%20bat.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/zebra.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/bridge.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/cactus.npy\n",
            "https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/matches.npy\n",
            "loading ...\n",
            "Training Data :  80000\n",
            "Testing  Data :  20000\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1000x2000 with 1 Axes>"
            ],
            "image/png": "[encoded data, 466552 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "W5Jm2OPyk4FF"
      },
      "source": [
        "## Create the model"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1k0Qnajnk3ey"
      },
      "source": [
        "model = Sequential()                                 # Linear stacking of layers\n",
        "\n",
        "# Convolution Layer 1\n",
        "model.add(Conv2D(64, (5, 5), input_shape=(28,28,1))) # 16 different 5x5 kernels -- so 16 feature maps\n",
        "model.add(BatchNormalization())                      # weights normalization\n",
        "model.add(Activation('relu') )                       # activation\n",
        "model.add(MaxPooling2D(pool_size=(2,2)))             # Pool the max values over a 2x2 kernel\n",
        "\n",
        "\n",
        "# Convolution Layer 2\n",
        "model.add(Conv2D(32, (5, 5)))                        # 32 different 5x5 kernels -- so 32 feature maps\n",
        "model.add(BatchNormalization())                      # weights normalization\n",
        "model.add(Activation('relu'))                        # activation\n",
        "model.add(MaxPooling2D(pool_size=(2,2)))             # Pool the max values over a 2x2 kernel\n",
        "\n",
        "model.add(Flatten())                                 # Flatten final output matrix into a vector\n",
        "\n",
        "# Fully Connected Layer\n",
        "model.add(Dense(128))                                # 128 FC nodes\n",
        "model.add(Activation('relu'))                        # activation\n",
        "\n",
        "# Fully Connected Layer\n",
        "model.add(Dense(10))                                 # final 10 FC nodes\n",
        "model.add(Activation('softmax'))                     # softmax activation\n",
        "\n",
        "\n",
        "# we'll use the same optimizer\n",
        "adam = tf.optimizers.Adam(learning_rate=0.001)\n",
        "model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])"
      ],
      "execution_count": 63,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "HmR_tm0gmR0u"
      },
      "source": [
        "# data augmentation prevents overfitting by slightly changing the data randomly\n",
        "# Keras has a great built-in feature to do automatic augmentation\n",
        "\n",
        "gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3,\n",
        "                         height_shift_range=0.08, zoom_range=0.08, validation_split=0.2)\n",
        "\n",
        "test_gen = ImageDataGenerator()\n",
        "\n",
        "train_generator = gen.flow(X_train, Y_train, batch_size=128, subset='training')\n",
        "valid_generator = gen.flow(X_train, Y_train, batch_size=128, subset='validation')\n",
        "test_generator = test_gen.flow(X_test, Y_test, batch_size=128)"
      ],
      "execution_count": 64,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "p-jeOuI8nfl9",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "e9544c03-ba7e-4de9-d41b-6b63d26f9700"
      },
      "source": [
        "model.fit(train_generator, steps_per_epoch=64000//128, epochs=5, verbose=1, validation_data=valid_generator, validation_steps = 16000 // 128)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch 1/5\n",
            "500/500 [==============================] - 27s 49ms/step - loss: 0.7584 - accuracy: 0.7635 - val_loss: 1.1462 - val_accuracy: 0.6129\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "OlzMJklYooX8"
      },
      "source": [
        "score = model.evaluate(X_test, Y_test)\n",
        "print('Test score:', score[0])\n",
        "print('Test accuracy:', score[1])"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "sAhuIjARoqhp"
      },
      "source": [
        "# The predict_classes function outputs the highest probability class\n",
        "# according to the trained classifier for each input example.\n",
        "predicted = model.predict(X_test)\n",
        "predicted_classes = np.argmax(predicted, axis=1)\n",
        "\n",
        "# Check which items we got right / wrong\n",
        "correct_indices = np.nonzero(predicted_classes == y_test)[0]\n",
        "\n",
        "incorrect_indices = np.nonzero(predicted_classes != y_test)[0]\n",
        "\n",
        "cnf_matrix = confusion_matrix(y_test, predicted_classes)\n",
        "\n",
        "# Plot non-normalized confusion matrix\n",
        "plt.figure()\n",
        "plot_confusion_matrix(cnf_matrix, classes=class_names,\n",
        "                      title='Confusion matrix, without normalization')\n",
        "\n",
        "plt.show()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "BKDRjvPFow6L"
      },
      "source": [
        "show_samples(correct_indices, predicted, X_test, y_test, 5, class_names)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "lgtxWrarps6b"
      },
      "source": [
        "show_samples(incorrect_indices, predicted, X_test, y_test, 5, class_names)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "r29_OvwJtyM7"
      },
      "source": [
        "# CIFAR-10"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nuo0JHXDzxT7"
      },
      "source": [
        "## Ładowanie zbioru danych"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "pPrx-UWet0Ng"
      },
      "source": [
        "from keras.datasets import cifar10\n",
        "\n",
        "(X_train, y_train), (X_test, y_test) = cifar10.load_data()\n",
        "\n",
        "X_train = X_train.astype('float32')         # change integers to 32-bit floating point numbers\n",
        "X_test = X_test.astype('float32')\n",
        "\n",
        "X_train /= 255                              # normalize each value for each pixel for the entire vector for each input\n",
        "X_test /= 255\n",
        "\n",
        "y_train = y_train.reshape((1,-1))[0]\n",
        "y_test = y_test.reshape((1,-1))[0]\n",
        "\n",
        "print(\"Training matrix shape\", X_train.shape, y_train.shape)\n",
        "print(\"Testing matrix shape\", X_test.shape, y_test.shape)\n",
        "\n",
        "# one-hot format classes\n",
        "\n",
        "nb_classes = 10\n",
        "\n",
        "Y_train = to_categorical(y_train, nb_classes)\n",
        "Y_test = to_categorical(y_test, nb_classes)\n",
        "\n",
        "cifar_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Q2LGp6AVzzqC"
      },
      "source": [
        "## Podgląd zbioru treningowego"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "o6OJ7XPdxe1i"
      },
      "source": [
        "for i in range(0, 10):\n",
        "  img_batch = X_train[y_train == i][0:10]\n",
        "  img_batch = np.reshape(img_batch, (img_batch.shape[0]*img_batch.shape[1], img_batch.shape[2], img_batch.shape[3]))\n",
        "  if i > 0:\n",
        "    img = np.concatenate([img, img_batch], axis = 1)\n",
        "  else:\n",
        "    img = img_batch\n",
        "plt.figure(figsize=(10,20))\n",
        "plt.axis('off')\n",
        "plt.imshow(img, cmap='gray')\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6EF56o2gz3Mz"
      },
      "source": [
        "## Przygotowanie modelu"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6UvabhsguqWc"
      },
      "source": [
        "def generate_model():\n",
        "  model = Sequential()                                 # Linear stacking of layers\n",
        "\n",
        "  # Convolution Layer 1\n",
        "  model.add(Conv2D(16, (3, 3), input_shape=(32,32,3)))\n",
        "  model.add(Activation('relu') )\n",
        "\n",
        "  # ...\n",
        "\n",
        "  model.add(Flatten())                                 # Flatten final output matrix into a vector\n",
        "\n",
        "  # ...\n",
        "\n",
        "  # Fully Connected Layer\n",
        "  model.add(Dense(10))                                 # final 10 FC nodes\n",
        "  model.add(Activation('softmax'))                     # softmax activation\n",
        "\n",
        "  model.summary()\n",
        "\n",
        "  adam = tf.optimizers.Adam(learning_rate=0.001)\n",
        "  model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])\n",
        "\n",
        "  return model"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "PSQkAq9IvJsS"
      },
      "source": [
        "model = generate_model()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YMtJVU_oz55g"
      },
      "source": [
        "## Trening"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "sjZaiBLJvQkP"
      },
      "source": [
        "gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3,\n",
        "                         height_shift_range=0.08, zoom_range=0.08, validation_split=0.2)\n",
        "\n",
        "train_generator = gen.flow(X_train, Y_train, batch_size=128, subset='training')\n",
        "valid_generator = gen.flow(X_train, Y_train, batch_size=128, subset='validation')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "MlMHmbOsvbWP"
      },
      "source": [
        "model.fit(train_generator, steps_per_epoch=40000//128, epochs=2, verbose=1, validation_data=valid_generator, validation_steps = 10000 // 128)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mN5zKMDNz8Jp"
      },
      "source": [
        "## Test"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "lLZORONWvqex"
      },
      "source": [
        "score = model.evaluate(X_test, Y_test)\n",
        "print('Test score:', score[0])\n",
        "print('Test accuracy:', score[1])\n",
        "\n",
        "# The predict_classes function outputs the highest probability class\n",
        "# according to the trained classifier for each input example.\n",
        "predicted = model.predict(X_test)\n",
        "predicted_classes = np.argmax(predicted, axis=1)\n",
        "\n",
        "# Check which items we got right / wrong\n",
        "correct_indices = np.nonzero(predicted_classes == y_test)[0]\n",
        "\n",
        "incorrect_indices = np.nonzero(predicted_classes != y_test)[0]\n",
        "\n",
        "\n",
        "cnf_matrix = confusion_matrix(y_test, predicted_classes)\n",
        "\n",
        "class_names = [str(i) for i in range(10)]\n",
        "\n",
        "# Plot non-normalized confusion matrix\n",
        "plt.figure()\n",
        "plot_confusion_matrix(cnf_matrix, classes=class_names,\n",
        "                      title='Confusion matrix, without normalization')\n",
        "\n",
        "plt.show()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ktfHkYc1zHsY"
      },
      "source": [
        "def show_samples_rgb(indices, preds, images, labels, count=3, names = []):\n",
        "    plt.figure()\n",
        "    for i, sample in enumerate(indices[:count**2]):\n",
        "        pred_id = int(np.argmax(preds[sample]))\n",
        "        real_id = int(labels[sample])\n",
        "        pred_score = preds[sample][pred_id]\n",
        "        real_score = preds[sample][real_id]\n",
        "        plt.subplot(count,count,i+1)\n",
        "        plt.imshow(images[sample], interpolation='none')\n",
        "        plt.axis('off')\n",
        "        if len(names) > 0:\n",
        "          plt.title(\"P: {} ({:.2f})\\nE: {} ({:.2f})\".format(names[pred_id], pred_score, names[real_id], real_score))\n",
        "        else:\n",
        "          plt.title(\"P: {} ({:.2f})\\nE: {} ({:.2f})\".format(pred_id, pred_score, real_id, real_score))\n",
        "\n",
        "    plt.tight_layout()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "F8hS785GzoGJ"
      },
      "source": [
        "## Poprawne klasyfikacje"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "IUcJVbOlzm5z"
      },
      "source": [
        "show_samples_rgb(correct_indices, predicted, X_test, y_test, 5, cifar_names)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PiyibL4yzpup"
      },
      "source": [
        "## Błędne klasyfikacje"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ECh_2RW6zgKB"
      },
      "source": [
        "show_samples_rgb(incorrect_indices, predicted, X_test, y_test, 5, cifar_names)"
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}