Module d'optimisation torch.optim de PyTorch

Les optimiseurs sont des composants essentiels de l'apprentissage profond, chargés d'ajuster les paramètres du modèle en fonction du gradient de la fonction de perte, permettant au modèle de s'approcher progressivement de la solution optimale.

Dans PyTorch, le module torch.optim fournit des implémentations de divers algorithmes d'optimisation et est un outil indispensable pour l'entraînement des réseaux de neurones.


Pourquoi les optimiseurs sont nécessaires

Les optimiseurs jouent un rôle vital dans l'apprentissage profond, en résolvant le problème fastidieux de la mise à jour manuelle des paramètres.

  • Mises à jour automatisées des paramètres: Le calcul et la mise à jour manuels de chaque paramètre sont très fastidieux, et l'optimiseur le fait automatiquement.
  • Accélérer la convergence: L'utilisation d'algorithmes d'optimisation permet de trouver la solution optimale plus rapidement qu'une simple descente de gradient.
  • Éviter les optima locaux: Certains optimiseurs ont la capacité d'échapper aux optima locaux.

Types d'optimiseurs courants

Différents optimiseurs conviennent à différents scénarios. Choisir le bon optimiseur peut considérablement améliorer les résultats d'entraînement.

Nom de l'optimiseurFonctionnalités principalesScénarios applicables
SGDSimple et basique, prend en charge le momentumEnseignement de base, modèles simples, CNN
AdamTaux d'apprentissage adaptatifLa plupart des tâches d'apprentissage profond
AdamWAdam + décroissance des poids découpléeTâches nécessitant une régularisation L2
RMSpropTaux d'apprentissage adaptatifRéseaux RNN, reconnaissance vocale
AdagradTaux d'apprentissage par paramètreDonnées creuses, traitement du texte
AdadeltaTaux d'apprentissage adaptatifTâches d'entraînement à long terme

API de base des optimiseurs

Maîtriser le flux d'utilisation de base des optimiseurs est la première étape de l'apprentissage profond.

Flux d'utilisation de base

L'utilisation d'un optimiseur suit un schéma fixe : créer une instance → remettre les gradients à zéro → rétropropagation → mettre à jour les paramètres.

Exemple

import torch
import torch.nn as nn
import torch.optim as optim

# 1. Définir un modèle simple
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(784, 10)

    def forward(self, x):
        return self.fc(x)

model = SimpleNet()

# 2. Créer une instance d'optimiseur
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 3. Boucle d'entraînement
for epoch in range(epochs):
    # Passe avant
    outputs = model(inputs)
    loss = criterion(outputs, labels)

    # Rétropropagation
    optimizer.zero_grad()  # Remettre à zéro le cache des gradients pour éviter l'accumulation des gradients
    loss.backward()        # Calculer les gradients

    # Mise à jour des paramètres
    optimizer.step()       # Mettre à jour les paramètres

Explication des méthodes clés

L'optimiseur fournit plusieurs méthodes principales pour gérer le processus de mise à jour des paramètres.

  • zero_grad(set_to_none=True): Remet à zéro le cache des gradients des paramètres. Lorsque la valeur est True, les gradients sont définis sur None, ce qui économise plus de mémoire que de les définir sur 0.
  • step(): Effectue une seule mise à jour des paramètres, en mettant à jour les paramètres du modèle en fonction des gradients et du taux d'apprentissage.
  • state_dict(): Récupère le dictionnaire d'état de l'optimiseur, qui peut être utilisé pour sauvegarder des points de contrôle.
  • load_state_dict(state_dict): Charge l'état de l'optimiseur pour reprendre l'entraînement.
  • add_param_group(param_group): Ajoute dynamiquement un groupe de paramètres.

Remarque : Vous devez appeler zero_grad() avant chaque passe de rétropropagation, sinon les gradients s'accumuleront et rendront l'entraînement instable. Il est recommandé d'utiliser zero_grad(set_to_none=True) pour économiser de la mémoire.

Sauvegarde et chargement de l'état de l'optimiseur

Lors de la reprise de l'entraînement, il est nécessaire de sauvegarder à la fois l'état du modèle et celui de l'optimiseur.

Exemple

# Enregistrer le point de contrôle (enregistrer le modèle, l'optimiseur et le planificateur ensemble)
checkpoint = {
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'scheduler_state_dict': scheduler.state_dict(),
    'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')

# Charger le point de contrôle
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
start_epoch = checkpoint['epoch'] + 1

Explication détaillée des optimiseurs courants

SGD (descente de gradient stochastique)

SGD est l'algorithme d'optimisation le plus élémentaire, mettant à jour les paramètres en calculant les gradients à partir d'un seul échantillon ou d'un mini-lot d'échantillons. Il est la pierre angulaire de l'optimisation en apprentissage profond, et de nombreux optimiseurs avancés sont développés sur la base de SGD.

Exemple

# Description des paramètres de l'optimiseur SGD
# params : paramètres à optimiser (généralement à partir de model.parameters())
# lr : taux d'apprentissage, contrôle la taille du pas des mises à jour des paramètres, par défaut 0.01
# momentum : facteur de momentum, utilisé pour accélérer la convergence et réduire les oscillations, par défaut 0
# weight_decay : coefficient de régularisation L2, utilisé pour éviter le surapprentissage, par défaut 0
# dampening : amortissement du momentum, contrôle le calcul du terme de momentum, par défaut 0
# nesterov : indique s'il faut utiliser le momentum de Nesterov, par défaut False
optimizer = optim.SGD(
    params=model.parameters(),
    lr=0.01,           # Taux d'apprentissage
    momentum=0.9,      # Facteur de momentum
    weight_decay=1e-4, # Régularisation L2
    nesterov=True      # Activer le momentum de Nesterov
)

Description des paramètres principaux :

  • lr (float) : taux d'apprentissage, contrôle la taille du pas des mises à jour des paramètres
  • momentum (float) : facteur de momentum, utilisé pour accélérer la convergence et réduire les oscillations, valeur courante 0.9
  • weight_decay (float) : coefficient de régularisation L2, utilisé pour éviter le surapprentissage, valeur courante 1e-4
  • nesterov (bool) : indique s'il faut utiliser le momentum de Nesterov ; l'activer peut réduire les oscillations

Fonctionnalités :

  • Simple à implémenter, c'est l'algorithme de base de l'optimisation en apprentissage profond
  • L'ajout d'un terme de momentum peut accélérer la convergence et améliorer la stabilité de l'entraînement
  • Vitesse de convergence plus lente, mais la précision finale peut être plus élevée
  • Adapté comme référence pour la comparaison avec d'autres optimiseurs

Bien que SGD soit simple, il peut souvent obtenir de très bons résultats avec des hyperparamètres appropriés, ce qui en fait un bon point de départ pour l'apprentissage des algorithmes d'optimisation. Dans les tâches de classification d'images, SGD avec momentum reste un choix courant.

Adam (estimation adaptative des moments)

Adam est l'un des optimiseurs les plus couramment utilisés, combinant les avantages du momentum et des taux d'apprentissage adaptatifs. Il ajuste de manière adaptative le taux d'apprentissage pour chaque paramètre en calculant des estimations de moments du premier et du second ordre des gradients.

Exemple

# Description des paramètres de l'optimiseur Adam
# params : paramètres à optimiser
# lr : taux d'apprentissage, par défaut 0.001 (valeur recommandée)
# betas : coefficients de moyenne mobile utilisés pour calculer les gradients et les gradients au carré (beta1, beta2)
# beta1 contrôle l'estimation du moment du premier ordre (momentum), par défaut 0.9
# beta2 contrôle l'estimation du moment du second ordre (variance), par défaut 0.999
# eps : terme de stabilité numérique, empêche la division par zéro, par défaut 1e-8
# weight_decay : coefficient de régularisation L2, par défaut 0
# amsgrad : indique s'il faut utiliser la variante AMSGrad, par défaut False
optimizer = optim.Adam(
    params=model.parameters(),
    lr=0.001,                      # Il est recommandé d'utiliser un taux d'apprentissage plus faible
    betas=(0.9, 0.999),            # Paramètres de momentum couramment utilisés
    eps=1e-8,                      # Terme de stabilité numérique
    weight_decay=1e-4,             # Régularisation L2
    amsgrad=False                  # Indique s'il faut utiliser AMSGrad
)

Description des paramètres principaux :

  • betas (Tuple[float, float]): contrôle la moyenne mobile exponentielle des gradients et des gradients au carré
  • eps (float): terme de stabilité numérique, empêche le dénominateur d'être nul
  • amsgrad (bool): indique s'il faut utiliser la variante AMSGrad, son utilisation peut garantir la convergence

Fonctionnalités :

  • Taux d'apprentissage adaptatif : ajuste automatiquement le taux d'apprentissage en fonction du gradient historique du paramètre
  • Intègre le concept de momentum : utilise l'estimation du moment de premier ordre pour accélérer la convergence
  • Grande robustesse : relativement insensible aux choix des hyperparamètres
  • Vitesse de convergence rapide, adaptée au prototypage rapide

Adam est le choix par défaut pour la plupart des tâches d'apprentissage profond, mais dans certains scénarios spécifiques (comme les GAN, l'apprentissage par renforcement), vous devrez peut-être essayer d'autres optimiseurs.

AdamW(Adam avec décroissance des poids)

AdamW est une version améliorée d'Adam qui découple la décroissance des poids des mises à jour de gradient, ce qui est théoriquement plus propice à la convergence. Dans les applications pratiques, AdamW est généralement plus performant qu'Adam.

Exemple

# Optimiseur AdamW
# Principale différence avec Adam : implémentation différente de weight_decay
# La décroissance des poids d'AdamW est plus correcte et n'affecte pas le calcul du gradient
optimizer = optim.AdamW(
    params=model.parameters(),
    lr=0.001,
    betas=(0.9, 0.999),
    weight_decay=0.01,    # Coefficient de décroissance des poids, généralement défini plus grand qu'avec Adam
    amsgrad=False
)

# Configuration recommandée : AdamW utilise généralement un weight_decay de 0.01
# Alors qu'Adam utilise généralement 0.001

Si votre tâche nécessite une décroissance des poids (régularisation L2), il est fortement recommandé d'utiliser AdamW au lieu d'Adam.

RMSprop

RMSprop est un optimiseur à taux d'apprentissage adaptatif, particulièrement adapté à la gestion d'objectifs non stationnaires et des réseaux de neurones récurrents.

Exemple

# Optimiseur RMSprop
# Normalise le taux d'apprentissage en le divisant par la moyenne pondérée exponentielle des gradients
optimizer = optim.RMSprop(
    params=model.parameters(),
    lr=0.01,               # Taux d'apprentissage
    alpha=0.99,            # Taux de décroissance exponentielle pour les gradients au carré
    eps=1e-8,              # Terme de stabilité numérique
    weight_decay=0,        # Régularisation L2
    momentum=0,            # Facteur de momentum
    centered=False         # Indique s'il faut centrer les gradients
)

Adagrad

Adagrad convient au traitement des données creuses ; il ajuste de manière adaptative le taux d'apprentissage de chaque paramètre.

Exemple

# Optimiseur Adagrad
# Adapté à l'optimisation de données creuses, utilise un taux d'apprentissage plus faible pour les paramètres fréquemment mis à jour
optimizer = optim.Adagrad(
    params=model.parameters(),
    lr=0.01,               # Taux d'apprentissage
    lr_decay=0,            # Décroissance du taux d'apprentissage
    weight_decay=0,       # Régularisation L2
    initial_accumulator_value=0  # Valeur initiale de l'accumulateur
)

Techniques avancées d'optimisation

Planification du taux d'apprentissage

La planification du taux d'apprentissage permet d'ajuster dynamiquement le taux d'apprentissage pendant l'entraînement, ce qui peut souvent améliorer significativement la convergence du modèle.

Exemple : plusieurs planificateurs de taux d'apprentissage

from torch.optim.lr_scheduler import (
    StepLR,                # Décroissance par étapes
    MultiStepLR,           # Décroissance multi-étapes
    ExponentialLR,         # Décroissance exponentielle
    CosineAnnealingLR,     # Recuit cosinus
    ReduceLROnPlateau,     # Ajustement automatique en fonction des métriques
)

# Méthode 1 : StepLR - décroît toutes les 30 époques
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)

# Méthode 2 : MultiStepLR - décroît aux époques spécifiées
optimizer = optim.SGD(model.parameters(), lr=0.1)
scheduler = MultiStepLR(optimizer, milestones=[30, 60, 80], gamma=0.1)

# Méthode 3 : CosineAnnealingLR - recuit de courbe cosinus
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=1e-6)

# Méthode 4 : ReduceLROnPlateau - ajuste automatiquement en fonction de la métrique surveillée
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = ReduceLROnPlateau(
    optimizer, mode='min',     # Surveiller la perte
    factor=0.5,                 # Facteur de décroissance
    patience=5,                 # Nombre d'époques à attendre
    verbose=True                # Afficher les informations
)

# Boucle d'entraînement
for epoch in range(100):
    train_loss = train(...)
    val_loss = validate(...)

    # StepLR et autres planificateurs
    scheduler.step()

    # ReduceLROnPlateau nécessite que la métrique surveillée soit transmise
    scheduler.step(val_loss)

Le planificateur de taux d'apprentissage doit être utilisé conjointement avec l'optimiseur ; step() doit être appelé après optimizer.step(), sinon la mise à jour du taux d'apprentissage peut être anormale.

Optimisation par groupes de paramètres

Le regroupement de paramètres permet de définir différents taux d'apprentissage pour différentes couches, ce qui est particulièrement utile dans l'apprentissage par transfert.

Exemple

# Exemple d'optimisation par regroupement de paramètres
# Définir différents taux d'apprentissage pour différentes couches
# Généralement : le réseau principal utilise un taux d'apprentissage plus faible, et la tête de classification utilise un taux d'apprentissage plus élevé
optimizer = optim.SGD([
    {'params': model.base.parameters(), 'lr': 1e-3},      # Couches de base : taux d'apprentissage élevé
    {'params': model.classifier.parameters(), 'lr': 1e-2} # Couche de classification : taux d'apprentissage élevé
], lr=1e-4)  # Taux d'apprentissage global par défaut (utilisé lorsqu'aucun groupe de paramètres n'est spécifié)

# Notation plus courante dans les applications pratiques
optimizer = optim.Adam([
    {'params': model.fc.parameters(), 'lr': 1e-3},       # Tête de classification
    {'params': [p for n, p in model.named_parameters()    # Réseau principal
                if not n.startswith('fc')],
     'lr': 1e-5},
])

Écrêtage du gradient

L'écrêtage des gradients peut empêcher l'explosion des gradients et améliorer la stabilité de l'entraînement. Il est particulièrement utile dans les réseaux profonds tels que RNN et LSTM.

Exemple

import torch.nn as nn

# Exemple d'écrêtage du gradient
# max_norm : norme maximale des gradients ; les gradients dépassant cette valeur seront mis à l'échelle
# norm_type : type de norme, par défaut 2 (norme L2)
nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# Position dans la boucle d'entraînement
for epoch in range(epochs):
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()

    # Écrêter les gradients après loss.backward() et avant optimizer.step()
    nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

    optimizer.step()

# Une autre façon d'écrêter les gradients : l'écrêtage par valeur
for param in model.parameters():
    if param.grad is not None:
        param.grad.data.clamp_(min=-1.0, max=1.0)

L'écrêtage des gradients est une technique courante pour entraîner des réseaux de neurones profonds (en particulier RNN, LSTM, etc.), et peut efficacement empêcher les plantages d'entraînement causés par l'explosion des gradients.

Accumulation de gradients

Lorsque la mémoire vidéo est insuffisante, l'accumulation de gradients peut être utilisée pour simuler l'effet d'entraînement d'une grande taille de lot.

Exemple

# Exemple d'accumulation de gradients
# batch_size réel = batch_size * accumulation_steps
accumulation_steps = 4   # Accumuler 4 petits batchs

optimizer.zero_grad()

for i, (inputs, labels) in enumerate(train_loader):
    outputs = model(inputs)
    loss = criterion(outputs, labels)

    # Diviser la perte par les étapes d'accumulation pour obtenir une moyenne
    loss = loss / accumulation_steps
    loss.backward()

    # Mettre à jour les paramètres une fois à chaque nombre spécifié d'étapes d'accumulation
    if (i + 1) % accumulation_steps == 0:
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()
        optimizer.zero_grad()

# Gérer les gradients restants
if (i + 1) % accumulation_steps != 0:
    optimizer.step()
    optimizer.zero_grad()

Exemple complet d'entraînement

Ce qui suit est un pipeline d'entraînement complet, illustrant les meilleures pratiques pour les optimiseurs.

Exemple

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR

# Configuration
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
EPOCHS = 100
BATCH_SIZE = 32
LR = 1e-3

# Créer le modèle et le déplacer vers le périphérique
model = SimpleNet().to(DEVICE)

# Fonction de perte et optimiseur
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=0.01)

# Planificateur de taux d'apprentissage (recuit cosinus)
scheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-6)

# Boucle d'entraînement
best_acc = 0.0
for epoch in range(EPOCHS):
    model.train()
    total_loss = 0.0
    correct = 0

    for inputs, labels in train_loader:
        inputs = inputs.to(DEVICE)
        labels = labels.to(DEVICE)

        # Effacer les gradients (il est recommandé d'utiliser set_to_none=True)
        optimizer.zero_grad(set_to_none=True)

        # Passe avant
        outputs = model(inputs)
        loss = criterion(outputs, labels)

        # Rétropropagation
        loss.backward()

        # Écrêtage des gradients (éviter l'explosion des gradients)
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

        # Mettre à jour les paramètres
        optimizer.step()

        # Statistiques
        total_loss += loss.item()
        correct += (outputs.argmax(1) == labels).sum().item()

    # Mettre à jour le taux d'apprentissage
    scheduler.step()

    # Afficher les informations d'entraînement
    avg_loss = total_loss / len(train_loader)
    accuracy = correct / len(train_loader.dataset)
    current_lr = scheduler.get_last_lr()[0]
    print(f"Époque {epoch+1}/{EPOCHS} | Perte : {avg_loss:.4f} | "
          f"Précision : {accuracy:.4f} | LR : {current_lr:.6f}")

    # Enregistrer le meilleur modèle
    if accuracy > best_acc:
        best_acc = accuracy
        torch.save({
            'epoch': epoch,
            'model_state_dict': model.state_dict(),
            'optimizer_state_dict': optimizer.state_dict(),
            'scheduler_state_dict': scheduler.state_dict(),
            'best_acc': best_acc,
        }, 'best_model.pth')

print(f"Entraînement terminé, meilleure précision : {best_acc:.4f}")

Guide de sélection de l'optimiseur

Le choix du bon optimiseur doit être déterminé en fonction de la tâche spécifique, des caractéristiques des données et de l'étape d'entraînement.

Sélection par tâche

Les différents types de tâches ont des recommandations d'optimiseur différentes.

Type de tâcheOptimiseur recommandéTaux d'apprentissage recommandéNotes
Classification d'images (CNN)SGD + Momentum0.01 ~ 0.1Convergence lente mais précision élevée
Classification d'images (CNN)AdamW0.001Convergence rapide
NLP / TransformerAdamW1e-5 ~ 1e-4Taux d'apprentissage plus faible
RNN / LSTMRMSprop / Adam0.001Taux d'apprentissage adaptatif
GANAdam (G) / Adam (D)0.0001Taux d'apprentissage plus faible
Apprentissage par renforcementAdam / RMSprop0.0001 ~ 0.001Dépend de la tâche spécifique
Expérimentation rapideAdam / AdamW0.001Convergence rapide

Comparaison des performances

OptimiseurVitesse de convergenceUtilisation de la mémoireSensibilité aux hyperparamètresPrécision finale
SGD + MomentumLentFaibleÉlevéeÉlevée
AdamRapideMoyenneFaibleMoyenne
AdamWRapideMoyenneFaibleÉlevée
RMSpropMoyenneMoyenneMoyenneMoyenne
AdagradMoyenneÉlevéeMoyenneFaible

Problèmes courants et solutions

  • Entraînement instable (oscillation de la perte) : réduire le taux d'apprentissage, ajouter un écrêtage du gradient
  • Convergence trop lente : utiliser Adam ou AdamW, augmenter le taux d'apprentissage
  • Surapprentissage : augmenter weight_decay, utiliser la régularisation
  • Mémoire vidéo insuffisante : réduire batch_size, utiliser l'accumulation de gradient

Le choix de l'optimiseur n'est pas absolu ; il est recommandé de commencer par Adam ou AdamW, et d'envisager d'autres optimiseurs si les résultats ne sont pas satisfaisants. Pour des tâches spécifiques, des expérimentations peuvent être nécessaires pour déterminer le choix optimal.

Pour plus d'informations détaillées, consultezla documentation officielle de PyTorch。

Autres extensions