1. 策略梯度方法的核心思想
策略梯度(Policy Gradients)是强化学习中最基础也最重要的算法家族之一。与基于价值函数的方法(如Q-learning)不同,策略梯度直接对策略进行参数化建模和优化。这种方法特别适合处理连续动作空间和高维状态空间的问题,比如机器人控制、游戏AI等场景。
我在实际项目中多次使用策略梯度方法解决控制问题,发现它有几个显著优势:首先,它可以直接学习随机策略,这在部分观测环境中非常有用;其次,它能自然地处理连续动作空间,而基于价值的方法通常需要离散化处理;最后,策略参数化可以引入先验知识,加速学习过程。
1.1 策略参数化表示
策略通常表示为πθ(a|s),其中θ是可学习的参数。常见的选择包括:
-
对于离散动作空间:softmax策略
python复制def softmax_policy(obs, theta): logits = np.dot(obs, theta) probs = np.exp(logits) / np.sum(np.exp(logits)) return np.random.choice(len(probs), p=probs) -
对于连续动作空间:高斯策略
python复制def gaussian_policy(obs, theta): mean = np.dot(obs, theta[:obs_dim]) std = np.exp(theta[-1]) # 保证标准差为正 return np.random.normal(mean, std)
在实际应用中,我通常会在神经网络中实现这些策略,因为神经网络可以自动提取特征并处理高维输入。例如,在Atari游戏中,我们会用CNN处理图像输入;在机器人控制中,则常用MLP处理状态向量。
1.2 策略梯度定理
策略梯度定理是这类算法的理论基础,它给出了目标函数J(θ)(通常是期望回报)关于策略参数的梯度:
∇θ J(θ) = Eπ[∇θ log πθ(a|s) Qπ(s,a)]
这个优雅的公式告诉我们:可以通过增加导致高回报动作的概率,减少导致低回报动作的概率来优化策略。我在实践中发现,理解这个定理对调试算法非常重要——当算法不收敛时,检查梯度计算是否正确往往是第一步。
关键提示:策略梯度估计通常方差很大,这是算法不稳定的主要原因。后续我们会讨论降低方差的技术。
2. 基础REINFORCE算法实现
REINFORCE是最基础的策略梯度算法,它直接使用蒙特卡洛采样得到的回报作为Qπ(s,a)的估计。让我们通过一个完整的Pendulum环境实现来理解它。
2.1 算法伪代码解析
code复制初始化策略参数θ
for 每个训练周期:
采样轨迹τ: s0,a0,r1,s1,a1,...,rT
计算每个时间步的回报Gt = Σγ^(k-t) rk
计算梯度估计 ∇J(θ) ≈ Σ ∇logπ(at|st) Gt
更新参数 θ ← θ + α∇J(θ)
我在实现时发现几个关键点:
- 轨迹需要完整采样到episode结束,不能像DQN那样用片段
- 回报Gt的计算需要考虑折扣因子γ
- 梯度是期望的估计,需要多个episode的平均
2.2 完整PyTorch实现
python复制import torch
import gym
class PolicyNet(torch.nn.Module):
def __init__(self, obs_dim, act_dim):
super().__init__()
self.fc = torch.nn.Sequential(
torch.nn.Linear(obs_dim, 64),
torch.nn.ReLU(),
torch.nn.Linear(64, act_dim)
)
self.log_std = torch.nn.Parameter(torch.zeros(act_dim))
def forward(self, x):
mean = self.fc(x)
std = torch.exp(self.log_std)
return torch.distributions.Normal(mean, std)
env = gym.make('Pendulum-v1')
policy = PolicyNet(env.observation_space.shape[0], env.action_space.shape[0])
optimizer = torch.optim.Adam(policy.parameters(), lr=1e-3)
for epoch in range(1000):
# 采样轨迹
states, actions, rewards = [], [], []
state = env.reset()
done = False
while not done:
dist = policy(torch.FloatTensor(state))
action = dist.sample()
next_state, reward, done, _ = env.step(action.numpy())
states.append(state)
actions.append(action)
rewards.append(reward)
state = next_state
# 计算回报
returns = []
G = 0
for r in reversed(rewards):
G = r + 0.99 * G # γ=0.99
returns.insert(0, G)
returns = torch.FloatTensor(returns)
# 归一化回报(减少方差)
returns = (returns - returns.mean()) / (returns.std() + 1e-8)
# 计算损失
loss = 0
for s, a, G in zip(states, actions, returns):
dist = policy(torch.FloatTensor(s))
log_prob = dist.log_prob(a).sum()
loss += -log_prob * G # 梯度上升
# 参数更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
这个实现有几个值得注意的技巧:
- 对回报进行了归一化处理,这相当于增加了基线(后面会详细讨论)
- 使用高斯策略处理连续动作空间
- 对数概率求和是因为多维动作空间的独立假设
3. 策略梯度的改进技术
基础REINFORCE算法虽然简单,但方差大、样本效率低。下面介绍我在实际项目中验证有效的几种改进方法。
3.1 基线(Baseline)方法
基线是降低梯度估计方差最有效的手段之一。其核心思想是减去一个不改变期望但能降低方差的函数b(s):
∇J(θ) = E[∇logπ(a|s)(Qπ(s,a)-b(s))]
常见选择包括:
- 状态值函数Vπ(s):这就是著名的Actor-Critic算法
- 移动平均回报:简单但有效
- 神经网络拟合的基线函数
我在Pendulum环境中测试发现,添加基线后训练稳定性显著提高。以下是实现片段:
python复制# 在REINFORCE代码基础上添加
baseline = 0
baseline_decay = 0.9 # 移动平均系数
for s, a, G in zip(states, actions, returns):
baseline = baseline_decay * baseline + (1-baseline_decay) * G
advantage = G - baseline
loss += -log_prob * advantage
3.2 优势函数(Advantage Function)
更精细的做法是使用Aπ(s,a)=Qπ(s,a)-Vπ(s)作为权重。这需要同时学习价值函数:
python复制class ValueNet(torch.nn.Module):
def __init__(self, obs_dim):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(obs_dim, 64),
torch.nn.ReLU(),
torch.nn.Linear(64, 1)
)
def forward(self, x):
return self.net(x)
value_net = ValueNet(obs_dim)
value_optim = torch.optim.Adam(value_net.parameters(), lr=1e-3)
# 在训练循环中添加价值函数学习
values = value_net(torch.FloatTensor(states)).squeeze()
deltas = returns - values
value_loss = deltas.pow(2).mean()
value_optim.zero_grad()
value_loss.backward()
value_optim.step()
# 然后使用deltas作为优势估计
advantages = deltas.detach()
这种Actor-Critic架构在实践中表现更好,但需要仔细调整两个网络的学习速率。
3.3 信任域方法(TRPO/PPO)
直接策略更新可能导致策略突变,破坏学习过程。信任域方法通过约束更新幅度来解决这个问题。PPO(Proximal Policy Optimization)是目前最流行的变体:
python复制# PPO的核心裁剪机制
ratio = (new_log_prob - old_log_prob).exp()
clipped_ratio = torch.clamp(ratio, 1-epsilon, 1+epsilon)
loss = -torch.min(ratio * advantage, clipped_ratio * advantage).mean()
我在多个机器人控制任务中对比发现,PPO的训练稳定性显著优于普通策略梯度,特别是对于长周期任务。
4. 实战技巧与调试经验
经过数十个项目实践,我总结了以下关键经验:
4.1 超参数调优指南
| 参数 | 典型值 | 影响 | 调整建议 |
|---|---|---|---|
| 学习率 | 3e-4 ~ 1e-3 | 太大导致不稳定,太小收敛慢 | 从3e-4开始,观察梯度更新幅度 |
| 折扣因子γ | 0.9 ~ 0.99 | 影响未来回报的权重 | 短期任务用较小γ,长期任务接近1 |
| 轨迹长度 | 200~5000步 | 影响方差和计算效率 | 环境episode较长时需分段 |
| 批量大小 | 64~4096 | 影响梯度估计质量 | 根据内存和计算资源调整 |
4.2 常见问题排查
-
策略不更新:
- 检查梯度计算是否正确(反向传播是否被阻断)
- 检查回报是否合理(环境奖励设置是否正确)
- 尝试增大学习率
-
策略过早收敛到次优解:
- 增加探索(如增大策略的初始标准差)
- 尝试熵正则化:
loss -= 0.01 * dist.entropy().mean() - 检查奖励函数设计是否存在局部最优
-
训练不稳定:
- 添加基线或改用Actor-Critic
- 减小学习率
- 改用PPO等信任域方法
4.3 高级技巧
-
状态归一化:对输入状态进行running mean/std归一化能显著提高性能
python复制class RunningNormalizer: def __init__(self, shape): self.mean = np.zeros(shape) self.var = np.ones(shape) self.count = 1e-4 def update(self, x): batch_mean = np.mean(x, axis=0) batch_var = np.var(x, axis=0) delta = batch_mean - self.mean self.mean += delta * len(x) / (self.count + len(x)) self.var = (self.count * self.var + len(x) * batch_var + np.square(delta) * self.count * len(x) / (self.count + len(x))) / (self.count + len(x)) self.count += len(x) def normalize(self, x): return (x - self.mean) / np.sqrt(self.var + 1e-8) -
并行采样:使用多环境并行采样加速数据收集
python复制from multiprocessing import Process, Queue def worker(env_fn, queue): env = env_fn() while True: trajectory = collect_one_episode(env) queue.put(trajectory) envs = [lambda: gym.make('Pendulum-v1') for _ in range(8)] queue = Queue() workers = [Process(target=worker, args=(env, queue)) for env in envs] for w in workers: w.start() # 主训练循环中从queue获取数据
5. 策略梯度的应用场景
策略梯度方法在以下场景表现尤为突出:
5.1 连续控制任务
如机器人 locomotion、机械臂控制等。我曾在六足机器人控制项目中使用PPO算法,成功实现了复杂地形下的稳定行走。关键点在于:
- 使用合适的高斯策略参数化
- 设计合理的奖励函数组合(速度、稳定性、能耗等)
- 添加适当的观测噪声增强鲁棒性
5.2 部分观测环境
当环境不完全可观测时(如遮挡、传感器限制),策略梯度比价值方法更有优势。在一个无人机避障项目中,我们使用LSTM策略网络处理部分观测:
python复制class LSTM_Policy(torch.nn.Module):
def __init__(self, obs_dim, act_dim):
super().__init__()
self.lstm = torch.nn.LSTM(obs_dim, 64, batch_first=True)
self.head = torch.nn.Linear(64, act_dim)
self.log_std = torch.nn.Parameter(torch.zeros(act_dim))
def forward(self, x, h=None):
x, h = self.lstm(x, h)
mean = self.head(x)
std = torch.exp(self.log_std)
return torch.distributions.Normal(mean, std), h
5.3 多智能体系统
策略梯度天然适合多智能体强化学习。我们在群体机器人协作任务中使用了MADDPG算法(策略梯度的多智能体扩展),实现了高效的分布式协作。