EfficientNet : Guide Complet — Scaling Composite de Réseaux de Neurones

EfficientNet : Guide Complet — Scaling Composite de Réseaux de Neurones

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):

  1. Expansion 1×1: augmente les canaux par un facteur d’expansion (typiquement 6)
  2. Depthwise 3×3 ou 5×5: convolution par canal (beaucoup moins de paramètres qu’une conv standard)
  3. Squeeze-and-Excitation: pooling global average → FC → sigmoid → poids par canal
  4. 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

  1. Efficacité : Le même niveau de précision avec 10x moins de FLOPs que les architectures précédentes.
  2. Scalabilité systématique : La méthode de compound scaling s’applique à n’importe quelle architecture, pas seulement EfficientNet.
  3. Famille complète : 8 modèles (B0-B7) couvrant toutes les contraintes de ressources, du mobile au serveur.
  4. Transfer learning : Les weights pré-entraînés d’EfficientNet sont parmi les meilleurs backbones disponibles pour la vision.

Limites

  1. 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.
  2. Architecture fixe : Les blocs MBConv ne sont pas automatiquement optimisés — ils sont choisis manuellement comme backbone.
  3. 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


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.