Travaux pratiques - Deep Learning avancé

Préambule : Google Colab AVANT DE COMMENCER

Il est conseiller d’executer ce TP avec un GPU pour accéléer les calculs en inférence et en entraînement. Si vous n’avez pas accès à un GPU, vous pouvez utiliser l’interface Google Colab qui permet d’éxecuter un notebook python et même d’avoir accès à un GPU pour une durée déterminée. Vous trouverez ce TP dans Google Colab ici.

Vous pouvez faire une copie de ce notebook dans votre drive pour être sur que vos modifications seront enregistrées.

Pour cela, aller dans File > Save a copy in Drive

Pour avoir accès à un GPU allez dans Runtime > Change Runtime Type > GPU

Préliminaires Google Colab

Pour ne pas avoir à télécharger vos donner à chaque fois que vous relancer ce notebook, vous pouvez les charger dans votre drive. Attention, si vous choisisez cette option, une place non négligeable de votre Drive sera occupée par les jeux de donées téléchargés. Vous pouvez choisir de ne pas le faire et vous travaillerez alors dans une session temporaire. Pour récupérer les données sur vore drive, il vous faut le monter avec la commande suivante :

from google.colab import drive
drive.mount('/content/drive')
%cd drive/MyDrive

Pour la suite du TP, nous placerons nos données dans le répertoire TRIED/TP3

!mkdir RCP209
!mkdir RCP209/TP5
!mkdir RCP209/TP5/data
%cd RCP209/TP5/data
!pwd

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 http://host.robots.ox.ac.uk/pascal/VOC/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éalable avoir téléchargé les données : - D’apprentissage : http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar

Et décompresser le tout dans un unique dossier.

!wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
!tar -xvf VOCtrainval_06-Nov-2007.tar
!rm VOCtrainval_06-Nov-2007.tar
!wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
!tar -xvf VOCtest_06-Nov-2007.tar
!rm VOCtest_06-Nov-2007.tar
%cd ..
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 [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 : https://github.com/fchollet/deep-learning-models/blob/master/resnet50.py.
Avec Keras, ce réseau est accessible avec le code suivant :
from tensorflow.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').

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 :

model.layers.pop()
<keras.layers.core.dense.Dense at 0x7f8a00436d50>

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 : https://keras.io/getting-started/functional-api-guide/. Pour que le dépilement de la dernière couche ait un effet sur le modèle, il faut le préciser explicitement :

from tensorflow.keras.models import Model
model = Model(inputs=model.input, outputs=model.layers[-2].output)

TODO

  • Vérifier l’architecture du modèle

  • Le compiler (pour l’extraction futures des Deep Features). On prendra une descente de gradient stochastique comme méthode d’optimisation.

##################### VOTRE CODE ICI ############
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 : http://cedric.cnam.fr/~thomen/cours/US330X/data_gen.py. On instanciera le générateur sur la base d’apprentissage de la manière suivante :
!wget http://cedric.cnam.fr/~thomen/cours/US330X/data_gen.py
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 :

batch_size = 32
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 » :

TODO

  • Utiliser ce générateur afin d’extraire séquentiellement les données de PASCAL VOC 2007

  • Extraire les Deep Features de la base en train

  • Répéter l’extraction sur les images de test

import numpy as np
batch_size = 32
generator = data_generator_train.flow(batch_size=batch_size)
Nb_images = len(data_generator_train.images_ids_in_subset) # Nombre d'images
# Initilisation des matrices contenant les Deep Features et les labels
X_train = # TODO...
Y_train = # TODO...
# Calcul du nombre de 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
    # TODO...
data_generator_test = PascalVOCDataGenerator('test', data_dir)
batch_size = 32
generator = data_generator_test.flow(batch_size=batch_size)
Nb_images = len(data_generator_test.images_ids_in_subset) # Nombre d'images
# Extraction des images et des deep features
# TODO...

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 :

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 : http://cedric.cnam.fr/~thomen/cours/US330X/DF_ResNet50_VOC2007.npz) :

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.

TODO

  • Créer un modèle d’une seule couche permettant la classification des features

  • On prendra une fonction d’activation de type sigmoïde

  • Le compiler

  • L’entrainer

########## VOTRE CODE ICI #####################

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 :

Question :

Observer le code source de la fonction binary_crossentropy : https://github.com/keras-team/keras/blob/master/keras/losses.py/. 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 :
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).

TODO

  • Calculer la précision pour chaque classe en utilisant la fonction average_precision_score de la bibliothèque Scikit-Learn

from sklearn.metrics import average_precision_score
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):
    #TODO...

print("MAP TRAIN =", AP_train.mean()*100)
print("MAP TEST =", AP_test.mean()*100)

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éseau seront initialisées sur la base ImageNet, mais fine-tunées sur VOC2007 pour spécialiser les représentations internes à la base 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 :

TODO

  • Retirer la dernière couche du modèle et la remplacer par une couche complètement connectée de taille adaptée

# Load ResNet50 architecture & its weights
model = ResNet50(include_top=True, weights='imagenet')
model.layers.pop()
# Modify top layers
# TODO...

On spécifiera ensuite qu’on souhaite apprendre les paramètres de l’ensemble du réseau :

for i in range(len(model.layers)):
  model.layers[i].trainable = True

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 :
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

  • fit_generator va prendre en paramètre la fonction flow qui va renvoyer un batch d’exemples 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.

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)

TODO

  • Évaluer le modèle sur la base de test

  • Calculer le MAP

Question :

Quel MAP obtenez-vous sur la base de test dans ce régime de fine-tuning ? Conclure.