Sur cette page se trouvent des exercices de TP sur le Chapitre 5. Ils sont classés par niveau de difficulté suivant :
Dans cet exercice, vous allez créer un réseau de neurones convolutif (CNN) pour classifier des chiffres manuscrits du dataset MNIST.
Objectif : Comprendre la différence entre un MLP classique et un CNN sur des images.
On va travailler avec les données MNIST :
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# Charger MNIST
transform = transforms.Compose([transforms.ToTensor()])
train_dataset = datasets.MNIST(root='./data', train=True,
download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False,
download=True, transform=transform)
Pour simplifier cet exercice d'introduction, nous utilisons uniquement les datasets train et test, mais en pratique il faudrait aussi un dataset de validation pour surveiller l'overfitting pendant l'entraînement.
Consigne : Écrire un programme qui :
1) Crée deux modèles différents :
2) Entraîne les deux modèles pendant 5 epochs avec :
3) Compare le nombre de paramètres de chaque modèle (utiliser sum(p.numel() for p in model.parameters()))
4) Évalue la précision (accuracy) sur le test set pour les deux modèles
5) Affiche quelques exemples de prédictions (correctes et incorrectes) pour chaque modèle
Questions :
6) Quel modèle a le moins de paramètres ?
7) Quel modèle obtient la meilleure accuracy ?
8) Pourquoi le CNN est-il plus efficace malgré moins de paramètres ?
Astuce :
x.view(x.size(0), -1) pour aplatir l'image(predicted == labels).sum().item() / len(labels)torch.no_grad() lors de l'évaluation pour économiser la mémoireAstuce avancée :
Voici le code pour visualiser les prédictions correctes et incorrectes :
import matplotlib.pyplot as plt # Fonction pour afficher des exemples de prédictions def visualize_predictions(images, labels, predictions, model_name, num_examples=5, correct=True): """ Affiche des exemples de prédictions correctes ou incorrectes Args: images: liste d'images labels: vrais labels predictions: prédictions du modèle model_name: nom du modèle (pour le titre) num_examples: nombre d'exemples à afficher correct: True pour afficher les prédictions correctes, False pour les incorrectes """ # Trouver les indices selon le critère if correct: indices = [i for i in range(len(predictions)) if predictions[i] == labels[i]] title_color = 'green' main_title = f'{model_name} - Prédictions CORRECTES' else: indices = [i for i in range(len(predictions)) if predictions[i] != labels[i]] title_color = 'red' main_title = f'{model_name} - Prédictions INCORRECTES' # Créer la figure fig, axes = plt.subplots(1, num_examples, figsize=(15, 3)) fig.suptitle(main_title, fontsize=14, fontweight='bold') # Afficher les exemples for i in range(min(num_examples, len(indices))): idx = indices[i] axes[i].imshow(images[idx].squeeze(), cmap='gray') axes[i].set_title(f'Vrai: {labels[idx]}\\nPrédit: {predictions[idx]}', color=title_color, fontweight='bold') axes[i].axis('off') plt.tight_layout() plt.show() # Utilisation après évaluation : # visualize_predictions(mlp_images, mlp_labels, mlp_preds, "MLP", correct=True) # visualize_predictions(mlp_images, mlp_labels, mlp_preds, "MLP", correct=False)
Résultat attendu :
Dans cet exercice, vous allez explorer comment les paramètres padding et stride affectent la taille des feature maps dans un CNN.
Objectif :
Comprendre l'impact du padding et du stride sur les dimensions spatiales et visualiser les feature maps.
On va créer un mini-dataset synthétique avec des formes géométriques :
import torch
import matplotlib.pyplot as plt
# Créer des images synthétiques avec des formes
def create_shape_image(shape_type='square'):
img = torch.zeros(1, 1, 28, 28)
if shape_type == 'square':
img[0, 0, 10:18, 10:18] = 1.0
elif shape_type == 'cross':
img[0, 0, 14, :] = 1.0
img[0, 0, :, 14] = 1.0
elif shape_type == 'diagonal':
for i in range(28):
img[0, 0, i, i] = 1.0
return img
Consigne : Écrire un programme qui :
1) Crée 3 images : un carré, une croix, une diagonale
2) Définit 4 configurations de convolution différentes :
3) Pour chaque configuration :
4) Visualise les 8 feature maps obtenues pour chaque configuration
5) Applique ensuite un MaxPooling \(2×2\) après la convolution et observe la nouvelle taille
Questions :
6) Quelle configuration préserve la taille spatiale de l'image ?
7) Quelle configuration réduit le plus la taille ?
8) Que se passe-t-il si on applique plusieurs convolutions successives sans padding ?
9) Pourquoi utilise-t-on souvent padding=1 avec kernel=3 ?
Astuce :
nn.Conv2d(1, 8, kernel_size=k, stride=s, padding=p)plt.imshow(feature_map[0, i].detach(), cmap='viridis')Résultat attendu :
Les tailles de sortie attendues pour une image \(28×28\) :
Après MaxPooling \(2×2\), les tailles sont divisées par 2.
Cet exercice vous guide d'un CNN et l'utilisation de data augmentation pour améliorer les performances.
Objectif :
Consigne : Écrire un programme qui :
1) Charge le dataset CIFAR-10 avec deux types de transformations :
2) Divise le training set en train (80%) et validation (20%) avec random_split
3) Crée un CNN avec l'architecture suivante :
# Bloc 1
Conv2d(3, 64, kernel=3, padding=1) + ReLU
Conv2d(64, 64, kernel=3, padding=1) + ReLU
MaxPool2d(2, 2) # 32×32 → 16×16
# Bloc 2
Conv2d(64, 128, kernel=3, padding=1) + ReLU
Conv2d(128, 128, kernel=3, padding=1) + ReLU
MaxPool2d(2, 2) # 16×16 → 8×8
# Bloc 3
Conv2d(128, 256, kernel=3, padding=1) + ReLU
Conv2d(256, 256, kernel=3, padding=1) + ReLU
MaxPool2d(2, 2) # 8×8 → 4×4
# Classification
Flatten
Linear(256 * 4 * 4, 512) + ReLU
Dropout(0.5)
Linear(512, 10)
4) Entraîne deux versions du modèle (10 epochs chacune) :
5) Pour chaque epoch, calcule et stocke :
6) Implémente un système de sauvegarde qui garde le meilleur modèle basé sur la validation accuracy
7) Trace 4 courbes sur un même graphique :
8) Évalue le meilleur modèle (A et B) sur le test set et affiche :
Questions :
9) Quel modèle (A ou B) généralise mieux ? Comment le voyez-vous sur les courbes ?
10) Observe-t-on de l'overfitting ? Sur quel modèle et comment ?
11) Comment la data augmentation aide-t-elle à réduire l'overfitting ?
12) Quelle est la différence de performance sur le test set ?
Astuce :
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])from sklearn.metrics import confusion_matrix, classification_reportmodel.train() avant l'entraînement et model.eval() avant l'évaluationtorch.save({'model_state_dict': model.state_dict(), 'accuracy': best_acc}, 'best_model.pth')Résultats attendus :
Dans cette section, il y a des exercices supplémentaires pour vous entraîner. Ils suivent le même classement de difficulté que précédemment.
Cet exercice propose de visualiser ce que les filtres convolutifs ont appris après l'entraînement.
Objectif : Comprendre ce que les filtres convolutifs détectent dans les premières couches d'un CNN.
Consignes :
1) Entraîner un CNN simple sur MNIST pendant 3 epochs :
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.fc = nn.Linear(32 * 7 * 7, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), 2)
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
2) Après l'entraînement, extraire les poids de la première couche convolutive :
filters = model.conv1.weight.data # shape: [16, 1, 3, 3]
3) Visualiser les 16 filtres \(3×3\) de la première couche sur une grille \(4×4\)
4) Prendre une image de test et visualiser les feature maps produites par la première couche convolutive :
model.conv1(image) puis F.relu()5) Faire de même pour la deuxième couche convolutive (afficher 32 feature maps sur une grille 4×8)
Questions :
6) Que détectent les filtres de la première couche ? (contours, textures, ...)
7) Les feature maps de la deuxième couche sont-elles plus abstraites que celles de la première ?
8) Comment évoluent les patterns détectés entre les couches ?
Astuce :
plt.imshow(filters[i, 0].cpu(), cmap='gray')with torch.no_grad(): features = F.relu(model.conv1(image))plt.subplots() pour créer une grille de visualisationRésultats attendus :
Dans cet exercice, vous allez utiliser un modèle pré-entraîné (ResNet18) et le fine-tuner sur un nouveau dataset.
⏰ Attention : Temps d'entraînement très long !
Cet exercice nécessite d'entraîner 2 modèles ResNet18 sur des images \(224×224\), ce qui prend beaucoup de temps (plusieurs heures sans GPU, et reste long même avec GPU). Il n'est PAS possible de terminer l'entraînement pendant la séance de TP. Il est recommandé de :
ResNet18 est une architecture CNN de 18 couches qui a gagné le concours ImageNet en 2015. ImageNet est une immense base de 1.2 million d'images réparties en 1000 classes (animaux, véhicules, objets du quotidien). Le transfer learning consiste à réutiliser les filtres appris sur ImageNet (qui détectent contours, textures, formes génériques) pour classifier CIFAR-10 (10 classes seulement) : au lieu de tout réapprendre depuis zéro, on adapte juste la dernière couche !
Objectif :
Consignes :
1) Charger le dataset CIFAR-10 avec des transformations appropriées :
from torchvision import models
transform = transforms.Compose([
transforms.Resize(224), # ResNet attend du 224×224
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
2) Créer deux modèles :
# Modèle A
model_scratch = models.resnet18(pretrained=False)
model_scratch.fc = nn.Linear(model_scratch.fc.in_features, 10)
# Modèle B
model_transfer = models.resnet18(pretrained=True)
# Geler toutes les couches
for param in model_transfer.parameters():
param.requires_grad = False
# Remplacer la dernière couche et la dégeler
model_transfer.fc = nn.Linear(model_transfer.fc.in_features, 10)
3) Entraîner les deux modèles pendant 10 epochs avec :
4) Pour chaque modèle, tracer :
5) Comparer le temps d'entraînement par epoch pour les deux modèles
6) Évaluer les deux modèles sur le test set
7) Afficher une matrice de confusion pour chaque modèle
Questions :
8) Quel modèle converge le plus rapidement ?
9) Quel modèle atteint la meilleure accuracy finale ?
10) Pourquoi le transfer learning est-il plus efficace ?
11) Que se passerait-il si on dégelait aussi les couches intermédiaires ?
Astuce :
import time; start = time.time(); ... ; elapsed = time.time() - startRésultats attendus :
Dans cet exercice, vous allez implémenter un système complet d'entraînement avec early stopping et ajustement dynamique du learning rate.
⏰ Attention : Temps d'entraînement long !
Cet exercice nécessite d'entraîner 3 modèles différents sur CIFAR-10, ce qui prend un temps conséquent (le temps varie beaucoup selon votre GPU). Il n'est pas possible de terminer l'entraînement pendant la séance de TP. Options recommandées :
Objectif :
Consignes :
1) Utiliser CIFAR-10 avec data augmentation et diviser en train/val/test (70%/15%/15%)
2) Créer un CNN de taille moyenne :
class MediumCNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 64, 3, padding=1),
nn.ReLU(),
nn.Conv2d(64, 64, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1),
nn.ReLU(),
nn.Conv2d(128, 128, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
)
self.classifier = nn.Sequential(
nn.Linear(128 * 8 * 8, 256),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(256, 10)
)
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x
3) Implémenter une classe EarlyStopping avec les paramètres :
patience : nombre d'epochs sans amélioration avant d'arrêtermin_delta : amélioration minimale pour considérer un progrès4) Entraîner 3 versions du modèle (max 50 epochs) :
ReduceLROnPlateau + early stopping (patience=7)5) Pour chaque modèle, tracer sur un même graphique :
6) Comparer les résultats finaux sur le test set
7) Afficher pour chaque modèle :
Questions :
8) Quel modèle évite le mieux l'overfitting ?
9) Quel est l'impact du learning rate scheduler ?
10) L'early stopping permet-il de gagner du temps d'entraînement ?
11) Quelle stratégie recommanderiez-vous pour un nouveau projet ?
Astuce :
Implémentation de la classe EarlyStopping :
class EarlyStopping: def __init__(self, patience=5, min_delta=0, path='checkpoint.pth'): self.patience = patience self.min_delta = min_delta self.path = path self.counter = 0 self.best_loss = None self.early_stop = False def __call__(self, val_loss, model): if self.best_loss is None: self.best_loss = val_loss self.save_checkpoint(model) elif val_loss > self.best_loss - self.min_delta: self.counter += 1 if self.counter >= self.patience: self.early_stop = True else: self.best_loss = val_loss self.save_checkpoint(model) self.counter = 0 def save_checkpoint(self, model): torch.save(model.state_dict(), self.path)Pour le scheduler :
from torch.optim.lr_scheduler import ReduceLROnPlateau scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=3, verbose=True) # Dans la boucle d'entraînement, après validation : scheduler.step(val_loss)
Résultats attendus :