Entrainement d'un classifieur binaire sur MNIST¶

Quelques rappels¶

Nous voulons entraîner un réseau de neuronnes à couches denses (ou multi-layer perceptron (MLP)) dont le but est de classifier des nombres écrits à la main suivant q'ils sont plus grand ou plus petit que $4$.

Nous prenons pour fonction d'activation $\sigma(x) = x\mathbf{1}_{x\geq 0}$, et appliquons la fonction softmax en sortie de la dernière couche, à la place de $\sigma$. La fonction de coût considérée est l'entropie croisée $$c(\hat y,y) = - \log(\hat y_{i_y})$$ où $y$ est l'étiquette, de la forme $(0,0, \cdots,0,1,0,\cdots,0)$ d'une data $x$, et où $i_y$ est l'indice de la valeur $1$. Nous fixerons la dimension des couches à $(784,500,75,2)$. On rappelle la formule du mode forward (évaluation) d'un tel MLP :

$$ z_0 = x $$ $$ a^{(i)} = W^{(i)}z^{(i-1)}+b^{(i)}, ~~ \forall i \in \{1,2,3 \}$$ $$z^{(i)} = \sigma(a^{(i)}), ~~ \forall i \in \{1,2 \}$$ $$ \hat y = \mathrm{softmax}(a^{(3)})$$ $$ \text{coût} = c(\hat y,y)$$

Nous avons donc : $z^{(0)}\in \R^{784}$, $a^{(1)} \in \R^500$, $a^{(2)} \in \R^{75}$ et $a^{(3)} \in \R^2$.

Pour $\theta := (W^{(1)}, b^{(1)}, W^{(2)}, b^{(2)}, W^{(3))}, b^{(3)})$, on note $g_\theta(x)$ la quantité $\hat y$ obtenue par ce calcul direct. C'est le vecteur de prédiction de $x$, représentant un probabilité la probabilité de chaque label pour cette image.

Par "entraîner le réseau", on entend minimiser la fonction $$\phi : \theta \mapsto \mathbb{E}_{x,y \sim \mathbb{P}}\left [ c(g_\theta(x),y) \right ]$$ où $\mathbb P$ est la distribution des données. Nous appliquons pour cela l'algorithme de descente de gradient stochastique à passage unique:

  • Cela signifie que pour le calcul de $\theta_{k+1}$, la direction $\nabla_\theta c(g_{\theta_k}(x_k),y_k)$ est considérée comme une bonne approximation de $\nabla_\theta \phi(\theta_k)$ en moyenne.
  • Ici le couple $(x_k,y_k)$ désigne une donnée d'entrainement prise au hasard dans X_train$\times$Y_train

Nous devons donc être capable d'écrire le mode backward du graphe computationel de la fonction $\theta \mapsto c(g_{\theta_k}(x),y)$ à donnée $(x,y)$ fixée.

Remarque : Pour se simplifier la vie ici :

  • on ne remélange pas les données entre deux epochs
  • on prend un pas constant pour le gradient

Questions préliminaires :¶

  1. Quelles sont les dimensions des matrices $W^{(i)}$ ? (Nombre de ligne et de colonnes)
  2. Calculer sur papier la Jacobienne de la fonction softmax.
  3. En déduire la formule backward sur l'étape softmax de notre classifieur (i.e. de l'étape $a^{(3)} \mapsto \hat y$)
  4. Partant du noeud $\delta a^{(3)}$, calculer $\delta z^{(2)}$, $\delta b^{(3)}$, $\delta W^{(3)}$.
  5. En déduire le mode backward du graphe computationnel de la fonction coût.

Implémentation¶

Préparation des données¶

In [ ]:
! pip install keras
In [ ]:
import numpy as np
import matplotlib.pyplot as plt 
from PIL import Image
from keras.datasets import mnist

# Chargement de MNIST
(X_train,Y_train),(X_test,Y_test) = mnist.load_data()
In [ ]:
# Affichage d'une donnée de MNIST
plt.figure()
plt.imshow(X_train[27],cmap = "binary")
plt.colorbar()
print("Label_27 = ",Y_train[27])
In [ ]:
## Transformer les images en vecteurs colonnes
(X_train,Y_train),(X_test,Y_test) = mnist.load_data()
dim_input = X_train.shape[1]*X_train.shape[2]
X_train = X_train.reshape((X_train.shape[0],dim_input)) 
X_test = X_test.reshape((X_test.shape[0],dim_input))
X_train = X_train/255
X_test = X_test/255

## Shuffle des données
np.random.seed(238)
nbr_training_data = X_train.shape[0]
shuffle_index = np.random.permutation(nbr_training_data)
X_train, Y_train = X_train[shuffle_index], Y_train[shuffle_index]
plt.imshow(X_train[27].reshape(28,28),cmap = "binary")
print("Label_27 = ",Y_train[27])

Entrainement¶

In [ ]:
dimensions = [784,500,75,2]
rate = 10**(-3)
nbr_epoch = 3
width = 0.1 #width of first initialization


## Definition de la fonction softmax
# def softmax(x):

## Initialisation : choisir des poids de l'ordre de grandeur de "width"

# W1 = ...


## Training with single pass SGD

k=0
print(nbr_training_data*nbr_epoch)
while k < nbr_training_data*nbr_epoch :

    # récupérer la donnée/label et adapter le label à notre classifieur binaire
 
    
    # créer une étiquette adaptée à la classification voulue


    # Calcul du mode forward : ATTENTION A LA SHAPE de array. Utiliser np.matmul...

    # Calcul du mode backward : Encore plus ATTENTION


    # Mise à jour des coefficients par la descente de gradient stochastique

    W3 = W3 - rate*dW3
    b3 = b3 - rate*db3
    W2 = W2 - rate*dW2
    b2 = b2-rate*db2
    W1 = W1 - rate*dW1
    b1 = b1 - rate*db1
    k = k +1
    # Print de la progression..

    
In [ ]:
## Test du classifieur

#definition de la fonction de classification
def classification(X):

    #return np.argmax(z3)



# Calcul du score moyen sur les données de test

size_test = X_test.shape[0]
success_rate = 0

for test in range(1,size_test):
    #if .....:
        #success_rate += 1/size_test
print("success rate = ", success_rate)

Test "à la main" du classifieur¶