ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从策略梯度到Actor-Critic:强化学习策略优化原理与工程实践

2026/8/14 3:17:37 拓冰建站 浏览量
从策略梯度到Actor-Critic:强化学习策略优化原理与工程实践

1. 项目概述:从“策略梯度”到“演员-评论家”的认知跃迁

在强化学习的探索之路上,我们走过了基础概念与价值迭代的平原,现在即将踏入一片更为崎岖但也更具创造力的高地。如果说前两篇笔记是学习如何“评价”一个动作的好坏(价值函数),那么“强化学习(三)”的核心,就是学习如何“生成”一个好的动作(策略)。这标志着我们从“评论家”转向了“演员”,从被动评估转向主动决策。很多朋友在学完Q-Learning、DQN后,面对策略梯度(Policy Gradient)和演员-评论家(Actor-Critic)框架时,会感到一阵迷茫:公式变复杂了,训练似乎更不稳定了,它到底强在哪里?今天,我就结合自己调参踩坑的经验,把这部分内容掰开揉碎,讲清楚我们为什么要走这条路,以及如何稳健地走下去。无论你是想自己手搓一个玩游戏的AI,还是理解前沿大模型背后的决策逻辑,掌握策略优化方法都是不可或缺的一环。

2. 核心思路解析:为什么我们不再只依赖价值函数?

在深度Q网络(DQN)中,智能体通过一个价值网络来评估在某个状态下每个动作的长期收益(Q值),然后选择Q值最大的动作。这个方法在离散动作空间(比如上下左右)中表现卓越。然而,它存在几个根本性的瓶颈,正是这些瓶颈催生了策略梯度方法的诞生。

2.1 离散与连续动作空间的天堑

想象一下你要控制一个机器人手臂去抓取杯子。手臂的动作是连续的:关节的角度可以是从0度到180度之间的任意值。DQN的做法是,为每一个可能的角度(比如0.1度, 0.2度...)都计算一个Q值,然后选最大的。这显然不现实,因为可能性是无限的。这就是连续动作空间问题。策略梯度方法直接输出一个动作的概率分布(例如,高斯分布的均值和方差),智能体从这个分布中采样得到具体动作,从而天然支持连续控制。

注意:即使对于离散动作(比如游戏中的几个技能),当动作数量非常多时(例如某些策略游戏有上百个技能组合),计算所有动作的Q值也会带来巨大的计算开销。策略梯度方法通过直接优化策略,避免了这种穷举式的评估。

2.2 探索与利用的固有矛盾

DQN采用ε-greedy策略进行探索:大部分时间选择最优动作(利用),小部分时间随机选择动作(探索)。在连续空间,这种“随机”探索效率极低,因为它可能尝试一些明显很差的动作。策略梯度方法则不同,其策略本身是一个概率分布。初期,这个分布方差较大,智能体会进行广泛的探索;随着学习,分布逐渐收紧(方差变小),聚焦于高回报区域。这种探索是定向的、渐进的,更符合学习规律。

2.3 策略梯度:绕开最大值,直接优化目标

DQN的核心是优化Q值估计的准确性(通过最小化时序差分误差),期望准确的Q值能引导出最优策略。这是一个“曲线救国”的方案。策略梯度则更加直接:我们最终关心的是策略π(θ)的性能,即期望总回报J(θ)。那么,何不直接通过梯度上升来优化J(θ)呢?

策略梯度定理给出了一个漂亮的结果:性能指标J(θ)关于参数θ的梯度,可以通过采样轨迹来无偏估计。公式虽然看起来复杂,但其直觉非常清晰:增加高回报轨迹出现的概率,减少低回报轨迹出现的概率。智能体通过实际尝试(采样),获得反馈(回报),然后调整策略参数,使得产生高回报动作的概率增大。

3. 核心细节解析:策略梯度与Actor-Critic的共生关系

理解了“为什么”之后,我们来看“是什么”。策略梯度是基石,Actor-Critic是在此基础上引入“评论家”来降低方差、加速收敛的工程优化。

3.1 策略梯度(REINFORCE)的朴素实现

最基础的策略梯度算法是REINFORCE。它的流程非常直观:

  1. 采样:用当前策略π(θ)与环境交互,生成一条完整的轨迹(s0, a0, r1, s1, a1, r2, ...)。
  2. 计算回报:从轨迹的每一步开始,计算未来累积折扣回报G_t。
  3. 估计梯度:对于轨迹中的每一步(s_t, a_t),计算梯度∇θ log π(a_t|s_t; θ) * G_t。这里的log π(a_t|s_t; θ)是动作的对数概率,G_t是衡量该动作好坏的标尺。
  4. 更新策略:将所有步的梯度求和,进行梯度上升更新。

实操心得:REINFORCE虽然简单,但方差(Variance)极大。因为G_t是从一次采样中得到的,可能运气好轨迹回报高,也可能运气差回报低,这个噪声会传导给梯度,导致训练极其不稳定,收敛缓慢。我在早期实验中经常看到智能体的表现“上蹿下跳”,就是高方差所致。

3.2 引入基线(Baseline):一个简单的方差削减技巧

一个直观的改进是:我们并不关心回报的绝对值,而是关心它相对于“平均表现”的好坏。因此,我们可以从G_t中减去一个基线b(s_t),通常取值为状态价值函数V(s_t)的估计。梯度变为∇θ log π(a_t|s_t; θ) * (G_t - b(s_t))(G_t - b(s_t))可以理解为“优势函数”(Advantage Function)A(s_t, a_t)的估计,表示动作a_t比平均动作好多少。减去基线后,梯度估计的方差显著降低。

3.3 Actor-Critic框架:分工协作的典范

既然我们用到了价值函数V(s)作为基线,何不把它学得更准确一点?于是,Actor-Critic框架应运而生。它包含两个核心组件:

  • 演员(Actor):即策略网络π(a|s; θ),负责根据状态生成动作。它接受“评论家”的评价来更新自己。
  • 评论家(Critic):即价值网络V(s; w),负责评估状态(或状态-动作对)的价值。它通过时序差分(TD)误差来学习,就像DQN一样。

两者的协作流程形成了一个优美的闭环:

  1. 演员根据当前状态s_t,输出动作a_t(或动作分布)。
  2. 环境执行a_t,转移到s_{t+1},给出奖励r_{t+1}。
  3. 评论家评估s_t和s_{t+1}的价值V(s_t)和V(s_{t+1})。
  4. 计算TD误差:δ_t = r_{t+1} + γ * V(s_{t+1}) - V(s_t)。这个δ_t就是优势函数A(s_t, a_t)的一个低方差估计
  5. 更新评论家:以最小化δ_t的平方为目标,更新价值网络参数w。这相当于让评论家更准确地预测回报。
  6. 更新演员:使用梯度∇θ log π(a_t|s_t; θ) * δ_t来更新策略网络参数θ。如果δ_t为正,说明这个动作比预期好,就增加其概率;反之则减少。

提示:这里的δ_t替代了REINFORCE中的(G_t - b(s_t))。由于δ_t基于单步奖励和下一个状态的估计,而非整条轨迹的回报,其方差大大降低,使得Actor-Critic的训练比REINFORCE稳定得多。

4. 实操过程:构建一个简单的连续控制Actor-Critic模型

理论需要落地。我们以经典的Pendulum-v1环境(钟摆立起任务)为例,构建一个处理连续动作空间的Actor-Critic模型。这个环境的动作是施加在钟摆上的力矩,范围是[-2.0, 2.0]。

4.1 网络结构设计

演员网络(Actor): 输入是状态(例如,钟摆的角度、角速度),输出是一个高斯分布的参数。通常我们输出均值(μ)和对数标准差(log_std)。使用对数标准差是为了保证标准差永远是正数。

import torch import torch.nn as nn import torch.nn.functional as F class ActorNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim=256): super(ActorNetwork, self).__init__() self.fc1 = nn.Linear(state_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.mu_head = nn.Linear(hidden_dim, action_dim) # 输出均值 self.log_std_head = nn.Linear(hidden_dim, action_dim) # 输出对数标准差 def forward(self, state): x = F.relu(self.fc1(state)) x = F.relu(self.fc2(x)) mu = torch.tanh(self.mu_head(x)) * 2.0 # 映射到[-2, 2]的动作范围 log_std = self.log_std_head(x) log_std = torch.clamp(log_std, min=-20, max=2) # 防止标准差过大或过小 std = torch.exp(log_std) return mu, std

评论家网络(Critic): 输入是状态,输出是该状态的标量价值V(s)。

class CriticNetwork(nn.Module): def __init__(self, state_dim, hidden_dim=256): super(CriticNetwork, self).__init__() self.fc1 = nn.Linear(state_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.value_head = nn.Linear(hidden_dim, 1) def forward(self, state): x = F.relu(self.fc1(state)) x = F.relu(self.fc2(x)) value = self.value_head(x) return value

4.2 动作采样与对数概率计算

这是策略梯度实现中的关键数学细节。给定均值和标准差,我们如何采样并计算该动作的对数概率?

class Actor: def __init__(self, network): self.network = network def act(self, state): state = torch.FloatTensor(state).unsqueeze(0) mu, std = self.network(state) dist = torch.distributions.Normal(mu, std) # 构建正态分布 action = dist.rsample() # 使用rsample()支持重参数化技巧,便于反向传播 action = torch.tanh(action) # 如果环境要求动作在[-1,1],可再加一层tanh log_prob = dist.log_prob(action).sum(dim=-1) # 计算对数概率,对多维度动作求和 return action.detach().numpy().flatten(), log_prob def evaluate(self, state, action): mu, std = self.network(state) dist = torch.distributions.Normal(mu, std) log_prob = dist.log_prob(action).sum(dim=-1) entropy = dist.entropy().sum(dim=-1) # 熵,可用于鼓励探索 return log_prob, entropy

为什么用rsample()log_prob这是“重参数化技巧”(Reparameterization Trick)的核心。直接从分布中采样(sample())是一个随机操作,梯度无法回传。rsample()将随机性转移到一个独立的噪声变量上,使得采样操作可微。log_prob则是策略梯度公式中∇θ log π(a|s)的计算基础。

4.3 训练循环与核心更新逻辑

我们采用在线更新的方式,即每一步交互后都进行更新(类似于SARSA风格)。

def update(self, state, action, reward, next_state, done): state = torch.FloatTensor(state).unsqueeze(0) next_state = torch.FloatTensor(next_state).unsqueeze(0) action = torch.FloatTensor(action).unsqueeze(0) reward = torch.FloatTensor([reward]) # 1. 更新评论家 (Critic) current_value = self.critic(state) next_value = self.critic(next_state) target_value = reward + self.gamma * next_value * (1 - done) td_error = target_value - current_value critic_loss = F.mse_loss(current_value, target_value.detach()) # 目标值不参与梯度计算 self.critic_optimizer.zero_grad() critic_loss.backward() # 可以添加梯度裁剪,防止爆炸 torch.nn.utils.clip_grad_norm_(self.critic.parameters(), max_norm=0.5) self.critic_optimizer.step() # 2. 更新演员 (Actor) log_prob, entropy = self.actor.evaluate(state, action) # 使用刚更新过的评论家计算新的TD误差,作为优势估计 with torch.no_grad(): current_value_new = self.critic(state) next_value_new = self.critic(next_state) target_value_new = reward + self.gamma * next_value_new * (1 - done) td_error_new = target_value_new - current_value_new actor_loss = - (log_prob * td_error_new.detach()).mean() # 梯度上升,所以是负号 # 可以加入熵正则项,鼓励探索: actor_loss -= self.entropy_coef * entropy.mean() self.actor_optimizer.zero_grad() actor_loss.backward() torch.nn.utils.clip_grad_norm_(self.actor.parameters(), max_norm=0.5) self.actor_optimizer.step()

参数选择与技巧

  • 折扣因子γ:通常设为0.99,让智能体更关注长期回报。
  • 学习率:演员的学习率通常比评论家小一个数量级(例如,演员3e-4,评论家1e-3),因为策略更新比价值估计更敏感。
  • 梯度裁剪:对Actor和Critic网络的梯度进行裁剪(如max_norm=0.5),是稳定训练的关键,能防止因个别大的TD误差导致策略突变。
  • 熵正则项:在actor_loss中加入熵的负加权(-β * entropy),可以显式地鼓励探索,防止策略过早收敛到次优解。系数β需要仔细调整,通常从0.01开始尝试。

5. 高级技巧与演进:从A2C到PPO

基础的Actor-Critic(有时也叫A2C,Advantage Actor-Critic)已经比REINFORCE强很多,但在复杂环境中仍面临挑战,主要是策略更新步长难以确定。步长太大,一次糟糕的更新可能让策略性能崩溃且难以恢复(灾难性遗忘);步长太小,学习速度太慢。

5.1 信赖域与自然策略梯度(TRPO)

TRPO的核心思想是:在更新策略时,要求新旧策略之间的KL散度(一种分布差异度量)不超过一个阈值δ。这相当于为策略更新划定了一个“信赖域”,保证新策略不会偏离旧策略太远。其优化目标复杂,需要计算费舍尔信息矩阵(FIM)或其近似,实现起来比较繁琐。

5.2 近端策略优化(PPO):工程实践的胜利

PPO是TRPO的一个高效近似,它通过一个简单的裁剪(Clipping)目标函数,就达到了约束策略更新幅度的目的,成为当前最流行的策略梯度算法。其目标函数如下:

L(θ) = E_t [ min( ratio_t * A_t, clip(ratio_t, 1-ε, 1+ε) * A_t ) ]

其中,ratio_t = π_θ(a_t|s_t) / π_θ_old(a_t|s_t),是新旧策略概率比。A_t是优势函数估计。

这个公式如何工作?

  • 当A_t > 0(动作好)时,我们希望增加π_θ(a_t|s_t),即让ratio_t增大。但min函数的第一项ratio_t * A_t会无限制增大,而第二项clip(ratio_t, 1-ε, 1+ε) * A_t将ratio_t限制在[1-ε, 1+ε]内。最终取两者最小值,意味着如果ratio_t想超过1+ε,目标函数值会被后者限制住,从而阻止策略过大的更新。
  • 当A_t < 0(动作差)时,我们希望减少π_θ(a_t|s_t),即让ratio_t减小。同样,clip操作会阻止ratio_t小于1-ε,避免策略对该动作的概率降得过低。

实操心得:PPO的超参数ε(通常取0.1~0.2)非常关键。它直接控制了策略更新的“信任区间”。ε太小,策略更新过于保守,学习慢;ε太大,约束力弱,可能不稳定。我通常从0.2开始,如果训练不稳定(回报曲线剧烈震荡),就适当调小。

5.3 优势函数估计的优化

无论是A2C还是PPO,优势函数A_t的估计质量都至关重要。最简单的就是用TD误差δ_t。更常用的方法是广义优势估计(GAE),它是对多步TD误差的指数加权平均,在偏差和方差之间取得了更好的平衡。

A_t^GAE = Σ (γλ)^l * δ_{t+l}, 其中λ是[0,1]之间的参数。

λ=0时,退化为单步TD误差(高偏差,低方差);λ=1时,退化为蒙特卡洛回报(低偏差,高方差)。通常λ取0.95~0.99。在实现PPO时,结合GAE是标准操作,能显著提升样本效率和最终性能。

6. 常见问题与排查技巧实录

在实际训练中,你会遇到各种各样的问题。下面是我总结的一些典型症状和排查思路。

6.1 回报不上升,智能体“摆烂”

  • 症状:训练多个回合后,累计回报始终在很低水平徘徊,没有增长趋势。
  • 排查
    1. 检查奖励设计:首先确认环境奖励是否合理。是否奖励稀疏?正向奖励是否太难获取?尝试在关键子任务上设置稀疏奖励(Dense Reward)。
    2. 检查探索:智能体是否被困在局部最优?增加熵正则项的系数β,或适当增大策略网络输出动作分布的初始标准差。
    3. 检查网络容量:状态是否被正确编码?Actor和Critic网络是否足够深/宽以拟合复杂函数?尝试增加隐藏层神经元数量。
    4. 检查优势估计:Critic网络是否训练正常?观察Critic Loss是否在下降。如果Critic学得不好,给Actor提供的优势信号就是错误的。可以暂时调大Critic的学习率,或先让Critic单独预训练几轮。

6.2 训练不稳定,回报曲线“上蹿下跳”

  • 症状:回报曲线出现剧烈的峰值和谷值,没有平滑上升的趋势。
  • 排查
    1. 梯度裁剪:这是首要检查项。确保对Actor和Critic的梯度都进行了裁剪(clip_grad_norm_)。最大范数通常设在0.5到1.0之间。
    2. 学习率过高:特别是Actor的学习率。尝试将其降低(例如,从3e-4降到1e-4)。
    3. PPO的ε参数:如果使用PPO,ε可能设得太大。尝试减小ε(例如从0.3降到0.15)。
    4. 批次大小(Batch Size):如果使用经验回放池进行小批量更新,批次大小不宜过小。过小的批次会导致梯度估计噪声大。尝试增大批次大小(如从64增到256或512)。

6.3 Critic Loss下降,但Actor Loss不降或为NaN

  • 症状:Critic网络似乎学得很好(Loss稳步下降),但Actor的Loss不下降,甚至变成NaN。
  • 排查
    1. 数值不稳定:检查策略网络输出的对数标准差log_std。如果log_std变得非常小(导致std接近0)或非常大,在计算对数概率log_prob时会出现数值问题。这就是为什么在Actor网络中对log_std进行clamp操作(例如限制在[-20, 2])至关重要。
    2. 概率比(ratio)爆炸:在PPO中,如果新旧策略差异突然变得极大,ratio_t可能接近0或无穷大,导致计算溢出。确保在计算ratio时,对新旧策略的概率都加上一个极小值(eps=1e-8)防止除零,并检查clip操作是否生效。
    3. 优势值尺度:如果优势函数A_t的绝对值过大,会导致梯度爆炸。可以考虑对每批样本的优势函数进行标准化(减去均值,除以标准差),使其均值为0,方差为1。这是一个非常有效的稳定化技巧。

6.4 智能体表现达到平台期后无法突破

  • 症状:回报上升到一定水平后,长时间不再提高。
  • 排查
    1. 探索衰减:熵正则项系数β可能随着训练衰减得太快,导致策略过早确定性化,停止探索。可以尝试使用一个衰减计划,让β随着训练步数缓慢衰减,而不是固定不变。
    2. 网络表达能力:当前网络结构可能已无法表征更优的策略。考虑使用更深的网络,或引入注意力机制等更复杂的结构。
    3. 算法瓶颈:基础的在线Actor-Critic或PPO可能样本效率不够高。可以考虑切换到更先进的算法,如使用经验回放的SAC(Soft Actor-Critic,特别适合连续控制),或分布式训练框架如IMPALA。

为了便于快速诊断,我将上述常见问题及应对策略整理成下表:

症状可能原因排查与解决方向
回报不上升1. 奖励设计不合理
2. 探索不足
3. 网络容量不足
4. Critic学得差
1. 重塑奖励函数
2. 增大熵系数β或初始探索噪声
3. 增加网络层数/宽度
4. 检查Critic学习率、单独预训练Critic
训练不稳定1. 梯度爆炸
2. Actor学习率过高
3. PPO中ε太大
4. 批次大小太小
1. 添加梯度裁剪(norm=0.5~1.0)
2. 降低Actor学习率(如3e-4 -> 1e-4)
3. 减小PPO的ε(如0.3 -> 0.15)
4. 增大批次大小(64 -> 256)
Actor Loss异常1. 数值计算溢出(log_std)
2. PPO概率比异常
3. 优势值尺度太大
1. 对log_std输出进行clamp限制
2. 计算ratio时加eps,确保clip生效
3. 对优势函数进行批次标准化
性能平台期1. 探索过早终止
2. 网络表达能力瓶颈
3. 算法样本效率低
1. 放缓熵系数β的衰减速度
2. 使用更深的网络或先进结构(如ResNet)
3. 尝试SAC、TD3等更高效算法

从基础的策略梯度到强大的PPO,强化学习的策略优化之旅充满了对“探索-利用”和“稳定性-效率”的权衡艺术。没有放之四海而皆准的超参,最好的老师就是实验记录和可视化图表。我的习惯是,任何一次训练都务必记录下关键指标:每轮回报、Critic Loss、Actor Loss、策略熵、平均优势值、梯度范数等。当问题出现时,这些图表是定位根源最有力的工具。理解每个公式背后的直觉,远比死记硬背更重要。当你看到智能体从零开始,通过一次次试错最终学会了一个复杂技能时,那种成就感正是驱动我们在这个领域不断深耕的动力。下一步,我们可以聊聊如何将这些方法应用到多智能体环境,或者如何与大型语言模型结合,赋予AI更复杂的决策能力,那将是另一片广阔的天地。