Pendant que les diapositives sont présentées, nous vous invitons à installer les dépendances
pip install torch torchvision torchsummary matplotlib numpy scikit-learn
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:
data/ est le dossier dans lequel le jeu de données sera téléchargétrain=True indique que l'on veut les données d'entraînementdownload=True indique qu'on souhaite télécharger les données si besointransform=torchvision.transforms.ToTensor() permet de transformer les données en tenseur compatible avec torchChargez 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ù:
image est une image (convertie en tenseur torch, qui ressemble fortement aux tableaux numpy)label est le label, c'est à dire le chiffre présent sur l'image[0] l'indice de l'exemple (ici le premier)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)
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 ?
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.
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.
À 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"))
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.
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 :
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!
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.
À 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.