资讯动态

策略梯度入门实战:从零推导REINFORCE算法

发布时间:2026/8/20 5:57:05 来源:尧图企业网站定制
1. 为什么需要策略梯度方法在强化学习领域我们最熟悉的可能是基于值函数的方法比如Q-learning和DQN。这些方法通过估计每个状态-动作对的期望回报来选择最优动作。但我在实际项目中发现这类方法存在几个明显的局限性首先基于值的方法在处理连续动作空间时非常吃力。想象你要控制一个机械臂每个关节的角度都是连续值。如果用Q-learning你需要离散化这些角度但精细的离散化会导致维度灾难粗略的离散化又会损失控制精度。我曾在机器人控制项目里为此头疼不已。其次基于值的方法通常只能输出确定性策略。但在某些场景下随机策略反而更优。比如在石头剪刀布游戏中纯确定性策略很容易被对手预测。策略梯度方法直接参数化策略本身可以自然地输出动作概率分布。最让我印象深刻的是部分可观测环境下的表现。曾经用DQN训练一个游戏AI发现它经常在两个视觉相似但实际不同的场景下做出相同错误决策。后来改用策略梯度方法因为其直接学习策略映射反而避开了这个坑。2. 策略梯度的数学直觉理解策略梯度关键在于把握其核心思想通过调整策略参数使得高回报的动作更可能被选择。这听起来简单但如何用数学表达呢假设我们有个参数化的策略π(a|s;θ)目标是最大化期望回报J(θ)。这里有个巧妙的思路与其直接求J(θ)对θ的梯度不如找到一种采样估计方法。就像蒙特卡洛积分通过采样来近似期望值。我在白板上推导时发现策略梯度定理给出了一个优雅的表达式 ∇J(θ) ∝ E[G_t ∇lnπ(A_t|S_t;θ)] 这个式子告诉我们可以通过采样轨迹计算每个时间步的回报G_t与对数策略梯度的乘积来估计真实梯度。举个生活中的例子假设你在教小狗做动作。当它偶然做出你想要的动作时高G_t你就加强这个动作对应的指令增大θ。经过多次尝试小狗就学会了哪些指令对应着哪些受欢迎的动作。3. REINFORCE算法详解REINFORCE是最基础的策略梯度算法它的核心流程非常直接用当前策略π_θ采样完整轨迹计算每个时间步的回报G_t更新参数θ ← θ αG_t∇lnπ(A_t|S_t;θ)我在实现时发现几个关键点需要注意G_t是t时刻后的累计折扣回报需要从后往前计算对数概率的梯度计算可以用自动微分工具自动处理学习率α需要仔细调整太大容易不稳定这里有个容易踩的坑直接实现时初始阶段策略很差采样到的G_t可能都是负值导致训练困难。我的解决方案是引入基线baseline比如减去这批轨迹的平均回报显著提高了稳定性。4. 从理论到代码的实现细节让我们用PyTorch实现一个完整的REINFORCE算法。先定义策略网络import torch import torch.nn as nn import torch.optim as optim class PolicyNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_size128): super().__init__() self.fc1 nn.Linear(state_dim, hidden_size) self.fc2 nn.Linear(hidden_size, action_dim) def forward(self, state): x torch.relu(self.fc1(state)) return torch.softmax(self.fc2(x), dim-1)接下来是REINFORCE的核心训练逻辑def train(env, policy, episodes1000, gamma0.99, lr0.01): optimizer optim.Adam(policy.parameters(), lrlr) for ep in range(episodes): state env.reset() rewards [] log_probs [] # 采样轨迹 while True: state torch.FloatTensor(state) probs policy(state) action torch.multinomial(probs, 1).item() next_state, reward, done, _ env.step(action) log_prob torch.log(probs[action]) log_probs.append(log_prob) rewards.append(reward) state next_state if done: break # 计算回报 returns [] G 0 for r in reversed(rewards): G r gamma * G returns.insert(0, G) # 归一化回报 returns torch.tensor(returns) returns (returns - returns.mean()) / (returns.std() 1e-9) # 计算损失 policy_loss [] for log_prob, G in zip(log_probs, returns): policy_loss.append(-log_prob * G) # 参数更新 optimizer.zero_grad() loss torch.stack(policy_loss).sum() loss.backward() optimizer.step()这段代码有几个实用技巧回报归一化减均值除标准差能显著提高稳定性使用负对数概率乘以回报作为损失因为优化器默认是最小化自动微分自动计算∇lnπ避免手动推导5. 实战中的调参经验在实际项目中我发现REINFORCE对超参数相当敏感。经过多次实验总结出以下经验学习率选择开始可以尝试1e-3到1e-2如果回报波动剧烈适当降低可以结合学习率调度器动态调整折扣因子γ接近1的值考虑长期回报通常在0.9到0.99之间对于回合制任务可以设为1批量大小完全在线更新每步更新方差太大建议积累多个episode后再更新批量大小一般32-256效果较好一个实用的技巧是熵正则化在损失函数中加入策略熵的负值entropy -torch.sum(probs * torch.log(probs)) loss loss - 0.01 * entropy # 系数通常较小这能鼓励探索防止策略过早收敛到次优解。6. 算法变体与改进基础的REINFORCE虽然简单但存在高方差问题。以下是几种常见改进带基线的REINFORCE 减去状态相关的基线b(s)通常用价值函数V(s)估计advantage G_t - V(s_t)我在实现时发现即使简单的移动平均基线也能显著提升性能。Actor-Critic架构 用TD误差代替蒙特卡洛回报delta r gamma * V(s_next) - V(s)这样可以在每一步更新不再需要等待回合结束。自然策略梯度 考虑参数空间的曲率信息使用Fisher信息矩阵进行预处理# 需要计算二阶导数 loss -log_prob * advantage 0.5 * (delta * log_prob).pow(2).mean()在实际项目中我通常从带基线的REINFORCE开始等基础版本稳定后再尝试更复杂的变体。7. 典型问题与调试技巧新手实现REINFORCE时常遇到以下问题回报不增长检查梯度更新是否真的发生打印参数变化尝试更大的网络容量增加探索如提高熵正则系数回报波动剧烈减小学习率增大批量大小添加更稳定的基线策略过早收敛检查熵值是否降得太快添加明确的探索激励尝试不同的策略初始化一个实用的调试技巧是可视化绘制回报曲线和移动平均监控策略熵的变化对离散动作记录动作选择分布记得保存不同超参数配置的训练日志这对分析问题非常有用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价