Aller au contenu principal

Deep Learning 4 — Réseaux convolutifs (2/3)

:::tip Notebook Kaggle Le code complet et exécutable de ce chapitre est sur Kaggle : Ouvrir →

Versions anglaise et chinoise disponibles depuis la page d'accueil. :::

Au chapitre précédent, nous avons entraîné des CNN sur des datasets stockés en CSV (MNIST, CIFAR-10). Pratique pour démarrer, mais en pratique les images viennent presque toujours de fichiers PNG/JPG. Nous découvrons aussi deux techniques essentielles pour stabiliser et régulariser les CNN profonds : Batch Normalization et Dropout.

Pourquoi ce chapitre ?

Vous y apprenez :

  • à charger des images depuis le disque avec ImageFolder ;
  • à accélérer le pipeline de données (num_workers, pin_memory) ;
  • la Batch Normalization : pourquoi et où la placer ;
  • le Dropout pour réduire le surapprentissage.

ImageFolder : organisation par dossiers

PyTorch attend une organisation très simple : un dossier par classe.

dataset/
├── train/
│ ├── 0/
│ │ ├── img_001.png
│ │ └── img_002.png
│ ├── 1/
│ └── ...
└── test/
├── 0/
└── ...

Le nom du sous-dossier sert de label. torchvision.datasets.ImageFolder fait tout le travail.

from torchvision import datasets, transforms
from torch.utils.data import DataLoader

transform = transforms.Compose([
transforms.Resize((28, 28)),
transforms.ToTensor() # PIL [0,255] → tensor [0,1] (C, H, W)
])

train_dataset = datasets.ImageFolder(root='dataset/train', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

La magie de ToTensor()

ToTensor() fait trois choses en une :

  1. convertit la PIL Image en tenseur PyTorch ;
  2. divise par 255 → valeurs dans [0,1][0, 1] ;
  3. réorganise en CHW depuis HWC.

Conséquence pratique : avec ImageFolder + ToTensor(), on n'a plus jamais besoin de reshape ni de transpose. Les batchs arrivent directement au format (B,C,H,W)(B, C, H, W) que PyTorch attend.

SourceReshape nécessaire ?
CSV (chapitre 3)oui
ImageFolder (ici)non, déjà BCHW grâce à ToTensor()

Accélérer le chargement

Quand un entraînement « semble lent » sur GPU, la cause n'est presque jamais le modèle. C'est typiquement le pipeline de données qui rame. Quatre leviers :

num_workers

Par défaut, num_workers=0 : tout est fait dans le processus principal. Avec num_workers > 0, des processus séparés préparent les batchs en parallèle.

DataLoader(train_dataset, batch_size=256, shuffle=True, num_workers=4)

Valeur usuelle : 2-4 sur Kaggle, 8 sur une machine costaud.

pin_memory

Avec pin_memory=True, les tenseurs sont alloués en mémoire pinnée (page-locked), ce qui accélère le transfert vers GPU.

DataLoader(..., pin_memory=True)

for Xb, yb in train_loader:
Xb = Xb.to(device, non_blocking=True)

persistent_workers

Évite de recréer les workers à chaque epoch. Utile quand les epochs sont courtes.

DataLoader(..., num_workers=4, persistent_workers=True)

Récapitulatif

DataLoader(
train_dataset,
batch_size=128,
shuffle=True,
num_workers=4,
persistent_workers=True,
pin_memory=True,
)

L'objectif : que le GPU n'attende jamais un batch.

Batch Normalization

Le problème : à chaque pas de gradient, les poids changent, donc les distributions d'activations en entrée des couches suivantes changent aussi. Le modèle doit constamment se réajuster — entraînement lent et instable.

La solution : normaliser les activations à chaque batch. Pour chaque canal, on recentre (moyenne ≈ 0) et on remet à l'échelle (variance ≈ 1), puis on applique une transformation affine apprise (deux paramètres γ\gamma et β\beta par canal).

Bénéfices

  • entraînement beaucoup plus stable ;
  • convergence plus rapide (on peut souvent doubler ou tripler le lr) ;
  • moins de sensibilité à l'initialisation ;
  • léger effet régularisant.

Où placer la BN

Le placement standard : après la convolution, avant l'activation.

Conv2d → BatchNorm2d → ReLU
self.conv = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.bn = nn.BatchNorm2d(64) # 64 canaux en sortie de conv
self.relu = nn.ReLU()

PyTorch gère automatiquement le double mode :

  • en model.train() : statistiques calculées sur le batch courant ;
  • en model.eval() : statistiques moyennes accumulées pendant l'entraînement.

:::warning Toujours appeler eval() à l'évaluation Sans model.eval(), le modèle continuerait à utiliser les stats du batch courant — ce qui change ses prédictions. Réflexe à prendre. :::

Dropout

Le problème : un réseau profond peut surapprendre en s'appuyant trop fort sur certaines combinaisons de neurones, créant des chemins fragiles.

La solution : pendant l'entraînement, désactiver aléatoirement une fraction pp des neurones à chaque pas (typiquement p=0.5p = 0.5 pour les couches denses, p=0.2p = 0.2-0.30.3 pour le conv). Le réseau apprend à ne dépendre d'aucun neurone en particulier.

À l'évaluation, le dropout est désactivé automatiquement par model.eval().

Où placer le Dropout

Principalement dans les couches denses (Linear) en fin de réseau.

Linear → ReLU → Dropout
self.fc = nn.Linear(512, 256)
self.dropout = nn.Dropout(p=0.5)

Plus rarement après les couches conv (la BN joue déjà un rôle régularisant à ce niveau).

BN ou Dropout, ou les deux ?

TechniqueQuand l'utiliser
BatchNorm seulStandard moderne dans les CNN. Suffisant dans beaucoup de cas.
Dropout seulQuand BatchNorm pose problème (petits batchs, RNN, certains transformers).
Les deuxCompatible. BN dans la partie conv, Dropout dans la partie dense.

Ces deux techniques sont aussi importantes que ReLU dans la trousse à outils du deep learning.

Architecture CNN moderne typique

class CNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2),
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(128 * 4 * 4, 256), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(256, 10),
)

def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x

C'est le squelette qu'on retrouve dans la plupart des architectures « maison » modernes.


Notebook complet sur Kaggle (forkable) →