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)))]
où 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]
où 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 etstart_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_stepsexige 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
- Quoi ? Où ? Quand ? Maîtriser la Programmation Python : Guide Complet pour Débutants
- Maîtrisez le Tri par Paquets en Python : Guide Complet pour Débutants

