Easy RL 策略梯度入门:REINFORCE算法原理与代码实现
引言:从价值迭代到策略搜索的范式转换
你是否曾在强化学习实践中遇到这些困境:Q-learning在高维动作空间中陷入维度灾难?DQN的ε-贪婪策略导致探索效率低下?当面对连续动作空间(如机械臂控制、自动驾驶)时,基于价值函数的方法往往需要复杂的函数近似或离散化技巧。而策略梯度(Policy Gradient,PG) 算法直接参数化策略函数,通过梯度上升最大化累积回报,为解决这些问题提供了全新思路。
本文将系统讲解策略梯度的奠基性算法——REINFORCE(蒙特卡洛策略梯度),通过数学推导、代码实现和可视化分析,帮助你掌握:
- 策略梯度的核心数学原理与无偏估计
- REINFORCE算法的完整流程与实现技巧
- 基于PyTorch的策略网络设计与训练全流程
- 解决实际问题时的回报归一化、基线设置等关键优化
策略梯度核心原理:从期望回报到梯度上升
策略参数化与目标函数
策略梯度算法直接对策略函数进行参数化表示: $$\pi_\theta(a|s) = P(a|s;\theta)$$ 其中$\theta$为策略网络参数,$s$为状态,$a$为动作。对于离散动作空间,通常采用softmax函数输出概率分布;对于连续动作空间,则常用高斯分布建模。
目标函数定义为策略的期望累积回报: $$J(\theta) = \mathbb{E}{\tau \sim \pi\theta} [R(\tau)] = \sum_\tau P(\tau;\theta) R(\tau)$$ 其中$\tau = (s_1,a_1,r_1,...,s_T,a_T,r_T)$为完整轨迹,$R(\tau)=\sum_{t=1}^T \gamma^{t-1}r_t$为折扣累积回报。
策略梯度的数学推导
通过链式法则计算目标函数梯度: $$\nabla_\theta J(\theta) = \mathbb{E}{\tau \sim \pi\theta} [\sum_{t=1}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot G_t]$$ 其中$G_t = \sum_{k=t}^T \gamma^{k-t} r_k$为从时刻$t$开始的折扣回报。这一重要结论表明:策略梯度可通过采样轨迹的对数概率梯度与累积回报的乘积来估计。
直观理解:若某动作$a_t$在状态$s_t$下产生正回报$G_t$,则通过梯度上升增大$\pi_\theta(a_t|s_t)$的概率;反之则减小其概率。
REINFORCE算法详解:蒙特卡洛策略梯度
算法流程
REINFORCE算法采用蒙特卡洛方法,在完整采样一回合后进行参数更新,具体步骤如下:
关键实现技巧
-
折扣回报计算
采用逆序迭代高效计算:running_add = 0 for i in reversed(range(len(reward_pool))): running_add = running_add * gamma + reward_pool[i] reward_pool[i] = running_add # G_t -
回报归一化
通过标准化处理降低梯度估计方差:reward_pool = (reward_pool - np.mean(reward_pool)) / (np.std(reward_pool) + 1e-9) -
基线减法
引入状态价值函数$V(s)$作为基线(Baseline),将目标改为优势估计$A(s,a)=G_t - V(s_t)$,进一步减小方差。
代码实现:基于PyTorch的REINFORCE
环境准备与依赖安装
项目依赖项:
pip install torch==1.10.0 gym==0.21.0 numpy==1.21.2 matplotlib==3.5.1
策略网络设计
import torch
import torch.nn as nn
import torch.nn.functional as F
class PGNet(nn.Module):
def __init__(self, input_dim, output_dim, hidden_dim=128):
super(PGNet, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim) # 输入层
self.fc2 = nn.Linear(hidden_dim, hidden_dim) # 隐藏层
self.fc3 = nn.Linear(hidden_dim, output_dim) # 输出层
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = torch.sigmoid(self.fc3(x)) # 适用于二值动作空间
return x
完整算法实现
import numpy as np
from torch.distributions import Bernoulli
class PolicyGradient:
def __init__(self, model, memory, cfg):
self.gamma = cfg['gamma'] # 折扣因子
self.device = torch.device(cfg['device'])
self.policy_net = model.to(self.device)
self.optimizer = torch.optim.RMSprop(self.policy_net.parameters(), lr=cfg['lr'])
self.memory = memory # 经验回放缓冲区
def sample_action(self, state):
state = torch.from_numpy(state).float().to(self.device)
probs = self.policy_net(state)
m = Bernoulli(probs) # 伯努利分布采样(适用于二值动作)
action = m.sample().item()
return int(action)
def update(self):
# 从经验池采样数据
state_pool, action_pool, reward_pool = self.memory.sample()
# 计算折扣回报
running_add = 0
discounted_rewards = []
for r in reversed(reward_pool):
running_add = running_add * self.gamma + r
discounted_rewards.insert(0, running_add)
discounted_rewards = torch.tensor(discounted_rewards).to(self.device)
# 回报归一化
discounted_rewards = (discounted_rewards - discounted_rewards.mean()) / \
(discounted_rewards.std() + 1e-9)
# 计算损失并更新参数
self.optimizer.zero_grad()
loss = 0
for i in range(len(reward_pool)):
state = torch.from_numpy(state_pool[i]).float().to(self.device)
action = torch.tensor(action_pool[i]).float().to(self.device)
probs = self.policy_net(state)
m = Bernoulli(probs)
# 策略梯度损失:-log_prob(a) * G_t
loss += -m.log_prob(action) * discounted_rewards[i]
loss.backward()
self.optimizer.step()
self.memory.clear() # 清空经验池
CartPole环境应用示例
import gym
from collections import deque
class Memory:
def __init__(self):
self.state_pool = deque()
self.action_pool = deque()
self.reward_pool = deque()
def push(self, state, action, reward):
self.state_pool.append(state)
self.action_pool.append(action)
self.reward_pool.append(reward)
def sample(self):
return list(self.state_pool), list(self.action_pool), list(self.reward_pool)
def clear(self):
self.state_pool.clear()
self.action_pool.clear()
self.reward_pool.clear()
# 配置参数
cfg = {
'gamma': 0.99,
'lr': 0.01,
'device': 'cpu',
'episodes': 500,
'max_steps': 200
}
# 初始化环境和智能体
env = gym.make('CartPole-v1')
input_dim = env.observation_space.shape[0]
output_dim = 1 # 二值动作空间
model = PGNet(input_dim, output_dim)
memory = Memory()
agent = PolicyGradient(model, memory, cfg)
# 训练主循环
rewards = []
for episode in range(cfg['episodes']):
state = env.reset()
total_reward = 0
for step in range(cfg['max_steps']):
action = agent.sample_action(state)
next_state, reward, done, _ = env.step(action)
agent.memory.push(state, action, reward)
state = next_state
total_reward += reward
if done:
break
agent.update() # 回合结束后更新策略
rewards.append(total_reward)
# 打印训练进度
if episode % 50 == 0:
print(f"Episode {episode}, Avg Reward: {np.mean(rewards[-50:]):.2f}")
实验分析与可视化
训练曲线分析
REINFORCE在CartPole环境中的典型训练曲线如下:
关键观察:
- 初期奖励波动较大(蒙特卡洛估计方差导致)
- 中期奖励稳步上升(策略逐渐优化)
- 后期收敛至最大奖励(CartPole最大步数500)
超参数敏感性分析
| 超参数 | 推荐值范围 | 对性能影响分析 |
|---|---|---|
| 学习率(lr) | 0.001-0.01 | 过小导致收敛慢,过大致使训练不稳定 |
| 折扣因子(γ) | 0.9-0.99 | 过小过度关注即时奖励,过大增加方差 |
| 隐藏层维度 | 64-256 | 过小表达能力不足,过大易过拟合 |
进阶方向与算法改进
方差减小技术
- 优势函数估计:使用时序差分(TD)方法估计$A(s,a) = Q(s,a) - V(s)$,如A2C算法
- 广义优势估计(GAE):结合多步TD和蒙特卡洛的优势估计: $$\hat{A}t^{GAE(\lambda)} = \sum{k=0}^\infty (\gamma\lambda)^k \delta_{t+k}$$
- 异步更新:A3C算法通过多线程并行采样降低梯度估计方差
连续动作空间扩展
对于连续动作空间,策略网络输出高斯分布参数: $$\pi_\theta(a|s) = \mathcal{N}(\mu_\theta(s), \sigma^2 I)$$ 其中$\mu_\theta(s)$为均值向量,通过神经网络直接输出。
总结与展望
REINFORCE作为最基础的策略梯度算法,展示了直接策略优化的强大潜力:
- 优势:天然支持连续动作空间,无需价值函数迭代
- 局限:蒙特卡洛更新导致高方差,收敛速度较慢
后续改进可关注:
- 结合价值函数的演员-评论员(Actor-Critic)架构
- 信任区域策略优化(TRPO、PPO)等稳定性提升方法
- 分布式强化学习框架(如IMPALA)的工程实现
通过掌握REINFORCE算法,你已迈出策略梯度方法的第一步。建议进一步实践PPO算法(当前最流行的策略梯度变种),并尝试在Mujoco等高维连续控制环境中应用。
扩展资源
- 项目代码仓库:https://gitcode.com/datawhalechina/easy-rl
- 推荐论文:
- 《Policy Gradient Methods for Reinforcement Learning with Function Approximation》
- 《Simple Statistical Gradient-Following Algorithms for Connectionist Reinforcement Learning》
- 实践环境:
- OpenAI Gym:经典控制任务
- MuJoCo:连续控制物理仿真环境
行动建议:立即克隆仓库,在CartPole环境复现本文实验,然后尝试修改代码实现带基线的REINFORCE算法,观察方差减小效果!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



