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'optimiseur | Fonctionnalités principales | Scénarios applicables |
|---|---|---|
| SGD | Simple et basique, prend en charge le momentum | Enseignement de base, modèles simples, CNN |
| Adam | Taux d'apprentissage adaptatif | La plupart des tâches d'apprentissage profond |
| AdamW | Adam + décroissance des poids découplée | Tâches nécessitant une régularisation L2 |
| RMSprop | Taux d'apprentissage adaptatif | Réseaux RNN, reconnaissance vocale |
| Adagrad | Taux d'apprentissage par paramètre | Données creuses, traitement du texte |
| Adadelta | Taux d'apprentissage adaptatif | Tâ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.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
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
# 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
# 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
# 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
# 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
# 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
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
# 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
# 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
# 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.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âche | Optimiseur recommandé | Taux d'apprentissage recommandé | Notes |
|---|---|---|---|
| Classification d'images (CNN) | SGD + Momentum | 0.01 ~ 0.1 | Convergence lente mais précision élevée |
| Classification d'images (CNN) | AdamW | 0.001 | Convergence rapide |
| NLP / Transformer | AdamW | 1e-5 ~ 1e-4 | Taux d'apprentissage plus faible |
| RNN / LSTM | RMSprop / Adam | 0.001 | Taux d'apprentissage adaptatif |
| GAN | Adam (G) / Adam (D) | 0.0001 | Taux d'apprentissage plus faible |
| Apprentissage par renforcement | Adam / RMSprop | 0.0001 ~ 0.001 | Dépend de la tâche spécifique |
| Expérimentation rapide | Adam / AdamW | 0.001 | Convergence rapide |
Comparaison des performances
| Optimiseur | Vitesse de convergence | Utilisation de la mémoire | Sensibilité aux hyperparamètres | Précision finale |
|---|---|---|---|---|
| SGD + Momentum | Lent | Faible | Élevée | Élevée |
| Adam | Rapide | Moyenne | Faible | Moyenne |
| AdamW | Rapide | Moyenne | Faible | Élevée |
| RMSprop | Moyenne | Moyenne | Moyenne | Moyenne |
| Adagrad | Moyenne | Élevée | Moyenne | Faible |
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