尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

RLHF核心算法解析:从Actor-Critic到PPO

RLHF核心算法解析:从Actor-Critic到PPO 学强化学习的同学到RLHF阶段绕不开演员-评论家算法也就是Actor-Critic。配合TD误差和策略梯度正好是PPO那套更新逻辑的底层。很多人一看到这几个词以为又冒出来一个新算法。其实RLHF不是把语言模型换成另一个强化学习模型而是在已有奖励模型的基础上用强化学习算法去微调策略模型。PPO就是目前最常用的一种策略优化器它的框架本身就属于Actor-Critic。下面我会从RLHF的问题入手拆解Actor-Critic的结构、TD误差的来龙去脉以及actor和critic更新公式的推导过程最后补一些实际训练时容易踩的坑。1. RLHF里的策略优化为什么是Actor-Critic1.1 RLHF没有重新发明强化学习算法RLHF的完整流程一般包括四段先有一个预训练语言模型再做有监督指令微调然后训练一个奖励模型最后用强化学习算法根据奖励模型调整策略。前两步为后面的训练准备基础第三步把人类偏好压缩成标量奖励第四步才是真正的强化学习优化环节。很多初学者会以为RLHF内部包含一种特殊的“RLHF算法”。实际上RLHF是一个训练范式它依赖的强化学习算法更多是PPO、REINFORCE等常见方法。PPO全称是近端策略优化是Actor-Critic框架下的一种实现。所以理解RLHF其实要先理解Actor-Critic否则看PPO的loss时根本不知道每个项从哪里来。这个环节解决的问题很具体奖励模型已经可以给一个回复打分了但我们不能直接用梯度上升去更新语言模型。奖励模型不是语言模型生成器的可微函数。你输入文本拿到标量中间隔着离散token采样整体不可导。所以只能把语言模型当成一个策略网络通过采样生成回复再用奖励分数和优势估计去调整分布。这就是强化学习在RLHF中的位置。1.2 演员生成动作评论家评估状态先给Actor-Critic一个直观理解。actor是策略网络在RLHF里就是被微调的语言模型负责根据输入决定下一步生成什么token。critic是价值网络负责预测当前状态最终能拿到多少未来奖励输出是一个标量。actor像运动员critic像教练。教练不会替运动员打球但会根据场上情况判断一个动作是否值得做。训练时actor先采样动作critic对该动作给出价值判断两者结合起来更新。critic的价值估计能起到baseline的作用减少actor策略梯度的方差。如果不使用baselineactor更新时会非常不稳定。因为同一动作在不同轨迹中的回报差异很大很容易把原本不错的动作误判成差动作。在语言模型场景中actor和critic的输入通常一样一段prompt和已经生成的token序列。actor输出下一个token的概率分布critic输出当前状态的价值。两者输出维度不同这一点第一次接触代码时很容易搞混。2. 把强化学习术语翻译成文本生成任务2.1 状态、动作、奖励分别对应什么在标准强化学习里智能体每一步观察状态选择动作环境反馈奖励并转移状态。放到RLHF的文本生成任务中每个元素都有对应关系。状态s对话历史加上模型已经生成的部分例如“用户提问...\n助手...”。动作a在某个位置的下一步选择。如果按token级别拆分动作就是下一个token如果按句子级别拆分动作就是完整回复。PPO实现中通常按token级别的概率计算方便对齐损失函数。奖励r奖励模型给出的分数表示这段回复被人类偏好的程度。中间每个token不一定都有奖励通常整段回复结束时才给一个最终分数中间步骤奖励为0。折扣因子γ控制未来奖励的衰减。因为中间奖励稀疏γ一般取接近1的值让模型把整段回复的结果回溯到每一步。理解这个映射很重要。很多人写代码时不知道actor该输出什么、critic该输出什么就是因为没有把token序列看作一个马尔可夫决策过程。2.2 策略、状态价值、动作价值和优势策略π_θ(a|s)就是语言模型在给定上下文时输出下一个token的概率分布。θ是模型参数。状态价值V(s)表示从状态s开始按照当前策略继续生成到最后能获得的期望折扣回报。critic要逼近的正是这个V(s)。动作价值Q(s,a)则是在状态s下选择了动作a之后再按当前策略继续生成能获得的期望折扣回报。有了这两个价值就能定义优势函数$A(s,a) Q(s,a) - V(s)$直观含义是在当前状态下选择动作a相比于按当前策略的平均水平到底好多少。如果优势大于0说明这个动作值得提高概率如果小于0说明这个动作比平均差应该降低概率。状态价值和动作价值不一样。状态价值把当前状态下所有动作按策略概率加权平均动作价值是针对某个确定动作的。实际计算中不需要显式估计Q可以通过TD误差或GAE来逼近优势这是下一部分的内容。3. TD误差连接“预测”和“现实”的桥梁3.1 为什么不能一直等蒙特卡洛回报在文本生成任务中一个回复可能有几十到几百个token。如果采用蒙特卡洛方法需要等整段回复生成完拿到最终奖励再把这个奖励当作每一步动作的回报。这样做的问题在于方差很大。同样的prompt模型可能生成完全不同的回复风格整段奖励波动也会很大。单次样本很难区分“这一步确实生成得好”和“这次只是随机运气好”。蒙特卡洛的另一个问题是训练效率低。每一步都要等整条轨迹走完无法一有反馈就更新。对于语言模型这种高维参数模型等几百个token只更新一次样本利用率很低。TD方法即时间差分方法只需要一步或几步的真实奖励然后用价值网络对下一状态的估计来补充剩余部分。这种“用预测更新预测”的做法被称为自举。它的方差比蒙特卡洛小但会引入一定的估计偏差。Actor-Critic里大量使用TD类更新就是想在偏差和方差之间找一个平衡点。3.2 TD误差的数学定义最基础的TD(0)更新使用下面的目标$V_{\text{target}}(s_t) r_t \gamma V(s_{t1})$其中$V(s_{t1})$是critic对下一状态的价值预测$r_t$是当前一步拿到的即时奖励。TD误差定义为$\delta_t r_t \gamma V(s_{t1}) - V(s_t)$这个δ_t的含义是当前价值估计与“真实奖励下一状态估计”之间的差。critic的训练目标就是让δ_t尽可能接近0。用均方误差表示$L_C \frac{1}{2}\mathbb{E}\left[\left(r_t \gamma V_{\text{target}}(s_{t1}) - V_{\phi}(s_t)\right)^2\right]$需要注意当使用TD方法更新critic时$r_t \gamma V_{\text{target}}(s_{t1})$这一部分通常不参与梯度回传在PyTorch里需要做detach。否则critic的更新会让target也随参数变化导致优化目标不固定训练不稳定。3.3 从TD误差到优势估计动作价值Q(s_t,a_t)无法直接观测但TD误差可以看作优势函数的一个估计。更准确地说在策略评估收敛的条件下TD误差的期望等于优势函数$A(s_t,a_t) \approx \mathbb{E}[\delta_t | s_t, a_t]$因此actor更新可以用δ_t代替优势函数A$\nabla J(\theta) \approx \mathbb{E}\left[\nabla_{\theta}\log\pi_{\theta}(a_t|s_t)\delta_t\right]$在实际PPO实现中通常不直接使用单步TD误差而是使用GAE广义优势估计对多步TD误差进行加权平均。这样做既能保留单步更新效率又能减少方差。4. Actor和Critic的更新公式到底怎么推4.1 Critic的梯度让价值预测追TD targetcritic的参数记为φ输入状态输出V_φ(s)。我们希望V_φ能逼近真实的状态价值但在强化学习中并没有真实价值可以直接拿只能用TD target当老师。定义critic损失$L_C(\phi) \frac{1}{2}\left( r_t \gamma V_{\bar{\phi}}(s_{t1}) - V_{\phi}(s_t) \right)^2$这里$V_{\bar{\phi}}(s_{t1})$表示用旧参数或detach后的目标网络计算避免梯度同时流向两个时间步。对φ求梯度$\nabla_{\phi} L_C -\left( r_t \gamma V_{\bar{\phi}}(s_{t1}) - V_{\phi}(s_t) \right) \nabla_{\phi} V_{\phi}(s_t)$更新方向是让$V_{\phi}(s_t)$向TD target靠近。训练初期critic loss会快速下降之后缓慢逼近。如果critic loss一直不平滑或不停震荡先检查value网络输出尺度、reward分数尺度和学习率。这三个因素比模型结构更容易让critic失控。4.2 Actor的策略梯度为什么会出现log概率actor的目标是最大化期望回报$J(\theta) \mathbb{E}{\tau \sim \pi{\theta}}\left[\sum_{t} r_t\right]$直接对θ求梯度很麻烦因为期望中的采样分布也依赖θ。策略梯度定理给出了一个便于采样的形式$\nabla_{\theta} J(\theta) \mathbb{E}\left[\sum_t \nabla_{\theta}\log\pi_{\theta}(a_t|s_t) Q(s_t,a_t)\right]$这里的logπ是关键。因为$\nabla_{\theta} \pi_{\theta}(a|s) \pi_{\theta}(a|s) \nabla_{\theta}\log\pi_{\theta}(a|s)$所以对概率取log后梯度变成对动作的“指示”如果一个动作带来的回报高就朝提高其概率的方向走。采样时用策略分布生成一批动作然后用上式估计梯度。这个估计是无偏的但方差较高所以需要引入baseline。如果将Q(s,a)替换为优势A(s,a)Q(s,a)-V(s)梯度变为$\nabla_{\theta} J(\theta) \approx \mathbb{E}\left[\nabla_{\theta}\log\pi_{\theta}(a_t|s_t) A(s_t,a_t)\right]$减去V(s)不改变期望因为对同一状态下所有动作$\mathbb{E}{a\sim\pi}[\nabla{\theta}\log\pi_{\theta}(a|s)V(s)]0$。这一点可以自己验算对策略概率归一化条件求导会得到这个期望为零。baseline的作用是降低方差而不是改变优化方向。4.3 Actor的损失函数和熵正则用上面的梯度可以设计actor损失$L_A(\theta) -\mathbb{E}\left[\log\pi_{\theta}(a_t|s_t) A(s_t,a_t)\right]$因为梯度下降求的是最小值所以这里加负号。如果优势为正损失会倾向于提高对应动作的概率如果优势为负则降低对应动作概率。为了防止模型过早陷入单一模式很多实现还会加熵正则$L_{\text{total}} L_A c_{\text{ent}} \cdot \mathbb{E}\left[H(\pi_{\theta}(\cdot|s_t))\right]$熵H大于0最大化熵可以让token分布不要过于极端。判断熵是否合理可以看训练中entropy是否下降过快。如果下降太快模型可能已经开始过度自信需要降低学习率或调整KL权重。5. 从Actor-Critic到PPO公式发生了什么变化5.1 为什么RLHF里几乎都用PPO标准Actor-Critic每次更新后都会改变策略分布下一步采样就要重新开始。如果学习率大策略变化幅度会很大语言模型一旦被推到某个极端分布后面很难拉回来。PPO在actor更新时加入概率比和clip限制让每次更新幅度可控。RLHF除了限制单次更新幅度还经常引入一个参考策略模型。当前策略不能离参考策略太远否则会丢失语言能力和指令理解能力。常用做法是在目标函数中加入KL惩罚项$-\beta \cdot KL(\pi_{\theta} | \pi_{\text{ref}})$β控制让模型保持“像原来模型”的强度。β太小模型会只顾奖励模型高分输出可能出现重复、模式化或不符合事实的内容。β太大优化效果不明显奖励提升很慢。实际训练中经常要反复试这个系数。5.2 PPO的actor目标具体长什么样设旧策略为$\pi_{\theta_{old}}$新策略为$\pi_{\theta}$。定义重要性采样比例$\rho_t \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}$用旧策略采样得到轨迹再按这个比例估计新策略下的回报。PPO的actor目标为$L^{clip}(\theta) \mathbb{E}\left[\min\left(\rho_t A_t, \operatorname{clip}(\rho_t, 1-\epsilon, 1\epsilon) A_t\right)\right]$其中ε一般取0.2附近但具体以论文和实现为准。clip的作用是当ρ在[1-ε,1ε]范围内时允许正常策略梯度一旦超出范围梯度被截断防止一步更新过大。如果A_t大于0梯度只允许提高到一定比例内如果A_t小于0也只允许降低到一定比例内。在这个目标下actor不再像普通Actor-Critic那样直接最大化对数概率乘优势而是先限制概率比再决定是否更新。这就是PPO比原始AC在稳定性上更好的关键。5.3 整体loss组合和伪代码一个典型的PPO整体损失可以写成$L - L^{clip}(\theta) c_1 L_C(\phi) - c_2 H(\pi_{\theta}) \text{KL惩罚}$每个实现的组合方式略有不同。不要直接背这个式子要关注每项的作用第一项提高好动作概率第二项让价值估计更准第三项保持多样性KL惩罚防止跑偏。下面是一个用于理解PPO更新结构的伪代码# 伪代码用于理解PPO更新结构 for batch in dataloader: # 1. 用旧策略生成回复 sequences, old_log_probs, values old_policy.sample(batch) # 2. 奖励模型打分 rewards reward_model(sequences) # 3. 计算GAE优势 advantages compute_gae(rewards, values) # 4. 多次更新 for _ in range(ppo_epochs): new_log_probs, new_values policy(sequences) ratio exp(new_log_probs - old_log_probs) clipped_ratio clamp(ratio, 1 - eps, 1 eps) loss -min(ratio * advantages, clipped_ratio * advantages).mean() loss value_coef * mse_loss(new_values, returns) loss - entropy_coef * entropy(new_log_probs).mean() loss kl_coef * kl_divergence(policy, ref_policy) loss.backward() optimizer.step()这是一个思路正确的简化版。实际RLHF代码里还要处理padding、token mask、长度归一化、分布式采样等问题但在学习阶段先用这个框架理解就够了。伪代码中的eps、value_coef、entropy_coef、kl_coef都是超参数跑实验时需要单独调整。6. RLHF训练时最容易踩的坑和排查顺序6.1 先把实验规模缩小不要一上来就训练几十B模型或跑完整数据集。建议先选一个1B左右的模型准备几百到几千条prompt不做太多并行只验证管线是否通。更推荐用已有小模型的RLHF样例代码起步先跑通再换大模型。运行前要确认三件事actor能正常生成并返回log_probcritic能输出和序列长度匹配的value奖励模型能对完整回复返回标量分数。这三个条件不满足后续优化无从谈起。输出shape对不上是最常见的启动错误。资源方面模型大小、batch size、序列长度三者共同决定显存占用。如果OOM优先降低batch size和序列长度而不是马上换机器。梯度累积可以弥补batch size减小带来的稳定性问题但累积步数不要设置得太大否则训练节奏会变得很慢。6.2 第一次训练拆成三步第一步跑单条样本。输入一个prompt采样生成一个回复打印old_log_prob、value、reward、advantage看数据尺度。如果reward在几十而value在0.1以下critic更新会很难收敛需要先做奖励归一化。第二步跑一个小batch关掉更新或只更新几步。检查梯度是否存在loss能否下降有没有NaN。很多问题不是算法逻辑错而是数值溢出。比如log_prob为-inf、advantage为NaN。出现这类情况先检查输入长度和mask不要先怀疑模型结构。第三步再开启完整更新周期。按照情况调整KL系数、clip范围、学习率。记录reward mean、entropy、KL、value loss这几个指标。它们比单看reward更有判断价值。reward只是最终结果entropy和KL能告诉你策略是否已经走偏。6.3 训练不稳定时的排查链路出现以下情况按这个顺序排查reward一直涨但生成质量明显变差。先看KL值是否过大或过小再调小学习率或调大KL系数。不要继续加大batch size这通常是策略崩溃的典型信号。critic loss不下降。先看value输出是否被初始化为0reward是否过大GAE的lambda是否需要调整。再把value loss和reward的scale打出来如果量级差太远需要归一化。梯度爆炸或NaN。先看log_prob和advantage是否有nan再看混合精度是否开启。多数时候是输入数据中有超长序列或空回复。卡住不动。先确认不是死锁看日志最后一条输出在哪个步骤再检查进程数、端口和输出目录权限。这类问题通常是环境或工程问题不是算法问题不要一上来就改模型。6.4 记住三件边界事第一能跑通小模型不等于能直接生产。大模型RLHF对训练策略、KL控制、奖励模型质量的要求完全不同。第二默认参数只适合入门。PPO的epsilon、GAE的lambda、价值损失系数都要根据任务调整不能照着抄一遍就期望稳定。第三任何“reward提升”都不一定代表真实质量。奖励模型本身会过拟合需要人工抽看生成结果否则你优化的只是奖励模型的偏好。7. 给自己建立一份推导检查清单7.1 推导时要反复核对哪些点每次看到一个新的AC变体可以先把这些位置确认一遍而不是直接闷头看loss代码符号量纲动作是否对应actor的输出状态是否对应critic的输入。两者不能混。梯度来源actor的更新是否来自log概率乘以优势critic的更新是否来自TD误差平方的导数。target是否detach如果TD target没有detachcritic可能在“追自己的影子”训练目标会漂移。优势是否归一化很多实现在更新前会对advantage做标准化训练会更稳定。clip是否作用于概率比而不是直接裁剪advantage。这两者看起来像结果完全不同。熵正则的方向最大化熵时loss里通常带负号写反了会加速收敛到单峰分布。7.2 一份可操作的检查表我建议在本地准备一个简单表格记录你和标准实现的差异。每次跑新实验先填这几项模型规模、batch size、序列长度、kl系数、clip范围、GAE的lambda、学习率、价值损失系数。这样出现训练不稳定时能快速定位是哪个参数改坏了。也可以写一个小脚本用固定seed在固定数据上跑几步比较修改前后的loss曲线。如果不固定seed可能只是采样波动却会被误认为算法改进。这是强化学习实验里最常见的虚假信号。7.3 推荐的学习顺序如果你刚接触这部分不要着急看懂完整RLHF代码。先用一个只有3个状态、2个动作的表格例子手动算一次价值迭代再实现一个最简单的Actor-Critic在标准控制环境里跑一遍然后回到RLHF代码把AC和PPO的公式映射到每个tensor。每次遇到新公式用“符号翻译维度验证数值样例”三步来消化。先翻译成中文是什么再看输入输出维度能不能对齐最后用一个简单数字例子手算一遍。坚持几轮之后再复杂的推导也不会怕。很多问题不是数学难而是符号和实际tensor对不上。最后留一个个人习惯我会把每个新实验最先跑通的那份配置单独存一份不覆盖。后面调参失败时还能回到第一个稳定点重新看。强化学习训练本来就容易波动保留一个确定性的起点能省很多时间。
返回列表