2026-06-11
算法
00

目录

轨迹概率计算
数学前提知识
公式推导

策略梯度定理(Policy Gradient Theorem)是强化学习中极为优雅且核心的基石。对于需要处理连续动作空间、复杂动力学系统的任务(例如自主导航系统中的力矩控制或机器人的运动规划)来说,理解这个定理是设计高效RL智能体的前提。本文将详细解释这个定理的数学原理和具体实现。

轨迹概率计算

在一场游戏或者仿真环境中,我们把环境输出的s 与演员(Actor)输出的动作a 全部组合起来,就是一个轨迹,

τ={s1,a1,s2,a2,,st,at}\tau=\{s_{1} , a_{1} , s_{2} , a_{2} , \cdots, s_{t} , a_{t} \}

给定演员的参数θθ,我们可以计算某个轨迹ττ 发生的概率为:

pθ(τ)=p(s1)pθ(a1s1)p(s2s1,a1)pθ(a2s2)p(s3s2,a2)=p(s1)t=1Tpθ(atst)p(st+1st,at)\begin{aligned} {p_{\theta} ( \tau)} & {{}=p \left( s_{1} \right) p_{\theta} \left( a_{1} | s_{1} \right) p \left( s_{2} | s_{1} , a_{1} \right) p_{\theta} \left( a_{2} | s_{2} \right) p \left( s_{3} | s_{2} , a_{2} \right) \cdots} \\ {} & {{}=p \left( s_{1} \right) \prod_{t=1}^{T} p_{\theta} \left( a_{t} | s_{t} \right) p \left( s_{t+1} | s_{t} , a_{t} \right)} \\ \end{aligned}

我们先计算环境输出s1s_1 的概率p(s1)p(s_1), 在根据s1s_1 执行a1a_1 的概率pθ(a1s1)p_θ (a_1|s_1) 。在某一场游戏的某一个回合里面,我们会得到R(τ)R(τ )。我们要做的就是调整演员内部的参数θθ,使得R(τ)R(τ ) 的值越大越好。

所以R(τ)R(τ ) 是一个随机变量。我们能够计算的是R(τ)R(τ ) 的期望值。 给定某一组参数θθ,我们可计算rθr_θ 的期望值为:

Rˉθ=τR(τ)pθ(τ)=Eτpθ(τ)[R(τ)]\bar{R}_{\theta}=\sum_{\tau} R ( \tau) p_{\theta} ( \tau)=\mathbb{E}_{\tau\sim p_{\theta} ( \tau)} [ R ( \tau) ]

我们要最大化期望奖励。因为我们要让奖励越大越好,所以可以使用梯度上升(gradient ascent)来最大化期望奖励。

数学前提知识

现在我们补充一点点数学基础知识: 对于自然对数它的导数是:

log(x)=1x\nabla\operatorname{l o g} ( x )=\cfrac{1} {x}

结合链式法则,如果我们对一个函数取f(x)f(x)对数然后再求导,结果是:

logf(x)=f(x)f(x)\nabla\operatorname{l o g} f ( x )=\cfrac{\nabla f ( x )} {f ( x )}

把分母 f(x)f(x)乘到左边去,把等式左右互换,就得到了大名鼎鼎的 Log-Derivative Trick 恒等式:

f(x)=f(x)logf(x)\pmb{\nabla} \mathbf{f ( x )=f ( x )} \pmb{\nabla} \operatorname{l o g} \mathbf{f ( x )}

除此之外,我们还需要知道: 对于一个连续型随机变量 XX,其概率密度函数为 p(x)p(x)。如果我们想求某个关于 XX 的函数f(X)f(X)的数学期望,其期望与积分的转换公式为:

EXp(x)[f(X)]=+p(x)f(x)dx\mathbb{E}_{X \sim p ( x )} [ f ( X ) ]=\int_{-\infty}^{+\infty} p ( x ) f ( x ) d x
  • EXp(x)[]\mathbb{E}_{X \sim p ( x )} [ \cdot] : 表示在概率分布 p(x)p(x) 下求期望。

公式推导

现在把期望奖励公式和 Log_derivative trick 公式结合进行推导:

Rθ=τR(τ)pθ(τ)=τR(τ)pθ(τ)pθ(τ)pθ(τ)=τR(τ)pθ(τ)logpθ(τ)=Eτpθ(τ)[R(τ)logpθ(τ)]\begin{aligned} {\nabla R_{\theta}} & {{}=\sum_{\tau} R ( \tau) \nabla p_{\theta} ( \tau)} \\ {} & {{}=\sum_{\tau} R ( \tau) p_{\theta} ( \tau) \frac{\nabla p_{\theta} ( \tau)} {p_{\theta} ( \tau)}} \\ {} & {{}=\sum_{\tau} R ( \tau) p_{\theta} ( \tau) \nabla\operatorname{l o g} p_{\theta} ( \tau)} \\ {} & {{}=\mathbb{E}_{\tau\sim p_{\theta} ( \tau)} \left[ R ( \tau) \nabla\operatorname{l o g} p_{\theta} ( \tau) \right]} \\ \end{aligned}

实际上奖励期望值无法直接计算,所以我们用采样的方式采样NN ττ 并计算每一个的值,把每一个的值加起来,就可以得到梯度,即:

Eτpθ(τ)[R(τ)logpθ(τ)]1Nn=1NR(τn)logpθ(τn)=1Nn=1Nt=1TnR(τn)logpθ(atnstn)\begin{aligned} {\mathbb{E}_{\tau\sim p_{\theta} ( \tau)} \left[ R ( \tau) \nabla\operatorname{l o g} p_{\theta} ( \tau) \right]} & {{} \approx\frac{1} {N} \sum_{n=1}^{N} R \left( \tau^{n} \right) \nabla\operatorname{l o g} p_{\theta} \left( \tau^{n} \right)} \\ {} & {{}=\frac{1} {N} \sum_{n=1}^{N} \sum_{t=1}^{T_{n}} R \left( \tau^{n} \right) \nabla\operatorname{l o g} p_{\theta} \left( a_{t}^{n} \mid s_{t}^{n} \right)} \\ \end{aligned}

这里的 logpθ(atnstn)\operatorname{l o g} p_{\theta} \left( a_{t}^{n} \mid s_{t}^{n} \right) 可以通过演员网络根据权重参数θ\theta 计算出动作概率对数。

这个公式推导有个细节,需要对轨迹概率计算公式进行求导:

logpθ(τ)=(logp(s1)+t=1Tlogpθ(atst)+t=1Tlogp(st+1st,at))=t=1Tlogpθ(atst)\begin{aligned} {\nabla\operatorname{l o g} p_{\theta} ( \tau)} & {{}=\nabla\left( \operatorname{l o g} p ( s_{1} )+\sum_{t=1}^{T} \operatorname{l o g} p_{\theta} ( a_{t} | s_{t} )+\sum_{t=1}^{T} \operatorname{l o g} p ( s_{t+1} | s_{t} , a_{t} ) \right)} \\ {} & {{}=\sum_{t=1}^{T} \nabla\operatorname{l o g} p_{\theta} ( a_{t} | s_{t} )} \end{aligned}

因为公式其它几项不带θ\theta, 所以求导为0.实际上要计算梯度,首先我们要收集很多s 与a的对(pair),还要知道这些s 与a 在与环境交互的时候,会得到多少奖励。

θθ+ηRˉθ\theta\gets\theta+\eta\nabla\bar{R}_{\theta}

现在已经知道奖励期望梯度如何计算,下面给出一份代码:

python
import gymnasium as gym import torch import torch.nn as nn import torch.optim as optim from torch.distributions import Categorical # ========================================== # 1. 定义策略网络 (Policy Network) # 对应数学公式中的 π_θ(a|s) # ========================================== class PolicyNetwork(nn.Module): def __init__(self, obs_dim, act_dim): super(PolicyNetwork, self).__init__() # 简单的多层感知机 (MLP) self.net = nn.Sequential( nn.Linear(obs_dim, 64), nn.ReLU(), nn.Linear(64, act_dim) ) def forward(self, state): # 输出每个动作的未归一化概率 (Logits) logits = self.net(state) # 封装为离散的类别分布 (Categorical Distribution) # 这在底层会自动帮我们处理 Softmax 和对数概率的计算 return Categorical(logits=logits) # ========================================== # 2. 训练主循环 # ========================================== def train_policy_gradient(): # 创建环境 env = gym.make('CartPole-v1') obs_dim = env.observation_space.shape[0] # 状态维度:4 (位置, 速度, 角度, 角速度) act_dim = env.action_space.n # 动作维度:2 (向左, 向右) # 初始化策略网络和优化器 policy = PolicyNetwork(obs_dim, act_dim) optimizer = optim.Adam(policy.parameters(), lr=1e-2) gamma = 0.99 # 折扣因子 (Discount factor) for episode in range(500): # 训练 500 个回合 (Episodes) state, _ = env.reset() # 用于记录这一条轨迹 (Trajectory) 的数据 log_probs = [] rewards = [] # ------------------------------------------ # 阶段 A:与环境交互,采样一条完整的轨迹 τ # ------------------------------------------ while True: # 状态转为 Tensor state_tensor = torch.FloatTensor(state).unsqueeze(0) # 策略网络输出动作分布 action_dist = policy(state_tensor) # 根据概率分布采样一个动作 a_t action = action_dist.sample() # 记录该动作的对数概率 log p_θ(a_t|s_t) log_probs.append(action_dist.log_prob(action)) # 环境执行动作,返回下一步状态和奖励 next_state, reward, terminated, truncated, _ = env.step(action.item()) rewards.append(reward) state = next_state # 回合结束(杆子倒了,或达到最大步数) if terminated or truncated: break # ------------------------------------------ # 阶段 B:计算累积折扣回报 (Discounted Reward-to-go) # 对应数学公式中的 R(τ) # ------------------------------------------ returns = [] G = 0 # 从最后一步往前推算,计算每个时间步 t 之后的总回报 for r in reversed(rewards): G = r + gamma * G returns.insert(0, G) returns = torch.tensor(returns) # 【工程Trick】:回报归一化 (Baseline) # 减去均值除以标准差,降低方差,防止梯度震荡,加快网络收敛 returns = (returns - returns.mean()) / (returns.std() + 1e-8) # ------------------------------------------ # 阶段 C:计算策略梯度损失并反向传播 # 对应公式:∇J(θ) = E [ ∇log p_θ(a_t|s_t) * R ] # ------------------------------------------ policy_loss = [] for log_prob, G_t in zip(log_probs, returns): # 因为 PyTorch 是做梯度下降,我们要最大化目标,所以加负号 policy_loss.append(-log_prob * G_t) # 将一个回合中所有时间步的损失求和 loss = torch.cat(policy_loss).sum() # 梯度清零,反向传播,更新网络参数 θ optimizer.zero_grad() loss.backward() # 这里触发 Autograd 机制,计算 ∇_θ optimizer.step() # ------------------------------------------ # 打印训练进度 # ------------------------------------------ if episode % 50 == 0: print(f"Episode {episode} | Total Reward: {sum(rewards)} | Loss: {loss.item():.4f}") env.close() print("Training finished!") if __name__ == '__main__': train_policy_gradient()

本文作者:James

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!