EfficientNet : Guide complet — Scaling Composite de Réseaux de Neurones
Résumé — EfficientNet, proposé par Tan et Le de Google Brain en 2019, est une famille de modèles de vision qui utilise une approche systématique de scaling pour atteindre un état de l’art en classification d’images avec beaucoup moins de paramètres et de FLOPs que les architectures précédentes. Au lieu de scaler arbitrairement une seule dimension (profondeur, largeur ou résolution), EfficientNet applique le compound scaling: un équilibre mathématiquement optimisé des trois dimensions simultanément. Le résultat: EfficientNet-B7 atteint 84.3% top-1 accuracy sur ImageNet avec 8.4x moins de paramètres que GPipe, l’architecture la plus précise de l’époque.
Principe mathématique
1. Le problème: comment scaler un réseau de neurones?
Avant EfficientNet, il y avait trois approches de scaling:
– Profondeur (ResNet): ajouter des couches (de 18 à 152)
– Largeur (Wide ResNet): augmenter les canaux
– Résolution (GPipe): augmenter la taille des images d’entrée
Chaque méthode améliore la précision mais avec des rendements décroissants. Plus important, scaler une dimension sans les autres gaspille des ressources: un réseau très profond avec peu de canaux n’a pas la capacité de traiter les features, et un réseau large avec peu de couches ne peut pas capturer des patterns complexes.
2. Le compound scaling
EfficientNet propose de scaler les trois dimensions ensemble avec des coefficients optimisés:
depth: d = alpha^phi
width: w = beta^phi
résolution: r = gamma^phi
s.t. alpha · beta² · gamma² ≈ 2, alpha >= 1, beta >= 1, gamma >= 1
Où phi est un coefficient utilisateur (budget de ressources) et alpha, beta, gamma sont des constantes fixées par grid search sur un petit modèle. La contrainte alpha · beta² · gamma² ≈ 2 vient du fait que doubler la profondeur double le nombre d’opérations (FLOPs), tandis que doubler la largeur ou la résolution quadruple les FLOPs.
Pour le modèle de base B0, les coefficients trouvés sont:
alpha = 1.2, beta = 1.1, gamma = 1.15
Ces coefficients sont ensuite appliqués pour générer B1 à B7:
B0: phi = 0 → 5.3M params, 76.3% top-1
B1: phi = 1.0 → 7.8M params, 78.4% top-1
B2: phi = 2.0 → 9.2M params, 79.8% top-1
B3: phi = 3.0 → 12M params, 80.9% top-1
B4: phi = 4.0 → 19M params, 82.4% top-1
B5: phi = 5.0 → 30M params, 83.0% top-1
B6: phi = 6.0 → 43M params, 83.6% top-1
B7: phi = 7.0 → 66M params, 84.3% top-1
3. Le bloc MBConv (Mobile Inverted Bottleneck Convolution)
Chaque bloc d’EfficientNet est un MBConv avec squeeze-and-excitation (inspiré de MobileNetV2):
- Expansion 1×1: augmente les canaux par un facteur d’expansion (typiquement 6)
- Depthwise 3×3 ou 5×5: convolution par canal (beaucoup moins de paramètres qu’une conv standard)
- Squeeze-and-Excitation: pooling global average → FC → sigmoid → poids par canal
- Projection 1×1: réduit les canaux au nombre de sortie
La connexion résiduelle est ajoutée uniquement quand les canaux d’entrée et de sortie sont identiques.
4. Architecture détaillée de B0
Étape 1: Conv 3x3, stride=2, 32 canaux
Étape 2: MBConv1, k=3x3, 16 canaux, 1 layer
Étape 3: MBConv6, k=3x3, 24 canaux, 2 layers
Étape 4: MBConv6, k=5x5, 40 canaux, 2 layers
Étape 5: MBConv6, k=3x3, 80 canaux, 3 layers
Étape 6: MBConv6, k=5x5, 112 canaux, 3 layers
Étape 7: MBConv6, k=5x5, 192 canaux, 4 layers
Étape 8: MBConv6, k=3x3, 320 canaux, 1 layer
Étape 9: Conv 1x1, 1280 canaux → GAP → FC 1000 classes
Intuition
Scaler un modèle de deep learning, c’est comme préparer un repas pour plus d’invités.
Si vous avez 10 invités au lieu de 5, vous ne faites pas QUE:
– Augmenter la quantité de nourriture (plus de couches = plus profond)
– Améliorer la qualité des ingrédients (plus de canaux = plus large)
– Utiliser de plus grandes assiettes (plus de résolution = plus de détails)
Vous faites les trois, en proportion. Le compound scaling d’EfficientNet, c’est exactement ce dosage équilibré. Et la recette de base (B0) a été testée et optimisée pour être le meilleur point de départ avant d’augmenter les portions.
Implémentation Python
1. MBConv Block avec Squeeze-and-Excitation
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class SEBlock(nn.Module):
"""Squeeze-and-Excitation: recalibre les canaux par importance."""
def __init__(self, channels, se_ratio=0.25):
super().__init__()
se_channels = max(1, int(channels * se_ratio))
self.pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channels, se_channels), nn.SiLU(inplace=True),
nn.Linear(se_channels, channels), nn.Sigmoid()
)
def forward(self, x):
b, c, h, w = x.shape
y = self.pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y
class MBConv(nn.Module):
"""MBConv Block avec expansion, depthwise et squeeze-excitation."""
def __init__(self, in_channels, out_channels, kernel_size, stride, expansion, se_ratio=0.25):
super().__init__()
self.use_residual = (stride == 1) and (in_channels == out_channels)
expanded = in_channels * expansion
layers = []
# Expansion 1x1
if expansion != 1:
layers.extend([nn.Conv2d(in_channels, expanded, 1, bias=False),
nn.BatchNorm2d(expanded), nn.SiLU(inplace=True)])
# Depthwise convolution
padding = (kernel_size - 1) // 2
layers.extend([nn.Conv2d(expanded, expanded, kernel_size, stride, padding,
groups=expanded, bias=False),
nn.BatchNorm2d(expanded), nn.SiLU(inplace=True)])
# Squeeze-and-Excitation
layers.append(SEBlock(expanded, se_ratio))
# Projection 1x1
layers.extend([nn.Conv2d(expanded, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels)])
self.block = nn.Sequential(*layers)
def forward(self, x):
out = self.block(x)
if self.use_residual:
out = out + x
return out
### 2. EfficientNet-B0 from scratch
def efficientnet_b0_cfg():
"""Configuration de EfficientNet-B0."""
return [
# repeat, out, kernel, stride, expand_ratio
(1, 16, 3, 1, 1),
(2, 24, 3, 2, 6),
(2, 40, 5, 2, 6),
(3, 80, 3, 2, 6),
(3, 112, 5, 1, 6),
(4, 192, 5, 2, 6),
(1, 320, 3, 1, 6),
]
def get_width_depth_coeff(phi, base_w=1.0, base_d=1.0):
"""Calcule les coefficients de scaling pour un phi donné (B0: phi=0, B7: phi=7)."""
alpha, beta, gamma = 1.2, 1.1, 1.15
w = int(beta ** phi * 32) # base 32 canaux
d = alpha ** phi # depth multiplier
r = int(gamma ** phi * 224) # résolution d'entrée
return max(8, int(w * base_w)), max(1.0, base_d * d), r
def create_efficientnet(cfg, input_channels=3, num_classes=1000, drop_connect_rate=0.2):
layers = [nn.Conv2d(input_channels, 32, 3, 2, 1, bias=False), nn.BatchNorm2d(32), nn.SiLU(inplace=True)]
in_c = 32
for repeat, out_c, kernel, stride, expand in cfg:
for i in range(repeat):
s = stride if i == 0 else 1
layers.append(MBConv(in_c, out_c, kernel, s, expand))
in_c = out_c
layers.extend([nn.Conv2d(in_c, 1280, 1, bias=False), nn.BatchNorm2d(1280), nn.SiLU(inplace=True),
nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Dropout(0.2), nn.Linear(1280, num_classes)])
return nn.Sequential(*layers)
# Créer EfficientNet-B0
model_b0 = create_efficientnet(efficientnet_b0_cfg())
params_b0 = sum(p.numel() for p in model_b0.parameters())
print(f'EfficientNet-B0: {params_b0/1e6:.1f}M paramètres')
# Créer EfficientNet-B7
w_b7, d_b7, r_b7 = get_width_depth_coeff(7)
print(f'EfficientNet-B7: width={w_b7}, depth_mult={d_b7:.2f}, resolution={r_b7}')
# Entraînement
optimizer = torch.optim.Adam(model_b0.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
for images, labels in train_loader:
preds = model_b0(images)
loss = criterion(preds, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
Hyperparamètres
| Hyperparamètre | Valeur | Description |
|---|---|---|
| phi | 0-7 | Coefficient de compound scaling (B0-B7) |
| width_coefficient | beta^phi | Multiplicateur de canaux |
| depth_coefficient | alpha^phi | Multiplicateur de couches |
| resolution | gamma^phi | Taille d’entrée (224 pour B0, 600 pour B7) |
| drop_connect_rate | 0.2 | Taux de stochastic depth (augmente linéairement) |
| dropout_rate | 0.2 | Dropout avant le classifieur final |
Avantages
- Efficacité : Le même niveau de précision avec 10x moins de FLOPs que les architectures précédentes.
- Scalabilité systématique : La méthode de compound scaling s’applique à n’importe quelle architecture, pas seulement EfficientNet.
- Famille complète : 8 modèles (B0-B7) couvrant toutes les contraintes de ressources, du mobile au serveur.
- Transfer learning : Les weights pré-entraînés d’EfficientNet sont parmi les meilleurs backbones disponibles pour la vision.
Limites
- Grands modèles lents : B7 est très précis mais lent à l’inférence. Pour le temps réel, B0-B3 sont plus adaptés.
- Architecture fixe : Les blocs MBConv ne sont pas automatiquement optimisés — ils sont choisis manuellement comme backbone.
- Moins populaire : Moins de frameworks supportent EfficientNet par rapport à ResNet, bien que la situation s’améliore.
4 cas d’usage concrets
1. Classification d’industriel sur edge devices
EfficientNet-B0 et B1, avec leurs 5-8M paramètres, sont parfaits pour le déploiement sur des appareils embarqués (Raspberry Pi, smartphones) pour la classification de produits en usine.
2. Détection de maladies sur radiographies
EfficientNet-B3 pré-entraîné sur ImageNet est finetuné sur des radiographies thoraciques pour détecter la pneumonie, atteignant une AUC de 0.98 avec beaucoup moins de données d’entraînement qu’un ResNet équivalent.
3. Recherche d’images e-commerce
Les features extraites d’EfficientNet (couche avant le classifieur) servent d’embeddings pour la recherche d’images similaires sur des catalogues de millions de produits.
4. Backbone pour détection/segmentation
EfficientNet remplace ResNet comme backbone dans les architectures de détection (RetinaNet, EfficientDet) et de segmentation, offrant un meilleur compromis précision/vitesse.
Conclusion
EfficientNet a prouvé qu’une approche systématique et mathématique du scaling peut surpasser le scaling ad hoc qui dominait la recherche. Le compound scaling est maintenant une pratique standard, et la famille B0-B7 couvre un spectre de applications sans équivalent.
Les successeurs (EfficientNetV2 avec Fused-MBConv et training-aware scaling) ont amélioré encore ces résultats, mais le principe fondamental reste le même: équilibrer profondeur, largeur et résolution de manière calculée, pas arbitraire.
Voir aussi
- Somme des Chemins en Python : Quatre Méthodes Incontournables pour Maîtriser l’Algorithme
- Démystifier la Constante de Champernowne en Python : Guide Complet et Tutoriel Pratique

