十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Actor-Critic算法精讲:从策略梯度、TD误差到RLHF与PPO实战

Actor-Critic算法精讲:从策略梯度、TD误差到RLHF与PPO实战 强化学习这两年在业界和学术圈的热度不用多说从游戏博弈到机器人控制再到 ChatGPT 背后的 RLHFReinforcement Learning from Human Feedback人类反馈强化学习几乎处处都能看到它的影子。而不管算法包装成什么样只要深入到大模型对齐那一层就必然会碰到一个绕不开的组合Actor-Critic 算法。网上关于强化学习的资料很多但普遍存在两个问题要么只讲概念不给推导要么直接甩出一堆公式不讲直觉。这篇教程会把演员-评论家算法作为主线从策略梯度为什么需要评论家讲起先把 TD 误差的数学含义拆开再一步步推导 Actor-Critic 的更新公式最后结合 RLHF 场景说明 PPO 在语言模型对齐中的落地方式并给出一份可以运行的 PyTorch 示例代码。适合刚接触强化学习、希望看懂 RLHF 内部原理或者准备在实际项目中使用 Actor-Critic 框架的开发者阅读。1. 强化学习与 RLHF 的关系1.1 什么是强化学习强化学习研究的是一个智能体Agent在与环境Environment交互过程中如何通过不断试错来学习最优策略Policy的问题。每次交互可以拆解为智能体在当前状态 (s) 下根据策略 (\pi(a|s)) 选择一个动作 (a)环境返回一个即时奖励 (r)并转移到新状态 (s)。如此循环智能体的目标不再是最大化某一步的奖励而是最大化长期累积奖励的期望。这个过程和人们常说的“监督学习”有本质区别。监督学习有明确的标准答案模型通过拟合标签来学习输入输出映射而强化学习没有标准答案只有奖励信号。奖励信号往往稀疏、延迟、存在噪声这就导致算法必须解决“信用分配”问题到底哪一步动作导致了最终的高回报传统表格型方法在状态空间较小时可以工作一旦状态空间连续或维度很高就必须借助函数近似例如神经网络。深度强化学习Deep Reinforcement Learning就是“强化学习 深度神经网络”用深度网络来表示策略、价值函数或模型。1.2 从强化学习到 RLHFRLHF 严格来说并不是一种全新的强化学习算法而是一种“训练范式”。它解决的是“如何让模型行为符合人类偏好”的问题。以大语言模型为例普通的监督微调只能让模型学会模仿人类给出的答案但无法真正理解“什么回答更好”。于是研究者设计了一条流程先训练一个奖励模型Reward Model让它学会对人类偏好打分。再用强化学习算法去优化语言模型让模型生成的回答获得更高的奖励分数。在这个流程中语言模型本身就是强化学习里的 Actor它负责生成文本奖励模型输出的分数就是奖励信号。为了让训练稳定通常还会引入一个评论家网络Critic来估计状态价值或动作价值。因此 RLHF 在实现层面大量使用 Actor-Critic 框架尤其是 PPOProximal Policy Optimization算法。这也是本文把“Actor-Critic”和“RLHF”放在一起讲的原因。1.3 容易混淆的概念实际阅读资料时有几组概念经常被混用先在这里做一次区分概念含义常见误区Actor策略网络输入状态输出动作分布被误认为只是“生成动作的模型”Critic价值网络输入状态输出价值估计被误认为必须和 Actor 共享结构TD 误差时序差分误差用于衡量真实奖励与估计之间的偏差被误认为是“优势函数”本身优势函数某个动作相对平均水平的优势A(s,a)Q(s,a)-V(s)被误认为只能用 TD 误差估计RLHF用人类反馈训练奖励模型再用强化学习优化策略被误认为是一种具体算法理解这些概念后再看 Actor-Critic 推导会顺畅很多。2. 策略梯度与 Actor-Critic 的动机2.1 策略梯度为什么直接用奖励会不稳定在策略梯度方法中我们希望最大化期望累积奖励[ J(\theta) \mathbb{E}{\tau \sim \pi\theta} [R(\tau)] ]其中 (\tau (s_0, a_0, r_0, s_1, a_1, r_1, \dots)) 是一条轨迹(R(\tau) \sum_{t0}^{T} \gamma^t r_t)。对参数 (\theta) 求梯度可以得到著名的策略梯度定理[ \nabla_\theta J(\theta) \mathbb{E}{\tau \sim \pi\theta} \left[ \sum_{t0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot R_t \right] ]这里的 (R_t \sum_{kt}^{T} \gamma^{k-t} r_k) 是轨迹从时刻 (t) 起的累积折扣回报。这个公式看起来很简单但直接使用存在一个问题方差极大。因为不同轨迹之间的回报差异可能非常大同一个动作在高回报轨迹和低回报轨迹中都可能出现算法难以判断动作本身是好是坏。为了降低方差通常会给奖励减去一个基线Baseline改成[ \nabla_\theta J(\theta) \mathbb{E} \left[ \sum_t \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot \left( R_t - b(s_t) \right) \right] ]基线 (b(s_t)) 只要不依赖于动作 (a_t)就不会改变梯度的期望但能显著降低方差。一个自然的选择是状态价值函数 (V^\pi(s_t))。于是策略梯度中的“回报减去基线”就变成了“动作价值减去状态价值”也就是优势函数 (A^\pi(s_t, a_t) Q^\pi(s_t, a_t) - V^\pi(s_t))。这正是 Actor-Critic 的思想起点。2.2 从 REINFORCE 到 Actor-CriticREINFORCE 是典型的蒙特卡洛策略梯度算法它必须等到一条轨迹结束后才能计算累积回报。这种做法的优点是估计无偏缺点是方差大、学习效率低。如果能在每个时间步都获取一个“即时反馈”就可以更快更新。于是评论家出现了。评论家网络负责估计价值函数例如 (V(s)) 或 (Q(s,a))。Actor 网络负责生成策略。训练时Actor 根据评论家提供的优势估计更新策略评论家则根据实际观察到的奖励和下一状态的价值来更新自己。两者交替训练这就是 Actor-Critic 框架。从 REINFORCE 到 Actor-Critic核心变化是把“采样完整轨迹再计算回报”改成“用价值函数估计作为回报近似”。这种做法引入了偏差因为价值函数本身估计不准但换来的是方差大幅下降。在深度强化学习实践中方差问题往往比偏差问题更致命因此 Actor-Critic 方法成为主流。2.3 为什么需要 CriticCritic 网络承担了“评估当前策略好坏”的任务。如果没有 CriticActor 就只能依赖真实奖励信号而真实奖励通常是稀疏的。例如机械臂抓取任务只有抓到物体才给 1 奖励其他时候都是 0这种稀疏奖励让策略梯度很难学习。Critic 可以通过学习状态价值为每一步提供一个稠密的“预期收益”信号即使当前没有真实奖励也能通过 (r \gamma V(s) - V(s)) 给出一个更新信号。这就是时序差分Temporal DifferenceTD学习的价值。3. TD 误差概念与公式推导3.1 TD 误差的定义时序差分学习是强化学习中的核心思想之一。它结合了蒙特卡洛采样和动态规划的思想用一步真实奖励加上下一状态的价值估计来更新当前状态的价值估计。TD 误差定义为[ \delta_t r_t \gamma V(s_{t1}) - V(s_t) ]其中(r_t) 是时刻 (t) 执行动作后获得的即时奖励(\gamma) 是折扣因子取值范围通常为 ([0, 1])(V(s_t)) 是当前状态价值的估计值(V(s_{t1})) 是下一状态价值的估计值。如果 (V) 是真实价值函数那么根据贝尔曼方程(r_t \gamma V(s_{t1})) 的期望应该等于 (V(s_t))因此 TD 误差的期望为 0。如果估计有偏差TD 误差就反映了这种偏差。在 Actor-Critic 中TD 误差不仅是评论家的更新目标也可以作为动作优势的近似估计。3.2 TD 误差与优势函数的关系优势函数定义为[ A^\pi(s_t, a_t) Q^\pi(s_t, a_t) - V^\pi(s_t) ]而动作价值 (Q^\pi(s_t, a_t)) 满足[ Q^\pi(s_t, a_t) r_t \gamma V^\pi(s_{t1}) ]将上式代入优势函数[ A^\pi(s_t, a_t) r_t \gamma V^\pi(s_{t1}) - V^\pi(s_t) \delta_t ]因此 TD 误差可以被看作优势函数的一种单步采样估计。当然实际使用中由于价值函数 (V) 是近似估计TD 误差存在偏差但它在每一步都能计算非常适合在线学习。3.3 多步 TD 与 GAE单步 TD 虽然方差低但偏差可能较大尤其是在奖励延迟明显的任务中。为了平衡偏差和方差可以使用多步回报[ A^{(n)}t \sum{k0}^{n-1} \gamma^k r_{tk} \gamma^n V(s_{tn}) - V(s_t) ]更进一步GAEGeneralized Advantage Estimation广义优势估计通过对不同步数的 TD 误差做指数加权平均得到更灵活的优势估计[ A^{GAE}t \sum{l0}^{\infty} (\gamma \lambda)^l \delta_{tl} ]其中 (\lambda \in [0,1]) 控制偏差和方差的权衡。(\lambda 0) 时退化为单步 TD(\lambda 1) 时接近蒙特卡洛估计。PPO 等现代算法普遍使用 GAE就是因为它在稀疏奖励场景下表现更好。关于 GAE 的推导本质上是把多步优势估计按照 (\lambda) 做指数平滑这里不再展开矩阵形式但理解这个加权思路对调参很有帮助。4. Actor-Critic 算法数学推导4.1 目标函数与梯度Actor 的目标是最大化期望累积奖励但为了引入基线我们写成[ \nabla_\theta J(\theta) \mathbb{E}{\pi\theta} \left[ \sum_t \nabla_\theta \log \pi_\theta(a_t|s_t) A(s_t, a_t) \right] ]其中 (A(s_t, a_t)) 代替了原来的回报 (R_t)。这就是带基线策略梯度的一般形式。在实际实现中期望用采样近似因此 Actor 的损失函数一般取负的代理目标[ L_{actor} -\frac{1}{N} \sum_{i1}^{N} \log \pi_\theta(a_i|s_i) \cdot A_i ]如果使用 TD 误差作为优势估计则 (A_i) 用 (\delta_i r_i \gamma V_{\phi}(s_{i1}) - V_{\phi}(s_i)) 代替。但单步 TD 偏差较大所以实际项目中更常用 GAE 或使用评论家输出 (V(s)) 计算多步优势。4.2 评论家的目标函数评论家的目标是让价值函数估计更准通常使用均方误差MSE损失[ L_{critic} \frac{1}{N} \sum_{i1}^{N} \left( V_\phi(s_i) - \hat{R}_i \right)^2 ]其中 (\hat{R}_i) 是目标回报例如[ \hat{R}i r_i \gamma V{\phi_{target}}(s_{i1}) ]需要注意评论家网络 (V_\phi) 如果和目标回报使用同一组参数会造成自举偏差的累积。因此很多实现会使用一个目标网络Target Network或延迟更新机制。在 PPO 中由于策略更新幅度被限制评论家网络通常直接和 Actor 共享部分底层特征或完全独立具体取决于状态空间和动作空间的复杂度。4.3 Actor-Critic 更新算法流程一个标准的 Actor-Critic 循环可以写成下面的伪代码初始化 Actor 网络 π_θ 和 Critic 网络 V_φ for 每个回合: 初始化状态 s for 每个时间步 t: 根据 π_θ(·|s) 采样动作 a 执行动作 a观察奖励 r 和下一状态 s 计算 TD 误差: δ r γ V_φ(s) - V_φ(s) 存储 (s, a, r, s, δ) 更新 Critic: φ ← φ - α_c * ∇_φ (δ)^2 更新 Actor: θ ← θ α_a * ∇_θ log π_θ(a|s) * δ s ← s实际深度强化学习中通常不是单步更新而是先收集一批数据再用小批量梯度更新类似于经验回放。为什么需要批量因为单个样本的 TD 误差噪声太大直接在线更新会让网络参数震荡。4.4 从 Actor-Critic 到 PPOActor-Critic 框架虽然有效但直接使用策略梯度更新 Actor 时如果学习率设置不当一次更新过大策略会发生突变导致训练崩溃。PPO 的解决方案是裁剪代理目标[ L^{CLIP}(\theta) \mathbb{E} \left[ \min\left( r_t(\theta) A_t, ; \text{clip}(r_t(\theta), 1-\epsilon, 1\epsilon) A_t \right) \right] ]其中 (r_t(\theta) \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}) 是新旧策略的比率。通过裁剪PPO 限制每次更新的幅度从而保持训练稳定。PPO 仍然使用 Actor-Critic 结构Critic 估计状态价值Actor 输出策略分布。这也是 RLHF 中最常用的强化学习算法。5. RLHF 中的 Actor-Critic 应用5.1 RLHF 训练流程拆解RLHF 的完整流程可以分成三个阶段监督微调SFT先让语言模型具备基础对话能力。奖励模型训练收集人类对多个回答的排序或打分训练一个奖励模型用来代替人类的实时反馈。强化学习优化用 PPO 等算法以奖励模型给出的分数作为奖励信号更新语言模型策略。在第三个阶段Actor 就是语言模型本身它的输入是提示Prompt输出是生成的整段文本。Critic 则是一个价值网络输入状态通常是提示 已生成文本的某种嵌入到当前步为止的上下文输出一个标量价值。因为生成阶段被建模为马尔可夫决策过程每一步生成一个 token 相当于一个动作。5.2 为什么 RLHF 要用 PPO 而不是直接策略梯度直接使用原始策略梯度在语言模型场景下有几个问题语言模型参数规模庞大一次采样一条完整回答成本很高逐 token 的奖励非常稀疏只有整个回答生成完毕才能得到奖励模型的分数如果单步更新太大模型会产生语法崩坏、重复冗余的输出。PPO 通过重要性采样和裁剪解决了更新幅度问题同时利用 Critic 对每个 token 的状态价值进行估计能够把“整段奖励”逐 token 分解成优势信号从而更新每一步的 token 生成策略。此外PPO 还加入 KL 散度惩罚项防止模型为了奖励模型的高分而偏离原始 SFT 模型太远。KL 惩罚的实际作用可以理解为“不要为了分数牺牲可读性”。5.3 PPO 在语言模型中的损失函数完整 PPO 损失通常包含三个部分Actor 的裁剪策略目标Critic 的价值函数损失熵正则Entropy Bonus和 KL 惩罚项。可以写成[ L^{PPO} - L^{CLIP}(\theta) c_1 L^{VF}(\phi) - c_2 \mathcal{H}[\pi_\theta] \beta \cdot \text{KL} ]其中 (L^{VF}) 是评论家价值损失(\mathcal{H}) 是策略熵KL 项衡量当前策略与参考策略一般是 SFT 模型的分布距离。实际工程中各项系数 (c_1, c_2, \beta) 都需要针对具体任务调整。一个常见问题是KL 惩罚加在奖励上还是加在损失函数上两种方式都存在。把 KL 惩罚加到实时奖励上更直观PPO 采样时每个 token 奖励可以设置为[ r_t^{total} r_t^{reward_model}(samples) \cdot \mathbb{I}(t T) \beta \cdot \log \frac{\pi_{\theta}(a_t|s_t)}{\pi_{ref}(a_t|s_t)} ]这里只有最后一个 token 收到奖励模型的标量奖励其余 token 只收到 KL 惩罚信号。但实际实现中强化学习训练时整个序列的每个 token 位置都会计算 Critic 的价值因此需要把序列级别的奖励映射到每一个 token 位置。这也是 RLHF 训练代码中最容易踩坑的地方。5.4 一个简化的 RLHF 训练伪代码# 伪代码仅用于理解流程不直接可运行 for prompt in dataset: # 1. Actor 生成回答 response actor.generate(prompt) # 2. 计算每个 token 的 logprob 和 KL log_probs actor.get_logprobs(prompt, response) ref_log_probs ref_model.get_logprobs(prompt, response) kl log_probs - ref_log_probs # 3. 奖励模型打分整段分数 reward_score reward_model(prompt, response) # 4. 将整段奖励分配到每个 token常见做法最后一个 token 得到奖励 token_rewards torch.zeros_like(log_probs) token_rewards[-1] reward_score - beta * kl.mean() token_rewards[:-1] - beta * kl[:-1] # 5. 用 GAE 计算优势 advantages gae(values, token_rewards, masks) # 6. 更新 Actor 和 Critic actor_loss clip_actor_loss(log_probs, old_log_probs, advantages) critic_loss mse_loss(values, returns) loss actor_loss critic_weight * critic_loss - entropy_weight * entropy loss.backward() optimizer.step()注意真实 RLHF 代码会比这段复杂得多包括共享内存采样、微批量更新、动态 KL 系数、长度归一化奖励等。理解流程后再去看开源实现会轻松很多。6. 最小可运行示例PyTorch 实现 Actor-Critic接下来给出一份可以直接运行的 Actor-Critic 示例代码环境使用 OpenAI Gym 的 CartPole-v1。这个任务相对简单不需要 GPU适合验证算法流程。代码核心思路是用一个共享两层全连接网络分别输出策略分布参数和价值标量通过 TD 误差更新。6.1 环境准备本示例建议使用以下环境Python 3.8 及以上PyTorch 1.13 或 2.xgymnasium 0.28 或以上注意新版已从 gym 改名为 gymnasium安装命令pip install torch gymnasium如果你的环境已经安装了老版本gym也可以跳过安装但需要将代码导入部分改为import gym。版本差异不影响算法逻辑。6.2 完整代码# 文件路径actor_critic_cartpole.py import torch import torch.nn as nn import torch.optim as optim import gymnasium as gym import math class ActorCritic(nn.Module): 共享底层特征的 Actor-Critic 网络。 Actor 输出动作概率的 logitsCritic 输出状态价值 V(s)。 def __init__(self, state_dim, action_dim, hidden_dim128): super(ActorCritic, self).__init__() self.fc1 nn.Linear(state_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.actor_head nn.Linear(hidden_dim, action_dim) self.critic_head nn.Linear(hidden_dim, 1) def forward(self, state): x torch.relu(self.fc1(state)) x torch.relu(self.fc2(x)) policy_logits self.actor_head(x) value self.critic_head(x) return policy_logits, value def get_action(self, state): policy_logits, value self.forward(state) dist torch.distributions.Categorical(logitspolicy_logits) action dist.sample() log_prob dist.log_prob(action) return action.item(), log_prob, value def train(env, agent, optimizer, gamma0.99, max_steps1000): 单回合训练采集轨迹计算 TD 误差并更新网络。 log_probs [] rewards [] values [] dones [] state, _ env.reset() state torch.tensor(state, dtypetorch.float32) total_reward 0 for step in range(max_steps): action, log_prob, value agent.get_action(state) next_state, reward, terminated, truncated, _ env.step(action) done terminated or truncated log_probs.append(log_prob) values.append(value) rewards.append(reward) dones.append(done) state torch.tensor(next_state, dtypetorch.float32) total_reward reward if done: break # 计算 TD 误差和 Actor/Critic 损失 returns [] G 0.0 for i in reversed(range(len(rewards))): G rewards[i] gamma * G * (1 - dones[i]) returns.insert(0, G) returns torch.tensor(returns, dtypetorch.float32) values_tensor torch.cat(values).squeeze() log_probs_tensor torch.stack(log_probs) # Critic 损失价值估计与累计回报之间的 MSE critic_loss nn.functional.mse_loss(values_tensor, returns) # Actor 损失策略梯度的负对数似然 * 优势用 TD 误差近似 advantages returns - values_tensor.detach() actor_loss -(log_probs_tensor * advantages).mean() loss actor_loss critic_loss optimizer.zero_grad() loss.backward() optimizer.step() return total_reward if __name__ __main__: env gym.make(CartPole-v1) state_dim env.observation_space.shape[0] action_dim env.action_space.n agent ActorCritic(state_dim, action_dim) optimizer optim.Adam(agent.parameters(), lr1e-3) episodes 500 for episode in range(episodes): reward train(env, agent, optimizer) if (episode 1) % 20 0: print(fEpisode {episode 1}, total reward: {reward})6.3 代码说明这段代码虽然简单但完整展示了 Actor-Critic 的核心流程。ActorCritic类中actor_head输出动作分布的 logitscritic_head输出状态价值。get_action通过 Categorical 分布采样动作返回动作、动作对数概率和当前状态价值估计。训练函数中先与环境交互收集一整条轨迹然后在轨迹结束时计算累积回报G并把累积回报作为 Critic 的回归目标。优势函数使用returns - values.detach()这里并没有直接用 TD 误差而是用蒙特卡洛回报近似优势因为单步 TD 在 CartPole 上也可以运行但方差更大。如果你希望观察 TD 误差的作用可以把优势换成rewards gamma * next_value - value注意需要额外计算下一状态价值。运行后如果网络正常收敛你会在 200 到 400 个回合之间看到回报稳定在 200 左右CartPole 的最大步数限制通常为 200。如果你的代码在 100 回合内回报不升反降一般是因为学习率过大或优势计算错误。6.4 扩展到 PPO 需要考虑什么上面的示例是单步更新的“原始 Actor-Critic”与 PPO 还有差距。要把它改成 PPO需要额外做几件事保存每步的old_log_prob使用 GAE 计算优势对 Actor 损失进行裁剪多轮小批量更新而不是每回合只更新一次增加熵正则项鼓励探索。如果这篇文章发布后大家感兴趣我可以再单独写一篇“从零实现 PPO”的教程把这三处改造逐一拆开。现在先把 Actor-Critic 的基础打好。7. 常见问题与排查思路问题现象常见原因解决思路训练一开始回报就在下降然后陷入震荡学习率过大Actor 更新步长过大调小学习率或使用 PPO 裁剪机制回报一直很低但 Critic 损失很小Critic 已经收敛但 Actor 策略陷入局部最优增大熵正则系数或增加动作探索优势值波动非常大梯度爆炸奖励尺度差异过大或 GAE 中的 lambda 设置不当对奖励做归一化例如除以奖励均值调整 gamma 和 lambdaRLHF 训练中模型输出变得重复、死板KL 惩罚过小模型过度优化奖励模型增大 KL 惩罚系数或使用动态 KL 系数语言模型生成退化输出无意义符号PPO 更新时熵正则系数为 0策略过早确定添加熵正则并监控 token 级别的熵多个进程采样时动作分布不一致随机种子未固定或模型权重不同步对齐随机种子统一初始化权重Actor 和 Critic 共享网络导致两者梯度冲突两个任务的梯度方向不一致使用双头网络但底层分离或使用两个独立网络训练不稳定损失出现 NaN数值溢出通常是 log 概率为 0 或梯度爆炸给 log_prob 加 epsilon使用梯度裁剪这些坑在学术示例和实际项目中都很常见。特别是 RLHF 场景问题往往不是算法本身而是奖励设计和 KL 约束没有调好。建议每训练一段时间就手动查看模型生成文本不要只盯着奖励曲线。8. 最佳实践与工程建议8.1 优势函数与价值网络的设计在 Actor-Critic 框架中Critic 的作用是为 Actor 提供低方差的学习信号。因此价值网络的预测准确性直接影响训练效果。建议在同一个 batch 中Critic 可以多更新几次而 Actor 每次更新幅度不要太大。在 PPO 中Critic 通常使用与 Actor 相同的 batch 数据但损失函数权重可以独立调节。如果 Critic 和 Actor 使用共享底层特征需要小心梯度竞争。很多成熟实现例如 Stable-Baselines3默认使用两个独立的 MLP或共享一个 Encoder但分开 Head。8.2 奖励工程设计强化学习对奖励尺度极其敏感。RLHF 中奖励模型输出的分数范围可能很大直接在多个任务上使用会不稳定。一种常见做法是对奖励做标准化z-score或者除以奖励标准差。但也不要过度归一化否则会损失区分度。对于语言模型还有一种有效做法是按回答长度归一化奖励避免模型通过生成超长文本来刷分。这是工业界总结出的宝贵经验在学术论文中很容易被忽略。8.3 探索与利用的平衡Actor-Critic 的本质是“边探索边利用”策略熵正则项是控制探索能力的关键。熵太大会导致策略过于随机无法收敛熵太大会导致过早收敛到局部最优。实践中一般将熵系数设置为 0.01 到 0.001并在训练后期线性衰减。监控每批次策略熵的平均值如果熵突然跌到 0说明策略几乎变成了确定性策略这在许多任务中是危险的。8.4 RLHF 工程中的安全与合规RLHF 的目的是让模型行为对齐人类偏好但“人类偏好”本身带有主观性必须遵守法律法规和伦理边界。在训练奖励模型时要使用合法合规的数据集避免通过对抗样本或恶意提示来“攻击”奖励模型。不要试图让模型绕过安全限制也不要让模型在敏感领域产生虚假信息。工程上RLHF 训练需要在受控环境中进行设置人工审核环节对于人工反馈数据要做匿名化处理并确保数据采集获得用户授权。8.5 代码可持续性强化学习实验代码比普通训练代码更容易腐化。建议从一开始就把“环境配置”“采样循环”“模型更新”拆成独立模块。采样环境使用向量化环境例如gymnasium.vector可以提升训练吞吐量。模型定义和损失函数用独立函数封装方便切换算法。日志系统记录每个 step 的奖励、价值损失、策略熵、KL 散度这能在排错时省下大量时间。保存模型时除了权重还要保存 optimizer 状态、随机种子和超参数否则无法复现实验结果。8.6 性能优化CartPole 这类小环境用单进程训练没问题但真实 RLHF 任务通常要并行采样。语言模型生成文本很慢一般使用 vLLM 等推理框架加速采样Critic 的价值网络可以用单一的 GPU 作为独立服务。数据传输建议使用共享内存队列避免序列化开销。在更新阶段Actor 和 Critic 可以轮流更新也可以同时更新。PPO 通常采用“先收集大量样本再多次更新”的方式因此采样进程和训练进程分离是必要的。如果你需要在多机环境训练还涉及参数同步和通信开销建议先单机多卡跑通再扩展。9. 总结与学习建议Actor-Critic 算法的价值在于它为深度强化学习和 RLHF 提供了一套稳定的训练框架。理解了 Actor 输出策略、Critic 输出价值、TD 误差充当学习信号这三者之间的关系就相当于打通了强化学习的主流脉络。在此基础上继续学习 GAE、PPO、TRPO、SAC 会更快。RLHF 本质上是 Actor-Critic 在语言模型对齐中的应用核心难点并不在策略梯度本身而在于奖励模型的质量、KL 约束的调节以及大规模分布式训练工程。如果你想继续深入建议按以下路线实践先手动推导一遍策略梯度定理和 TD 误差公式运行本文的 CartPole 示例修改优势估计方式观察方差变化阅读 Stable-Baselines3 的 PPO 源码逐行对照公式用开源 RLHF 框架如 TRL、DeepSpeed-Chat跑一个小模型查看训练日志和生成效果。强化学习是一门“看公式觉得懂了写代码立刻懵”的学问但多写几次代码多调几次参数后直觉会逐渐建立起来。希望这篇教程能成为你理解 Actor-Critic 和 RLHF 的第一块垫脚石遇到问题也欢迎在评论区讨论。
返回列表