{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# I - Préliminaires - Fonctions utiles" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import itertools\n", "import numpy as np\n", "def plot_confusion_matrix(cm, classes,\n", " normalize=False,\n", " title='Matrice de confusion',\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", " from http://scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html\n", " :param cm: (numpy matrix) confusion matrix\n", " :param classes: [str]\n", " :param normalize: (bool)\n", " :param title: (str)\n", " :param cmap: (matplotlib color map)\n", " \"\"\"\n", " if normalize:\n", " cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n", " \n", " plt.figure(figsize=(8, 8)) \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.tight_layout()\n", " plt.ylabel('Vérité Terrain')\n", " plt.xlabel('Prédiction')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# II - Entraînement d'un CNN pour la classification sur CIFAR10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### II.1. Chargement et Dimensionnement de la base CIFAR10" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras import backend as K\n", "print(K.backend())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.datasets import cifar10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Réduction de la taille du dataset (pour accélérer l'apprentissage), et standardisation des données" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(x_train_full, y_train_full), (x_test_full, y_test_full) = cifar10.load_data()\n", "print(\"Dimension de la base d'apprentissage CIFAR10 :\",x_train_full.shape)\n", "print(\"Dimension des vecteurs d'étiquette de classe :\",y_train_full.shape)\n", "print(\"Dimension de la base de test CIFAR10 :\",x_test_full.shape)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "classes = ('plane', 'car', 'bird', 'cat',\n", " 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_training_samples = 5000\n", "n_other_samples = 2000\n", "\n", "def standardize(img_data):\n", " img_data_mean = np.mean(img_data, axis=(1,2), keepdims=True)\n", " img_data_std = np.std(img_data, axis=(1,2), keepdims=True)\n", " img_data = (img_data - img_data_mean) / img_data_std\n", " return img_data\n", "\n", "train_ids = np.random.choice(len(x_train_full), size=n_training_samples, replace=False)\n", "other_ids = np.random.choice(len(x_test_full), size=n_other_samples, replace=False)\n", "\n", "n_valid = n_other_samples // 2\n", "val_ids = other_ids[:n_valid]\n", "test_ids = other_ids[n_valid:]\n", "\n", "x_train_initial, y_train = x_train_full[train_ids], y_train_full[train_ids]\n", "x_val_initial, y_val = x_test_full[val_ids], y_test_full[val_ids]\n", "x_test_initial, y_test = x_test_full[test_ids], y_test_full[test_ids]\n", "\n", "x_train = standardize(x_train_initial)\n", "x_val = standardize(x_val_initial)\n", "x_test = standardize(x_test_initial)\n", "\n", "print(\"Dimension de notre base d'apprentissage :\",x_train.shape)\n", "print(\"Dimension des vecteurs d'étiquette de classe :\",y_train.shape)\n", "print(\"Dimension de notre base de validation :\",x_val.shape)\n", "print(\"Dimension de notre base de test :\",x_test.shape)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On affiche quelques images d'entraînement avec leur étiquette..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_display = 12\n", "random_ids = np.random.choice(len(x_train), n_display, replace=False)\n", "f, axarr = plt.subplots(1,n_display,figsize=(16,16))\n", "for k in range(n_display):\n", " axarr[k].imshow(x_train_initial[random_ids[k]])\n", " axarr[k].title.set_text(classes[y_train[random_ids[k]][0]])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.utils import to_categorical\n", "y_train = to_categorical(y_train)\n", "y_val = to_categorical(y_val)\n", "y_test = to_categorical(y_test)\n", "\n", "print(\"Dimension des matrices d'étiquette de classe (train) :\",y_train.shape)\n", "print(\"Dimension des matrices d'étiquette de classe (val) :\",y_val.shape)\n", "print(\"Dimension des matrices d'étiquette de classe (test) :\",y_test.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### II.2. Definition de l'architecture du CNN" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Un simple réseau convolutionnel... " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.models import Sequential\n", "from keras.layers import Conv2D, MaxPool2D, Flatten, Dense, Dropout, Activation\n", "from keras.regularizers import l2\n", "\n", "model = Sequential()\n", "model.add(Conv2D(filters=8, \n", " kernel_size = (3, 3),\n", " activation = 'relu',\n", " padding = 'same',\n", " input_shape = (32, 32, 3),\n", " kernel_regularizer = l2(0.00)))\n", "model.add(Dropout(0.0))\n", "model.add(MaxPool2D(pool_size=(2,2)))\n", "model.add(Flatten())\n", "model.add(Dense(64, activation='relu', kernel_regularizer = l2(0.00)))\n", "model.add(Dropout(0.0))\n", "model.add(Dense(10, activation='softmax', kernel_regularizer = l2(0.00)))\n", "model.add(Dropout(0.0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On enregistre les poids initiaux pour plus tard..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "weights_init = model.get_weights()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### II.3. Definition de la fonction de coût et choix de l'algorithme d'optimisation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.optimizers import Adam, SGD\n", "\n", "opt = SGD(lr=0.01,momentum=0.0)\n", "# opt = Adam(lr=0.001)\n", "model.compile(optimizer=opt,\n", " loss='categorical_crossentropy',\n", " metrics=['acc'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On affiche un résumé de la structure du modèle..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(model.summary())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### II.4. Entraînement du CNN" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Definition du callback. A passer en première lecture.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.callbacks import Callback\n", "from keras.callbacks import ModelCheckpoint\n", "import time\n", "\n", "class TimeHistory(Callback):\n", " def on_train_begin(self, logs={}):\n", " self.times = []\n", "\n", " def on_epoch_begin(self, batch, logs={}):\n", " self.epoch_time_start = time.time()\n", "\n", " def on_epoch_end(self, batch, logs={}):\n", " self.times.append(time.time() - self.epoch_time_start)\n", "time_callback = TimeHistory()\n", "filepath = \"my_model.h5\"\n", "checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max', period=2)\n", "\n", "callbacks = [time_callback, checkpoint]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "history = model.fit(x_train, y_train, batch_size=32, epochs=20, verbose=1, validation_data=(x_val, y_val),\n", " callbacks=callbacks)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Statistiques sur le temps d'entraînement d'une epoch" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "times = time_callback.times\n", "print(\"Mean: {}\".format(np.mean(times)))\n", "print(\"Std: {}\".format(np.std(times)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tracé des courbes d'évolution des fonctions de coût" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "history.history.keys()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "history_dict = history.history\n", "loss_values = history_dict['loss']\n", "val_loss_values = history_dict['val_loss']\n", "acc_values = history_dict['acc']\n", "val_acc_values = history_dict['val_acc']\n", "\n", "epochs = range(1, len(history_dict['acc']) + 1)\n", "\n", "fig, ax1 = plt.subplots()\n", "ax1.set_xlabel('Epochs')\n", "ax1.set_ylabel('Loss')\n", "ax1.plot(epochs, loss_values, color='b', label='Training loss')\n", "ax1.plot(epochs, val_loss_values, color='g', label='Validation loss')\n", "ax1.tick_params(axis='y')\n", "plt.legend()\n", "\n", "ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis\n", "\n", "ax2.set_ylabel('Accuracy') # we already handled the x-label with ax1\n", "ax2.plot(epochs, acc_values, color='c', label='Training accuracy')\n", "ax2.plot(epochs, val_acc_values, color='r', label='Validation accuracy')\n", "ax2.tick_params(axis='y')\n", "\n", "fig.tight_layout() # otherwise the right y-label is slightly clipped\n", "plt.legend(loc=1)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### II.5. Entraînement d'un CNN avec points d'arrêt et reprises (à passer en première lecture)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pour des entraînements plus conséquents, il est primordial d'enregistrer les modèles intermédiaires afin de ne pas tout perdre si l'apprentissage venait à s'interrompre de façon impromptue. On peut enregistrer le modèle dans un fichier .hdf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Si vous regardez plus attentivement, lors de l'entrainement précédent nous avons crée un object **ModelCheckpoint**. Il permet d'enregistrer le meilleur modèle (au sens d'une metrique à préciser) dans un fichier nommé *my_model.h5*. Verifiez que ce fichier se trouve bien votre espace de travail." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "L'argument ***period*** de l'objet ModelCheckpoint vous permet de definir la fréquence des enregistrements." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.models import load_model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Une fois le modele obtenu, on peut l'enregister et le recharger comme ceci: load_model(filepath)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.models import load_model\n", "\n", "import pathlib\n", "file = pathlib.Path(filepath)\n", "if file.exists():\n", " model = load_model(filepath)\n", "else:\n", " model.set_weights(weights_init)\n", " callbacks = [time_callback,checkpoint]\n", "history_2 = model.fit(x_train, y_train, batch_size=8, epochs=10, verbose=1, validation_data=(x_val, y_val),\n", " callbacks = callbacks)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# III - Test et Évaluation du modèle en prédiction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### III.1. Test du modèle sur les données de test" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Testons la prédiction de notre modèle sur quelques images de test au hasard..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "random_ids = np.random.choice(len(x_test), n_display, replace=False)\n", "pred = np.argmax(model.predict(x_test[random_ids]), axis=1)\n", "f, axarr = plt.subplots(1,n_display,figsize=(16,16))\n", "for k in range(n_display):\n", " axarr[k].imshow(x_test_initial[random_ids[k]])\n", " axarr[k].title.set_text(classes[pred[k]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vos premiers résultats semblent-ils corrects ?\n", "\n", "Hum ! Affichons à présent la précision sur l'ensemble de votre base :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Précision du réseau sur les {} images d'entraînement : {:.2f} %\".format(n_training_samples, 100 * history_dict['acc'][-1]))\n", "print(\"Précision du réseau sur les {} images de validation : {:.2f} %\".format(n_valid, 100 * history_dict['val_acc'][-1]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def accuracy_per_class(model):\n", " n_classes = len(classes)\n", " confusion_matrix = np.zeros((n_classes, n_classes), dtype=np.int64)\n", " \n", " pred = np.argmax(model.predict(x_test), axis=1)\n", " for i in range(len(y_test)):\n", " confusion_matrix[np.argmax(y_test[i]), pred[i]] += 1\n", " \n", " print(\"{:<10} {:^10}\".format(\"Classe\", \"Précision (%)\"))\n", " total_correct = 0\n", " for i in range(n_classes):\n", " class_total = confusion_matrix[i, :].sum()\n", " class_correct = confusion_matrix[i, i]\n", " total_correct += class_correct\n", " percentage_correct = 100.0 * float(class_correct) / class_total\n", " print('{:<10} {:^10.2f}'.format(classes[i], percentage_correct))\n", " test_acc = 100.0 * float(total_correct) / len(y_test)\n", " print(\"Précision du réseau sur les {} images de test : {:.2f} %\".format(len(y_test),test_acc))\n", " return confusion_matrix\n", "\n", "confusion_matrix = accuracy_per_class(model)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### III.2. Matrices de Confusion\n", "\n", "Les matrices de confusion nous renseignent plus précisément sur la nature des erreurs commises par notre modèle." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Plot normalized confusion matrix\n", "plot_confusion_matrix(confusion_matrix, classes, normalize=True,\n", " title='Matrice de confusion normalisée')\n", "\n", "# Plot non-normalized confusion matrix\n", "plot_confusion_matrix(confusion_matrix, classes,\n", " title='Matrice de confusion non normalisée')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# IV - Visualisation des zones d'activation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from keras.models import Model\n", "\n", "reduced_model = Model(inputs=model.inputs, outputs=model.layers[1].output)\n", "reduced_model.summary()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "feature_maps = reduced_model.predict(x_test)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_mask(k):\n", " feature_maps_positive = np.maximum(feature_maps[k], 0)\n", " mask = np.sum(feature_maps_positive,axis=2)\n", " mask = mask / np.max(mask)\n", " return mask" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "random_ids = np.random.choice(len(x_test), n_display, replace=False)\n", "f, rd_img = plt.subplots(1,n_display,figsize=(16,16))\n", "for k in range(n_display):\n", " img = x_test_initial[random_ids[k]]\n", " rd_img[k].imshow(img)\n", " rd_img[k].axis('off')\n", "f, rd_maps = plt.subplots(1,n_display,figsize=(16,16))\n", "for k in range(n_display):\n", " mask = get_mask(random_ids[k])\n", " rd_maps[k].imshow(mask)\n", " rd_maps[k].axis('off')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 2 }