Deep Deterministic Policy Gradient : Guide complet — Actor-Critic pour Actions Continues
Résumé — Le DDPG (Deep Deterministic Policy Gradient) est un algorithme de reinforcement learning actor-critic conçu pour les espaces d’action continus. Conçu par Lillicrap et al. en 2015, il combine trois idées fondamentales: un actor déterministe (qui produit une action précise plutôt qu’une distribution), des target networks pour stabiliser l’apprentissage, et un replay buffer pour décorréler les expériences. C’est l’extension naturelle du DQN aux environnements où les actions sont continues (ex: angle d’un volant, couple d’un moteur) plutôt que discrètes (gauche/droite/saut).
Principe mathématique
1. Le problème des actions continues
Le DQN fonctionne pour des actions discrètes car il calcule Q(s,a) pour chaque action possible et prend le max. Avec des actions continues (ex: un angle entre -180° et +180°), le max sur un espace continu est impossible à calculer directement.
2. Politique déterministe
DDPG utilise un actor déterministe mu(s|theta^mu) qui produit directement l’action optimale (pas une distribution de probabilités). Le critic Q(s,a|theta^Q) évalue cette action:
Actor: a = mu(s | theta^mu) # action déterministe
Critic: Q(s, a | theta^Q) # valeur Q de l'état-action
3. Mise à jour du critic
On utilise des target networks pour stabiliser, comme dans DQN:
y_i = r_i + gamma · Q'(s_{i+1}, mu'(s_{i+1} | theta^{mu'}) | theta^{Q'})
Loss critic: L = 1/N · somme (y_i - Q(s_i, a_i | theta^Q))^2
4. Mise à jour de l’actor
L’actor est mis à jour par ascent de gradient de la politique, en utilisant le gradient du critic:
grad_theta_mu J = E[grad_a Q(s,a | theta^Q) · grad_{theta^mu} mu(s | theta^mu)]
Intuitivement: on demande au critic “comment améliorer l’action?” (grad_a Q) et on propage ce signal à travers l’actor pour mettre à jour ses paramètres.
5. Soft update des target networks
Au lieu de copier périodiquement les poids (hard update), DDPG met à jour les targets continûment par moyenne mobile (Polyak averaging):
theta' <-- tau · theta + (1 - tau) · theta'
Où tau << 1 (typiquement 0.001). Cela assure une évolution douce des targets, évitant les oscillations du DQN classique.
6. Bruit d’exploration: Ornstein-Uhlenbeck
Pour l’exploration dans les espaces continus, DDPG ajoute un bruit corrélé dans le temps au lieu du bruit indépendant d’un epsilon-greedy:
dx_t = theta_ou · (mu_ou - x_t) · dt + sigma_ou · dW_t
Le bruit OU a une mémoire: si une action a été tirée dans une direction, le bruit tend à persister dans cette direction, ce qui est utile pour les contrôles physiques (inertie).
Intuition
DDPG est comme un apprenti chef cuisinier. Le critic est le critique gastronomique qui goûte chaque plat et note sa qualité sur 10. L’actor est le cuisinier qui propose des recettes avec des actions continues: quantité de sel (de 0 à 5g), temps de cuisson (de 0 à 60 min), température (de 100 à 250°C).
Le critique ne dit pas juste “bon” ou “mauvais” — il donne une note précise et suggère: “un peu plus de sel améliorerait le plat”. Le cuisinier ajuste sa recette selon ces notes. Les deux s’améliorent ensemble au fil des essais.
Implémentation Python
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class Actor(nn.Module):
"""Réseau déterministe: état -> action continue."""
def __init__(self, state_dim, action_dim, max_action):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 256), nn.ReLU(),
nn.Linear(256, 256), nn.ReLU(),
nn.Linear(256, action_dim), nn.Tanh()
)
self.max_action = max_action
def forward(self, state):
return self.net(state) * self.max_action
class Critic(nn.Module):
"""Réseau Q: (état, action) -> valeur scalaire."""
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim + action_dim, 256), nn.ReLU(),
nn.Linear(256, 256), nn.ReLU(),
nn.Linear(256, 1)
)
def forward(self, state, action):
return self.net(torch.cat([state, action], dim=1))
class OUNoise:
"""Bruit d'Ornstein-Uhlenbeck pour l'exploration."""
def __init__(self, action_dim, mu=0.0, theta=0.15, sigma=0.2):
self.action_dim = action_dim
self.mu = mu
self.theta = theta
self.sigma = sigma
self.state = np.ones(self.action_dim) * self.mu
def sample(self):
dx = self.theta * (self.mu - self.state)
dx += self.sigma * np.random.randn(self.action_dim)
self.state += dx
return self.state.copy()
class ReplayBuffer:
"""Buffer d'expériences pour briser la corrélation temporelle."""
def __init__(self, capacity):
self.buffer = []
self.capacity = capacity
self.pos = 0
def push(self, state, action, reward, next_state, done):
if len(self.buffer) < self.capacity:
self.buffer.append(None)
self.buffer[self.pos] = (state, action, reward, next_state, done)
self.pos = (self.pos + 1) % self.capacity
def sample(self, batch_size):
batch = np.random.choice(len(self.buffer), batch_size, replace=False)
states = torch.FloatTensor(np.array([self.buffer[i][0] for i in batch]))
actions = torch.FloatTensor(np.array([self.buffer[i][1] for i in batch]))
rewards = torch.FloatTensor(np.array([self.buffer[i][2] for i in batch])).unsqueeze(1)
next_states = torch.FloatTensor(np.array([self.buffer[i][3] for i in batch]))
dones = torch.FloatTensor(np.array([self.buffer[i][4] for i in batch])).unsqueeze(1)
return states, actions, rewards, next_states, dones
# DDPG Agent
class DDPG:
def __init__(self, state_dim, action_dim, max_action, tau=0.005, gamma=0.99, lr=3e-4):
self.actor = Actor(state_dim, action_dim, max_action)
self.actor_target = Actor(state_dim, action_dim, max_action)
self.critic = Critic(state_dim, action_dim)
self.critic_target = Critic(state_dim, action_dim)
# Copier les poids initiaux
self.actor_target.load_state_dict(self.actor.state_dict())
self.critic_target.load_state_dict(self.critic.state_dict())
self.actor_opt = torch.optim.Adam(self.actor.parameters(), lr=lr)
self.critic_opt = torch.optim.Adam(self.critic.parameters(), lr=lr)
self.tau = tau
self.gamma = gamma
def select_action(self, state, noise=None):
state = torch.FloatTensor(state).unsqueeze(0)
with torch.no_grad():
action = self.actor(state).cpu().numpy()[0]
if noise is not None:
action += noise.sample()
return np.clip(action, -self.actor.max_action, self.actor.max_action)
def update(self, replay_buffer, batch_size=256):
states, actions, rewards, next_states, dones = replay_buffer.sample(batch_size)
# Critic loss
with torch.no_grad():
target_actions = self.actor_target(next_states)
target_q = self.critic_target(next_states, target_actions)
target_q = rewards + (1 - dones) * self.gamma * target_q
current_q = self.critic(states, actions)
critic_loss = F.mse_loss(current_q, target_q)
self.critic_opt.zero_grad()
critic_loss.backward()
self.critic_opt.step()
# Actor loss
actor_loss = -self.critic(states, self.actor(states)).mean()
self.actor_opt.zero_grad()
actor_loss.backward()
self.actor_opt.step()
# Soft update des targets
for param, target in zip(self.actor.parameters(), self.actor_target.parameters()):
target.data.copy_(self.tau * param.data + (1 - self.tau) * target.data)
for param, target in zip(self.critic.parameters(), self.critic_target.parameters()):
target.data.copy_(self.tau * param.data + (1 - self.tau) * target.data)
# Test sur Pendulum-v1 (environnement d'action continue)
try:
import gymnasium as gym
except ImportError:
import gym as gym
env = gym.make('Pendulum-v1')
agent = DDPG(env.observation_space.shape[0], env.action_space.shape[0], env.action_space.high[0])
noise = OUNoise(env.action_space.shape[0])
buffer = ReplayBuffer(100000)
for episode in range(100):
state, _ = env.reset()
episode_reward = 0
for step in range(200):
action = agent.select_action(state, noise)
next_state, reward, done, _, _ = env.step(action)
buffer.push(state, action, reward, next_state, done)
if len(buffer.buffer) > 1000:
agent.update(buffer)
state = next_state
episode_reward += reward
if done:
break
print('Episode ' + str(episode) + ' | Reward: ' + str(round(episode_reward, 1)))
env.close()
Hyperparamètres
| Hyperparamètre | Valeur typique | Description |
|---|---|---|
| tau | 0.001-0.01 | Soft update rate pour les target networks |
| gamma | 0.95-0.99 | Discount factor pour les récompenses futures |
| actor_lr | 1e-4 – 1e-3 | Taux d’apprentissage de l’actor |
| critic_lr | 1e-4 – 1e-3 | Taux d’apprentissage du critic |
| buffer_size | 1e5 – 1e6 | Capacité du replay buffer |
| noise_sigma | 0.1-0.3 | Écart-type du bruit OU pour l’exploration |
Avantages
- Actions continues : Contrairement au DQN, DDPG gère naturellement les espaces d’action continus.
- Efficace en échantillons : Le replay buffer réutilise les expériences passées, améliorant l’efficacité.
- Stable : Target networks + soft updates + replay buffer = apprentissage stable comparé aux politiques gradient policy gradient naïfs.
Limites
- Hypersensible aux hyperparamètres : DDPG est connu pour être instable si les hyperparamètres ne sont pas bien réglés (surtout la taille du bruit d’exploration).
- Surestimation du critic : Comme DQN, le critic a tendance à surestimer les valeurs Q, ce qui dégrade les performances. TD3 (Twin Delayed DDPG) corrige ce problème avec deux critics et un délai de mise à jour de l’actor.
- Exploration limitée : Le bruit OU est un ajout heuristique à l’action, pas une exploration structurelle de l’espace d’action.
- Non applicable aux actions discrètes : DDPG est conçu pour les actions continues. Pour les discrètes, on utilise DQN ou PPO.
4 cas d’usage concrets
1. Contrôle de bras robotique
DDPG entraîne un bras robotique à saisir des objets. Les actions sont les angles des articulations (continus), les récompenses sont basées sur la distance à l’objet et la réussite de la préhension.
2. Commerce algorithmique
Les actions continues de DDPG (proportion du portefeuille à allouer à chaque actif) sont adaptées au trading algorithmique où les décisions sont continues et non discrètes.
3. Conduite autonome
Le contrôle de direction et d’accélération/freinage sont des actions continues. DDPG apprend à maintenir la voie et la vitesse sécuritaire dans des environnements simulés.
4. Optimisation de consommation énergétique
Dans les data centers, DDPG optimise la ventilation et la climatisation en continu (températures, vitesse des ventilateurs) pour minimiser la consommation énergétique tout en respectant les contraintes thermiques.
Conclusion
DDPG a ouvert la voie au reinforcement learning pour les espaces d’action continus. Bien que dépassé par TD3 et SAC (Soft Actor-Critic) en termes de stabilité et de performance, ses principes fondamentaux (actor-critic déterministe, target networks, replay buffer) restent la base de la plupart des algorithmes modernes pour les actions continues.
Son successeur direct, TD3, corrige les principaux problèmes de DDPG (surestimation Q, sensibilité aux hyperparamètres) avec seulement quelques modifications élégantes: deux critics pour réduire la surestimation, un délai pour mettre à jour l’actor moins souvent, et du bruit smooth sur les actions cibles.
Voir aussi
- Démystifiez le Module ‘abc’ en Python : Guide Complet avec Exemples Pratiques
- Maîtriser le Jeu ‘Lights Out’ avec Python : Tutoriel Complet et Code Source

