Deep learning

Slides

Pendant que les diapositives sont présentées, nous vous invitons à installer les dépendances

pip install torch torchvision torchsummary matplotlib numpy scikit-learn

1) Chargement des données

Ajoutez les imports suivants:

import matplotlib.pyplot as plt
import torch
import torchsummary
import torchvision

Nous allons charger le jeu de données MNIST, qui contient des chiffres de 0 à 9 écrits à la main (60,000 exemples pour l'entraînement et 10,000 pour la validation).

Exemple d'images du jeu de données

Pour cela, vous pouvez utiliser le code suivant:

train = torchvision.datasets.MNIST(
    "data/",
    train=True,
    download=True,
    transform=torchvision.transforms.ToTensor()
)

Ici:

Chargez les données, et faites :

print(train)

Pour observer le résultat.

Il est possible d'accéder aux données de cette façon:

# Récupère le premier exemple
image, label = train[0]

Où:

Faites une boucle qui affiche les images ainsi que leur label (vous pourrez utiliser plt.imshow)

Remarquez que les image chargées sont de la forme [1, 28, 28] (ici C, H, W). Pour changer l'ordre des dimensions, vous pouvez utiliser : image.permute(1, 2, 0)

2) Réseau de neurones

Créez un réseau de neurones de cette façon:

net = torch.nn.Sequential(
    torch.nn.Flatten(),
    torch.nn.Linear(28 * 28, 256),
    torch.nn.SiLU(),
    torch.nn.Linear(256, 128),
    torch.nn.SiLU(),
    torch.nn.Linear(128, 64),
    torch.nn.SiLU(),
    torch.nn.Linear(64, 10),
)

À quoi sert la couche Flatten en entrée ?

Combien de couches cachées ce réseau a t-il ?

Quelle est la fonction d'activation du réseau ?

En utilisant torchsummary.summary : combien de paramètres entraînable y'a t-il dans ce réseau ?

Enfin, créez un DataLoader, ce dernier nous permettra d'obtenir efficacement des mini-batch, ici de 256 exemples :

dataset = torch.utils.data.DataLoader(
    train, batch_size=256, shuffle=True
)

Vous pouvez le tester de la façon suivante:

for images, labels in dataset:
    print(f"Images: {images.shape}, Labels: {labels.shape}")

Quelles sont les dimensions observées ?

3) Test du réseau (non entraîné)

Testons notre réseau (non entraîné) sur quelques images !

image, label = train[0]
pred = net(image)

Ici, l'appel à net(image) exécute le réseau sur l'image passée en paramètre.

Quelle est la taille de pred ?

Le chiffre sélectionné est celui pour lequel la sortie est la plus importante, utilisez la fonction torch.argmax pour obtenir cet indice, et comparez le avec le label sur quelques exemples.

Pour le moment, ça ne devrait pas très bien marcher.

4) Entraînement

Créez maintenant un optimiseur, nous utiliserons Adam, qui est très standard :

optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)

Le problème que nous adressons est un problème de classification, nous allons donc utiliser le critère CrossEntropyLoss :

criterion = torch.nn.CrossEntropyLoss()

Cette fonction permet de mesurer la "distance" entre deux distributions statistiques (plus elle est haute, plus les distributions sont différentes)

Enfin, vous pouvez écrire une boucle d'entraînement:

for epoch in range(4):
    for images, labels in dataset:
        pred = net(images)
        loss = criterion(pred, labels)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        print(loss)

Observez la valeur de loss diminuer.

5) Sauvegarde et chargement

À la fin de l'entraînement, sauvegardez les poids :

print("Sauvegarde des poids")
torch.save(net.state_dict(), "model.pt")

Ajoutez en haut du fichier un paramètre :

training = False

Qui détermine si l'entraînement se fait ou non. Si l'entraînement ne se fait pas, vous pourrez charger les poids :

print("Chargement des poids")
net.load_state_dict(torch.load("model.pt"))

6) Évaluation

Utilisez la même méthode que la partie 3) pour vérifier si votre réseau prédit bien les étiquettes sur les données d'entraînement.

Calculez la précision (accuracy) sur l'ensemble du jeu de données d'entraînement.

7) Données de test

Chargez maintenant les données de test, ainsi qu'un loader associé :

test = torchvision.datasets.MNIST(
    "data/",
    train=False,
    download=True,
    transform=torchvision.transforms.ToTensor()
)
dataset_test = torch.utils.data.DataLoader(
    test, batch_size=256, shuffle=True, num_workers=4
)

Modifiez le code qui évalue le modèle (son accuracy) afin de vérifier que notre réseau fonctionne bien sur le jeu test (non rencontré à l'entraînement), au lieu de train.

Lors de l'entraînement, estimez la loss des données de validation, sur un batch uniquement.

Il est possible d'obtenir uniquement un mini-batch en invoquant l'itérateur :

images, labels = next(iter(dataset_test))

Produisez un graphique indiquant la loss (train) et la loss (test) au cours des étapes. Si vous laissez tourner assez longtemps (par exemple ici 16 epochs), vous observerez un décrochage léger entre l'entraînement et la validation, indiquant un sur-apprentissage :

8) Matrice de confusion

Et importez :

from sklearn.metrics import ConfusionMatrixDisplay

Calculez la matrice de confusion sur les données de test.

À l'aide de ConfusionMatrixDisplay, affichez-là

Écrivez du code qui va chercher les exemples qui ne marchent pas, et les affiche :

Comme vous pouvez le constater, certains exemples mal classifiés sont en fait assez difficiles!

9) Monitoring des métriques

Modifiez l'entraînement pour calculer l'accuracy (train) sur le mini-batch utilisé à chaque étape, ainsi que l'accuracy (test) sur un mini-batch du jeu de test.

Utilisez WandB pour publier vos métriques en temps réel et les surveiller :

En lissant les courbes, on observe ici le phénomène de su-rapprentissage.

10) Test manuel

À l'aide d'un outil d'édition d'image (comme par exemple Gimp), créez vos propres images de taille 28x28, en blanc sur fond noir.

Testez votre réseau sur les images que vous avez créées.