1. 策略梯度方法的核心直觉
第一次接触策略梯度(Policy Gradient)这个概念时,最让我困惑的是:为什么我们要直接对策略进行优化?毕竟在价值函数方法中,我们通过迭代更新价值函数间接优化策略,这种思路看起来更直观。直到在实际项目中尝试用DQN解决连续动作空间问题时,我才真正理解策略梯度的必要性。
想象你在教一个机器人打乒乓球。如果使用基于价值的方法,你需要为每个可能的动作(挥拍角度、力度)计算价值,这在连续动作空间中几乎不可能实现。而策略梯度方法直接学习一个参数化的策略函数,输入状态,输出动作的概率分布——就像人类运动员凭"感觉"做出反应,而不是先计算每个动作的得分。
关键理解:策略梯度方法将策略表示为可微函数,通过梯度上升直接优化策略参数,特别适合连续动作空间和高维问题。
2. 从直觉到数学的形式化推导
2.1 目标函数的定义
我们首先需要明确优化目标。在强化学习中,目标通常是最大化期望回报:
J(θ) = E[Σγ^t r_t | π_θ]
这个期望涉及两个随机性来源:1) 策略π_θ产生的动作分布 2) 环境的状态转移概率。直接计算这个期望的梯度非常困难,因为环境动态通常是未知的。
2.2 策略梯度定理的推导
通过巧妙的数学变换,我们可以得到策略梯度定理:
∇J(θ) = E[∇logπ_θ(a|s) Q^π(s,a)]
这个定理的美妙之处在于:梯度计算不需要知道环境动态,只需要能够采样轨迹和对Q函数进行估计。我在首次推导时最惊讶的是log概率的出现——它实际上起到了"加权"作用,高回报的动作方向会获得更大的更新幅度。
推导过程中的关键步骤:
- 将期望回报表示为轨迹概率的积分
- 使用log-derivative技巧:∇p = p ∇logp
- 证明环境动态梯度项相互抵消
3. REINFORCE算法实现细节
3.1 蒙特卡洛策略梯度
REINFORCE是最基础的策略梯度算法,其更新规则为:
θ ← θ + αγ^t G_t ∇logπ_θ(a_t|s_t)
实际实现时有几个易错点:
- 需要完整的episode计算回报G_t
- 梯度更新中的γ^t容易遗漏
- 建议对回报进行基线减除(如减去均值)减少方差
python复制# REINFORCE伪代码核心部分
for episode in episodes:
states, actions, rewards = run_episode(env, policy)
discounts = [γ**t for t in range(len(rewards))]
returns = [sum(r*d for r,d in zip(rewards[t:], discounts[:len(rewards)-t]))
for t in range(len(rewards))]
policy_loss = []
for s, a, G in zip(states, actions, returns):
log_prob = policy.get_log_prob(s, a)
policy_loss.append(-log_prob * G) # 负号因为PyTorch最小化损失
optimizer.zero_grad()
loss = torch.stack(policy_loss).mean()
loss.backward()
optimizer.step()
3.2 方差缩减技术
原始REINFORCE的方差很高,实践中常用:
- 状态依赖的基线b(s):通常用价值函数V(s)作为基线
- 优势函数估计:A(s,a) = Q(s,a) - V(s)
- 使用critic网络估计价值函数(引出Actor-Critic方法)
实测技巧:在小型环境中,简单的回报归一化(减去均值除以标准差)就能显著提升训练稳定性。
4. 策略参数化的工程实践
4.1 神经网络结构设计
对于策略网络π_θ(a|s),常见设计选择:
- 连续动作空间:输出高斯分布的均值和方差
python复制class GaussianPolicy(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.fc1 = nn.Linear(state_dim, 64) self.fc2 = nn.Linear(64, 64) self.mean = nn.Linear(64, action_dim) self.log_std = nn.Parameter(torch.zeros(action_dim)) def forward(self, state): x = F.relu(self.fc1(state)) x = F.relu(self.fc2(x)) mean = self.mean(x) std = torch.exp(self.log_std) return torch.distributions.Normal(mean, std) - 离散动作空间:输出softmax概率分布
4.2 训练中的数值稳定性
常见陷阱及解决方案:
- log概率计算下溢:使用log_softmax而非手动计算
- 方差参数爆炸:对log_std添加约束或使用tanh激活
- 梯度爆炸:梯度裁剪(Gradient Clipping)
5. 策略梯度方法的局限性
尽管策略梯度很强大,但在实际项目中我发现几个关键挑战:
- 采样效率低:需要大量与环境交互的样本
- 高方差问题:即使使用基线,训练仍不稳定
- 超参数敏感:学习率、网络结构等对结果影响大
这些限制促使了后续Actor-Critic、PPO等改进算法的出现。在我参与的机器人控制项目中,从原始REINFORCE切换到PPO后,训练时间缩短了约60%。
6. 实战建议与调试技巧
根据多个项目的经验总结:
- 监控指标:除了回报,还要跟踪策略熵、梯度幅值、最大log概率
- 初始探索:开始时增大策略熵鼓励探索,逐渐衰减
- 并行采样:使用多个环境实例并行收集数据加速训练
- 随机种子影响:策略梯度对初始条件敏感,建议多次运行取平均
一个典型的调试过程:
- 如果回报不增长:检查梯度是否更新(参数是否有变化)
- 如果回报波动大:尝试减小学习率或增加批量大小
- 如果策略过早收敛:增加熵系数或探索噪声
最后分享一个在MuJoCo环境中训练HalfCheetah时的心得:当发现智能体"偷懒"获得基础回报时,在奖励函数中添加前进速度的平方项能有效激励学习有意义的移动策略。
