第4章 策略梯度方法:REINFORCE算法推导、策略梯度定理、Baseline设计与方差缩减
好,咱们进入策略梯度这部分。说实话,这是我觉得强化学习里最优雅的一块内容之一。为什么?因为它直接对策略本身下手,不像值函数方法那样绕个弯子。
4.1 策略梯度定理——核心思想
先问个问题:我们为什么要用策略梯度?
你想想看,值函数方法(比如DQN)有个硬伤——它只能处理离散动作。连续控制怎么办?策略梯度直接输出动作概率分布,天然适合连续空间。我在做机器人抓取项目时,一开始用DQN,动作空间一离散化,精度就上不去。后来换成策略梯度,效果立竿见影。
策略梯度定理说白了就是:目标函数对策略参数的梯度,可以写成期望形式。
数学上,我们定义目标函数:
J(θ) = E[ Σ γ^t * r_t ] (从初始状态开始的累计回报期望)
策略梯度定理告诉我们:
∇J(θ) = E[ ∇log π(a|s,θ) * Q(s,a) ]
这个公式看着简单,但背后有个关键点——它把梯度计算转化成了采样估计。你不需要知道环境模型,只需要采样轨迹,就能更新策略。这就是基于学习的端到端控制的核心优势。
重要理解:∇log π(a|s,θ) 是「得分函数」,它告诉我们在当前状态下,往哪个方向调整参数能增加动作的概率。Q(s,a) 则是「权重」,好的动作权重高,坏的动作权重低。
4.2 REINFORCE算法——最朴素的实现
REINFORCE是策略梯度里最基础的算法。我习惯叫它「蒙特卡洛策略梯度」。为什么?因为它用整条轨迹的回报来估计Q值。
算法流程其实很直观:
- 用当前策略 π(θ) 采样一条完整轨迹
- 计算每个时间步的累计回报 G_t
- 用 G_t 作为 Q(s_t, a_t) 的估计,更新策略参数
代码实现大概长这样:
def reinforce(env, policy, lr=0.001, gamma=0.99):
for episode in range(1000):
# 采样轨迹
states, actions, rewards = [], [], []
state = env.reset()
done = False
while not done:
action = policy.sample_action(state)
next_state, reward, done, _ = env.step(action)
states.append(state)
actions.append(action)
rewards.append(reward)
state = next_state
# 计算累计回报
G = 0
returns = []
for r in reversed(rewards):
G = r + gamma * G
returns.insert(0, G)
# 更新策略
for s, a, G in zip(states, actions, returns):
# ∇log π(a|s) * G
policy.update(s, a, G, lr)
嗯,这里要注意:REINFORCE用的是整条轨迹的回报,这意味着它必须等到episode结束才能更新。我做过一个实验,在仿真环境里跑没问题,但换到真实机器人上,每次都要跑完完整轨迹才能更新,效率太低了。
避坑指南:我曾经在真实机器人上直接套用REINFORCE,结果发现采样效率极低。真实环境不像仿真,你不能随便跑几千个episode。后来我改用Actor-Critic架构,才解决了这个问题。
4.3 Baseline设计——方差缩减的关键
REINFORCE有个大问题——方差太大。为什么?因为单条轨迹的回报 G_t 波动很大。你想想看,同样的策略,这次运气好拿到高分,下次运气差拿到低分,梯度方向可能完全相反。
解决办法是什么?引入Baseline。
策略梯度定理可以改写为:
∇J(θ) = E[ ∇log π(a|s,θ) * (Q(s,a) - b(s)) ]
其中 b(s) 是Baseline,它只依赖于状态s,不依赖于动作a。数学上可以证明,引入Baseline不会改变梯度的期望值,但能降低方差。
最常用的Baseline是什么?状态值函数 V(s)。
这时候,Q(s,a) - V(s) 就是优势函数 A(s,a)。它衡量的是「在这个状态下,选这个动作比平均好多少」。
直观理解:如果Baseline是平均表现,那么优势函数就是「超额收益」。正的优势意味着这个动作比平均水平好,应该增加概率;负的优势则相反。
我个人的习惯是,用同一个网络同时估计策略和值函数。这样既节省参数,又能让两者共享特征提取层。当然,这需要小心处理梯度冲突的问题。
4.4 方差缩减的实用技巧
除了Baseline,还有几个我常用的方差缩减技巧:
| 技巧 | 原理 | 我的经验 |
|---|---|---|
| 折扣因子 γ | 降低远期回报的权重 | γ=0.99 是常见选择,但任务周期短时可以设小点 |
| GAE (广义优势估计) | 平衡偏差和方差 | λ=0.95 效果不错,我一般从0.9开始调 |
| 多个轨迹平均 | 并行采样取平均梯度 | 能并行就并行,方差直接除以√N |
| 梯度裁剪 | 限制梯度范数 | max_norm=1.0 是安全起点 |
说到GAE,我得多说两句。GAE的核心思想是:用多个时间步的TD误差加权平均来估计优势。公式是:
A_t = Σ (γλ)^l * δ_{t+l}
其中 δ_t = r_t + γV(s_{t+1}) - V(s_t) 是TD误差。当λ=0时,GAE退化为单步TD误差;当λ=1时,退化为蒙特卡洛回报。λ在中间取值,就能平衡偏差和方差。
实用建议:如果你刚开始调参,我建议先用GAE λ=0.95。这个值在大多数任务上表现稳健。我在机械臂控制任务上试过,从λ=0.9调到0.99,效果差异不大,但0.95是最稳定的。
4.5 实际项目中的坑与经验
最后分享几个我在项目中踩过的坑:
- 学习率太敏感:策略梯度对学习率特别敏感。我习惯用Adam优化器,初始学习率设在1e-4到3e-4之间。如果训练不稳定,先降学习率。
- 奖励归一化:不同任务的奖励尺度差异很大。我建议把每个batch的奖励归一化到均值为0、标准差为1。这能显著提升训练稳定性。
- 策略熵正则化:防止策略过早收敛到确定性策略。加一个熵惩罚项,鼓励探索。系数一般设0.01左右。
- 不要忽视Baseline网络:很多人只关注策略网络,Baseline网络随便写写。实际上,Baseline估计不准,优势函数就有偏差,整个训练都会受影响。
我曾经在一个四足机器人控制项目上,花了两周时间调参,结果发现是Baseline网络层数太少,拟合能力不够。换成两层256的隐藏层后,效果立竿见影。所以说,细节决定成败。
好了,这一章的内容就到这里。策略梯度是端到端控制的基础,REINFORCE虽然简单,但它的思想贯穿了整个策略梯度家族。下一章我们会讲Actor-Critic,那才是真正实用的方法。