SAC : Guide Complet — Actor-Critic avec Entropie Maximale

SAC : Guide Complet — Actor-Critic avec Entropie Maximale

Soft Actor-Critic (SAC) : Le Guide Complet

Résumé

Le Soft Actor-Critic (SAC) est l’un des algorithmes d’apprentissage par renforcement les plus performants pour les espaces d’actions continus. Développé par Tuomas Haarnoja et ses collaborateurs à l’Université de Berkeley en 2018, SAC se distingue par une idée fondamentale : maximiser non seulement la récompense attendue, mais aussi l’entropie de la politique. Cette double objectivité permet à l’agent d’explorer efficacement son environnement tout en convergeant vers des politiques robustes et généralisables.

Contrairement à DDPG qui exploite de manière déterministe et risque de rester piégé dans des optima locaux, ou au PPO qui nécessite de nombreux ajustements d’hyperparamètres, le Soft Actor-Critic offre une stabilité remarquable grâce à plusieurs innovations techniques combinées : le double Q-learning, le reparameterization trick, et l’ajustement automatique de la température d’entropie. Ces mécanismes en font l’algorithme de choix pour de nombreuses applications industrielles, de la robotique à la finance quantitative.

Ce guide complet explore le principe mathématique derrière le Soft Actor-Critic, son intuition profonde, et propose une implémentation PyTorch fonctionnelle avec ajustement automatique d’alpha.


Principe Mathématique du Soft Actor-Critic

L’Objectif Entropique

Dans l’apprentissage par renforcement classique, l’agent cherche une politique π* qui maximise la somme des récompenses actualisées :

J(π) = E[Σ γ^t · r(s_t, a_t)]

Le Soft Actor-Critic modifie radicalement cette formule en ajoutant un terme d’entropie :

J_SAC(π) = E[Σ γ^t · (r(s_t, a_t) + α · H(π(·|s_t)))]

H(π(·|s)) = -E[log π(a|s)] est l’entropie de la politique, mesurant le degré d’incertitude ou de « surprise » de la distribution d’actions. Le coefficient α (alpha) contrôle le compromis exploration-exploitation : une valeur élevée pousse l’agent à explorer davantage, tandis qu’une valeur faible le rend plus exploitant.

Ce terme d’entropie joue un rôle crucial. En encourageant la politique à rester « incertaine » sur ses actions, SAC évite la convergence prématurée vers des stratégies sous-optimales. L’agent est récompensé non seulement pour bien agir, mais aussi pour rester curieux face à son environnement.

Les Équations de Bellman Modifiées

Dans le cadre entropique, la valeur douce (soft value) se définit par :

V_soft(s) = E_a[Q_soft(s, a) - α · log π(a|s)]

et la fonction Q douce satisfait l’équation de Bellman modifiée :

Q_soft(s, a) = r(s, a) + γ · E_s'[V_soft(s')]

Cette modification signifie que chaque état porte intrinsèquement une valeur liée à la diversité des actions disponibles — pas seulement à la récompense immédiate.

Double Q-Learning : Réduire la Surestimation

Comme le DQN avant lui, le Soft Actor-Critic souffre du problème de surestimation des valeurs Q. Pour y remédier, SAC maintient deux réseaux critiques Q₁ et Q₂ avec leurs propres paramètres. Lors du calcul de la cible, on prend le minimum des deux :

Q_cible = r + γ · (min(Q₁(s', π(s')), Q₂(s', π')) - α · log π(a'|s'))

Cette approche conservative, empruntée au Double DQN et au TD3, réduit significativement le biais de surestimation qui déstabilise l’apprentissage.

Le Reparameterization Trick

L’une des difficultés majeures du Soft Actor-Critic est de différencier l’espérance sur les actions par rapport aux paramètres de la politique. SAC résout ce problème grâce au reparameterization trick :

Au lieu d’échantillonner directement a ~ π(·|s), on exprime l’action comme une fonction différentiable du bruit :

a = f_φ(ε, s)  où ε ~ N(0, I)

Concrètement, la politique gaussienne paramétrée produit une moyenne μ(s) et un écart-type σ(s), puis :

a = μ(s) + σ(s) ⊙ ε

où ⊙ désigne la multiplication élément par élément. Cette reformulation permet de calculer le gradient de l’espérance en différenciant directement à travers l’échantillonnage, ce qui réduit considérablement la variance de l’estimateur de gradient par rapport à l’estimateur REINFORCE classique.

Ajustement Automatique de la Température

Au lieu de fixer manuellement α, le Soft Actor-Critic l’ajuste automatiquement en résolvant le problème de minimisation suivant :

J(α) = E_{a~π}[-α · log π(a|s) - α · H_cible]

H_cible = -dim(A) est l’entropie cible, généralement fixée à la dimension de l’espace d’actions négative. Cette approche permet à l’algorithme de trouver automatiquement le bon équilibre exploration-exploitation au fil de l’entraînement, sans intervention humaine.

La politique optimale dans le cadre entropique s’écrit :

π*(a|s) ∝ exp(Q_soft(s, a) / α)

Cette forme de Boltzmann montre clairement comment l’entropie influence la politique : plus α est grand, plus la distribution d’actions est uniforme (exploration) ; plus α est petit, plus la politique se concentre sur les actions de plus haute valeur (exploitation).


Intuition : Pourquoi l’Entropie Change Tout

Imaginez deux agents confrontés à un labyrinthe complexe. Le premier agent est extrêmement confiant : dès qu’il trouve un chemin fonctionnel, il s’y tient rigidement et n’explore plus. Il peut parfaitement manquer un raccourci caché ou une stratégie bien plus efficace. C’est le défaut des algorithmes purement exploitants comme DDPG.

Le deuxième agent, au contraire, explore de manière chaotique. Il teste tout, sans jamais vraiment se concentrer sur ce qui fonctionne. Ses performances restent médiocres car il ne parvient pas à consolider ses découvertes. C’est le risque d’une exploration non structurée.

Le Soft Actor-Critic trouve l’équilibre parfait entre ces deux extrêmes. Il maximise la récompense tout en restant aussi incertain que possible — exactement comme un scientifique curieux qui cherche des résultats concrets mais reste ouvert aux surprises et aux découvertes inattendues.

L’entropie n’est pas un ajout arbitraire : c’est le mécanisme qui permet à l’agent de maintenir une diversité comportementale suffisante pour découvrir de meilleures stratégies tout au long de l’apprentissage. C’est cette propriété qui donne à SAC sa robustesse supérieure dans les environnements complexes.

En pratique, cette approche entropique présente un avantage décisif : les politiques apprises généralisent mieux à des situations non rencontrées pendant l’entraînement. Un robot formé avec SAC saura s’adapter à des perturbations imprévues (frottements différents, charges variées) mieux qu’un robot formé avec un algorithme déterministe.


Implémentation Python avec PyTorch

Voici une implémentation complète du Soft Actor-Critic avec double Q-learning, réseau de politique gaussienne et ajustement automatique de la température d’entropie.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import gymnasium as gym
from collections import deque
import random
import math

# ============================================================
# Réseaux de Neurones
# ============================================================

class CriticNetwork(nn.Module):
    """Réseau critique Q(s, a) pour le double Q-learning."""

    def __init__(self, state_dim, action_dim, hidden_dim=256):
        super().__init__()
        self.q1 = nn.Sequential(
            nn.Linear(state_dim + action_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1)
        )
        self.q2 = nn.Sequential(
            nn.Linear(state_dim + action_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, 1)
        )

    def forward(self, state, action):
        x = torch.cat([state, action], dim=-1)
        q1 = self.q1(x)
        q2 = self.q2(x)
        return q1, q2

    def q_min(self, state, action):
        q1, q2 = self.forward(state, action)
        return torch.min(q1, q2)


class ActorNetwork(nn.Module):
    """Réseau de politique gaussienne avec reparameterization trick."""

    def __init__(self, state_dim, action_dim, hidden_dim=256,
                 action_scale=1.0, action_bias=0.0):
        super().__init__()
        self.action_scale = action_scale
        self.action_bias = action_bias

        self.shared = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU()
        )
        self.mean_layer = nn.Linear(hidden_dim, action_dim)
        self.log_std_layer = nn.Linear(hidden_dim, action_dim)

    def forward(self, state):
        x = self.shared(state)
        mean = self.mean_layer(x)
        log_std = self.log_std_layer(x)
        log_std = torch.clamp(log_std, min=-20, max=2)
        std = log_std.exp()
        return mean, std

    def sample(self, state, epsilon=1e-6):
        """Échantillonne une action avec le reparameterization trick."""
        mean, std = self.forward(state)

        # Reparameterization trick: a = mu + sigma * epsilon
        normal = torch.distributions.Normal(mean, std)
        x_t = normal.rsample()

        # Transformation tanh pour borner l'action
        y_t = torch.tanh(x_t)
        action = y_t * self.action_scale + self.action_bias

        # Correction de l'entropie due au tanh
        log_prob = normal.log_prob(x_t)
        log_prob = log_prob - torch.log(
            self.action_scale * (1 - y_t.pow(2)) + epsilon
        )
        log_prob = log_prob.sum(dim=-1, keepdim=True)

        # Tanh de la moyenne pour le calcul de la target
        mean_action = torch.tanh(mean) * self.action_scale + self.action_bias

        return action, log_prob, mean_action


# ============================================================
# Agent SAC
# ============================================================

class SACAgent:
    """Agent Soft Actor-Critic avec ajustement automatique de la température."""

    def __init__(self, state_dim, action_dim, action_low, action_high,
                 lr=3e-4, gamma=0.99, tau=0.005,
                 target_entropy_ratio=0.89,
                 hidden_dim=256):

        self.gamma = gamma
        self.tau = tau
        self.action_dim = action_dim

        # Calcul de l'entropie cible
        self.target_entropy = -target_entropy_ratio * action_dim

        # Initialisation du coefficient de température alpha
        self.log_alpha = torch.tensor(
            [0.0], dtype=torch.float32, requires_grad=True
        )
        self.alpha = self.log_alpha.exp().item()
        self.alpha_optimizer = optim.Adam([self.log_alpha], lr=lr)

        # Réseaux critiques principaux
        self.critic = CriticNetwork(state_dim, action_dim, hidden_dim)
        self.critic_optimizer = optim.Adam(
            self.critic.parameters(), lr=lr
        )

        # Réseaux critiques cibles (copiés)
        self.critic_target = CriticNetwork(state_dim, action_dim, hidden_dim)
        self.critic_target.load_state_dict(self.critic.state_dict())

        # Réseau de politique (acteur)
        action_scale = torch.tensor(
            (action_high - action_low) / 2.0, dtype=torch.float32
        )
        action_bias = torch.tensor(
            (action_high + action_low) / 2.0, dtype=torch.float32
        )
        self.actor = ActorNetwork(
            state_dim, action_dim, hidden_dim,
            action_scale, action_bias
        )
        self.actor_optimizer = optim.Adam(
            self.actor.parameters(), lr=lr
        )

        # Buffer d'expérience
        self.replay_buffer = deque(maxlen=1000000)

    def select_action(self, state, evaluate=False):
        """Sélectionne une action selon la politique actuelle."""
        state = torch.FloatTensor(state).unsqueeze(0)
        with torch.no_grad():
            action, _, mean_action = self.actor.sample(state)
            if evaluate:
                return mean_action.squeeze(0).numpy()
            return action.squeeze(0).numpy()

    def store_transition(self, state, action, reward, next_state, done):
        """Stocke une transition dans le buffer d'expérience."""
        self.replay_buffer.append({
            'state': state,
            'action': action,
            'reward': reward,
            'next_state': next_state,
            'done': done
        })

    def sample_batch(self, batch_size):
        """Échantillonne un mini-lot du buffer de replay."""
        batch = random.sample(self.replay_buffer, batch_size)
        states = torch.FloatTensor([t['state'] for t in batch])
        actions = torch.FloatTensor([t['action'] for t in batch])
        rewards = torch.FloatTensor([t['reward'] for t in batch]).unsqueeze(1)
        next_states = torch.FloatTensor([t['next_state'] for t in batch])
        dones = torch.FloatTensor([t['done'] for t in batch]).unsqueeze(1)
        return states, actions, rewards, next_states, dones

    def update(self, batch_size=256):
        """Met à jour tous les réseaux à partir d'un mini-lot."""
        if len(self.replay_buffer) < batch_size:
            return 0

        states, actions, rewards, next_states, dones = \
            self.sample_batch(batch_size)

        # ---- Mise à jour des Critiques (Double Q) ----
        with torch.no_grad():
            # Échantillonne les prochaines actions avec la politique cible
            next_actions, next_log_probs, _ = self.actor.sample(next_states)

            # Q minimum des deux réseaux cibles
            q1_target, q2_target = self.critic_target(
                next_states, next_actions
            )
            q_target_min = torch.min(q1_target, q2_target)

            # Cible avec terme d'entropie
            q_target = rewards + (1 - dones) * self.gamma * (
                q_target_min - self.alpha * next_log_probs
            )

        # Calcul de la perte des deux critiques
        q1_current, q2_current = self.critic(states, actions)
        q1_loss = F.mse_loss(q1_current, q_target)
        q2_loss = F.mse_loss(q2_current, q_target)
        critic_loss = q1_loss + q2_loss

        # Optimisation des critiques
        self.critic_optimizer.zero_grad()
        critic_loss.backward()
        self.critic_optimizer.step()

        # ---- Mise à jour de la Politique (Acteur) ----
        new_actions, log_probs, _ = self.actor.sample(states)

        # Nouvelle évaluation Q avec le minimum conservateur
        q_min = self.critic.q_min(states, new_actions)

        # Perte de la politique : on maximise Q - alpha * log_pi
        actor_loss = (self.alpha * log_probs - q_min).mean()

        self.actor_optimizer.zero_grad()
        actor_loss.backward()
        self.actor_optimizer.step()

        # ---- Ajustement Automatique d'Alpha ----
        alpha_loss = -(self.log_alpha.exp() * (
            log_probs.detach() + self.target_entropy
        )).mean()

        self.alpha_optimizer.zero_grad()
        alpha_loss.backward()
        self.alpha_optimizer.step()
        self.alpha = self.log_alpha.exp().item()

        # ---- Mise à Jour Douce des Cibles ----
        self._soft_update(self.critic, self.critic_target, self.tau)

        return critic_loss.item()

    def _soft_update(self, source, target, tau):
        """Mise à jour douce des paramètres cibles."""
        for target_param, source_param in zip(
            target.parameters(), source.parameters()
        ):
            target_param.data.copy_(
                tau * source_param.data + (1.0 - tau) * target_param.data
            )


# ============================================================
# Boucle d'Entraînement
# ============================================================

def train_sac(env_name="Pendulum-v1", episodes=500, batch_size=256,
              start_steps=1000, hidden_dim=256):
    """Entraîne un agent SAC sur un environnement Gymnasium."""

    env = gym.make(env_name)
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.shape[0]
    action_low = env.action_space.low
    action_high = env.action_space.high

    agent = SACAgent(
        state_dim=state_dim,
        action_dim=action_dim,
        action_low=action_low,
        action_high=action_high,
        lr=3e-4,
        gamma=0.99,
        tau=0.005,
        target_entropy_ratio=0.89,
        hidden_dim=hidden_dim
    )

    rewards_history = []
    alpha_history = []

    for episode in range(episodes):
        state, _ = env.reset()
        episode_reward = 0
        done = False

        while not done:
            # Exploration au début de l'entraînement
            if episode * 200 + len(agent.replay_buffer) < start_steps:
                action = env.action_space.sample()
            else:
                action = agent.select_action(state)

            next_state, reward, terminated, truncated, _ = env.step(action)
            done = terminated or truncated

            agent.store_transition(state, action, reward, next_state, done)
            agent.update(batch_size)

            state = next_state
            episode_reward += reward

        rewards_history.append(episode_reward)
        alpha_history.append(agent.alpha)

        if (episode + 1) % 50 == 0:
            avg_reward = np.mean(rewards_history[-50:])
            print(f"Épisode {episode+1}/{episodes} | "
                  f"Récompense moyenne (50 dern.) : {avg_reward:.2f} | "
                  f"Alpha : {agent.alpha:.4f}")

    env.close()
    return agent, rewards_history, alpha_history


if __name__ == "__main__":
    agent, rewards, alphas = train_sac(
        env_name="Pendulum-v1",
        episodes=300,
        batch_size=256,
        start_steps=1000
    )
    print(f"Entraînement terminé. Récompense finale moyenne : "
          f"{np.mean(rewards[-50:]):.2f}")

Hyperparamètres Clés

Le Soft Actor-Critic dépend de plusieurs hyperparamètres cruciaux :

Hyperparamètre Valeur typique Rôle
α (alpha) Ajusté automatiquement Coefficient de température, contrôle l’exploration
Taux d’apprentissage 3 × 10⁻⁴ Vitesse de mise à jour des réseaux
γ (gamma) 0.99 Facteur d’actualisation des récompenses futures
τ (tau) 0.005 Taux de mise à jour douce des réseaux cibles
Taille du buffer 1 000 000 Capacité du replay buffer
Taille du lot 256 Nombre de transitions par mise à jour
H_cible -dim(A) Entropie cible pour l’ajustement automatique
Dimensions cachées 256 Taille des couches cachées des réseaux

Conseils de réglage :

  • Pour des environnements simples comme Pendulum, réduisez hidden_dim à 128 pour accélérer l’entraînement sans perdre en performance.
  • Pour des environnements complexes comme HalfCheetah ou Humanoid, augmentez hidden_dim à 512 et start_steps à 10 000 pour une exploration initiale plus longue.
  • Si l’agent n’explore pas assez, réduisez target_entropy_ratio à 0.5 pour forcer une entropie plus élevée.
  • Si l’agent diverge, réduisez le taux d’apprentissage à 1 × 10⁻⁴ et augmentez tau à 0.01 pour des mises à jour plus progressives.

Avantages et Limites du Soft Actor-Critic

Avantages

  • Efficacité d’échantillonnage exceptionnelle : SAC tire pleinement parti du replay buffer, nécessitant souvent moins d’interactions avec l’environnement que PPO pour converger.
  • Robustesse aux hyperparamètres : l’ajustement automatique d’alpha réduit considérablement le besoin de réglage manuel par rapport à DDPG ou TD3.
  • Exploration intrinsèque : le terme d’entropie encourage naturellement la découverte de nouvelles stratégies sans mécanisme d’exploration artificiel comme ε-greedy.
  • Généralisation supérieure : les politiques entraînées avec SAC s’adaptent mieux à des conditions non rencontrées pendant l’entraînement grâce à la diversité comportementale encouragée.
  • Performances de pointe : dans les benchmarks MuJoCo standard, SAC atteint ou dépasse les performances de TD3 et PPO sur la plupart des environnements.

Limites

  • Complexité computationnelle : SAC nécessite de maintenir quatre réseaux (deux critiques + deux cibles) plus l’acteur, ce qui augmente significativement l’empreinte mémoire par rapport à des algorithmes plus simples.
  • Sensibilité aux environnements discrets : conçu pour les espaces continus, le Soft Actor-Critic nécessite des adaptations substantielles pour fonctionner efficacement avec des espaces d’actions discrets.
  • Temps de calcul par étape : chaque mise à jour implique le calcul de deux Q-networks et la propagation arrière à travers le reparameterization trick, ce qui rend l’entraînement plus lent par itération.
  • Étapes initiales nécessaires : le mécanisme start_steps exige un nombre minimum d’interactions aléatoires avant de commencer l’apprentissage, ce qui peut être problématique dans les environnements où les échecs sont coûteux.

4 Cas d’Usage Concrets

1. Contrôle de Robotique Avancée

Le Soft Actor-Critic excelle dans le contrôle de robots à articulations multiples. DeepMind a utilisé des variantes de SAC pour entraîner des politiques de manipulation d’objets sur des bras robotiques réels, avec transfert sim-to-real. L’entropie intrinsèque permet au robot de découvrir des stratégies de préhension créatives que les approches déterministes manqueraient. Par exemple, apprendre à retourner un objet dans la main sans contact visuel devient réalisable grâce à l’exploration entropique qui découvre des configurations de doigts non intuitives.

2. Trading Algorithmique et Finance Quantitative

Dans la gestion de portefeuille automatisée, l’espace d’actions est continu (poids alloués à chaque actif) et l’environnement est hautement bruyant. Le terme d’entropie de SAC agit comme un régularisateur naturel, empêchant l’agent de se concentrer excessivement sur un petit nombre de signaux de marché. Cette diversification implicite des stratégies conduit à des politiques de trading plus robustes face aux changements de régime de marché, réduisant ainsi le risque de surapprentissage sur des patterns temporaires.

3. Conduite Autonome et Navigation

Pour les systèmes de navigation autonome, SAC permet d’apprendre des politiques de contrôle continu (accélération, direction) dans des environnements partiellement observables. L’exploration entropique est particulièrement précieuse ici : un véhicule autonome entraîné avec SAC maintiendra une gamme plus large de comportements défensifs en situations ambiguës, plutôt que de converger vers une unique stratégie rigide. Des simulations sur des environnements CARLA ont montré que les politiques SAC généralisent mieux à des conditions météorologiques variées.

4. Optimisation de Systèmes Physiques

Le réglage de paramètres dans les systèmes industriels — centrales énergétiques, chaînes de production, systèmes de chauffage et climatisation — bénéficie naturellement de SAC. L’espace de contrôle est continu, les récompenses sont souvent bruitées ou retardées, et la robustesse est cruciale. L’exploration entropique permet de découvrir des configurations opératoires non conventionnelles qui réduisent la consommation énergétique tout en maintenant la qualité de production, là où les méthodes déterministes resteraient bloquées dans des configurations sous-optimales.


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.