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