🏛️ ResNet – Le Guide Ultime
Deep Dive : Le Problème de Dégradation, 🚀 Blocs Résiduels (Skip Connection), VGG vs ResNet50, Transfer Learning.
1. C'est quoi ResNet ?
Residual Network. Gagnant ILSVRC 2015. 3.57% d'erreur (mieux que l'humain).
ResNet ILSVRC 20152. Le Problème (Dégradation)
Le paradoxe : un réseau 56-couches était *moins bon* qu'un 20-couches (Vanishing Gradient).
Degradation Vanishing Gradient3. 🚀 La Solution : Skip Connection
Le "plugin" de génie : H(x) = F(x) + x. Le réseau apprend le "résidu" (F(x)).
4. 📈 Architecture (ResNet-50)
Une pile de "Bottleneck Blocks". 152 couches deviennent possibles.
ResNet-50 Architecture5. Le "Bottleneck Block"
Le "plugin" d'optimisation. 1x1 (réduit) -> 3x3 (traite) -> 1x1 (étend).
6. 📊 ResNet-18 vs 50 vs 152
VGG16 (138M params) vs ResNet50 (25M params). Plus profond, plus léger, plus précis.
VGG vs ResNet Params7. Code (PyTorch)
torchvision.models.resnet50(pretrained=True). model.fc = nn.Identity().
8. Code (Keras)
tf.keras.applications.ResNet50(). include_top=False (usage n°1).
9. 🚀 Cas: Transfer Learning
L'usage moderne. Geler le "backbone", remplacer la "tête" (Dense) pour un projet custom.
10. Projet: "Backbone" (Addon)
ResNet comme "Feature Extractor" (plugin) pour YOLO, Faster R-CNN, U-Net.
Backbone YOLO U-Net11. Frameworks Dérivés
L'héritage : ResNeXt (Groupes), DenseNet (Concat), ViT (Transformers).
DenseNet ViT12. Chiffres & Liens
Stats (FPS, GFLOPs). Liens vers le papier original, PyTorch, Keras.
Paper LinksResNet (Residual Network) est une architecture de réseau de neurones convolutif (CNN) qui a remporté le concours ILSVRC 2015 (ImageNet) sur *toutes* les catégories (Classification, Détection, Segmentation).
Il a été créé par Kaiming He (et al.) de Microsoft Research. Son impact est comparable à celui d'AlexNet (2012).
L'Impact (Les Chiffres)
ResNet a été le premier modèle à dépasser la performance humaine sur ImageNet.
| Modèle | Année | Taux d'Erreur (Top-5) | Profondeur |
|---|---|---|---|
| AlexNet | 2012 | 15.3% | 8 couches |
| VGG-16 | 2014 | 7.3% | 16 couches |
| GoogLeNet | 2014 | 6.7% | 22 couches |
| ResNet-152 | 2015 | 3.57% | 152 couches |
| Humain (A. Karpathy) | ~2014 | ~5.1% | N/A |
La question à laquelle ResNet a répondu est : "VGG a prouvé que 'plus profond' (19 couches) est mieux qu'AlexNet (8 couches). Peut-on aller *beaucoup* plus profond (50, 100, 150 couches) ?"
Avant ResNet, il y avait un paradoxe : l'ajout de couches (ex: passer de 20 à 50) rendait le modèle *moins bon*. Non seulement sur le set de *test* (overfitting), mais aussi sur le set *d'entraînement* (training) !
C'est le Problème de Dégradation (Degradation Problem).
La Cause : "Vanishing Gradient"
Pendant l'entraînement (backpropagation), le "signal d'erreur" (le gradient) doit remonter de la dernière couche à la première. Dans un réseau très profond (50+ couches), ce gradient est multiplié (par la "Chain Rule"). S'il est < 1, il tend vers zéro (0.9 * 0.9 * ... * 0.9 = 0.00...).
Résultat : Les premières couches (celles qui apprennent les features de base) ne reçoivent *aucun* signal d'apprentissage. Elles ne s'entraînent pas. Le réseau "s'effondre".
AlexNet a "résolu" cela avec ReLU (voir 1.4, AlexNet), mais cela n'a fonctionné que jusqu'à ~20 couches (VGG). ResNet a introduit la *vraie* solution.
L'idée de Kaiming He est d'une simplicité géniale. Si un bloc de couches (ex: 3 couches) a du mal à apprendre une fonction (H(x)), on va lui faciliter la tâche.
On va lui demander d'apprendre *seulement* le "Résidu" (F(x)) - la *différence* entre ce qu'il devrait apprendre (H(x)) et son entrée (x).
Mathématiquement : H(x) = F(x) + x
Le Diagramme : "Skip Connection"
Pour ce faire, ils ont introduit une "connexion de saut" (Skip Connection) qui "contourne" (bypass) le bloc.
(Input: x)
|
+-----> (Connexion "Identité" / "Skip") ----+
| |
▼ |
[ Conv 3x3 (Poids W1) ] |
[ Batch Norm ] |
[ ReLU ] |
[ Conv 3x3 (Poids W2) ] |
[ Batch Norm ] |
| |
▼ |
( F(x) ) |
| |
+-----------> [ ADDITION ] <---------------+
|
▼
[ ReLU ]
|
▼
(Sortie: H(x))
Pourquoi ça marche ?
Le "Vanishing Gradient" est résolu : Le gradient (signal d'erreur) peut *toujours* remonter par le "pont" (le "skip" + x), même si les gradients des couches F(x) deviennent nuls. Il a une autoroute directe vers les premières couches.
Apprentissage "facile" : Si un bloc n'est pas nécessaire (la meilleure fonction est H(x) = x), le réseau peut *très* facilement apprendre à F(x) = 0 (mettre les poids W1/W2 à 0). Sans le "skip", il aurait dû apprendre la fonction "identité" (H(x)=x), ce qui est très difficile pour des convolutions.
L'architecture de ResNet-50 (et plus) n'est pas juste un "stack" de 50 couches. C'est un stack de "Blocs Résiduels" (voir 1.3).
Diagramme (Architecture ResNet-50)
(Input: Image 224x224x3)
|
▼
[ Stage 0: (Conv 7x7, 64) + MaxPool(3x3, S2) ] (-> 56x56x64)
|
▼
[ Stage 1: (Bloc "Bottleneck") x 3 ] (-> 56x56x256)
|
▼
[ Stage 2: (Bloc "Bottleneck") x 4 ] (-> 28x28x512)
|
▼
[ Stage 3: (Bloc "Bottleneck") x 6 ] (-> 14x14x1024)
|
▼
[ Stage 4: (Bloc "Bottleneck") x 3 ] (-> 7x7x2048)
|
| (Flatten -> 7x7x2048)
▼
[ Global Average Pooling (GAP) ] (-> 2048)
|
▼
[ FC (Fully Connected) (1000 neurones) + Softmax ]
|
▼
(Sortie: 1000 classes ImageNet)
Global Average Pooling (GAP)
ResNet a (presque) tué les couches "Fully Connected" (FC) de VGG/AlexNet.
VGG : Prend la sortie (7x7x512), l'aplatit (Flatten) en 25 088 neurones, puis utilise 2 couches FC (100M+ de poids !).
ResNet : Prend la sortie (7x7x2048), applique un Global Average Pooling (fait la *moyenne* de chaque canal) -> (résultat 1x1x2048), puis 1 seule couche FC.
Résultat : Réduction *massive* du nombre de paramètres (poids) et de l'overfitting.
Les "petits" ResNets (ResNet-18, ResNet-34) utilisent des "Blocs Basiques" (2x Conv 3x3).
Les "gros" ResNets (50, 101, 152) utilisent le "plugin" "Bottleneck Block" pour être *plus profonds* avec *moins* de calculs. C'est une optimisation cruciale (inspirée de GoogLeNet).
L'astuce : Le 1x1 Conv (Réduction de Dimension)
Une convolution 1x1 est un "plugin" qui ne fait que "compresser" (ou "étendre") le nombre de *canaux* (features), sans changer la taille (hauteur/largeur).
Diagramme (Bloc "Bottleneck")
(Input: 28x28x256)
|
+------------(Skip)------------+
| |
▼ |
[ 1. Conv 1x1, 64 filtres ] (Réduit : 28x28x64)
| |
▼ |
[ 2. Conv 3x3, 64 filtres ] (Traite : 28x28x64)
| |
▼ |
[ 3. Conv 1x1, 256 filtres ] (Étend : 28x28x256)
| |
▼ |
+----------[ ADDITION ] <-------+
|
▼
[ ReLU ]
Gains de calcul (vs 2x Conv 3x3) : Ce "goulot d'étranglement" (bottleneck) (256 -> 64 -> 256) est *beaucoup* moins coûteux en calculs que de faire du Conv 3x3 sur 256 filtres.
La comparaison VGG vs ResNet montre l'évolution :
| Modèle | Profondeur | Paramètres (Poids) | Taille (Disque) | FLOPs (Calcul) | Erreur Top-1 (ImageNet) |
|---|---|---|---|---|---|
| VGG-16 | 16 couches | ~138 Millions | ~528 MB | ~15.5 GFLOPs | 28.5% |
| ResNet-50 | 50 couches | ~25.6 Millions | ~98 MB | ~4.1 GFLOPs | 22.8% |
Conclusion : ResNet-50 est 3 fois plus profond (50 vs 16), 5 fois plus léger (25M vs 138M params), 4 fois plus rapide (4.1 vs 15.5 GFLOPs) et beaucoup plus précis (22.8% vs 28.5%) que VGG16.
La Famille ResNet
| Modèle | Type de Bloc | Profondeur | Paramètres |
|---|---|---|---|
| ResNet-18 | Basic (2x 3x3) | 18 | 11.7 M |
| ResNet-34 | Basic (2x 3x3) | 34 | 21.8 M |
| ResNet-50 | Bottleneck (1x1, 3x3, 1x1) | 50 | 25.6 M |
| ResNet-101 | Bottleneck | 101 | 44.5 M |
| ResNet-152 | Bottleneck | 152 | 60.2 M |
Le "plugin" torchvision (l'addon officiel de PyTorch pour la vision) fournit le modèle ResNet pré-entraîné sur ImageNet.
Exemple (Inférence simple)
import torch
from torchvision import models, transforms
from PIL import Image
# 1. Charger ResNet-50 pré-entraîné
# (Télécharge ~98MB la 1ère fois)
# (Pour VGG: models.vgg16(pretrained=True))
model = models.resnet50(pretrained=True)
model.eval() # IMPORTANT: Mode évaluation (désactive BatchNorm/Dropout)
# 2. Définir les "Transforms" (standard ImageNet)
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
# 3. Charger & Pré-processer l'image
img = Image.open("mon_chien.jpg")
img_t = preprocess(img)
batch_t = torch.unsqueeze(img_t, 0) # (Ajoute la dimension batch)
# 4. Inférence (avec 'no_grad' pour économiser la mémoire)
with torch.no_grad():
output = model(batch_t) # (Output: [1, 1000])
# 5. Obtenir la prédiction
probabilities = torch.nn.functional.softmax(output[0], dim=0)
top_class_index = torch.argmax(probabilities).item()
# (Charger les 1000 labels d'ImageNet...)
print(f"Prédiction: Classe {top_class_index}")
L'écosystème Keras (intégré à TensorFlow) fournit également ResNet comme "plugin" (keras.applications).
include_top=False (Le "Plugin" Transfer Learning)
Keras rend le Transfer Learning (voir 2.3) *très* facile avec l'argument include_top.
import tensorflow as tf
from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input, decode_predictions
from tensorflow.keras.preprocessing import image
import numpy as np
# --- 1. Inférence Simple (Avec la "Tête") ---
model_full = ResNet50(weights='imagenet', include_top=True)
img = image.load_img('mon_chien.jpg', target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x) # (Normalisation Keras)
preds = model_full.predict(x)
print(decode_predictions(preds, top=3)[0])
# (Affiche les 3 top prédictions ImageNet)
# --- 2. Mode "Feature Extractor" (Sans la "Tête") ---
# (C'est la base du Transfer Learning, voir 2.3)
model_features = ResNet50(weights='imagenet', include_top=False)
# 'preds_features' n'est PAS (1, 1000),
# c'est la sortie du "Backbone" (1, 7, 7, 2048)
preds_features = model_features.predict(x)
C'est l'usage n°1 de ResNet (et VGG) aujourd'hui.
Le Problème : Je veux classer "Chat" vs "Chien", mais je n'ai que 1000 images.
La Solution : Le Transfer Learning (Apprentissage par Transfert).
Les premières couches d'un CNN (comme ResNet) apprennent des features *génériques* (détection de bords, textures, coins). Ces "yeux" (entraînés sur 1.1M d'images ImageNet) sont *plus performants* que tout ce qu'on peut entraîner sur 1000 images.
Le Workflow (Keras)
- Charger le "Backbone" : On charge ResNet-50 *sans* sa tête (
include_top=False). - Geler (Freeze) : On dit à Keras de ne *pas* ré-entraîner les 25M de poids (
trainable = False). - Ajouter notre "Tête" : On ajoute *nos propres* couches (
Dense) à la fin (ex:Dense(1, 'sigmoid')pour "chat" / "chien"). - Entraîner : On n'entraîne *que* notre (petite) tête sur nos 1000 images.
import tensorflow as tf
from tensorflow.keras import layers, models
# 1. Charger ResNet-50 (le "backbone") pré-entraîné
base_model = tf.keras.applications.ResNet50(
weights='imagenet',
include_top=False, # (Ne pas inclure la tête de 1000 classes)
input_shape=(224, 224, 3)
)
# 2. Geler (Freeze) le backbone
base_model.trainable = False
# 3. Créer notre nouveau modèle (le "plugin" par-dessus)
model = models.Sequential([
base_model, # (Le backbone gelé)
# 4. Notre "Tête" (Classifier)
layers.GlobalAveragePooling2D(), # (Mieux que Flatten)
layers.Dense(256, activation='relu'),
layers.Dropout(0.5),
layers.Dense(1, activation='sigmoid') # (1 neurone: chat/chien)
])
# 5. Compiler et entraîner
# (On n'entraîne QUE les 3 couches Dense)
model.compile(optimizer='adam', loss='binary_crossentropy', ...)
model.fit(mon_dataset, ...)
L'usage "plugin" le plus avancé de ResNet n'est pas pour la classification, mais comme "Backbone" (épine dorsale) pour des tâches plus complexes comme la Détection d'Objet ou la Segmentation.
L'idée est de *remplacer* le "feature extractor" d'un autre modèle (ex: YOLO, U-Net) par un ResNet-50.
Projet 1 : Détection d'Objet (ex: YOLO / Faster R-CNN)
Un modèle comme YOLOv8 a son propre backbone (C2f). Mais de nombreux autres détecteurs (ex: Faster R-CNN, RetinaNet) vous laissent *choisir* votre "plugin" backbone.
Workflow :
1. On prend un ResNet-50 (include_top=False).
2. On attache un "Neck" (FPN - Feature Pyramid Network) qui prend les sorties des *différents* stages (Stage 2, 3, 4, 5) pour avoir des features multi-échelles.
3. On attache la "Tête" (Head) de détection (ex: RPN + Classifier).
Projet 2 : Segmentation (ex: U-Net)
U-Net (utilisé en imagerie médicale) a un "Encoder" (descente) et un "Decoder" (remontée).
Workflow :
1. On *remplace* l'Encoder de U-Net par un ResNet-34 pré-entraîné (gelé).
2. On garde le Decoder (qui "remonte" l'image) et on connecte les "skip connections" de U-Net aux "skip connections" de ResNet.
Résultat : Le "U-Net-ResNet34" converge 100x plus vite et est beaucoup plus précis qu'un U-Net "from scratch".
ResNet (2015) a lancé une "explosion" de nouvelles architectures basées sur son "plugin" de "skip connection".
| Modèle (Dérivé) | Date | Innovation (Le "Plugin") |
|---|---|---|
| ResNeXt | 2016 | "Cardinality" (Grouped Convolutions). Au lieu d'un "bottleneck" (1x1->3x3->1x1), utilise *plusieurs* chemins parallèles (groupes) moins larges. (Inspiré de GoogLeNet). |
| DenseNet | 2017 | "Dense" Skip Connections. Au lieu de H(x) = F(x) + x (Addition), il concatène les features : H(x) = Concat(F(x), x).Chaque couche est connectée à *toutes* les couches précédentes. |
| Vision Transformer (ViT) | 2020 | La nouvelle révolution. Abandonne *totalement* les Convolutions (CNNs). Utilise 100% le "plugin" Transformer (Self-Attention) (venu du NLP/Langage). |
Tableau Comparatif (Taille/Vitesse/Précision)
| Modèle | Précision (Top-1 ImageNet) | Paramètres (Poids) | Taille (Disque) | FLOPs (Calcul) |
|---|---|---|---|---|
| AlexNet (2012) | 57.1% | ~61 M | ~233 MB | ~0.7 GFLOPs |
| VGG-16 (2014) | 71.5% | ~138 M | ~528 MB | ~15.5 GFLOPs |
| GoogLeNet (2014) | 69.1% | ~7 M | ~27 MB | ~1.5 GFLOPs |
| ResNet-50 (2015) | 75.2% | ~25.6 M | ~98 MB | ~4.1 GFLOPs |
| ResNet-152 (2015) | 77.0% | ~60.2 M | ~230 MB | ~11.3 GFLOPs |
Conclusion : ResNet-50 est le *meilleur compromis* historique. Bien plus précis et léger que VGG, et bien plus simple à comprendre que GoogLeNet.
