TD3 与 DDPG
概述
DDPG (Deep Deterministic Policy Gradient) 和 TD3 (Twin Delayed DDPG) 是连续动作空间中最经典的 Actor-Critic 算法。DDPG 开创了深度确定性策略梯度方法,TD3 则通过三项关键改进解决了 DDPG 的过高估计和训练不稳定问题。
本文详细介绍两种算法的原理、实现和对比。
1. DDPG: 深度确定性策略梯度
1.1 背景与动机
DQN 在离散动作空间取得了巨大成功,但无法直接处理连续动作空间(因为 \(\max_a Q(s,a)\) 在连续空间中需要优化)。DDPG 结合了:
- 确定性策略梯度 (DPG) 理论 (Silver et al., 2014)
- DQN 的稳定训练技巧: 经验回放 + 目标网络
- Actor-Critic 架构
1.2 确定性策略梯度定理
对于确定性策略 \(\mu_\theta(s)\),策略梯度为:
直觉理解: 沿着使 Q 值增大的方向调整策略输出。
与随机策略梯度对比:
| 维度 | 随机策略梯度 | 确定性策略梯度 |
|---|---|---|
| 策略 | \(\pi_\theta(a\|s)\) 概率分布 | \(\mu_\theta(s)\) 确定性映射 |
| 梯度 | 需要对动作积分 | 只需动作处的梯度 |
| 探索 | 策略自带随机性 | 需要额外探索噪声 |
| 样本效率 | 较低 | 较高 |
1.3 DDPG 架构
graph TB
subgraph Actor["Actor (策略网络)"]
S1[状态 s] --> MU["μ_θ(s)"]
MU --> A[动作 a]
end
subgraph Critic["Critic (Q网络)"]
S2[状态 s] --> Q["Q_φ(s, a)"]
A2[动作 a] --> Q
Q --> V[Q值]
end
subgraph Target["目标网络"]
MU_T["μ_θ'(s')"]
Q_T["Q_φ'(s', a')"]
end
A --> A2
style Actor fill:#e3f2fd
style Critic fill:#fff3e0
style Target fill:#f3e5f5
1.4 DDPG 算法详解
四个网络
| 网络 | 符号 | 作用 |
|---|---|---|
| Actor | \(\mu_\theta(s)\) | 输出确定性动作 |
| Critic | \(Q_\phi(s,a)\) | 评估状态-动作对的价值 |
| Target Actor | \(\mu_{\theta'}(s)\) | 计算目标动作 |
| Target Critic | \(Q_{\phi'}(s,a)\) | 计算目标Q值 |
Critic 更新
最小化 TD 误差:
其中目标值:
Actor 更新
最大化 Critic 评估的 Q 值:
目标网络软更新
其中 \(\tau \ll 1\) (通常 0.005)。
探索策略
在训练时添加噪声:
通常使用 Ornstein-Uhlenbeck (OU) 过程或简单高斯噪声。
1.5 DDPG 伪代码
# 初始化
actor = Actor(state_dim, action_dim)
critic = Critic(state_dim, action_dim)
target_actor = copy(actor)
target_critic = copy(critic)
replay_buffer = ReplayBuffer(capacity=1e6)
for episode in range(num_episodes):
state = env.reset()
for step in range(max_steps):
# 选择动作 + 探索噪声
action = actor(state) + noise.sample()
action = clip(action, action_low, action_high)
# 环境交互
next_state, reward, done = env.step(action)
replay_buffer.add(state, action, reward, next_state, done)
# 从经验池采样
batch = replay_buffer.sample(batch_size=256)
# Critic 更新
target_action = target_actor(batch.next_state)
target_q = batch.reward + gamma * target_critic(
batch.next_state, target_action) * (1 - batch.done)
critic_loss = MSE(critic(batch.state, batch.action), target_q)
update(critic, critic_loss)
# Actor 更新
actor_loss = -critic(batch.state, actor(batch.state)).mean()
update(actor, actor_loss)
# 目标网络软更新
soft_update(target_actor, actor, tau=0.005)
soft_update(target_critic, critic, tau=0.005)
1.6 DDPG 的问题
- Q值过高估计: Critic 倾向于过高估计 Q 值,导致策略次优
- 训练不稳定: 对超参数敏感,容易发散
- 脆弱的探索: OU 噪声效果不稳定
- Actor-Critic 耦合: Critic 误差会传播到 Actor
2. TD3: 三重延迟 DDPG
2.1 核心思想
TD3 (Fujimoto et al., 2018) 针对 DDPG 的问题提出三项关键改进:
- 裁剪双 Q 学习 (Clipped Double Q-Learning)
- 延迟策略更新 (Delayed Policy Updates)
- 目标策略平滑 (Target Policy Smoothing)
2.2 改进一: 裁剪双 Q 学习
问题: 单个 Q 网络容易过高估计。
解决: 使用两个独立的 Critic 网络,取较小的 Q 值:
原理: 类似 Double DQN 的思想,但更加保守。取最小值可以有效抑制过高估计,即使可能引入轻微低估(低估通常比高估更安全)。
2.3 改进二: 延迟策略更新
问题: Actor 基于不准确的 Critic 更新会导致策略振荡。
解决: Critic 每更新 \(d\) 次,Actor 才更新一次(通常 \(d=2\))。
原理: 让 Critic 先稳定下来,再用更准确的 Q 值指导 Actor 更新。
for step in range(total_steps):
# 每步都更新 Critic
update_critic()
# 每 d 步才更新 Actor 和目标网络
if step % d == 0:
update_actor()
soft_update_targets()
2.4 改进三: 目标策略平滑
问题: 目标 Q 值对特定动作过度拟合。
解决: 在目标动作上添加裁剪的噪声:
原理: 类似正则化,使 Q 函数在动作空间中更平滑,减少对单个动作的过拟合。
2.5 TD3 完整算法
其中:
Critic 损失 (两个 Critic 分别更新):
Actor 损失 (每 \(d\) 步更新一次):
2.6 TD3 伪代码
# 初始化
actor = Actor(state_dim, action_dim)
critic_1 = Critic(state_dim, action_dim)
critic_2 = Critic(state_dim, action_dim)
target_actor = copy(actor)
target_critic_1 = copy(critic_1)
target_critic_2 = copy(critic_2)
for step in range(total_steps):
# 选择动作 + 探索噪声
action = actor(state) + N(0, sigma)
# 环境交互并存储
next_state, reward, done = env.step(action)
replay_buffer.add(state, action, reward, next_state, done)
batch = replay_buffer.sample(batch_size)
# 计算目标 (Target Policy Smoothing)
noise = clip(N(0, sigma_tilde), -c, c)
target_action = clip(target_actor(batch.next_state) + noise,
action_low, action_high)
# Clipped Double Q-Learning
target_q1 = target_critic_1(batch.next_state, target_action)
target_q2 = target_critic_2(batch.next_state, target_action)
target_q = batch.reward + gamma * min(target_q1, target_q2) * (1 - batch.done)
# 更新两个 Critic
loss_1 = MSE(critic_1(batch.state, batch.action), target_q)
loss_2 = MSE(critic_2(batch.state, batch.action), target_q)
update(critic_1, loss_1)
update(critic_2, loss_2)
# Delayed Policy Update
if step % policy_delay == 0:
actor_loss = -critic_1(batch.state, actor(batch.state)).mean()
update(actor, actor_loss)
soft_update(target_actor, actor, tau)
soft_update(target_critic_1, critic_1, tau)
soft_update(target_critic_2, critic_2, tau)
2.7 超参数
| 超参数 | 典型值 | 说明 |
|---|---|---|
| \(\gamma\) | 0.99 | 折扣因子 |
| \(\tau\) | 0.005 | 目标网络软更新率 |
| \(\sigma\) | 0.1 | 探索噪声标准差 |
| \(\tilde{\sigma}\) | 0.2 | 目标策略平滑噪声 |
| \(c\) | 0.5 | 噪声裁剪范围 |
| \(d\) | 2 | 策略更新延迟 |
| batch size | 256 | 批量大小 |
| buffer size | \(10^6\) | 经验池大小 |
| lr (actor) | \(3 \times 10^{-4}\) | Actor 学习率 |
| lr (critic) | \(3 \times 10^{-4}\) | Critic 学习率 |
3. DDPG vs TD3 vs SAC 对比
3.1 核心差异
| 特性 | DDPG | TD3 | SAC |
|---|---|---|---|
| Critic 数量 | 1 | 2 | 2 |
| 策略类型 | 确定性 | 确定性 | 随机 (最大熵) |
| Q值估计 | 倾向过高 | 裁剪取最小 | 裁剪取最小 |
| 策略更新频率 | 每步 | 延迟 (\(d\)步) | 每步 |
| 目标平滑 | 无 | 有 (加噪声) | 无 (熵正则) |
| 探索机制 | 外部噪声 (OU/Gaussian) | 外部噪声 (Gaussian) | 熵最大化 (内在) |
| 温度参数 | 无 | 无 | \(\alpha\) (可自动调节) |
| 训练稳定性 | 较差 | 较好 | 最好 |
3.2 性能对比
在 MuJoCo 连续控制基准上的典型表现:
| 环境 | DDPG | TD3 | SAC |
|---|---|---|---|
| HalfCheetah | ~8,000 | ~10,000 | ~11,000 |
| Ant | ~1,000 | ~4,500 | ~5,500 |
| Walker2d | ~2,000 | ~4,500 | ~5,000 |
| Humanoid | ~500 | ~5,000 | ~6,000 |
注意
以上数值为典型参考值,实际表现受超参数和随机种子影响较大。
3.3 选择建议
连续控制任务的算法选择:
├── 需要最稳定的训练 → SAC
├── 需要简单实现 → TD3
├── 需要确定性策略 → TD3 / DDPG
├── 需要自动探索调节 → SAC
└── 作为 baseline → TD3 (简单、效果好)
4. 实践要点
4.1 常见问题与解决
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| Q值爆炸 | 过高估计 | 使用TD3/双Q网络 |
| 训练不收敛 | 学习率太大 | 降低学习率,增大batch |
| 策略振荡 | Actor-Critic不同步 | 延迟策略更新 |
| 探索不足 | 噪声太小 | 增大噪声,或用SAC |
| 动作超出范围 | 缺少裁剪 | 输出层用tanh+缩放 |
4.2 网络架构建议
# Actor 网络
class Actor(nn.Module):
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() # 输出 [-1, 1]
)
self.max_action = max_action
def forward(self, state):
return self.max_action * self.net(state)
# Critic 网络
class Critic(nn.Module):
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))
4.3 训练技巧
- 归一化观测: 对状态进行运行均值/方差归一化
- 奖励缩放: 将奖励缩放到合理范围
- 预热期: 前 N 步使用随机策略填充经验池
- 梯度裁剪: 防止梯度爆炸
- 多种子评估: 运行多个随机种子取平均
5. 从 TD3 到更先进的方法
5.1 发展脉络
DPG (2014)
→ DDPG (2015): + Deep Networks + Target Net + Replay
→ TD3 (2018): + Double Q + Delay + Smoothing
→ SAC (2018): + Maximum Entropy + Stochastic Policy
→ DrQ (2021): + Data Augmentation
→ RLPD (2023): + Pre-training Data
5.2 SAC 的核心区别
SAC 使用随机策略和最大熵框架:
相比 TD3,SAC 通过熵正则化自动平衡探索与利用,通常更加稳定。详见 SAC 算法详解。
参考资料
- Lillicrap, T. et al. (2016). Continuous control with deep reinforcement learning. ICLR 2016.
- Silver, D. et al. (2014). Deterministic Policy Gradient Algorithms. ICML 2014.
- Fujimoto, S. et al. (2018). Addressing Function Approximation Error in Actor-Critic Methods. ICML 2018.
- Haarnoja, T. et al. (2018). Soft Actor-Critic. ICML 2018.
延伸阅读
- SAC 算法详解 — 最大熵Actor-Critic
- PPO 算法 — 另一种主流策略优化方法
- 深度强化学习入门 — DQN与基础概念
- TRPO 与自然梯度 — 信赖域方法