Ce calepin reprend le même objectif que les calepins de l'Atelier MNIST et sur les mêmes données mais en utilisant cette fois les librairies Keras
et TensorFlow
pour aborder l'apprentissage profond. Il est une adpatation du tutoriel de Keras.
In [ ]:
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sb
sb.set()
import pandas as pd
import numpy as np
import time
import keras.utils as ku
import keras.models as km
import keras.layers as kl
import keras.optimizers as ko
from sklearn.metrics import confusion_matrix
# Paramètres
batch_size = 128
epochs = 10
import sys
print(sys.version)
from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())
In [ ]:
import keras
keras.__version__
Les données peuvent être préalablement téléchargées ou directement lues. Ce sont celles originales du site MNIST DataBase mais préalablement converties au format .csv, certes plus volumineux mais plus facile à lire. Attention le fichier mnist_train.zip
présent dans le dépôt est compressé.
In [ ]:
# Lecture des données d'apprentissage
N_classes = 10
# path="" # Si les données sont dans le répertoire courant sinon:
path=""
Dtrain=pd.read_csv(path+"mnist_train.zip",header=None)
X_train = Dtrain.values[:,:-1]
Y_train = Dtrain.values[:,-1]
Dtest=pd.read_csv(path+"mnist_test.csv",header=None)
X_test = Dtest.values[:,:-1]
Y_test = Dtest.values[:,-1]
Attention, avec Keras, la variable réponse doit être une matrice binaire où chaque classe est représentée par une indicatrice: pour chaque individu, l'élément de la colone correspondant à la classe à laquelle il appartient est à 1, sinon il est à 0.
Keras possède une fonction to_catergorical
permettant de convertir directement le vecteur variable Y_train
de réponse en matrice (array numpy
) indicatriceY_train_cat
.
C'est l'équivalent de get_dummies
de pandas
ou OneHotEncoder
de scikit-learn
.
In [ ]:
Y_train_cat = ku.to_categorical(Y_train, N_classes)
Y_test_cat = ku.to_categorical(Y_test, N_classes)
Première tentative d'appliquer un réseaux de neurone de type Perceptron classique avec 4 couches:
Une dernière couche softmax fournit la classification
In [ ]:
X_train.shape
In [ ]:
# Définition du réseau
model = km.Sequential()
model.add(kl.Dense(128, activation='relu', input_shape=(784,)))
model.add(kl.Dropout(0.2))
model.add(kl.Dense(128, activation='relu'))
model.add(kl.Dropout(0.2))
model.add(kl.Dense(N_classes, activation='softmax'))
# Réumé
model.summary()
Q Retrouvez manuellement le nombre de paramètres.
In [ ]:
# apprentissage
model.compile(loss='categorical_crossentropy',
optimizer=ko.RMSprop(),
metrics=['accuracy'])
ts = time.time()
history = model.fit(X_train, Y_train_cat,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(X_test, Y_test_cat))
te = time.time()
t_train_mpl = te-ts
In [ ]:
score_mpl = model.evaluate(X_test, Y_test_cat, verbose=0)
predict_mpl = model.predict(X_test)
print('Test loss:', score_mpl[0])
print('Test accuracy:', score_mpl[1])
print("Time Running: %.2f seconds" %t_train_mpl )
fig=plt.figure(figsize=(7,6))
ax = fig.add_subplot(1,1,1)
ax = sb.heatmap(pd.DataFrame(confusion_matrix(Y_test, predict_mpl.argmax(1))), annot=True, fmt="d")
Q Que dire de ces résultats ?
Q Faites tourner de nouveaux l'algorithme en normalisant les données afin que celles-ci soit comprises entre 0 et 1. Qu'observez vous?
Dans les exemples précédents. Les données était "applaties". Une imade de $28\times 28=784$ pixels est considérée comme un vecteur.
Pour pouvoir utiliser le principe de la convolution la structure des images est conservée. Une image n'est pas un vecteur de tailles $784\times 1$ mais une matrice de taille $28\times 28$. Une troisième dimension est également nécessaire pour décrire afin de prendre en compte les différents channels
de l'image. Dans le cas de MNIST
cette dernière dimension est de taille 1 car les pixels ne sont décrits qu'avec un seul niveau de gris. Cependant, des images couleurs en RGB sont généralement codées avec trois niveaux d'intensité (Rouge, Vert et Bleus).
Ainsi X_train
est réorganisée en cube ou multitableau de dimensions $60000\times 28\times 28\times 1$ pour être utilisé dans un réseau de convolution avec Keras
.
In [ ]:
X_train_conv = X_train.reshape(60000, 28, 28, 1)
X_test_conv = X_test.reshape(10000, 28, 28, 1)
Visualisation des données
In [ ]:
import keras.preprocessing.image as kpi
fig = plt.figure(figsize=(5,5))
ax = fig.add_subplot(1,1,1)
x = kpi.img_to_array(X_train_conv[0])
ax.imshow(x[:,:,0]/255, interpolation='nearest', cmap="binary")
ax.grid(False)
plt.show()
In [ ]:
from keras.models import Sequential
from keras.layers import Conv2D
conv_filter = np.array([
[0.2, -0.2, 0],
[0.2, -0.2, 0],
[0.2, -0.2, 0],
])
def my_init_filter(shape, conv_filter = conv_filter, dtype=None):
xf,yf = conv_filter.shape
array = conv_filter.reshape(xf, yf, 1, 1)
return array
my_init_filter(0).shape
conv_edge = Sequential([
Conv2D(kernel_size=(3,3), filters=1, kernel_initializer=my_init_filter, input_shape=(28, 28, 1))
])
Q Notez que dans la fonction my_init_filter
les dimensions de l'image sont modifiés. A quoi correspondent les deux dimensions ajoutées?
In [ ]:
img_in = np.expand_dims(x, 0)
img_out = conv_edge.predict(img_in)
fig, (ax0, ax1, ax2) = plt.subplots(ncols=3, figsize=(15, 5))
ax0.imshow(img_in[0,:,:,0], cmap="binary")
ax0.set_title("Image originale")
ax0.grid(False)
norm_conv_filter = (conv_filter-conv_filter.min())/conv_filter.max()
ax1.imshow(norm_conv_filter.astype(np.uint8), cmap="binary")
ax1.set_title("Filtre")
ax1.grid(False)
ax2.imshow(img_out[0,:,:,0].astype(np.uint8), cmap="binary")
ax2.set_title("Image Filtre")
ax2.grid(False)
Q Que constatez vous? Verifiez que les dimensions de l'image en sortie sont cohérentes.
Q Testez ce même code avec un filtre différent.
In [ ]:
from keras.models import Sequential
from keras.layers import Conv2D
conv_filter = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0],
])
def my_init_filter(shape, conv_filter = conv_filter, dtype=None):
xf,yf = conv_filter.shape
array = conv_filter.reshape(xf, yf, 1, 1)
return array
my_init_filter(0).shape
conv_sp = Sequential([
Conv2D(kernel_size=(3,3), filters=1, kernel_initializer=my_init_filter, input_shape=(28, 28, 1),
strides=2,
padding="SAME") ])
Q Quel est l'effet du filtre défini ici?
In [ ]:
img_in = np.expand_dims(x, 0)
img_out = conv_sp.predict(img_in)
fig, (ax0, ax1, ax2) = plt.subplots(ncols=3, figsize=(15, 5))
ax0.imshow(img_in[0,:,:,0].astype(np.uint8),
cmap="binary");
ax0.grid(False)
norm_conv_filter = (conv_filter-conv_filter.min())/conv_filter.max()
ax1.imshow(norm_conv_filter.astype(np.uint8),
cmap="binary");
ax1.grid(False)
ax2.imshow(img_out[0,:,:,0].astype(np.uint8),
cmap="binary");
ax2.grid(False)
Q Modifiez les paramètres stride
et padding
, et observez l'effet sur la dimension des images.
Exercice Ecrivez un code similaire pour observez l'effet du MaxPooling.
In [ ]:
# %load max_pooling.py
In [ ]:
LeNet5model = km.Sequential()
LeNet5model.add(kl.Conv2D(filters = 6, kernel_size = 5, strides = 1, activation = 'tanh',
input_shape = (28,28,1)))
LeNet5model.add(kl.MaxPooling2D(pool_size = 2, strides = 2))
LeNet5model.add(kl.Conv2D(filters = 16, kernel_size = 5,strides = 1, activation = 'tanh'))
LeNet5model.add(kl.MaxPooling2D(pool_size = 2, strides = 2))
LeNet5model.add(kl.Flatten())
LeNet5model.add(kl.Dense(units = 120, activation = 'tanh'))
LeNet5model.add(kl.Dense(units = 84, activation = 'tanh'))
LeNet5model.add(kl.Dense(units = 10, activation = 'softmax'))
LeNet5model.summary()
Q Retrouvez manuellement le nombre de paramètres.
Q Que dire du nombre de paramètres de ce réseau par rapport au réseau dense précédement défini?
In [ ]:
# Apprentissage
LeNet5model.compile(loss="categorical_crossentropy",
optimizer=ko.Adadelta(),
metrics=['accuracy'])
ts=time.time()
LeNet5model.fit(X_train_conv, Y_train_cat,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(X_test_conv, Y_test_cat))
te=time.time()
t_train_conv = te-ts
Q Que dire du temps de calcul? Pourquoi est-il plus long que le réseau Dense?
In [ ]:
score_conv = LeNet5model.evaluate(X_test_conv, Y_test_cat, verbose=0)
predict_conv = LeNet5model.predict(X_test_conv)
print('Test loss:', score_conv[0])
print('Test accuracy:', score_conv[1])
print("Time Running: %.2f seconds" %t_train_conv )
fig=plt.figure(figsize=(7,6))
ax = fig.add_subplot(1,1,1)
ax = sb.heatmap(pd.DataFrame(confusion_matrix(Y_test, predict_conv.argmax(1))), annot=True, fmt="d")
Test d'un réseau de convolution constitué de 7 couches:
Une couche softmax fournit la classification
In [ ]:
# descrition du réseau
model = km.Sequential()
model.add(kl.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28,28, 1), data_format="channels_last"))
model.add(kl.Conv2D(64, (3, 3), activation='relu'))
model.add(kl.MaxPooling2D(pool_size=(2, 2)))
model.add(kl.Dropout(0.25))
model.add(kl.Flatten())
model.add(kl.Dense(128, activation='relu'))
model.add(kl.Dropout(0.5))
model.add(kl.Dense(N_classes, activation='softmax'))
# Résumé
model.summary()
# Apprentissage
model.compile(loss="categorical_crossentropy",
optimizer=ko.Adadelta(),
metrics=['accuracy'])
ts=time.time()
model.fit(X_train_conv, Y_train_cat,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(X_test_conv, Y_test_cat))
te=time.time()
t_train_conv = te-ts
In [ ]:
score_conv = model.evaluate(X_test_conv, Y_test_cat, verbose=0)
predict_conv = model.predict(X_test_conv)
print('Test loss:', score_conv[0])
print('Test accuracy:', score_conv[1])
print("Time Running: %.2f seconds" %t_train_conv )
fig=plt.figure(figsize=(7,6))
ax = fig.add_subplot(1,1,1)
ax = sb.heatmap(pd.DataFrame(confusion_matrix(Y_test, predict_conv.argmax(1))), annot=True, fmt="d")
Q Commenter les résultats. Comparer avec les autres techniques d'apprentissage.
Q Comment améliorer encore ces résultats?