Travaux pratiques - Apprentissage auto-supervisé: RotNet

Apprentissage auto-supervisé et tâche prétexte

L’apprentissage auto-supervisé ou self-supervised learning (SSL) est une question importante en apprentissage automatique. Le SSL regroupe des approches prometteuses pour exploiter de grande quantité d’information non annotée. La difficulté realtive au manque d’annotation est typiquement un frein important pour les méthodes d’apprentissage supervisées que nous avons pu voir dans ce cours. Le SSL vise à encoder l’information de grands jeux de données dans l’espace latent d’un modèle pour pouvoir ensuite facileemnt l’adapter par transfert à une nouvelle tâche.

Parmi les méthodes basées SSL, une famille de méthodes consiste à résoudre des tâches prétextes pour capter les statistiques des données d’entrées. Ces tâches prétextes varient selon les modalités considérées. On retrouve en particulier des tâches de restauration de données corrompues ou cachées, de remise en ordre de séquences mélangées ou de caractérisation de transformations appliquées aux données. D’autres approches basées sur des modèles génératifs s’appliquent à directement apprendre la distribution des données et peuvent représenter des fonction de régularisation puissante pour palier au manque d’annotations.

Définition de la tâche prétexte

Dans ce TP, nous nous intéresserons à une tâche prétexte en particulier : la caractérisation de rotations appliquées à des images. Cette stratégie a été présentée dans le papier Unsupervised Representation Learning by Predicting Image Rotations https://arxiv.org/abs/1803.07728 par Gidaris et Al. en 2018. L’idée est relativement simple et consiste à pré-entraîner un CNN sur une tâche de classification auxiliaire. Les entrées ici sont des images auquelles on applique une rotation d’un angle \(\theta \in \Theta\)\(\Theta\) est l’ensemble des rotations autorisée. Pour une image \(\mathbf x_i\), on tire uniformément \(\theta_i\) parmi \(\Theta\) paramétrisant la rotation à appliquer et le modèle doit retrouver la valeur de \(\theta_i\). La tâche cible ou downstream considéré sera ensuite une tâche de classification standard.

Pour cet exemple, nous utiliserons l’ensemble de données Fashion MNIST. L’ensemble de données contient des images de 28 * 28 pixels représentant différents types d’articles de mode, tels que des hauts, des bas, des bottes, etc. L’ensemble de données se compose d’un total de 70 000 images. Sur ces 70 000 images, un total de 50 000 seront utilisées pour le pré-entraînement sur la tâche prétexte, dont 40 000 pour l’entraînement et 10 000 pour la validation. Parmi les 20 000 images restantes, 10 000 seront utilisées pour entraîner le modèle sur la tâche finale, et 10 000 images seront utilisées comme ensemble de validation.

Chargement des données

# Importing Libraries

import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
data=keras.datasets.fashion_mnist

# Getting the data
(X_train, y_train), (X_test, y_test) = data.load_data()

# Normalizing the dataset

X_train = X_train/255
X_test = X_test/255


# Splitting the test dataset into validation and test dataset
X_val = X_test[:5000]
y_val = y_test[:5000]

X_test = X_test[5000:]
y_test = y_test[5000:]

# Creating a un-labeled dataset from training dataset
X_unlabeled = X_train[10000:]

# Creating a labeled dataset from training dataset
n_labeled = 5000 # can be changed to study the impact of the label quantity
X_labeled = X_train[:n_labeled]
y_labeled = y_train[:n_labeled]

# Reshaping the Inputs
X_labeled=X_labeled.reshape(-1, 28, 28, 1)
X_val=X_val.reshape(-1, 28, 28, 1)
X_test=X_test.reshape(-1, 28, 28, 1)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
29515/29515 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26421880/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
5148/5148 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4422102/4422102 [==============================] - 0s 0us/step

Création des pseudo-labels

Maintenant que nous avons créé un ensemble de données non étiquetées, l’étape suivante consiste à créer des pseudo-labels pour cet ensemble de données non étiquetées. Comme la tâche prétexte est la classification d’angles de rotation, nous allons faire pivoter l’ensemble des données de \(\theta \in \Theta = \{ 0°, 90°, 180°, 270°\}\) et leur attribuer les pseudo-labels correspondants.

Exercice

Compléter le code suivant pour la création du jeu d’apprentissage

# X_train_0 dataset will contain images rotated by 0 degrees(No rotation)
X_train_0=X_unlabeled.copy()

# X_train_90 dataset will contain images rotated by 90 degrees
X_train_90=... # use np.rot90()

# X_train_180 dataset will contain images rotated by 180 degrees
X_train_180=... # use np.rot90()

# X_train_270 dataset will contain images rotated by 270 degrees
X_train_270=... # use np.rot90()

# Assigning pseudo-labels to rotated image datasets
y_train_0=np.full((50000), 0)
y_train_90=np.full((50000), 1)
y_train_180=np.full((50000), 2)
y_train_270=np.full((50000), 3)

# Concatenating Datasets
X_train_unlabeled_full=np.concatenate((X_train_0, X_train_90, X_train_180, X_train_270), axis=0)
y_train_unlabeled_full=np.concatenate((y_train_0, y_train_90, y_train_180, y_train_270), axis=0)

# Identically shuffle the train and test datasets

p = np.random.permutation(len(X_train_unlabeled_full))
n = 20000 # take n = len(X_train_unlabeled_full) for higher performances
X_train_unlabeled_full = X_train_unlabeled_full[p[:n]]
y_train_unlabeled_full = y_train_unlabeled_full[p[:n]]

L’ensemble des données d’entraînement est divisé en un ensemble de validation (X_rot_val, y_rot_val) et un ensemble d’entraînement (X_rot_train, y_rot_train) pour la tâche prétexte. Étant donné que nous entraînons le modèle sur la tâche prétexte sur laquelle le modèle ne sera pas évalué, nous n’utilisons pas d’ensemble de test.

# Creating Validation and Test Dataset for Pretext Task
nn = n//2
X_rot_val, X_rot_train = X_train_unlabeled_full[:nn], X_train_unlabeled_full[nn:]
y_rot_val, y_rot_train = y_train_unlabeled_full[:nn], y_train_unlabeled_full[nn:]

# Reshaping the Inputs
X_rot_val=X_rot_val.reshape(-1, 28, 28, 1)
X_rot_train=X_rot_train.reshape(-1, 28, 28, 1)

Entraînement auto-supervisé sur tâche prétexte

Maintenant que nous avons l’ensemble de données prêt ainsi que les pseudo-labels, nous pouvons créer un CNN et l’entraîner sur la tâche prétexte.

# Creating a Convolutional Neural Network
model = keras.models.Sequential([
        keras.layers.Conv2D(64, 7, activation="relu", padding="same",
                            input_shape=[28, 28, 1]),
        keras.layers.MaxPooling2D(2),
        keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
        keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
        keras.layers.MaxPooling2D(2),
        keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
        keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
        keras.layers.MaxPooling2D(2),
        keras.layers.Flatten(),
        keras.layers.Dense(128, activation="relu",name='dense1'),
        keras.layers.Dropout(0.5),
        keras.layers.Dense(64, activation="relu",name='dense2'),
        keras.layers.Dropout(0.5),
        keras.layers.Dense(4, activation="softmax",name="dense3")
])

Exercice

Compiler et entraîner le modèle défini précédement sur la tâche prétexte.

Pour la compilation du modèle nous utilisons la loss sparse_categorical_cross_entropy. Les loss categorical_cross_entropy et sparse_categorical_cross_entropy correspondent à la même fonction et la seule différence réside dans le format dans lequel sont codés les labels. Si les labels sont encodés en “one-hot”, il convient d’utiliser la categorical_crossentropy. Mais si les labels sont des entiers, convient d’utiliser la loss sparse_categorical_crossentropy.

# Compiling the model

# Training the model on Pretext Task

Transfert sur une tâche de classification

À présent, nous souhaitons tirer parti des représentations apprises par notre modèle sur la tâche prétexte pour aborder une tâche de classification. Pour cela, nous allons garder les filtres appris et ré-entraîner la tête de classification.

Exercice

  • Retirer la dernière couche du modèle pour la remplasser par une projection linéaire adaptée au nombre de classes (ici 10)

  • Geler les paramètres associées aux couches de convolutions

  • Compiler le modèle (bien utiliser la sparse_categorical_crossentropy)

  • Entraîner le modèle sur la tâche finale en utilisant un petit ensemble de données étiquetées (X_labeled, y_labeled) pendant un total de 10 époques. À chaque époque, les performances du modèle sur l’ensemble de validation (X_val, y_val) sont contrôlées.

# Removing the top layer and addding a new top layer

# Freezing the Convolutional Layers while keeping Dense layers as Trainable

# Compiling the model

# Training the model on Downstream Task
Epoch 1/10
313/313 [==============================] - 61s 194ms/step - loss: 1.7298 - accuracy: 0.3778 - val_loss: 1.1147 - val_accuracy: 0.6352
Epoch 2/10
313/313 [==============================] - 62s 199ms/step - loss: 1.2852 - accuracy: 0.5362 - val_loss: 0.9628 - val_accuracy: 0.6718
Epoch 3/10
313/313 [==============================] - 61s 194ms/step - loss: 1.1311 - accuracy: 0.5761 - val_loss: 0.8393 - val_accuracy: 0.6964
Epoch 4/10
313/313 [==============================] - 58s 186ms/step - loss: 1.0371 - accuracy: 0.6133 - val_loss: 0.7891 - val_accuracy: 0.7166
Epoch 5/10
313/313 [==============================] - 61s 194ms/step - loss: 0.9904 - accuracy: 0.6337 - val_loss: 0.7654 - val_accuracy: 0.7276
Epoch 6/10
313/313 [==============================] - 61s 195ms/step - loss: 0.9376 - accuracy: 0.6510 - val_loss: 0.7378 - val_accuracy: 0.7348
Epoch 7/10
313/313 [==============================] - 59s 190ms/step - loss: 0.9082 - accuracy: 0.6683 - val_loss: 0.7202 - val_accuracy: 0.7358
Epoch 8/10
313/313 [==============================] - 58s 185ms/step - loss: 0.8786 - accuracy: 0.6767 - val_loss: 0.7000 - val_accuracy: 0.7500
Epoch 9/10
313/313 [==============================] - 62s 199ms/step - loss: 0.8580 - accuracy: 0.6872 - val_loss: 0.6939 - val_accuracy: 0.7524
Epoch 10/10
313/313 [==============================] - 61s 194ms/step - loss: 0.8373 - accuracy: 0.6934 - val_loss: 0.6881 - val_accuracy: 0.7374

Nous évaluons enfin le modèle sur le jeu de test

# Evaluating the model on the Test set
model.evaluate(X_test, y_test)
157/157 [==============================] - 21s 132ms/step - loss: 0.6942 - accuracy: 0.7310
[0.6942124366760254, 0.7310000061988831]

Vous devriez obtenir une accuracy proche de 84% (ou 73% si vous ne prenez qu’un ensemble réduit de 20000 élément en pé-apprentissage).

Exercice

Comparer les performances obtenues avec celles issues d’un modèle entrainé de manière complètement supervisée sur le jeu de donnée réduit X_labeled

# Creating a Convolutional Neural Network

# Compiling the model

# Training the model on classification Task

# Evaluating the model on the Test set

En regardant notamment les perforances en validations pour les premières epochs, conclure sur l’intérêt du pré-entraînement. Vous pouvez refaire l’expérience en diminuant la taille du jeu d’apprentissage.