Adagrad : Comprendre et Implémenter l'Optimiseur de Gradient Adaptatif

Lors de l'entraînement de modèles d'apprentissage profond, un défi courant est le choix et l'ajustement du taux d'apprentissage. Un taux trop élevé peut entraîner des oscillations et une divergence, tandis qu'un taux trop faible peut ralentir considérablement la convergence. La difficulté s'accentue lorsque les différents paramètres du modèle requièrent des ajustements à des échelles très différentes, ou lorsque les données sont éparses.

L'algorithme Adagrad (Adaptive Gradient), ou Gradient Adaptatif, est une innovation majeure dans le domaine de l'optimisation des réseaux de neurones. Il résout ces problèmes en ajustant dynamiquement le taux d'apprentissage pour chaque paramètre individuel. Cet article explore les principes fondamentaux d'Adagrad, sa formulation mathématique, les défis qu'il adresse, et sa mise en œuvre pratique.

Principe Fondamental de l'Algorithme Adagrad

Vue d'Ensemble du Concept

L'idée centrale d'Adagrad est simple mais puissante : il maintient une somme cumulative des carrés des gradients pour chaque paramètre. Cette information est ensuite utilisée pour adapter le taux d'apprantissage de manière spécifique à chaque paramètre :

  • Les paramètres associés à de grands gradients (c'est-à-dire qui ont eu des gradients importants dans le passé) verront leur taux d'apprentissage effectif réduit.
  • Inversement, les paramètres avec de petits gradients (moins mis à jour) conserveront un taux d'apprentissage relativement plus élevé.
  • Ceci est particulièrement avantageux pour les caractéristiques rares (données éparses), qui reçoivent ainsi des ajustements plus significatifs lorsqu'elles apparaissent.

Formulation Mathématique

Les règles de mise à jour pour Adagrad sont définies comme suit :

$$ \begin{aligned} \mathbf{g}_t &= \partial_{\mathbf{w}} l(y_t, f(\mathbf{x}_t, \mathbf{w})) \\ \mathbf{s}_t &= \mathbf{s}_{t-1} + \mathbf{g}_t^2 \\ \mathbf{w}_t &= \mathbf{w}_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t + \epsilon}} \cdot \mathbf{g}_t \end{aligned} $$ Où :

  • $\mathbf{g}_t$ représente le gradient au pas de temps $t$.
  • $\mathbf{s}_t$ est le vecteur de la somme cumulée des carrés des gradients jusqu'au pas $t$.
  • $\eta$ est le taux d'apprentissage global (un hyperparamètre fixe).
  • $\epsilon$ est une petite constante (souvent $1e-6$) ajoutée pour prévenir les divisions par zéro.

Défis Résolus par Adagrad

Optimisation des Caractéristiques Éparses

Dans des domaines comme le traitement du langage naturel ou les systèmes de recommandation, certaines caractéristiques ou mots apparaissent très rarement, mais peuvent être cruciaux. Les optimiseurs classiques peinent à leur accorder une attention suffisante. Adagrad, en réduisant moins agressivement le taux d'apprentissage pour les gradients peu fréquents, permet des ajustements plus précis pour ces caractéristiques éparses.

Gestion des Paramètres à Différentes Échelles

Lorsque les gradients de différents paramètres varient considérablement en magnitude, un taux d'apprentissage unique peut être inefficace :

  • Pour les paramètres avec de grands gradients, le taux peut être trop élevé, entraînant une instabilité.
  • Pour ceux avec de petits gradients, il peut être trop faible, menant à une convergence très lente.

Adagrad normalise le gradient de chaque paramètre par la racine carrée de la somme de ses gradients passés au carré, gérant ainsi naturellement ces différences d'échelle.

Analyse Mathématique Approfondie

Interprétation par le Préconditionnement

D'un point de vue théorique, Adagrad peut être vu comme une méthode de préconditionnement approximative. L'objectif idéal serait d'utiliser l'inverse de la matrice Hessienne pour préconditionner les gradients, mais cela est impraticable pour les modèles profonds. Adagrad utilise de manière ingénieuse la somme cumulée des carrés des gradients comme une approximation diagonale de l'inverse de la Hessienne, simplifiant le processus tout en offrant des avantages adaptatifs.

Propriétés de Convergence

Pour les fonctions convexes, Adagrad est garanti de converger, généralement à une vitesse de $O(1/\sqrt{T})$. Son avantage réside dans sa capacité à s'adapter automatiquement au conditionnement du problème d'optimisation, ce qui le rend robuste à diverses formes de fonctions de perte.

Détails de l'Implémentation en Code

Implémentation "From Scratch" en Python

Voici comment implémenter Adagrad de manière élémentaire avec PyTorch :

import torch

def initialiser_etats_adagrad(dimension_caracteristique):
    """
    Initialise les variables d'état pour Adagrad.
    Ces variables accumulent le carré des gradients pour chaque paramètre.
    """
    somme_carres_gradients_poids = torch.zeros((dimension_caracteristique, 1))
    somme_carres_gradients_biais = torch.zeros(1)
    return (somme_carres_gradients_poids, somme_carres_gradients_biais)

def appliquer_adagrad(parametres_modele, etats_accumules, config_optim):
    """
    Applique la règle de mise à jour d'Adagrad à un ensemble de paramètres.
    """
    epsilon = 1e-6 # Constante pour éviter la division par zéro
    taux_apprentissage_global = config_optim['taux_apprentissage']

    for parametre, accumulateur_carres_grad in zip(parametres_modele, etats_accumules):
        if parametre.grad is None:
            continue
        
        with torch.no_grad():
            # Accumuler le carré des gradients
            accumulateur_carres_grad.add_(torch.square(parametre.grad))
            
            # Calculer le taux d'apprentissage adaptatif pour ce paramètre
            taux_adaptatif = taux_apprentissage_global / torch.sqrt(accumulateur_carres_grad + epsilon)
            
            # Mettre à jour le paramètre
            parametre.sub_(taux_adaptatif * parametre.grad)
        
        # Réinitialiser le gradient
        parametre.grad.zero_()

Intégration avec les Frameworks

Les bibliothèques d'apprentissage profond les plus utilisées offrent des implémentations optimisées d'Adagrad :

# PyTorch
import torch.optim as optim
optimiseur = optim.Adagrad(model.parameters(), lr=0.01)

# TensorFlow
import tensorflow as tf
optimiseur = tf.keras.optimizers.Adagrad(learning_rate=0.01)

# PaddlePaddle
import paddle.optimizer as optim
optimiseur = optim.Adagrad(learning_rate=0.01, parameters=model.parameters())

Cas d'Usage Pratiques

Cas 1 : Optimisation d'une Fonction Quadratique

Considérons la fonction $f(x_1, x_2) = 0.1x_1^2 + 2x_2^2$, qui présente des courbures très différentes selon les directions. C'est un excellent test pour les optimiseurs adaptatifs.

import math

def mise_a_jour_adagrad_2d(val_x1, val_x2, accum_grad_sq1, accum_grad_sq2, lr_globale):
    """
    Implémentation d'Adagrad pour une fonction bidimensionnelle.
    Utilisée pour illustrer l'adaptation des taux d'apprentissage.
    """
    epsilon = 1e-6
    
    # Calcul des gradients pour la fonction f(x1, x2) = 0.1*x1^2 + 2*x2^2
    gradient_x1 = 0.2 * val_x1
    gradient_x2 = 4.0 * val_x2 
    
    # Accumulation des carrés des gradients
    accum_grad_sq1 += gradient_x1 ** 2
    accum_grad_sq2 += gradient_x2 ** 2
    
    # Mise à jour adaptative des variables
    val_x1 -= lr_globale / math.sqrt(accum_grad_sq1 + epsilon) * gradient_x1
    val_x2 -= lr_globale / math.sqrt(accum_grad_sq2 + epsilon) * gradient_x2
    
    return val_x1, val_x2, accum_grad_sq1, accum_grad_sq2

Cas 2 : Tâche de Régression Linéaire

L'application d'Adagrad à un problème de régression linéaire sur des données réelles suit le même principe :

import torch

# Placeholder: l'acquisition des données (similaire à d2l.get_data_ch11)
# serait effectuée ici, renvoyant un itérateur de données et la dimension des caractéristiques.
# Pour cet exemple, nous simulons la dimension et les paramètres du dataset.
# data_iter, dimension_caracteristiques = get_data_pour_reg_lin(batch_size=10)

def entrainer_modele(fonction_optimiseur, etats_optimiseur, config_optim, iterateur_donnees, dimension_caracteristiques, nombre_epochs):
    """
    Fonction générique pour entraîner un modèle avec un optimiseur donné.
    Illustre l'intégration d'Adagrad dans un cycle d'entraînement standard.
    """
    # Initialisation des paramètres du modèle (poids et biais pour régression linéaire)
    poids = torch.normal(mean=0.0, std=0.01, size=(dimension_caracteristiques, 1), requires_grad=True)
    biais = torch.zeros(1, requires_grad=True)
    
    # Définition du réseau (exemple simple de régression linéaire)
    def reseau(X, w, b):
        return torch.matmul(X, w) + b

    # Définition de la fonction de perte (exemple de MSE)
    fonction_perte = torch.nn.MSELoss(reduction='mean')

    # Boucle d'entraînement
    for epoch in range(nombre_epochs):
        for X_batch, y_batch in iterateur_donnees:
            # Calcul de la prédiction
            predictions = reseau(X_batch, poids, biais)
            # Calcul de la perte
            perte = fonction_perte(predictions, y_batch)
            
            # Rétropropagation pour calculer les gradients
            perte.backward()
            
            # Application de l'algorithme d'optimisation (Adagrad dans ce cas)
            fonction_optimiseur([poids, biais], etats_optimiseur, config_optim)
            
            # (Optionnel) Ici, on pourrait ajouter une logique de logging de la perte
            # print(f"Epoch {epoch}, Loss: {perte.item()}")

Avantages et Limites d'Adagrad

Comparaison des Points Forts

Caractéristique SGD Momentum Adagrad
Taux d'apprentissage uniforme
Taux d'apprentissage adaptatif
Gestion des caractéristiques éparses Faible Moyenne Excellente
Consommation mémoire Faible Faible Modérée
Nombre d'hyperparamètres 1 2 2

Limites et Évolutions

Le principal inconvénient d'Adagrad réside dans l'accumulation monotone des carrés des gradients, ce qui fait que le taux d'apprentissage effectif diminue continuellement et peut devenir excessivement petit au fil du temps. Cela conduit à :

  1. Un déclin trop rapide du taux d'apprentissage : La performance peut se dégrader après un certain nombre d'époques.
  2. La nécessité de définir un taux d'apprentissage global : Bien qu'adaptatif, il reste un hyperparamètre global à ajuster.
  3. Des garanties théoriques limitées : Particulièrement pour les fonctions non convexes, communes en apprentissage profond.

Ces limitations ont motivé le développement d'optimiseurs plus sophistiqués tels qu'Adadelta, RMSprop et Adam, qui cherchent à remédier au problème du déclin excessif du taux d'apprentissage.

Conseils de Bonnes Pratiques

Optimisation des Hyperparamètres

# Paramètres typiques pour Adagrad
parametres_config = {
    'taux_apprentissage': 0.01, # Un taux global souvent plus petit est recommandé
    'epsilon': 1e-7,            # Pour la stabilité numérique
}

# Utilisation d'un scheduler de taux d'apprentissage pour une gestion plus fine
# scheduler = torch.optim.lr_scheduler.StepLR(optimiseur, step_size=30, gamma=0.1)

Scénarios d'Application

Adagrad est particulièrement recommandé pour :

  • Les ensembles de données avec des caractéristiques très éparses, comme en NLP ou dans les systèmes de recommandation.
  • Les tâches où les fréquences d'apparition des caractéristiques varient significativement.
  • Les situations nécessitant une adaptation fine des taux d'apprentissage par paramètre.

Il est moins adapté pour :

  • Les réseaux de neurones très profonds nécessitant une convergence sur un grand nombre d'époques.
  • Les tâches où une décroissance lente du taux d'apprentissage est souhaitée.

Techniques d'Optimisation des Performances

Gestion de la Mémoire

# Utiliser des opérations in-place (avec underscore) pour réduire la consommation mémoire
s.add_(gradient.pow(2))

# Appliquer le clipping de gradient pour prévenir les explosions numériques et stabiliser l'entraînement
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

Efficacité Calculatoire

# Privilégier les opérations vectorisées pour une meilleure performance
accumulateur_carres_grad = accumulateur_carres_grad + parametre.grad**2

# Tirer parti des capacités de calcul parallèle des GPU
if torch.cuda.is_available():
    model = model.cuda()
    # Assurez-vous que les états de l'optimiseur sont aussi sur le GPU
    # for s in etats_optimiseur: s = s.cuda()

Étiquettes: Adagrad Optimisation ApprentissageProfond TauxApprentissageAdaptatif GradientDescent

Publié le 26 juillet à 12h03