主题
DDPG:深度确定性策略梯度
DDPG(Deep Deterministic Policy Gradient,Lillicrap et al., 2016)把 DQN 的经验回放和目标网络思想扩展到连续动作空间。它学习一个确定性策略
动机:DQN 为什么不能直接用于连续动作
DQN 的目标值和动作选择都需要
DDPG 的思路:再学一个网络
确定性策略梯度
DPG 定理
对确定性策略
直观理解:Critic 告诉 Actor “往哪个方向调整动作能让 Q 值变大”(
由于期望只对状态求,而状态可以来自任何行为策略产生的数据,DPG 天然是异策略的,可以使用经验回放。
扩展阅读:确定性策略梯度定理的证明思路
设确定性策略
第 1 步:对
第 2 步:展开第二项。
第 3 步:递推展开。 记
(这里
与随机策略梯度的联系。 Silver 等(2014)还证明:若随机策略
为什么可以异策略。 公式中的期望只对状态,不涉及对动作的积分,所以只要状态来自(近似)合适的分布,就不需要对动作做重要性采样。DDPG 实际使用回放缓冲区中的状态分布代替
Actor 与 Critic 的更新
Critic 像 DQN 一样最小化 TD 误差,只是目标中的
Actor 最大化 Critic 的评估:
在 PyTorch 中,只需把 -critic(s, actor(s)).mean() 作为 Actor 的损失,自动求导会完成链式法则。
目标网络与软更新
与 DQN 一样,DDPG 为 Actor 和 Critic 都维护目标网络,但使用软更新(Polyak 平均):
探索噪声
确定性策略本身不探索,需要在执行时加噪声:
原论文使用 Ornstein-Uhlenbeck(OU)过程产生时间相关的噪声;后续研究(如 TD3 论文)发现简单的不相关高斯噪声效果同样好,现在更常用。此外,训练开始前先用均匀随机动作收集一些数据(warm-up)也很有帮助。
算法流程:
text
初始化 Actor μ_θ、Critic Q_φ 及其目标网络,回放缓冲区 D
对每一步:
a = clip(μ_θ(s) + 噪声)(前若干步使用随机动作)
执行 a,观察 r, s', d,存入 D
从 D 采样小批量:
y = r + γ(1−d) Q_φ⁻(s', μ_θ⁻(s'))
更新 Critic:最小化 (Q_φ(s,a) − y)²
更新 Actor:最大化 Q_φ(s, μ_θ(s))
软更新目标网络1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
代码实现
环境为 Pendulum-v1:把摆杆从任意位置摆到竖直向上并保持住。观测为
Actor 输出经过 tanh 压缩到 max_action;回放缓冲区中存储归一化后的动作。
py
"""DDPG:Pendulum-v1。"""
import copy
import random
import gymnasium as gym
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
GAMMA, TAU, LR_A, LR_C, BATCH = 0.99, 0.005, 1e-3, 1e-3, 256
TOTAL_STEPS, START_STEPS, EXPL_NOISE = 20_000, 1_000, 0.1
random.seed(0); np.random.seed(0); torch.manual_seed(0)
env = gym.make("Pendulum-v1")
env.action_space.seed(0) # 预热阶段的随机动作也要可复现
obs_dim, act_dim = env.observation_space.shape[0], env.action_space.shape[0]
max_action = float(env.action_space.high[0]) # Pendulum 的力矩范围 [-2, 2]
def mlp(i, o, out_act=None):
layers = [nn.Linear(i, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, o)]
return nn.Sequential(*layers, *( [out_act] if out_act else [] ))
actor = mlp(obs_dim, act_dim, nn.Tanh()) # μ(s) ∈ [-1, 1],再乘 max_action
critic = mlp(obs_dim + act_dim, 1) # Q(s, a)
actor_t, critic_t = copy.deepcopy(actor), copy.deepcopy(critic)
opt_a = torch.optim.Adam(actor.parameters(), lr=LR_A)
opt_c = torch.optim.Adam(critic.parameters(), lr=LR_C)
class Replay:
def __init__(self, size=100_000):
self.s = np.zeros((size, obs_dim), np.float32); self.a = np.zeros((size, act_dim), np.float32)
self.r = np.zeros((size, 1), np.float32); self.s2 = np.zeros((size, obs_dim), np.float32)
self.d = np.zeros((size, 1), np.float32); self.ptr, self.n, self.size = 0, 0, size
def add(self, s, a, r, s2, d):
self.s[self.ptr], self.a[self.ptr], self.r[self.ptr], self.s2[self.ptr], self.d[self.ptr] = s, a, r, s2, d
self.ptr = (self.ptr + 1) % self.size; self.n = min(self.n + 1, self.size)
def sample(self, k):
i = np.random.randint(0, self.n, k)
return [torch.as_tensor(x[i]) for x in (self.s, self.a, self.r, self.s2, self.d)]
def soft_update(net, target):
with torch.no_grad():
for p, tp in zip(net.parameters(), target.parameters()):
tp.mul_(1 - TAU).add_(TAU * p)
buf = Replay()
obs, _ = env.reset(seed=0)
ep_ret, returns = 0.0, []
for step in range(TOTAL_STEPS):
if step < START_STEPS:
a = env.action_space.sample() # 预热:均匀随机动作
else:
with torch.no_grad():
a = max_action * actor(torch.as_tensor(obs, dtype=torch.float32)).numpy()
a = np.clip(a + np.random.normal(0, EXPL_NOISE * max_action, act_dim), -max_action, max_action)
obs2, r, term, trunc, _ = env.step(a)
buf.add(obs, a / max_action, r, obs2, float(term)) # 存归一化后的动作
obs, ep_ret = obs2, ep_ret + r
if term or trunc:
returns.append(ep_ret); obs, _ = env.reset(); ep_ret = 0.0
if len(returns) % 10 == 0:
print(f"step {step + 1:6d} | 最近10回合平均回报 {np.mean(returns[-10:]):8.1f}")
if step >= START_STEPS:
s, a_b, r_b, s2, d = buf.sample(BATCH)
with torch.no_grad():
y = r_b + GAMMA * (1 - d) * critic_t(torch.cat([s2, actor_t(s2)], 1))
c_loss = F.mse_loss(critic(torch.cat([s, a_b], 1)), y)
opt_c.zero_grad(); c_loss.backward(); opt_c.step()
a_loss = -critic(torch.cat([s, actor(s)], 1)).mean() # 最大化 Q(s, μ(s))
opt_a.zero_grad(); a_loss.backward(); opt_a.step()
soft_update(actor, actor_t); soft_update(critic, critic_t)
# 确定性策略评估
scores = []
for i in range(10):
o, _ = env.reset(seed=1000 + i); done, total = False, 0.0
while not done:
with torch.no_grad():
o, r, te, tr, _ = env.step(max_action * actor(torch.as_tensor(o, dtype=torch.float32)).numpy())
total += r; done = te or tr
scores.append(total)
print(f"确定性策略评估(10 回合)平均回报: {np.mean(scores):.1f}")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
实际运行输出(2 万步,即 100 个回合):
text
step 2000 | 最近10回合平均回报 -1345.5
step 4000 | 最近10回合平均回报 -908.6
step 6000 | 最近10回合平均回报 -129.8
step 8000 | 最近10回合平均回报 -131.2
step 10000 | 最近10回合平均回报 -187.6
step 12000 | 最近10回合平均回报 -130.9
step 14000 | 最近10回合平均回报 -174.0
step 16000 | 最近10回合平均回报 -172.1
step 18000 | 最近10回合平均回报 -140.6
step 20000 | 最近10回合平均回报 -210.6
确定性策略评估(10 回合)平均回报: -168.01
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
DDPG 在约 6000 步后就学会了把摆杆摆起并保持,训练回报在 −130 到 −211 之间波动(Pendulum 的回报还取决于随机的初始角度),不加噪声的确定性策略评估平均为 −168.0。这是单个种子的结果(代码中对 env.action_space 也设置了种子,在本站环境中重复运行得到完全相同的数值;换种子、换机器或换 PyTorch 版本时数值会有所不同)。
常见问题
- Q 值高估:与 DQN 类似,Actor 会主动寻找 Critic 高估的动作,导致高估更严重,训练可能突然崩溃。→ TD3 的双 Critic。
- 对超参数敏感:学习率、噪声大小、网络结构的改变都可能让 DDPG 从成功变为失败。
- Critic 的输入:低维任务中直接把状态和动作拼接后输入;原论文在第二个隐藏层才拼接动作,现在一般直接在输入层拼接。
- 动作缩放:务必把网络输出映射到环境的动作范围,并在存储时保持一致。
实践技巧
| 超参数 | 常用值 |
|---|---|
| Actor / Critic 学习率 | |
| 软更新 | 0.005 |
| 批量大小 | 256 |
| 回放缓冲区 | |
| 探索噪声标准差 | 0.1 × 最大动作 |
| 预热步数 | 1000–25000 |
小结
- DDPG = DQN 的经验回放与目标网络 + 确定性策略梯度。
- Actor 通过
学习近似 ,从而处理连续动作。 - 异策略、样本效率高,但容易高估 Q 值、对超参数敏感。
- TD3 和 SAC 在 DDPG 的基础上解决了这些问题。
练习题
- 把探索噪声的标准差改为 0(不探索)和 0.5,观察训练结果。
- 实现 OU 噪声,与高斯噪声比较。
- 在训练中记录 Critic 对采样状态的 Q 值估计,与实际折扣回报比较,观察是否存在高估。
- 推导确定性策略梯度定理(提示:可参考 Silver et al., 2014 的附录)。
- 换用
MountainCarContinuous-v0,DDPG 能否学会?为什么这个任务对探索要求很高?
参考文献
- Silver, D., Lever, G., Heess, N., Degris, T., Wierstra, D., & Riedmiller, M. (2014). Deterministic Policy Gradient Algorithms. ICML.
- Lillicrap, T. P. et al. (2016). Continuous control with deep reinforcement learning. ICLR. arXiv:1509.02971
- Fujimoto, S., van Hoof, H., & Meger, D. (2018). Addressing Function Approximation Error in Actor-Critic Methods. ICML. arXiv:1802.09477