# DDPG：深度确定性策略梯度

> 来源：https://dqn.cn/advanced/ddpg ｜ 作者：DQN.CN 编辑部 ｜ 发布：2026-10-29 ｜ 更新：2026-10-30 ｜ 许可：CC BY-NC-SA 4.0（文字）/ MIT（代码）

> **要点速览**
>
> - DDPG 是一种面向连续动作空间的异策略 Actor-Critic 算法，可以看作 DQN 在连续动作上的扩展。
> - Actor 输出确定性动作 a = μ_θ(s)，沿 ∇_a Q(s,a)·∇_θ μ_θ(s) 的方向更新；Critic 用 TD 目标学习 Q(s,a)。
> - DDPG 沿用 DQN 的经验回放和目标网络，并用软更新 θ⁻ ← τθ + (1−τ)θ⁻ 让目标缓慢变化。
> - 由于策略是确定性的，探索依靠在动作上添加高斯或 OU 噪声；DDPG 对超参数敏感，TD3 和 SAC 对其做了改进。

DDPG（Deep Deterministic Policy Gradient，Lillicrap et al., 2016）把 [DQN](https://dqn.cn/advanced/dqn) 的经验回放和目标网络思想扩展到**连续动作空间**。它学习一个确定性策略 $\mu_\theta(s)$（Actor）和一个动作价值函数 $Q_\phi(s, a)$（Critic），利用确定性策略梯度定理（Silver et al., 2014）更新 Actor。DDPG 是异策略的 Actor-Critic 算法，样本效率比 PPO 等同策略方法高，但对超参数较敏感、容易高估 Q 值；它是理解 [TD3](https://dqn.cn/advanced/td3) 和 [SAC](https://dqn.cn/advanced/sac) 的基础。

## 动机：DQN 为什么不能直接用于连续动作

DQN 的目标值和动作选择都需要 $\max_a Q(s, a)$。动作离散时只需比较有限个输出；动作连续时（例如机械臂各关节的力矩），每一步都要解一个非凸优化问题，代价太高。简单地把连续动作离散化又会遇到维度灾难：7 个关节、每个关节 10 档，就有 $10^7$ 个动作。

DDPG 的思路：**再学一个网络 $\mu_\theta(s)$ 来近似 $\arg\max_a Q(s, a)$**，并用 Q 对动作的梯度来训练它。

## 确定性策略梯度

### DPG 定理

对确定性策略 $a = \mu_\theta(s)$，目标 $J(\theta) = \mathbb{E}_{s \sim \rho^\mu}[Q^\mu(s, \mu_\theta(s))]$。Silver 等人（2014）证明了**确定性策略梯度定理**：

$$
\nabla_\theta J(\theta) = \mathbb{E}_{s \sim \rho^\mu}\left[\nabla_a Q^\mu(s, a)\big|_{a = \mu_\theta(s)}\ \nabla_\theta \mu_\theta(s)\right]
$$

直观理解：Critic 告诉 Actor “往哪个方向调整动作能让 Q 值变大”（$\nabla_a Q$），Actor 再通过链式法则把这个方向传回自己的参数。与随机策略梯度不同，它**不需要对动作积分**，因此在高维动作空间中方差更小、样本效率更高。

由于期望只对状态求，而状态可以来自任何行为策略产生的数据，DPG 天然是**异策略**的，可以使用经验回放。

**扩展阅读：确定性策略梯度定理的证明思路**

设确定性策略 $\mu_\theta$，目标 $J(\theta) = v^\mu(s_0)$，其中 $v^\mu(s) = Q^\mu(s, \mu_\theta(s))$。证明与 [随机策略梯度定理](https://dqn.cn/advanced/reinforce) 完全平行，只是“对动作求和”变成了“在 $a = \mu_\theta(s)$ 处求值”。

**第 1 步：对 $v^\mu(s)$ 求导**（链式法则，$\theta$ 既通过动作 $\mu_\theta(s)$ 进入，也通过 $Q^\mu$ 本身进入）：

$$
\nabla_\theta v^\mu(s) = \nabla_\theta \mu_\theta(s)\, \nabla_a Q^\mu(s, a)\big|_{a=\mu_\theta(s)} + \nabla_\theta Q^\mu(s, a)\big|_{a=\mu_\theta(s)}
$$

**第 2 步：展开第二项。** $Q^\mu(s, a) = r(s, a) + \gamma \int p(s' \mid s, a)\, v^\mu(s')\, ds'$，固定 $a$ 时只有 $v^\mu(s')$ 依赖 $\theta$：

$$
\nabla_\theta Q^\mu(s, a)\big|_{a=\mu_\theta(s)} = \gamma \int p(s' \mid s, \mu_\theta(s))\, \nabla_\theta v^\mu(s')\, ds'
$$

**第 3 步：递推展开。** 记 $g(s) = \nabla_\theta \mu_\theta(s)\, \nabla_a Q^\mu(s, a)|_{a=\mu_\theta(s)}$，与随机情形一样反复代入，得到

$$
\nabla_\theta J(\theta) = \int \sum_{k=0}^{\infty} \gamma^k p(s_0 \to s, k, \mu)\, g(s)\, ds = \mathbb{E}_{s \sim d^\mu_\gamma}\left[\nabla_\theta \mu_\theta(s)\, \nabla_a Q^\mu(s, a)\big|_{a = \mu_\theta(s)}\right]
$$

（这里 $d^\mu_\gamma$ 是未归一化的折扣状态访问量。）推导中需要的正则性条件是：$p(s' \mid s, a)$、$\mu_\theta(s)$、$Q^\mu(s, a)$ 及其导数连续且有界，以便交换求导与积分。

**与随机策略梯度的联系。** Silver 等（2014）还证明：若随机策略 $\pi_{\theta, \sigma}$ 是“均值为 $\mu_\theta$、方差参数为 $\sigma$”的一族分布，则当 $\sigma \to 0$ 时随机策略梯度收敛到确定性策略梯度。因此 DPG 是随机策略梯度的极限情形，而不是一个完全不同的理论。

**为什么可以异策略。** 公式中的期望只对状态，不涉及对动作的积分，所以只要状态来自（近似）合适的分布，就不需要对动作做重要性采样。DDPG 实际使用回放缓冲区中的状态分布代替 $d^\mu_\gamma$，这是一个近似，但在实践中效果很好。

## Actor 与 Critic 的更新

**Critic** 像 DQN 一样最小化 TD 误差，只是目标中的 $\max$ 换成了目标 Actor 给出的动作：

$$
y = r + \gamma (1 - d)\, Q_{\phi^-}\big(s', \mu_{\theta^-}(s')\big), \qquad
L(\phi) = \mathbb{E}_{(s, a, r, s', d) \sim \mathcal{D}}\left[\left(Q_\phi(s, a) - y\right)^2\right]
$$

**Actor** 最大化 Critic 的评估：

$$
\max_\theta\ \mathbb{E}_{s \sim \mathcal{D}}\left[Q_\phi(s, \mu_\theta(s))\right]
$$

在 PyTorch 中，只需把 `-critic(s, actor(s)).mean()` 作为 Actor 的损失，自动求导会完成链式法则。

## 目标网络与软更新

与 DQN 一样，DDPG 为 Actor 和 Critic 都维护目标网络，但使用**软更新**（Polyak 平均）：

$$
\phi^- \leftarrow \tau \phi + (1 - \tau)\phi^-, \qquad \theta^- \leftarrow \tau \theta + (1 - \tau)\theta^-
$$

$\tau$ 通常取 0.005（原论文 0.001）。软更新让目标值缓慢变化，训练更稳定。

## 探索噪声

确定性策略本身不探索，需要在执行时加噪声：

$$
a = \text{clip}\left(\mu_\theta(s) + \mathcal{N}, a_{\text{low}}, a_{\text{high}}\right)
$$

原论文使用 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))
        软更新目标网络
```

## 代码实现

环境为 `Pendulum-v1`：把摆杆从任意位置摆到竖直向上并保持住。观测为 $(\cos\theta, \sin\theta, \dot\theta)$，动作为 $[-2, 2]$ 内的力矩，每步奖励 $-(\theta^2 + 0.1\dot\theta^2 + 0.001 a^2)$，每回合 200 步。回报越接近 0 越好，随机策略约为 −1200 左右，一般认为平均回报达到约 −200 以上就算学会了。

Actor 输出经过 tanh 压缩到 $[-1, 1]$，执行时乘以 `max_action`；回放缓冲区中存储归一化后的动作。

```python
"""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}")
```
实际运行输出（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.0
```

DDPG 在约 6000 步后就学会了把摆杆摆起并保持，训练回报在 −130 到 −211 之间波动（Pendulum 的回报还取决于随机的初始角度），不加噪声的确定性策略评估平均为 −168.0。这是单个种子的结果（代码中对 `env.action_space` 也设置了种子，在本站环境中重复运行得到完全相同的数值；换种子、换机器或换 PyTorch 版本时数值会有所不同）。

## 常见问题

- **Q 值高估**：与 DQN 类似，Actor 会主动寻找 Critic 高估的动作，导致高估更严重，训练可能突然崩溃。→ [TD3](https://dqn.cn/advanced/td3) 的双 Critic。
- **对超参数敏感**：学习率、噪声大小、网络结构的改变都可能让 DDPG 从成功变为失败。
- **Critic 的输入**：低维任务中直接把状态和动作拼接后输入；原论文在第二个隐藏层才拼接动作，现在一般直接在输入层拼接。
- **动作缩放**：务必把网络输出映射到环境的动作范围，并在存储时保持一致。

## 实践技巧

| 超参数 | 常用值 |
| --- | --- |
| Actor / Critic 学习率 | $10^{-3}$（TD3 论文的设置）或 $10^{-4}$ / $10^{-3}$（原论文） |
| 软更新 $\tau$ | 0.005 |
| 批量大小 | 256 |
| 回放缓冲区 | $10^6$ |
| 探索噪声标准差 | 0.1 × 最大动作 |
| 预热步数 | 1000–25000 |

## 小结

- DDPG = DQN 的经验回放与目标网络 + 确定性策略梯度。
- Actor 通过 $\nabla_a Q$ 学习近似 $\arg\max_a Q(s, a)$，从而处理连续动作。
- 异策略、样本效率高，但容易高估 Q 值、对超参数敏感。
- TD3 和 SAC 在 DDPG 的基础上解决了这些问题。

## 练习题

1. 把探索噪声的标准差改为 0（不探索）和 0.5，观察训练结果。
2. 实现 OU 噪声，与高斯噪声比较。
3. 在训练中记录 Critic 对采样状态的 Q 值估计，与实际折扣回报比较，观察是否存在高估。
4. 推导确定性策略梯度定理（提示：可参考 Silver et al., 2014 的附录）。
5. 换用 `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](https://arxiv.org/abs/1509.02971)
- Fujimoto, S., van Hoof, H., & Meger, D. (2018). Addressing Function Approximation Error in Actor-Critic Methods. *ICML*. [arXiv:1802.09477](https://arxiv.org/abs/1802.09477)

## 下一步

上一节：[PPO](https://dqn.cn/advanced/ppo)　｜　继续阅读：[TD3](https://dqn.cn/advanced/td3)

## 常见问题速查（FAQ）

**问：DDPG 和 DQN 有什么关系？**

答：两者都使用经验回放和目标网络。DQN 通过对离散动作取 max 选动作；DDPG 用一个 Actor 网络直接输出使 Q 最大的连续动作，避免了在连续空间中求 max。

**问：DDPG 的探索噪声用 OU 噪声还是高斯噪声？**

答：原论文使用 OU 过程噪声，后续研究（如 TD3）发现简单的高斯噪声效果相当，实现更简单。

**问：DDPG 为什么不稳定？**

答：Critic 的 Q 值容易被高估，而 Actor 恰好利用这些高估的动作，误差相互放大；TD3 用双 Q 网络、延迟更新和目标策略平滑来缓解。

## 如何引用本文

DQN.CN 编辑部. DDPG：深度确定性策略梯度[EB/OL]. DQN.CN, 2026-10-29（更新于 2026-10-30）. https://dqn.cn/advanced/ddpg.
