.. _chap-tpDeepLearning5:
##############################################################################
Travaux pratiques - Deep Learning avancé
##############################################################################
Exercice 0 : Fonction d'activation ReLU
******************************************************************
On va comparer la fonction d'activation sigmoide utilisée dans les TP précédents par une fonction ReLU (Rectifed Linear Unit).
- Reprendre le perceptron à une couche cachée du TP3 en utilisant une non-linéarité ReLU. Observer le nombre d'itérations (epochs) nécessaires pour atteindre la convergence de réseau.
- Reprendre le réseau convolutif du TP3 en utilisant des non-linéarités ReLU pour les couches convolutives. Observer le nombre d'itérations (epochs) nécessaires pour atteindre la convergence de réseau.
**Conclure sur l'intérêt de la fonction d'activation ReLU.**
##############################################################################
Transfer Learning et Fine-Tuning sur VOC2007
##############################################################################
Pour aller plus loin, nous allons nous intéresser aux propriétés de "transfert" des réseaux convolutifs profonds pré-entraînés sur des bases large échelle comme ImageNet.
Nous allons nous intéresser à la base PASCAL VOC2007 ``_, qui est une base contenant:
- 20 classes (parmi les macro-catégories Person, Animal, Vehicle, Indoor), mais avec des étiquettes "multi-labels". Par exemple, une image peut contenir à la fois une personne et un cheval.
- L'ensemble d'apprentissage (train+val) contient environ 5000 images, l'ensemble de test contient également environ 5000 images.
- Les images sont de tailles variables mais autour de 500x300.
Avec des volumes de données tels que ceux de PASCAL VOC, il est impossible d’entraîner des réseaux de neurones avec autant de paramètres que ceux utilisés pour ImageNet sans être confrontés au problème du sur-apprentissage.
Nous allons étudier des solutions d'apprentissage par transfert pour surmonter ce problème.
Il faut au préalabale avoir téléchargé les données :
- D'apprentissage : ``_
- Et de test : ``_
Et décompresser le tout dans un unique dossier.
Exercice 1 : Modèle ResNet-50 avec ``Keras``
*****************************************************
Nous allons récupérer une architecture de réseau convolutif donnant des très bonnes performances sur ImageNet. On va ici s'intéresser aux réseaux ResNet :cite:`He_2016_CVPR`, qui ont remporté le challenge ILSVRC en 2015.
On utilisera un réseau ResNet-50 dont l'architecture détaillée peut être trouvé ici : ``_.
Avec ``Keras``, ce réseau est accessible avec le code suivant :
.. code-block:: python
from keras.applications.resnet50 import ResNet50
model = ResNet50(include_top=True, weights='imagenet')
Où les poids issus de l'entraînement sur ImageNet sont directement récupérés (``weights='imagenet'``).
Exercice 2 : Extraction de "Deep Features"
*****************************************************
Une première solution pour surmonter le manque d'exemples d'apprentissage est de se servir des réseaux
pré-entraînés sur ImageNet comme extracteur de descripteurs. Nous allons appliquer le réseau ResNet50 et extraire la couche d'activations du réseau avant les 1000
classes d'ImageNet, couche de taille 2048. Ainsi, l'application du réseau sur chaque image de la base produit un vecteur de taille 2048, appelé "Deep Feature".
On peut pour cela utiliser le code suivant :
.. code-block:: python
model.layers.pop()
L'utilisation de la fonction ``pop()`` permet de supprimer la dernière couche des (1000) classes d'ImageNet. La classe ``ResNet50`` du module
``keras.applications.resnet50`` charge un modèle de l'API fonctionnelle de Keras : ``_.
Pour que le dépilement de la dernière couche ait un effet sur le modèle, il faut le préciser explicitement :
.. code-block:: python
model = Model(input=model.input,output=model.layers[-1].output)
On pourra ensuite vérifier l'architecture du modèle, et le compiler (pour l'extraction futures des Deep Features):
.. code-block:: python
model.summary()
model.compile(loss='binary_crossentropy', optimizer=SGD(lr, momentum=0.9), metrics=['binary_accuracy'])
**Chargement des données de la base.**
Stocker en mémoire l'ensemble des données devient impossible pour des bases de données massives.
On arrive à la limite sur VOC 2007 ou le tenseur d'entrée prend plusieurs Go de mémoire.
Au lieu de charger l'intégralité des données on va s'appuyer sur une fonction génératrice, *i.e.* capable de générer à la volée un batch d'exemples sur lequel calculer une
étape forward pour l'extraction des "Deep Features".
Sur la base VOC 2007, on utilisera le générateur ``PascalVOCDataGenerator`` fourni : ``_.
On instanciera le générateur sur la base d'apprentissage de la manière suivante :
.. code-block:: python
from data_gen import PascalVOCDataGenerator
data_dir = '/data/VOCdevkit/VOC2007/' # A changer avec votre chemin
data_generator_train = PascalVOCDataGenerator('trainval', data_dir)
Le générateur contient un dictionnaire dont les clés correspondent aux identifiants des images de la base (*e.g.* 000012), et les clés sont le vecteur codé au format "one-hot"
(par exemple [0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0] va indiquer que l'image contient les classes bicycle et person). Un générateur va être crée par l'appel de la méthode ``flow`` :
.. code-block:: python
generator = data_generator_train.flow(batch_size=batch_size)
L'appel de ``next()`` sur le générateur va permettre d'exécuter le code à l'intérieur de la bouche ``while`` de ``flow``, *i.e.* :
- Charger en mémoire le batch d'images et de labels suivant
- Retailler chaque image en une taille précisée (224x224)
- Appliquer un pré-traitement à l'image (soustraction de l'image moyenne d'ImageNet)
Ainsi, on va pouvoir utiliser ce générateur afin d'extraire séquentiellement les données des PASCAL VOC 2007, et d'extraire les "Deep Features" :
.. code-block:: python
batch_size = 32
generator = data_generator_train.flow(batch_size=batch_size)
# Initilisation des matrices contenant les Deep Features et les labels
X_train = np.zeros((len(data_generator_train.images_ids_in_subset),2048))
Y_train = np.zeros((len(data_generator_train.images_ids_in_subset),20))
# Calcul du nombre e batchs
nb_batches = int(len(data_generator_train.images_ids_in_subset) / batch_size) + 1
for i in range(nb_batches):
# Pour chaque batch, on extrait les images d'entrée X et les labels y
X, y = next(generator)
# On récupère les Deep Feature par appel à predict
y_pred = model.predict(X)
X_train[i*batch_size:(i+1)*batch_size,:] = y_pred
Y_train[i*batch_size:(i+1)*batch_size,:] = y
Le temps d'extraction en CPU pour être long (plus d'une minute par batch de 32 images). L'utilisation du GPU accélère considérablement le calcul.
On calculera de manière identique la matrice des Deep Features sur la base de test. Finalement, on sauvegardera les Deep Features et labels de la manière suivante :
.. code-block:: python
outfile = 'DF_ResNet50_VOC2007'
np.savez(outfile, X_train=X_train, Y_train=Y_train,X_test=X_test, Y_test=Y_test)
Exercice 3 : Transfert sur VOC 2007
*****************************************************
On commencera par charger les données calculées à l'exercice précédent (téléchargeables directement ici :
``_) :
.. code-block:: python
outfile = 'DF_ResNet50_VOC2007.npz'
npzfile = np.load(outfile)
X_train = npzfile['X_train']
Y_train = npzfile['Y_train']
X_test = npzfile['X_test']
Y_test = npzfile['Y_test']
print "X_train=",X_train.shape, "Y_train=",Y_train.shape, " X_test=",X_test.shape, "Y_train=",Y_test.shape
On va maintenant considérer les Deep Features comme les données d'entrée et définir un réseau de neurones complètement connectés sans couche cachée pour prédire
les labels de sortie :
.. code-block:: python
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(20, input_dim=2048, name='fc1', activation='sigmoid'))
model.summary()
.. admonition:: Question :
Justifier le choix de la fonction d'activation de type sigmoïde par rapport à la fonction softmax habituelle.
On va maintenant compiler le modèle de la manière suivante :
.. code-block:: python
from keras.optimizers import SGD
learning_rate = 0.1
sgd = SGD(learning_rate)
model.compile(loss='binary_crossentropy',optimizer=sgd,metrics=['binary_accuracy'])
.. admonition:: Question :
Observer le code source de la fonction binary_crossentropy : ``_. Expliquer le calcul effectué dans notre cas et justifier pourquoi cette fonction de coût est adaptée
au contexte multi-label.
On va pouvoir entraîner et évaluer le modèle classiquement :
.. code-block:: python
model.fit(X_train, Y_train,batch_size=batch_size, epochs=nb_epoch,verbose=1)
scores = model.evaluate(X_test, Y_test, verbose=0)
print("%s TEST: %.2f%%" % (model.metrics_names[0], scores[0]*100))
print("%s TEST: %.2f%%" % (model.metrics_names[1], scores[1]*100))
**Évaluation finale de performances.** La métrique utilisée pour évaluer les performances sur PASCAL VOC est la Précision Moyenne (Average Precision). On pourra la calculer ainsi :
.. code-block:: python
y_pred_test = model.predict(X_test)
y_pred_train = model.predict(X_train)
AP_train = np.zeros(20)
AP_test = np.zeros(20)
for c in range(20):
AP_train[c] = average_precision_score(Y_train[:, c], y_pred_train[:, c])
AP_test[c] = average_precision_score(Y_test[:, c], y_pred_test[:, c])
print "MAP TRAIN =", AP_train.mean()*100
print "MAP TEST =", AP_test.mean()*100
.. admonition:: Question :
Quel MAP obtenez-vous sur la base de test ?
Exercice 4 : Fine-tuning sur VOC 2007
*****************************************************
Enfin, on va tester un apprentissage où les paramètres du réseaux seront initialisées sur la base ImageNet, mais fine-tunées sur VOC2007
pour spécialiser les représentations internes à la bas cible et améliorer les performances.
**N.B. : il faut impérativement utiliser une carte GPU dans cette partie.**
On commencera pour cela à charger le réseau et ajouter la couche de classification dédiée à VOC2007 :
.. code-block:: python
# Load ResNet50 architecture & its weights
model = ResNet50(include_top=True, weights='imagenet')
model.layers.pop()
# Modify top layers
x = model.layers[-1].output
x = Dense(data_generator_train.nb_classes, activation='sigmoid', name='predictions')(x)
model = Model(input=model.input,output=x)
On spécifiera ensuite qu'on souhaite apprendre les paramètres de l'ensemble du réseau :
.. code-block:: python
for i in range(nlayers):
model.layers[i].trainable = True
.. admonition:: Question :
Si on avait indiqué ``model.layers[i].trainable = False`` pour toutes les couches sauf la dernière, dans quel mode serions nous ?
On va ensuite compiler le modèle :
.. code-block:: python
lr = 0.1
model.compile(loss='binary_crossentropy', optimizer=SGD(lr=lr), metrics=['binary_accuracy'])
Et simplement utiliser la méthode ``fit_generator`` pour entraîner le modèle
.. code-block:: python
batch_size=32
nb_epochs=10
data_generator_train = PascalVOCDataGenerator('trainval', data_dir)
steps_per_epoch_train = int(len(data_generator_train.id_to_label) / batch_size) + 1
model.fit_generator(data_generator_train.flow(batch_size=batch_size),
steps_per_epoch=steps_per_epoch_train,
epochs=nb_epochs,
verbose=1)
- ``fit_generator`` va prendre a paramètre la fonction ``flow`` qui va renvoyer un batch d'exemple et de labels. La méthode forward va comparer la sortie prédite par le modèle aux labels données par la supervision puis l'étape backward va mettre à jour l'ensemble des paramètres du modèle.
.. admonition:: Question :
Quel MAP obtenez-vous sur la base de test dans ce régime de fine-tuning ? Conclure.
.. bibliography:: refs5.bib
:cited: