4、策略梯度方法入门:REINFORCE算法原理、从概率分布采样动作、蒙特卡洛策略梯度推导
好,咱们今天来聊一个硬核话题——策略梯度。说实话,我刚接触强化学习那会儿,看到“梯度”两个字就头大。但后来我发现,这东西其实没那么玄乎。说白了,策略梯度就是让智能体学会“什么情况下该做什么动作”,而且是用一种很自然的方式——试错。
你想想看,我们人类是怎么学骑自行车的?摔几次,就知道什么时候该拐弯、什么时候该刹车。策略梯度也是这个道理。它不像Q-learning那样去估计每个动作的价值,而是直接优化策略本身。嗯,这就是它的核心魅力。
4.1 从值函数到策略:为什么需要策略梯度?
我之前做项目时,遇到过一个机器人抓取任务。用DQN(深度Q网络)做,效果还行,但动作空间一变大,Q值估计就崩了。为什么?因为DQN需要计算每个动作的Q值,动作一多,计算量爆炸。
策略梯度方法绕开了这个问题。它不计算Q值,而是直接输出动作的概率分布。你想想看,这多省事?
核心区别:
- 值函数方法:先估计Q值,再根据Q值选动作(如ε-贪婪)
- 策略梯度方法:直接优化策略参数,输出动作概率
举个例子。假设你是一个游戏角色,面前有5个方向可以走。值函数方法会先算出每个方向能得多少分,然后选最高分的那个。策略梯度方法呢?它会直接告诉你:向左走的概率是0.4,向右是0.3,向前是0.2,向后是0.1。然后你按这个概率去采样。
我个人习惯用策略梯度处理连续动作空间的问题。比如控制机械臂的关节角度,动作是连续的,Q-learning根本没法搞。这时候策略梯度就派上用场了。
4.2 REINFORCE算法:最朴素的策略梯度
REINFORCE算法,名字挺唬人,其实原理很简单。它属于蒙特卡洛策略梯度方法,也就是说,它需要跑完一整条轨迹,才能更新一次策略。
我在项目中遇到过一个问题:用REINFORCE训练一个简单的导航任务,智能体总是原地打转。后来发现,是因为奖励稀疏,智能体很难得到正向反馈。嗯,这个坑咱们后面会讲怎么避。
REINFORCE的核心思想是:如果某个动作带来了好的回报,就增加这个动作的概率;反之,就降低它的概率。
它的更新公式长这样:
θ = θ + α * ∇θ log π(a|s) * G
其中:
- θ:策略网络的参数
- α:学习率
- π(a|s):在状态s下采取动作a的概率
- G:从当前时刻到结束的累计折扣回报
说白了,就是拿“回报”当权重,去调整动作概率。回报高,权重就大,这个动作的概率就往上提;回报低,权重就小,概率就往下压。
小技巧:我在实际项目中,通常会给G减去一个baseline(比如平均回报),这样可以降低方差,让训练更稳定。这个技巧叫“带基线的REINFORCE”。
4.3 从概率分布采样动作:随机性的艺术
策略梯度方法的一个关键点,就是从概率分布中采样动作。为什么要采样?而不是直接选概率最大的那个?
你想想看,如果每次都选概率最大的,那就变成了确定性策略,失去了探索的能力。智能体可能永远发现不了更好的动作。
举个例子。假设在某个状态下,向左走的概率是0.6,向右是0.4。如果每次都选向左,那向右这个动作就永远没机会尝试了。万一向右才是最优解呢?
所以,我们按概率分布去采样。这次可能向左,下次可能向右。这样既能利用已知的好动作,又能探索未知的可能性。
我记得有一次做游戏AI,智能体学会了走一条固定的路线,但那条路线并不是最优的。后来我检查代码,发现是采样逻辑写错了——我用了argmax而不是采样。改过来之后,智能体很快就找到了更短的路径。
避坑指南:我曾经在采样时忘记设置随机种子,导致每次实验的结果都不一样,调试起来非常痛苦。建议在训练时固定随机种子,保证可复现性。
4.4 蒙特卡洛策略梯度推导:一步步拆解
好,咱们来推导一下蒙特卡洛策略梯度。别怕,我会用最通俗的方式讲。
我们的目标是最大化期望回报:
J(θ) = E[G | πθ]
其中G是整条轨迹的累计回报。我们希望找到参数θ,让J(θ)最大。
怎么求梯度?用链式法则:
∇θ J(θ) = E[∇θ log π(a|s) * G]
这个公式就是策略梯度定理的核心。它告诉我们:梯度的方向,由“对数概率的梯度”乘以“回报”决定。
为什么是对数概率?因为对数函数能把乘法变成加法,方便求导。你想想看:
∇θ log π(a|s) = (1/π(a|s)) * ∇θ π(a|s)
这样,即使π(a|s)很小,梯度也不会爆炸。嗯,数学上很优雅。
在实际计算中,我们没法求期望,所以用蒙特卡洛采样来近似:
∇θ J(θ) ≈ (1/N) * Σ ∇θ log π(at|st) * Gt
其中N是采样的轨迹数量。这就是蒙特卡洛策略梯度的由来。
推导要点总结:
- 定义目标函数J(θ) = 期望回报
- 对J(θ)求梯度,得到∇θ J(θ)
- 利用对数技巧,把梯度写成∇θ log π(a|s) * G的形式
- 用蒙特卡洛采样近似期望
4.5 完整算法流程与代码示例
说了这么多,咱们来看看REINFORCE算法的完整流程:
1. 初始化策略网络参数θ
2. 循环每个episode:
a. 从初始状态s0开始
b. 循环每一步t:
- 从策略πθ采样动作at
- 执行at,得到奖励rt和下一状态st+1
- 记录(st, at, rt)
c. 计算每个时刻的累计回报Gt = Σ γ^(k-t) * rk
d. 更新策略参数:
θ = θ + α * Σ ∇θ log π(at|st) * Gt
3. 直到收敛
下面是一个简单的PyTorch实现片段:
import torch
import torch.nn as nn
import torch.optim as optim
class PolicyNetwork(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.fc = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, action_dim),
nn.Softmax(dim=-1)
)
def forward(self, state):
return self.fc(state)
# 训练循环
policy = PolicyNetwork(state_dim, action_dim)
optimizer = optim.Adam(policy.parameters(), lr=0.001)
for episode in range(1000):
states, actions, rewards = [], [], []
state = env.reset()
# 收集一条轨迹
while True:
probs = policy(state)
action = torch.multinomial(probs, 1).item()
next_state, reward, done, _ = env.step(action)
states.append(state)
actions.append(action)
rewards.append(reward)
if done:
break
state = next_state
# 计算累计回报
G = 0
returns = []
for r in reversed(rewards):
G = r + 0.99 * G
returns.insert(0, G)
returns = torch.tensor(returns)
# 计算损失并更新
loss = 0
for s, a, G in zip(states, actions, returns):
probs = policy(s)
log_prob = torch.log(probs[a])
loss -= log_prob * G
optimizer.zero_grad()
loss.backward()
optimizer.step()
个人经验:这段代码里,我用了torch.multinomial来采样动作,而不是argmax。这样能保证探索性。另外,学习率α我一般设0.001左右,太大容易发散,太小收敛太慢。
4.6 知识体系与核心逻辑
为了让你更直观地理解本章的知识结构,我画了一张流程图:
这张图把本章的三个核心内容串起来了。从左到右:REINFORCE算法是框架,概率分布采样是动作选择方式,蒙特卡洛梯度推导是数学基础。三者缺一不可。
4.7 常见问题与避坑指南
最后,分享几个我在项目中踩过的坑:
- 梯度方差大:REINFORCE的梯度方差通常很大,训练不稳定。我建议用带基线的版本,或者引入优势函数。
- 奖励设计:稀疏奖励会让REINFORCE很难收敛。我曾经试过给每一步都加一个小奖励,效果立竿见影。
- 学习率敏感:策略梯度对学习率很敏感。我一般先用0.001试,如果loss震荡就调小,如果收敛太慢就调大。
- 采样效率低:REINFORCE是on-policy的,每次更新都要重新采样。如果环境交互成本高,可以考虑用重要性采样改成off-policy。
重要提醒:千万不要在还没跑完一个episode时就更新策略!REINFORCE需要完整的轨迹来计算累计回报G。我刚开始学的时候犯过这个错,结果模型怎么都学不会。
好了,策略梯度入门就讲到这里。记住一句话:REINFORCE虽然简单,但它是理解更高级策略梯度方法(如PPO、A2C)的基础。把这个搞懂了,后面的路就好走了。