.. _chap-tpDeepLearning1: ############################################################################## Travaux pratiques - Introduction à l'apprentissage profond (*deep learning*) ############################################################################## L'objectif de cette première séance de travaux pratiques est de vous faire implémenter par vous même l'apprentissage de réseaux de neurones simples. Cettre prise en main sera très formatrice pour utiliser des modèles plus évolués et comprendre le fonctionnement des libaries (comme Keras) où l'apprentissage est automatisé. Nousa travaillerons avec la base de données image MNIST, constituée d'images de caractères manuscrits (60000 images en apprentissage, 10000 en test). Voici le code permettant de récupérer les données : .. code-block:: python from keras.datasets import mnist # the data, shuffled and split between train and test sets (X_train, y_train), (X_test, y_test) = mnist.load_data() # Reshape each 28x28 image -> 784 dim. vector X_train = X_train.reshape(60000, 784) X_test = X_test.reshape(10000, 784) X_train = X_train.astype('float32') X_test = X_test.astype('float32') # Normalization X_train /= 255 X_test /= 255 print(X_train.shape[0], 'train samples') print(X_test.shape[0], 'test samples') Exercice 0 : visualisation de quelques images de la base ******************************************************** Nous commencerons par afficher les 200 premières images de la base d'apprentissage. - Écrire un script ``exo0.py`` qui récupère les données avec le code précédent. - Compléter ``exo0.py`` pour permettre l'affichage demandé en utilisant le code suivant : .. code-block:: python import matplotlib as mpl mpl.use('TKAgg') import matplotlib.pyplot as plt plt.figure(figsize=(7.195, 3.841), dpi=100) for i in range(200): plt.subplot(10,20,i+1) plt.imshow(X_train[i,:].reshape([28,28]), cmap='gray') plt.axis('off') plt.show() Le script ``exo0.py`` doit produire l'affichage ci-dessous : .. image:: img/exo0.png :height: 400px :align: center .. admonition:: Question : Quel est l'espace dans lequel se trouvent les images ? Quelle est sa taille ? Exercice 1 : Régression Logistique **************************************** Modèle de prédiction ################################ Nous commencerons par créer un modèle de classification linéaire populaire, la régression logistique. Ce modèle correspond à un réseau de neurones à une seule couche, qui va projeter le vecteur d'entrée :math:`\mathbf{x_i}` pour une image MNIST (taille :math:`28^2=784`) avec un vecteur de de paramètres :math:`\mathbf{w_{c}}` pour chaque classe (plus un biais :math:`b_c`). Pour correspondre à la matrice des données de l'exercice précédent, on considère que chaque exemple :math:`\mathbf{x_i}` est un vecteur ligne - taille (1,784). En regroupant l'ensemble des jeux de paramètres :math:`\mathbf{w_{c}}` pour les 10 classes dans une matrice :math:`\mathbf{W}` (taille :math:`784\times 10`) et les biais dans un vecteur :math:`\mathbf{b}`, on obtient un vecteur :math:`\mathbf{\hat{s_i}} =\mathbf{x_i} \mathbf{W} + \mathbf{b}` de taille (1,10). Une fonction d'activation de type soft-max sur :math:`\mathbf{\hat{y_i}} =` ``softmax`` :math:`(\mathbf{s_i})` permet d'obtenir le vecteur de sortie prédit par le modèle :math:`\mathbf{\hat{y_i}}` - de taille (1,10) - qui représente la probabilité *a posteriori* :math:`p(\mathbf{\hat{y_i}} | \mathbf{x_i})` pour chacune des 10 classes : .. math:: p(\hat{y_{c,i}} | \mathbf{x_i}) ) = \frac{e^{\langle \mathbf{x_i} ; \mathbf{w_{c}}\rangle + b_{c}}}{\sum\limits_{c'=1}^{10} e^{\langle \mathbf{x_i} ; \mathbf{w_{c'}}\rangle + b_{c'}}} :label: softmax Le schéma ci-dessous illustre le modèle de régression logistique avec un réseau de neurones. .. image:: img/LR.png :height: 150px :align: center Quel est le nombre de paramètres du modèle ? Justifier le calcul. Formulation du problème d'apprentissage ################################################# Afin d'entraîner le réseau de neurones, on va comparer, pour chaque exemple d'apprentissage, la sortie prédite :math:`\mathbf{\hat{y_i}}` par le réseau (équation :eq:`softmax`) pour l'image :math:`\mathbf{x_i}` avec la sortie réelle :math:`\mathbf{y_i^*}` issue de la supervision qui correspond à la catégorie de l'image :math:`\mathbf{x_i}`: on utilisera en encodage de type *one-hot* pour :math:`\mathbf{y_i^*}`, *i.e.* : .. math:: y_{c,i}^* = \begin{cases} 1 & \text{si c correspond à l'indice de la classe de } \mathbf{x_i} \\ 0 & \text{sinon} \end{cases} :label: one-hot Pour mesurer l'erreur de prédiction, on utilisera une fonction de coût de type entropie croisée (*cross-entropy*) entre :math:`\mathbf{\hat{y_i}}` et :math:`\mathbf{y_i^*}` (l'entropie croisée est liée à la divergence de Kullback-Leiber, qui mesure une dissimilarité entre distributions de probabilités) : :math:`\mathcal{L}(\mathbf{\hat{y_i}}, \mathbf{y_i^*}) = -\sum\limits_{c=1}^{10} y_{c,i}^* log(\hat{y}_{c,i}) = - log(\hat{y}_{c^*,i})`, où :math:`c^*` correspond à l'indice de la classe donnée par la supervision pour l'image :math:`\mathbf{x_i}`. La fonction de coût finale consistera à moyenner l'entropie croisée sur l'ensemble de la base d'apprentissage :math:`\mathcal{D}` constituée de :math:`N=60000` images : .. math:: \mathcal{L}_{\mathbf{W},\mathbf{b}}(\mathcal{D}) = - \frac{1}{N}\sum_{i=1}^{N} log(\hat{y}_{c^*,i}) :label: CE .. admonition:: Question : La fonction de coût de :eq:`CE` est-elle convexe par rapport aux paramètres :math:`\mathbf{W}`, :math:`\mathbf{b}` du modèle ? Avec un pas de gradient bien choisi, peut-on assurer la convergence vers le minimum global de la solution ? Optimisation du modèle ################################################# Afin d'optimiser les paramètres :math:`\mathbf{W}` et :math:`\mathbf{b}` du modèle de régression logistique par descente de gradient, on va utiliser la règle des dérivées chaînées (*chain rule*) : .. math:: \frac{\partial \mathcal{L}}{\partial \mathbf{W}} = \frac{1}{N}\sum_{i=1}^{N} \frac{\partial \mathcal{L}}{\partial \mathbf{\hat{y_i}}} \frac{\partial \mathbf{\hat{y_i}}}{\partial \mathbf{s_i}} \frac{\partial \mathbf{s_i}}{\partial \mathbf{W}} .. math:: \frac{\partial \mathcal{L}}{\partial \mathbf{b}} = \frac{1}{N}\sum_{i=1}^{N} \frac{\partial \mathcal{L}}{\partial \mathbf{\hat{y_i}}} \frac{\partial \mathbf{\hat{y_i}}}{\partial \mathbf{s_i}} \frac{\partial \mathbf{s_i}}{\partial \mathbf{b}} .. admonition:: Montrer que : .. math:: \frac{\partial \mathcal{L}}{\partial \mathbf{s_i}} = \mathbf{\delta^y_i} = \frac{\partial \mathcal{L}}{\partial \mathbf{\hat{y_i}}} \frac{\partial \mathbf{\hat{y_i}}}{\partial \mathbf{s_i}} = \mathbf{\hat{y_i}} - \mathbf{y_i^*} .. admonition:: En déduire que les gradients de :math:`\mathcal{L}` par rapport aux paramètres du modèle s'écrivent : .. math:: \frac{\partial \mathcal{L}}{\partial \mathbf{W}} = \frac{1}{N} \mathbf{X}^T (\mathbf{\hat{Y}} - \mathbf{Y^*}) = \frac{1}{N} \mathbf{X}^T \mathbf{\Delta^y} :label: gradientW .. math:: \frac{\partial \mathcal{L}}{\partial \mathbf{b}} = \frac{1}{N}\sum_{i=1}^{N}(\mathbf{\hat{y_i}} - \mathbf{y_i^*}) :label: gradientB où :math:`\mathbf{X}` est la matrice des données (taille :math:`60000\times 784`), :math:`\mathbf{\hat{Y}}` est la matrice des labels prédits sur l'ensemble de la base d'apprentissage (taille :math:`60000\times 10`) et :math:`\mathbf{Y^*}` est la matrice des labels issue de la supervision (*ground truth*, taille :math:`60000\times 10`), et :math:`\mathbf{\Delta^y}=\mathbf{\hat{Y}}-\mathbf{Y^*}`. Implémentation de l'apprentissage ################################################# Les gradients aux équations :eq:`gradientW` et :eq:`gradientB` s'écrivent sous forme « vectorielle », ce qui rend les calculs efficaces avec des librairies de calculs scientifique comme ``numpy``. Après calcul du gradient, les paramètres seront mis à jour de la manière suivante : .. math:: \mathbf{W}^{(t+1)} = \mathbf{W}^{(t)} - \eta \frac{\partial \mathcal{L}}{\partial \mathbf{W}} :label: gradientupdateW .. math:: \mathbf{b}^{(t+1)} = \mathbf{b}^{(t)} - \eta \frac{\partial \mathcal{L}}{\partial \mathbf{b}} :label: gradientupdateB où :math:`\eta` est le pas de gradient (*learning rate*). Pour implémenter l'algorithme d'apprentissage, on utilisera une descente de gradient stochastique, c'est à dire que les gradients aux équations :eq:`gradientW` et :eq:`gradientB` ne seront pas calculés sur l'ensemble des :math:`N=60000` images d'apprentissage, mais sur un sous-ensemble appelé **batch**. Cette technique permet une mise à jour des paramètres plus fréquente qu'avec une descente de gradient classique, et une convergence plus rapide (au détriment d'un calcul de gradient approximé). **On demande de mettre en place un script** ``exo1.py`` **qui implémente l'algorithme de régression logistique sur la base MNIST.** Après avoir chargé les données (exercice 0), on utilisera le code suivant pour générer des étiquettes (*labels*) au format 0-1 encoding - :eq:`one-hot`. .. code-block:: python from keras.utils import np_utils K=10 # convert class vectors to binary class matrices Y_train = np_utils.to_categorical(y_train, K) Y_test = np_utils.to_categorical(y_test, K) On mettra alors en place un code dont le squellette est donné ci-dessous : .. code-block:: python import numpy as np N = X_train.shape[0] d = X_train.shape[1] W = np.zeros((d,K)) b = np.zeros((1,K)) numEp = 20 # Number of epochs for gradient descent eta = 1e-1 # Learning rate batch_size = 100 nb_batches = int(float(N) / batch_size) gradW = np.zeros((d,K)) gradb = np.zeros((1,K)) for epoch in range(numEp): for ex in range(nb_batches): # FORWARD PASS : compute prediction with current params for examples in batch # BACKWARD PASS : # 1) compute gradients for W and b # 2) update W and b parameters with gradient descent Pour compléter ce code, vous devez : - Mettre en place une fonction ``forward(batch, W, b)`` qui va calculer la prédiction pour un batch de données. La fonction ``forward`` sera appelée pour chaque itération de la double boucle précédente. - Si on considère un batch des données de taille :math:`tb\times 784`, les paramètres :math:`\mathbf{W}` (taille :math:`784\times 10`) et :math:`\mathbf{b}` (taille :math:`1\times 10`), la fonction ``forward`` renvoie la prédiction :math:`\mathbf{\hat{Y}}` sur le batch (taille :math:`tb\times 10`). - On pourra utiliser la fonction suivante pour calculer la fonction softmax sur chaque élément de de la matrice de la projection linéraire (taille :math:`tb\times 10`) : .. code-block:: python def softmax(X): # Input matrix X of size Nbxd - Output matrix of same size E = np.exp(X) return (E.T / np.sum(E,axis=1)).T - Compléter le code pour la passe *backward*, consistant à : - Calculer les gradients comme indiqué dans les équations :eq:`gradientW` et :eq:`gradientB`. - Mettre à jour les paramètres par descente de gradient comme indiqué dans les équations :eq:`gradientupdateW` et :eq:`gradientupdateB`. Enfin, vous pouvez utiliser la fonction ``accuracy(W, b, images, labels)`` fournie pour calculer le taux de bon classement (bonne reconnaissance) du modèle. Ceci permet de mesurer l'évolution des performances au cours des époques de l'algorithme d'apprentissage, et sur la base de test une fois le modèle appris. **Vous devez obtenir un score de l'ordre de 92\% sur la base de test pour ce modèle de régression logistique.** .. code-block:: python def accuracy(W, b, images, labels): pred = forward(images, W,b ) return np.where( pred.argmax(axis=1) != labels.argmax(axis=1) , 0.,1.).mean()*100.0 .. Solution : ``_.