想象你报名了一家特殊的驾校。老式驾校的教练只教你三件事:方向盘左打满、回正、右打满——就像 DQN 算法只能从有限的几个离散动作里选择。但真实开车时,方向盘可以转 15°、37°、128°……任何角度都行;油门也可以踩下任意深浅。这种“任意取值”的动作,就叫连续动作。 DDPG 的故事,就是这位拒绝“三档教学”的教练,换了一套新教法:让学员(Actor)直接输出任意角度,让坐在副驾的教练(Critic)给每个操作打一个精细的分数。学员负责操作,教练负责评分;行车记录仪把每段驾驶存下来供复盘,教练的评分手册不会天天换(目标网络),教练还会偶尔让学员故意绕路(OU 噪声)以见识更多路况。这就是 DDPG 的全部骨架。

岗位关联

学会 DDPG,你就拿到了进入连续控制世界的钥匙:机械臂抓取、自动驾驶转向、机器人关节控制。对想深耕强化学习的 AI 从业者来说,这是从“打游戏”走向“操控真实世界”的必经一步。

目录
  1. 可视化演示
  2. 连续动作困境
  3. Actor-Critic 框架
  4. 确定性策略梯度
  5. 经验回放机制
  6. 目标网络设计
  7. OU 噪声探索
  8. 与 DQN 的传承
  9. 连续控制落地
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示DDPG的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

DDPG:确定性策 确定性策略梯度定理核心思想 连续动作空间的确定性策略表示 核心机制可视化 DDPG整体架构与关键组件

连续动作困境

老式驾校的教练只会喊三句话:“左满舵!回正!右满舵!”可你很快发现,停车场倒库时方向盘转 30° 和转 35° 停出来的位置完全不同——离散档位根本不够用。DQN 这类经典算法面对的正是这种“档位式”动作空间:它把所有可能动作列成一张表,用网络给每个动作打分,然后选分数最高的那个。动作一多、一连续,这张表就列不完了。

机械臂的肩关节可以停在任意角度,自动驾驶的方向盘可以转任意度数,无人机的油门推杆可以停在任意位置——现实世界的控制需求天然是连续的。如果把每 1° 都当成一个“档位”,10 个关节的机械臂动作空间维度会爆炸到天文数字,表格穷举彻底失效。这正是 DDPG 要解决的第一个问题:怎么从无穷无尽的连续动作里,找到那个最优动作。

形式化地说,DQN 要解决的是这个求最大值问题:

所以 DDPG 的思路转过来了:不再“从一堆动作里挑”,而是训练一个学员网络,直接算出一个好动作。就像老教练放下“三档教学法”,换了一个能输出任意方向盘角度的新学员。

a∗=arg⁡max⁡a∈AQ(s,a),A⊂Rna^* = \arg\max_{a \in \mathcal{A}} Q(s, a), \quad \mathcal{A} \subset \mathbb{R}^n
这个公式的意思是:在所有可选动作里找出让 Q 值最大的那一个。问题在于,候选动作有无穷多个,根本没法挨个试。
  1. 动作向量 a方向盘角度、油门开度这类连续取值,每个分量代表一个自由度。
    a=(a1,a2,…,an)⊤a = (a_1, a_2, \ldots, a_n)^\top
  2. 状态 s 与 Q(s,a)s 是当前路况(传感器读数),Q 是教练给出的“这个操作值多少分”。
    Q:S×A→RQ: \mathcal{S} \times \mathcal{A} \to \mathbb{R}
  3. argmax 操作从候选动作中挑出分数最高的那个,是决策的最终目的。
    arg⁡max⁡\arg\max
  4. 连续集合 A ⊂ R^n动作被定义为 n 维实数空间里的子集,包含无限多个候选。
    A⊂Rn\mathcal{A} \subset \mathbb{R}^n

Actor-Critic 框架

这家新驾校的教法很特别:学员(Actor)只负责开车,教练(Critic)只负责打分。学员每打一把方向盘,教练就快速瞄一眼路况给出一个分数:偏左了扣 2 分,压线了扣 5 分,完美过弯给 8 分。学员不直接知道该怎么开,他只知道“我上次那套动作拿了几分”——于是他会试着往分数更高的方向调整。教练也不直接开车,他毕生的功夫都用来练就一双“毒辣的评分眼”。

在算法里,学员叫策略网络 μ_θ(s),他吃进状态 s,输出一个确定的连续动作;教练叫价值网络 Q_φ(s, a),他吃进状态和动作,输出一个实数分数。两个网络各司其职、端到端联合训练:学员越开越好,教练评分越评越准,形成良性循环——这正是 Actor-Critic(演员-评论家)架构的精髓。

学员和教练就这样配合着开过一圈又一圈。但光有分工还不够:学员到底该往哪个方向调整自己的动作,才能让分数变高? 这就是下一节“确定性策略梯度”要回答的问题——教练会手把手地揪着学员的手腕,告诉他:“往这个方向偏 5°,分能再高一点。”

μθ(s)↦a,Qϕ(s,a)↦R\mu_\theta(s) \mapsto a, \quad Q_\phi(s, a) \mapsto \mathbb{R}
学员把状态映射成动作,教练把“状态+动作”映射成分数。一个是“该怎么做”,一个是“做得怎么样”。
  1. 策略网络 μ_θ学员的大脑,θ 是它的全部参数(神经网络的权重)。
    μθ:S→A\mu_\theta: \mathcal{S} \to \mathcal{A}
  2. 映射到动作 a输入状态 s,输出一个连续动作,这是确定性的——同样路况永远给同样操作。
    a=μθ(s)a = \mu_\theta(s)
  3. 价值网络 Q_φ教练的评分眼,φ 是它的参数。
    Qϕ(s,a)Q_\phi(s, a)
  4. 映射到实数 R输出一个标量分数,代表该动作未来累计奖励的期望。
    Qϕ:S×A→RQ_\phi: \mathcal{S} \times \mathcal{A} \to \mathbb{R}

确定性策略梯度

教练教学员有个绝招:他先观察学员当前的动作,然后在脑海里“微微拨动”这个动作,看分数是升是降,从而知道该把学员的手往哪个方向推。比如学员方向盘打了 30°,教练心思一动:如果改成 31° 分数涨了,那就往 31° 方向推一把;如果跌了,就往 29° 方向推。这个“往分数上升方向推”的过程,就是梯度上升。

在数学上,这个“推”的方向由确定性策略梯度公式给出。它像一条链子:学员参数一变→输出动作一变→教练分数一变。链式法则把三段变化串起来,精确算出“参数该往哪调”:

教练就这样一遍遍“揪着手腕”教学员。但这里藏着一个隐患:如果教练的经验全部来自刚刚开过的这一段路,那么学员学会的只是“应付这段路”的狭窄技能。所以,这家驾校的车上装了行车记录仪。

∇θJ(θ)=Es∼D[∇aQ(s,a)∣a=μθ(s)⋅∇θμθ(s)]\displaystyle \nabla_{\theta} J(\theta) = \mathbb{E}_{s \sim \mathcal{D}}\left[ \nabla_{a} Q(s, a)\big|_{a = \mu_\theta(s)} \cdot \nabla_{\theta} \mu_\theta(s) \right]
学员参数 θ 的更新方向 = 教练对动作的敏感度 × 学员动作对参数的敏感度,在整个经验池上取平均。这比随机策略梯度少了对动作概率的采样,方差更低、更稳定。
  1. 目标函数 J(θ)学员从开始到结束能拿到的总收益,训练目标就是让它变大。
    J(θ)=E[∑tγtrt]J(\theta) = \mathbb{E}\left[ \sum_{t} \gamma^t r_t \right]
  2. 对 θ 求梯度 ∇_θ算出“参数往哪动,总收益升得最快”。
    ∇θJ(θ)\nabla_\theta J(\theta)
  3. 教练对动作的敏感度 ∇_a Q动作变一点点,评分变多少——这是教练给出的“推力方向”。
    ∇aQ(s,a)∣a=μθ(s)\nabla_a Q(s, a)\big|_{a = \mu_\theta(s)}
  4. 学员对参数的敏感度 ∇_θ μ参数变一点点,输出动作变多少——这是学员自身的“响应特性”。
    ∇θμθ(s)\nabla_\theta \mu_\theta(s)
  5. 链式相乘把两段敏感度乘起来,得到“参数→动作→评分”的完整传导链。
    ∇aQ⋅∇θμ\nabla_a Q \cdot \nabla_\theta \mu
  6. 期望 E 取平均把经验池里所有状态的平均梯度作为最终更新方向,避免单次样本的随机抖动。
    Es∼D[⋅]\mathbb{E}_{s \sim \mathcal{D}}[\cdot]

经验回放机制

真实驾校里,教练会让学员把每次练车的视频存进硬盘,晚上回家随机翻出几个片段复盘:“你看你第三天下午那个右转弯,方向盘回晚了。”为什么要随机翻旧片段,而不是只复盘今天刚开的?因为如果只盯着最近的路况,学员会被当下的连续几个相似弯道带偏,以为全世界都是这种弯道。随机抽取历史片段,才能让学员见识到多样化的路况。

这在算法里叫经验回放(Experience Replay)。每次交互得到的四元组(状态 s、动作 a、奖励 r、下一状态 s')被存进一个叫经验池的缓冲区;训练时从池子里随机抽一小批样本,计算梯度更新网络。这一招同时解决了两个痛点:一是打破时序相关性——相邻样本高度相似会让梯度像喝醉了一样乱晃,随机抽样能让每次更新面对“杂乱无章”的样本,符合独立同分布假设;二是提升样本利用率——一条经验可以被反复复习很多次,这在真实机器人上尤其宝贵,因为真机采样又慢又贵。

教练的“复盘失分”用如下损失函数衡量:

有了行车记录仪,学员见识的路况越来越丰富。可你还记得公式里出现了“目标网络”φ' 和 θ' 吗?这两个带着撇的参数,就是教练藏在抽屉里的那本“评分手册”。

L(ϕ)=E(s,a,r,s′)∼D[(r+γQϕ′(s′,μθ′(s′))−Qϕ(s,a))2]\displaystyle L(\phi) = \mathbb{E}_{(s,a,r,s') \sim \mathcal{D}}\left[ \left( r + \gamma Q_{\phi'}(s', \mu_{\theta'}(s')) - Q_\phi(s,a) \right)^2 \right]
教练要想办法让自己的评分更接近“现实答案”:本次奖励加上“未来仍能拿到的折扣奖励”(由目标网络给出)。差距越小,教练的眼光越准。
  1. 经验四元组 (s,a,r,s')行车记录仪里的一条完整片段:当时路况、学员操作、拿到的奖励、之后的路况。
    (st,at,rt,st+1)(s_t, a_t, r_t, s_{t+1})
  2. 从经验池抽样 D均匀随机地抽取,而不是按时间顺序取,这是打破相关性的关键。
    (s,a,r,s′)∼D(s, a, r, s') \sim \mathcal{D}
  3. TD 目标 y本次奖励 + γ 倍的“未来价值估计”,是教练评分的努力方向。
    y=r+γQϕ′(s′,μθ′(s′))y = r + \gamma Q_{\phi'}(s', \mu_{\theta'}(s'))
  4. 平方误差把“现实答案”与“当前评分”的差平方,惩罚偏差,让评分向现实靠拢。
    (y−Qϕ(s,a))2(y - Q_\phi(s, a))^2
  5. 期望取平均小批量样本误差的平均值,作为教练一次复盘的总失分。
    1N∑i(yi−Qϕ(si,ai))2\frac{1}{N}\sum_i (y_i - Q_\phi(s_i, a_i))^2

目标网络设计

如果教练的评分标准每天都在变——今天觉得压线扣 5 分,明天觉得压线扣 50 分——学员一定会崩溃。可学员明明在进步,教练的标准如果永远不变,又跟不上时代。怎么办?驾校的解决办法是:教练有两套标准。一套是当前标准(随训练实时更新),一套是参考手册(向当前标准缓慢靠拢)。评分时用参考手册打“标准分”,避免因为当前标准变化太快导致学员无所适从。

对应到算法里,目标网络就是这份“参考手册”。它不直接复制当前网络的参数,而是通过软更新一点点逼近:每次训练完,目标参数 θ' 只朝当前参数 θ 挪一小步,挪动比例由系数 τ 控制,通常取 0.005 这种极小的值。这样目标网络既不会僵化不变,也不会剧烈抖动——训练目标始终是稳定移动的靶子,而不是上蹿下跳的兔子。

有了这本“慢速手册”,学员终于能安心学车了。但还有一个问题:如果学员永远只走教练安排好的那一条固定路线,他永远不会知道自己错过多少新路况。于是,教练决定让学员偶尔“故意绕路”。

θ′←τθ+(1−τ)θ′,τ≪1\theta' \leftarrow \tau \theta + (1 - \tau) \theta', \quad \tau \ll 1
每训练一步,目标网络的参数按 τ 的比例向当前网络靠拢。τ 很小,所以“参考手册”变化得非常缓慢、平稳。
  1. 当前参数 θ学员/教练网络的最新状态,每步训练都在变。
    θ\theta
  2. 目标参数 θ'评分手册上的“标准答案”,变化缓慢。
    θ′\theta'
  3. 软更新系数 τ每次把标准答案向最新状态挪动的比例。
    τ∈(0,1)\tau \in (0, 1)
  4. 更新运算新旧参数的加权混合,τ 越小旧成分占比越大,变化越平滑。
    θnew′=τθold+(1−τ)θold′\theta'_{\text{new}} = \tau \theta_{\text{old}} + (1 - \tau) \theta'_{\text{old}}

OU 噪声探索

驾校里有个经典做法:教练会突然说“今天不走老路了,咱们绕到城东那条新修的盘山道试试”。为什么要主动制造陌生路况?因为如果学员只反复练习一条熟悉路线,遇到没见过的弯道就会手足无措——这就是强化学习里“探索”的重要性:要在未知区域试探,才能找到更好的策略。但真车绕路有个特点:绕路的方向有惯性——你不可能上一秒往左猛打、下一秒又往右猛打,车辆的物理特性决定了探索扰动应当是平滑、连续的。

标准的高斯噪声做不到这一点:它在每个时间步独立随机抽样,上一秒 +0.3、下一秒 -0.7,动作抖得像触电,对真实机械很不友好。DDPG 用的OU 噪声(Ornstein-Uhlenbeck 过程)则不同,它带着“记忆”:当前扰动由上一个时刻的扰动加上随机冲击演化而来,天然平滑、时序相关,会在一段时间内维持同一方向的探索趋势——就像绕路时的连贯转向。

训练后期,学员对常规路况已经驾轻就熟,教练会逐步放松“绕路”的频度和幅度——噪声按系数衰减,让学员从“大胆探索”慢慢过渡到“专注利用”。讲到这里,你可能已经嗅到 DQN 的味道了:经验回放、目标网络,这些不都是 DQN 的家底吗?没错,DDPG 正是从 DQN 那棵树上长出来的。

dxt=−θ(xt−μ) dt+σ dWtdx_t = -\theta (x_t - \mu) \, dt + \sigma \, dW_t
OU 噪声公式:扰动状态 x 受三个力控制——趋向中心 μ 的回复力 θ、随机冲击 σ dW、时间累积 dt。θ 让它不至于飘太远,σ 让它保持活性。
  1. 噪声状态 x_t当前时刻叠加在动作上的探索偏移量。
    xtx_t
  2. 回复系数 θ把偏移拉回均值 μ 的“橡皮筋”强度,防止噪声越飘越远。
    −θ(xt−μ)-\theta (x_t - \mu)
  3. 扰动幅度 σ随机冲击的强弱,控制探索的“胆量”。
    σ\sigma
  4. 布朗运动增量 dW_t每个瞬间的随机冲击,不可预测。
    dWtdW_t
  5. 时间累积 dt把瞬时变化累积起来,得到连续平滑的噪声轨迹。
    at=μθ(st)+xta_t = \mu_\theta(s_t) + x_t

与 DQN 的传承

如果把 DQN 比作手动挡变速箱:它只有几个明确档位(离散动作),网络要做的就是给每个档位打分,然后挂上分数最高的档。而 DDPG 是无级变速:没有档位概念,发动机(Actor)直接输出任意平滑的动力——也就是连续动作。两者同属于“Q 学习 + 深度网络”这个大家族:DQN 处理离散世界,DDPG 把 Q 学习的思想搬进了连续世界。

DQN 的核心更新公式是贝尔曼方程,其中最关键的一步是“取 max”——枚举所有离散动作找出分数最高的那个:

DDPG 的聪明之处在于:既然没法枚举无穷多个连续动作,那就训练一个网络替你做 max——用目标 Actor 网络 μ_θ'(s') 直接输出“那个最好的动作”,再代入目标 Q 网络算出对应的价值。所以 DDPG 既继承了 DQN 的经验回放和目标网络两大法宝,又引入 Actor-Critic 架构与确定性策略梯度,实现了从离散到连续的跨越。它们的关系不是取代,而是家族谱系的分支:DQN 像深耕离散决策的老大哥,DDPG 像专攻连续控制的开拓者。

从手动挡到无级变速,DDPG 把 Q 学习的血脉延续到了连续控制的世界。那么,这套技术到底开进了哪些真实的“道路”?

Q(s,a)←Q(s,a)+α[r+γmax⁡a′Q(s′,a′)−Q(s,a)]Q(s,a) \leftarrow Q(s,a) + \alpha \left[ r + \gamma \max_{a'} Q(s',a') - Q(s,a) \right]
max⁡a′Q(s′,a′)⟶Qϕ′(s′,μθ′(s′))\max_{a'} Q(s', a') \quad \longrightarrow \quad Q_{\phi'}(s', \mu_{\theta'}(s'))
DQN 用枚举找最大,DDPG 用网络直接生成最优动作的近似——这是整个算法家族从“表格”走向“函数拟合”的缩影。
  1. 贝尔曼更新用“现实答案”逐步修正当前估计,是 Q 学习的根基。
    Q(s,a)←Q(s,a)+αδQ(s, a) \leftarrow Q(s, a) + \alpha \delta
  2. 时序差分误差 δ未来折扣奖励与当前估计之差,驱动学习。
    δ=r+γmax⁡a′Q(s′,a′)−Q(s,a)\delta = r + \gamma \max_{a'} Q(s', a') - Q(s, a)
  3. 枚举 max 的局限动作连续时,max 无法通过遍历求得。
    max⁡a′Q(s′,a′),a′∈Rn\max_{a'} Q(s', a'), \quad a' \in \mathbb{R}^n
  4. DDPG 的近似替代用目标策略网络直接输出近似的“最优动作”,代入目标 Q 网络。
    Qϕ′(s′,μθ′(s′))Q_{\phi'}(s', \mu_{\theta'}(s'))

连续控制落地

学员终于毕业了,他被派去操作一台六自由度的机械臂,任务是从流水线上取起一个杯子放进箱子里。机械臂的每个关节都能停在任意角度——这就是连续动作的典型场景。状态是各个关节角、角速度、末端位置;动作是六个关节各自的目标角度;奖励是末端离杯子的距离的负值——越近分越高。DDPG 的 Actor 输出六个连续值,Critic 给这组值打分,一套流程完美匹配。

真正落地时,工程师手里要做的功课不少:一是奖励设计,稀疏奖励(只有拿起来才给分)训练极慢,通常要加“距离缩小就给小奖励”的塑形奖励;二是超参调优,学习率、OU 噪声的 θ 和 σ、软更新系数 τ 都要配合调整,τ 太大训练震荡、太小收敛迟缓;三是评估指标,不能只看累计奖励,还要看任务成功率、动作平滑度、有无抖动——毕竟真实机械臂不允许像仿真里那样莽撞。这些工程细节,决定了 DDPG 是“论文里跑得动”还是“产线上跑得动”。

DDPG 追求的最终目标,正是最大化一整段轨迹上的累计折扣回报:

从“三档驾校”的困局,到学员与教练的分工配合,再到行车记录仪、评分手册和故意绕路——DDPG 的每个部件都对应着解决真实连续控制问题的关键设计。学员已经顺利毕业,现在正稳稳地操控着机械臂。

---

J(θ)=Eτ∼pμ(τ)[∑t=0Tγtrt]\displaystyle J(\theta) = \mathbb{E}_{\tau \sim p_\mu(\tau)} \left[ \sum_{t=0}^{T} \gamma^t r_t \right]
目标就是:在所有由学员策略产生的驾驶轨迹上,把整段旅程的折扣奖励总和最大化。数学上简洁,工程上却充满细节。
  1. 轨迹 τ从起点到终点的一整段“状态-动作-奖励”序列,像一次完整的练车视频。
    τ=(s0,a0,r0,s1,a1,r1,…)\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \ldots)
  2. 轨迹分布 p_μ(τ)学员策略 μ 决定了哪些轨迹更容易出现。
    τ∼pμ(τ)\tau \sim p_\mu(\tau)
  3. 折扣因子 γ 的幂越遥远的奖励打折越多,让学员更关注眼前的收益。
    γt,0≤γ<1\gamma^t, \quad 0 \le \gamma < 1
  4. 累计折扣回报整段旅程所有奖励的折扣总和,是衡量总收益的标尺。
    ∑t=0Tγtrt\sum_{t=0}^{T} \gamma^t r_t

AI 应用场景

机械臂精密抓取

阶段:模型训练与部署(策略网络部署到真机)

解决的问题:传统机械臂抓取依赖人工编写轨迹规划代码,遇到物体位置变化、光照干扰就失效;DDPG 让机械臂像学员学车一样,通过试错学会直接输出各个关节的连续角度。它把“每个关节该转多少度”这个连续决策问题交给 Actor 网络实时求解,无需人工预设轨迹。

具体案例:某工厂的上下料机械臂,状态输入为关节角度、角速度和视觉定位的物体坐标,动作输出为 6 个关节目标角度,奖励为末端夹具与目标点的负距离。在仿真中训练 200 万步后迁移到真机,抓取成功率从传统手写轨迹的 71% 提升到 94%,且对不同摆放位置的物体表现出良好的泛化能力。

自动驾驶横向控制

阶段:策略优化与仿真验证

解决的问题:自动驾驶的转向控制本质是连续动作——方向盘角度可以是任意值。传统 PID 控制需要人工调参且难以适应复杂弯道;DDPG 直接学习“当前道路曲率下方向盘该打多少”,将感知状态映射为连续转向指令,实现类人化的平滑转向。

具体案例:某自动驾驶团队在仿真器中训练 DDPG 模型,状态为车道偏移、曲率与车速,动作为方向盘转角(-450° 到 450° 连续值),奖励为车道居中度与乘坐舒适度的加权。训练后的智能体在高速弯道和匝道场景下,横向偏差峰值比 PID 基线降低约 40%,方向盘抖动次数大幅减少。

连续控制游戏 AI

阶段:环境交互与奖励塑形

解决的问题:赛车游戏、飞行模拟器等游戏中,操控杆、油门、刹车都是连续量。用 DQN 需要把操控离散成“几个档位”,操作感粗糙;DDPG 输出连续的油门与转向值,让 AI 开出接近职业玩家的流畅走线。

具体案例:某游戏公司的 AI 陪练系统,在赛车游戏中让 DDPG 智能体逐圈学习最佳过弯路线。状态为车速、赛道边界距离与当前弯道角度,动作为油门开度和方向盘转角两个连续量。经过约 300 万步训练,AI 的单圈成绩从垫底提升至全球玩家前 5%,且过弯轨迹平滑无顿挫。

Python 演示

python128 行
import numpy as np  class ReplayBuffer:    def __init__(self, cap):        self.buf, self.cap, self.i = [], cap, 0     def push(self, s, a, r, s2):        if len(self.buf) < self.cap:            self.buf.append((s, a, r, s2))        else:            self.buf[self.i] = (s, a, r, s2)        self.i = (self.i + 1) % self.cap     def sample(self, n):        k = np.random.choice(len(self.buf), n)        return [self.buf[j] for j in k]  class OUNoise:    def __init__(self, dim, theta=0.15, sigma=0.3):        self.theta, self.sigma, self.x = theta, sigma, np.zeros(dim)     def reset(self):        self.x[:] = 0     def sample(self):        self.x += self.theta * (-self.x) + self.sigma * np.random.randn(len(self.x))        return self.x  class Actor:    def __init__(self, sd, ad):        self.w1 = np.random.randn(sd, 32) * 0.2        self.b1 = np.zeros(32)        self.w2 = np.random.randn(32, ad) * 0.2        self.b2 = np.zeros(ad)     def act(self, s):        h = np.tanh(s @ self.w1 + self.b1)        return np.tanh(h @ self.w2 + self.b2)  class Critic:    def __init__(self, sd, ad):        self.w1 = np.random.randn(sd + ad, 32) * 0.2        self.b1 = np.zeros(32)        self.w2 = np.random.randn(32, 1) * 0.2        self.b2 = np.zeros(1)     def q(self, s, a):        x = np.concatenate([s, a])        h = np.tanh(x @ self.w1 + self.b1)        return (h @ self.w2 + self.b2).item()  def grad(f, params, eps=1e-3):    gs = []    for p in params:        gp = np.zeros_like(p)        it = np.nditer(p, flags=["multi_index"])        while not it.finished:            idx = it.multi_index            old = p[idx]            p[idx] = old + eps            fp = f()            p[idx] = old - eps            fm = f()            p[idx] = old            gp[idx] = (fp - fm) / (2 * eps)            it.iternext()        gs.append(gp)    return gs  def train():    actor = Actor(1, 1)    critic = Critic(1, 1)    t_actor = Actor(1, 1)    t_critic = Critic(1, 1)    buf = ReplayBuffer(500)    noise = OUNoise(1)    tau, lr = 0.05, 0.005     for ep in range(50):        s = np.array([0.0])        dec = max(0.1, 1.0 - ep / 50.0)        noise.reset()        total = 0.0        for _ in range(30):            a = np.clip(actor.act(s) + dec * noise.sample(), -1, 1)            s2 = s + a            r = -abs(s2[0])            buf.push(s, a, r, s2)            s = s2            total += r         batch = buf.sample(32)         def act_loss():            return -sum(critic.q(si, actor.act(si)) for si, _, _, _ in batch) / 32         def cri_loss():            v = 0.0            for si, ai, ri, s2i in batch:                y = ri + 0.95 * t_critic.q(s2i, t_actor.act(s2i))                v += (y - critic.q(si, ai)) ** 2            return v / 32         for p, g in zip(actor.__dict__.values(), grad(act_loss, list(actor.__dict__.values()))):            p -= lr * g        for p, g in zip(critic.__dict__.values(), grad(cri_loss, list(critic.__dict__.values()))):            p -= lr * g         t_actor.w1 += tau * (actor.w1 - t_actor.w1)        t_actor.b1 += tau * (actor.b1 - t_actor.b1)        t_actor.w2 += tau * (actor.w2 - t_actor.w2)        t_actor.b2 += tau * (actor.b2 - t_actor.b2)        t_critic.w1 += tau * (critic.w1 - t_critic.w1)        t_critic.b1 += tau * (critic.b1 - t_critic.b1)        t_critic.w2 += tau * (critic.w2 - t_critic.w2)        t_critic.b2 += tau * (critic.b2 - t_critic.b2)         print("epoch", ep, "total_reward", round(total, 3))  if __name__ == "__main__":    train()
目的:用纯 NumPy 实现一个教学版 DDPG,在一个一维“小车接近目标点”任务上演示 Actor-Critic 训练、经验回放、OU 噪声、目标网络软更新的完整流程。梯度用有限差分近似,便于在不依赖深度学习框架的情况下看清算法骨架。
  1. class ReplayBuffer行车记录仪:把每次 (s, a, r, s2) 存进环形缓冲,满了就覆盖最旧的片段。
  2. a = np.clip(actor.act(s) + dec * noise.sample(), -1, 1)学员输出确定性动作,叠加 OU 噪声探索,再夹到合法范围。
  3. buf.push(s, a, r, s2)把这一条驾驶片段记入经验池,供之后随机复盘。
  4. def act_loss(): return -sum(critic.q(si, actor.act(si)) for si, _, _, _ in batch) / 32学员的考试分数:让教练给自己打出的分尽量高,所以取负作为损失。
  5. y = ri + 0.95 * t_critic.q(s2i, t_actor.act(s2i))用目标网络算出“本次奖励 + 未来折扣价值”,作为教练评分的现实答案(TD 目标)。
  6. for p, g in zip(actor.__dict__.values(), grad(act_loss, list(actor.__dict__.values()))):数值梯度上场:轻微扰动所有参数,观察考试分数变化,向上升方向更新。
  7. t_actor.w1 += tau * (actor.w1 - t_actor.w1)软更新:评分手册只向学员当前水平挪一小步,保证训练目标稳定。

常见误区

误区很多初学者从策略梯度算法(如 REINFORCE、PPO)过来,以为 DDPG 的 Actor 网络也输出“每个动作的概率”,然后按概率抽样。我一开始也这么以为,觉得连续动作应该对应一个高斯分布。

正解DDPG 的名字里“确定性”三个字就是关键。Actor 网络面对同样的状态,永远输出同一个确定性的连续动作,而不是分布。它不做概率抽样,直接给出“此刻就该打 31.5° 方向盘”。探索任务交给了额外的 OU 噪声,而不是靠动作抽样。

为何常见:主流的策略梯度算法大多基于随机策略,输出分布是常态;而 DDPG 走的是确定性策略这条支线,两者在“输出形式”上截然不同,容易混淆。

误区听名字像“目标”,就以为它是固定不变的基准,训练全程都不动。有人甚至把目标网络当成了只读的测试网络。

正解目标网络每个训练步都在变,只是变得很慢。它通过软更新 θ' ← τθ + (1-τ)θ' 持续向当前网络靠拢。τ 通常取 0.005 这种小值,所以目标网络像一支“缓慢移动的标杆”,既稳定又不至于落伍。

为何常见:“目标网络”这个译名容易让人联想到“最终目标、固定不动”,而忽略了“软更新”这个核心机制。

误区都是加在动作上的随机扰动,都是用来探索的,那直接用 `np.random.randn()` 不就行了?何必引入 OU 噪声这种复杂过程。

正解两者最大的区别是时序相关性。高斯噪声每个时间步独立抽样,上一秒 +0.3 下一秒 -0.7,动作剧烈抖动,对机械臂这类真实系统极不友好;OU 噪声带记忆,扰动会沿着上一时刻的方向平滑演变,形成一段时间的连续探索趋势,更符合物理世界的惯性。

为何常见:从离散动作的 ε-greedy 直觉出发,容易把一切探索都简化成“随机加一点噪声”,忽略了连续控制中时序平滑的重要性。

误区以为经验回放是个“数据复用技巧”,把旧数据存下来反复用,主要是为了省采样成本。

正解省样本只是副产品,经验回放更核心的作用是打破时序相关性。强化学习的数据来自同一策略的连续交互,相邻样本高度相关,直接用会让梯度更新剧烈震荡;随机打乱抽样后,每次更新面对的是近似独立的样本集,训练才稳定。DDPG 作为 off-policy 算法,能这样任意复盘旧数据,也正是它区别于 on-policy 算法的优势。

为何常见:从业者容易用监督学习的眼光看待经验池,以为它只是“缓存”,忽略了它在平稳化训练分布中的作用。

误区DDPG 能处理连续动作,听起来更“高级”,那玩围棋、打街机这些离散决策问题是不是也该换 DDPG?

正解DDPG 是为连续动作空间设计的。对于离散动作,DQN 的 argmax 枚举既精准又高效;硬用 DDPG 反而要先把离散动作编码成连续值再解码,画蛇添足且可能降低性能。算法没有绝对的强弱,只有适不适合——连续世界找 DDPG,离散世界找 DQN,这才是正确的分工。

为何常见:新手容易按“新旧”或“复杂程度”排算法等级,而忽略了每种算法背后对应的动作空间假设。

面试题

Q1. DDPG 中 Actor 网络的输出是什么?

单选题 · easy

  • A.一个确定性的连续动作值← 正确答案
  • B.每个动作的概率分布
  • C.状态价值函数的估计
  • D.动作空间的离散索引

答案:A。DDPG 是确定性策略算法,Actor 面对相同状态永远输出同一个连续动作,A 正确。输出概率分布是随机策略梯度(如 PPO)的做法;状态价值估计是 Critic 的职责之一但不是 Actor 输出;离散索引是 DQN 的思路。记住“确定性”三个字,此题就不会错。

Q2. “确定性策略”的含义是什么?

单选题 · easy

  • A.从策略分布随机采样得到动作
  • B.面对相同状态给出相同动作← 正确答案
  • C.根据概率大小贪心抽取动作
  • D.使用软最大化随机选择动作

答案:B。确定性策略是一个“状态→动作”的固定函数映射,同样的输入永远得到同样的输出,B 正确。A、C、D 都涉及随机抽样或概率选择,属于随机策略的特征。可以把确定性策略理解为“熟能生巧的学员:这条路况我闭着眼都知道打多少方向盘”。

Q3. DDPG 中经验回放最主要的作用是什么?

单选题 · mid

  • A.削减样本存储所需的内存开销
  • B.提升卷积层的特征表达能力
  • C.打破样本间时序相关性以稳定训练← 正确答案
  • D.将连续动作离散化以便处理

答案:C。强化学习产生的相邻样本高度相关,直接训练会让梯度震荡;经验回放随机抽样打乱顺序,让训练分布接近独立同分布,C 正确。内存开销只是附带问题,A 不是目的;特征表达是网络结构的事,B 无关;DDPG 恰恰是处理连续动作的,D 方向相反。

Q4. 目标网络软更新系数 τ = 0.005,训练一步后目标网络参数会怎样?

单选题 · mid

  • A.目标网络参数始终保持完全不变
  • B.一个训练步后完全复制主网参数
  • C.目标网络以 0.5% 比例靠近主网← 正确答案
  • D.主网参数以 0.5% 比例靠近目标网

答案:C。软更新公式 θ' ← τθ + (1-τ)θ',τ=0.005 意味着目标网络只向当前网络挪动 0.5%,C 正确。A 忽略了软更新;B 是全量复制(τ=1)的做法;D 方向反了,是目标网靠近主网,而不是主网靠近目标网。可以想象评分手册每次只修改一点点,保持整体稳定。

Q5. OU 噪声与纯高斯噪声最大区别是什么?

单选题 · mid

  • A.高斯噪声能记住历史采样值
  • B.OU 噪声具有时序记忆与平滑性← 正确答案
  • C.OU 噪声的方差永远大于高斯
  • D.二者在连续控制中作用完全相同

答案:B。OU 噪声是时序相关过程,当前扰动由上一时刻演化而来,轨迹平滑连续,适合真实机械系统,B 正确。高斯噪声每个时间步独立抽样,恰恰没有记忆,A 说反了;方差大小取决于参数无法直接比较,C 错;D 忽略了连续性上的本质差异。

Q6. DDPG 与 DQN 的核心关系是什么?

单选题 · hard

  • A.DDPG 将 DQN 思想拓展到连续动作域← 正确答案
  • B.DDPG 与 DQN 在机制上毫无关联
  • C.DQN 是 DDPG 的连续动作变体
  • D.DDPG 完全不需要目标网络参与

答案:A。DDPG 继承了 DQN 的经验回放与目标网络,并把“对动作枚举取 max”替换为“用 Actor 网络直接生成最优动作”,从而适用于连续空间,A 正确。二者共用了大量组件,B 错;方向反了,DDPG 才是 DQN 的连续版,C 错;目标网络是 DDPG 训练稳定的基石,D 错。

Q7. 关于 DDPG 训练不稳定问题及对策,下列说法正确的是?

多选题 · extreme

  • A.Q 值过高估计会引发连锁崩溃← 正确答案
  • B.目标网络软更新可有效抑制发散← 正确答案
  • C.经验回放会加速梯度爆炸
  • D.增加回放容量一定导致发散

答案:AB。DDPG 的 Critic 容易对未见过的状态-动作对给出虚高的 Q 值,进而把 Actor 带偏,A 正确;目标网络通过慢速更新稳定了 TD 目标,是抑制过高估计与发散的重要手段,B 正确;经验回放恰恰是缓解不稳定的,C 说反;回放容量是超参,增大通常有益而非导致发散,D 过于绝对。可以想象教练手册如果频繁大改,学员才会崩溃——软更新就是防止这种崩溃的缓冲。

Q8. 下列哪些是 DDPG 的核心组成部分?

多选题 · easy

  • A.Actor 策略网络← 正确答案
  • B.经验回放缓冲← 正确答案
  • C.目标网络← 正确答案
  • D.蒙特卡洛树搜索

答案:ABC。DDPG 由 Actor 网络输出动作、Critic 网络评分、经验回放储存样本、目标网络稳定训练四件套构成,A、B、C 正确。蒙特卡洛树搜索是 AlphaGo 系列中用于规划搜索的组件,与 DDPG 的架构无关,D 错。

Q9. 下列哪些问题适合用 DDPG 解决?

多选题 · mid

  • A.机械臂连续关节角度控制← 正确答案
  • B.围棋落子的离散位置选择
  • C.车辆油门与刹车的连续控制← 正确答案
  • D.街机游戏的离散按键选择

答案:AC。DDPG 专为连续动作空间设计。机械臂关节角度、油门刹车开度都是连续量,A、C 正确。围棋落子、街机按键本质是离散决策,用 DQN 等离散动作算法更直接高效,B、D 不适合。

Q10. DDPG 训练中一个常用且有效的工程技巧是什么?

单选题 · hard

  • A.对 Q 值不做任何限制任其增长
  • B.使用梯度裁剪并调小学习率← 正确答案
  • C.每一步训练后清空整个经验池
  • D.完全移除目标网络来加快训练

答案:B。DDPG 训练对超参敏感,梯度裁剪能防止单步更新过大导致参数崩溃,配合较小的学习率是公认的稳定训练手段,B 正确。Q 值不加限制会陷入过高估计恶性循环,A 错;清空经验池反而丢失宝贵样本、加剧相关性问题,C 错;移除目标网络会让训练目标剧烈晃动,D 错。工程上“稳”比“快”更重要。