
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》 《MySQL数据库学习》 《Python知识内容》 《cpolar知识学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在2023年年初,OpenAI推出了一种大型语言模型,名为ChatGPT.这个模型最大的特点是可以像聊天机器人一样进行对话.与其他语言模型不同的是,ChatGPT经过微调,能够以对话的方式执行各种任务,比如回答查询、解决编码、制定营销计划、解决数学问题等.用户只需使用电子邮件地址创建OpenAI账户,登录ChatGPT并输入查询,就可以与这个模型进行对话.ChatGPT可以用自然人类语言回答用户的问题,如果回答不满足用户的需求,可以微调输入查询,直到获得预期的结果为止.对于用户来说,ChatGPT是一个非常有用的工具,能够以更自然的方式与其互动,提高他们的参与度和满意度.本文将以实战为主,介绍ChatGPT模型训练的主要方法------RLHF(Reinforcement Learning fromHumanFeedback,人工强化学习反馈),并通过这个方法以GPT-2模型为基础训练我们自己的ChatGPT.


目录
- 1.什么是ChatGPT
- 2.RLHF模型简介
-
- 2.1RLHF技术分解
- 2.2RLHF中的具体实现------PPO算法
- 2.3RLHF三阶段:示范、比较与策略优化
- 2.4奖励模型:把偏好变成标量,但不是把价值观变成真理
- 2.5PPO与KL:两种约束、四类损失信号
-
- [PPO-Clip 约束新旧策略更新](#PPO-Clip 约束新旧策略更新)
- KL正则约束当前策略与参考模型
- GAE与价值损失
- [AdaptiveKLController 到底裁剪了什么](#AdaptiveKLController 到底裁剪了什么)
- 单个PPO批次的数据流
- 3.基于RLHF实战的ChatGPT正向评论的生成
- [4现代偏好优化:DPO、RLAIF 与PPO的适用边界](#4现代偏好优化:DPO、RLAIF 与PPO的适用边界)
- 5.总结
1.什么是ChatGPT
ChatGPT是一款基于人工智能技术驱动的自然语言处理工具,于2022年11月30日在美国发布.它通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,让用户感觉像在和真人聊天交流一样.除聊天外,ChatGPT还可以完成撰写邮件、视频脚本、文案、翻译、代码等任务,如图所示.

ChatGPT可以完成的任务
ChatGPT系列也是逐步从一个简单的语言模型发展起来的.相较于之前的人工智能技术,ChatGPT的最大不同在于其具备承认自身错误、质疑用户提问时预设的错误条件,并且拒绝不当请求的能力.这种智能化程度让ChatGPT可以更好地为用户提供服务和帮助.
ChatGPT的应用场景非常广泛,除了用来开发聊天机器人、编写和调试计算机程序外,还可以应用于文学、媒体相关领域的创作.例如,ChatGPT可以用鲁迅的文风进行文字创作,用Twitter
的高级数据工程师的口吻给马斯克写周报等.ChatGPT在教育、考试、回答测试问题方面的表现也非常优秀,甚至在某些测试情境下表现得比普通人类测试者更好.
2.RLHF模型简介
近年来,深度生成模型在生成结果的评估方面一直存在主观性和上下文依赖性的问题.现有的模型通常采用预测下一个单词的方式和简单的损失函数(如交叉熵)来建模,没有显式地引入人的偏好和主观意见.例如,我们希望模型生成一个有创意的故事、一段真实的信息性文本或者可执行的代码片段,这些结果难以用现有的、基于规则的文本生成指标来衡量.
如果我们使用生成文本的人工反馈作为性能衡量标准,或者进一步将该反馈用作损失来优化模型,这种方法也是可行的.这就是RLHF的思想:使用强化学习的方式直接优化带有人类反馈的语言模型.RLHF使得在一般文本数据语料库上训练的语言模型能够和复杂的人类价值观对齐.
早期,RLHF主要被应用在游戏、机器人等领域,在2019年以后,RLHF与语言模型相结合的工作开始陆续出现,如图所示.其中,OpenAI的InstructGPT是一个重要的里程碑式的成果,现在被誉为ChatGPT的兄弟模型.不过,当时并非只有OpenAI在关注RL4LM, DeepMind其实也关注到这一发展方向,先后发表了GopherCite和Sparrow两个基于RLHF训练的语言模型,前者是一个问答模型,后者是一个对话模型,可惜效果不够惊艳.

结合了RLHF的语言模型
OpenAI推出的ChatGPT对话模型掀起了新的AI热潮,它面对多种多样的问题对答如流,似乎已经打破了机器和人的边界.这一工作的背后是大型语言模型(Large Language Model,LLM)生成领域的新训练范式:RLHF,即以强化学习方式依据人类反馈优化语言模型.
2.1RLHF技术分解
在ChatGPT中,RLHF是一个复杂的概念,涉及多个模型和不同的训练阶段.为了更好地理解RLHF,我们可以将其分解为以下3个步骤:
· 预训练语言模型(Language Model, LM).
· 聚合问答数据并训练奖励模型(Reward Model, RM).
· 使用强化学习(Reinforcement Learning, RL)对LM进行微调.

RLHF微调语言模型的三个步骤
接下来,我们分别讲解这三个步骤.
1.基于监督学习的预训练语言模型
首先,我们使用经典的预训练目标来训练一个语言模型.在OpenAI发布的第一个RLHF模型InstructGPT中,使用了GPT-3的较小版本,参数约为1700亿个.然后,使用额外的文本或条件对这个语言模型进行微调,例如使用OpenAI对"更可取"(Preferable)的人工生成文本进行微调,如图所示.

对人工生成文本进行微调
接下来,我们基于LM来生成训练奖励模型(Reward Model,RM,也叫偏好模型)的数据,并在这一步引入人类的偏好信息.
2.训练奖励模型
RM的训练是RLHF的关键步骤.该模型接收一系列文本并返回一个标量奖励,用于量化人类的偏好.这个过程可以使用端到端方式进行LM建模,也可以使用模块化的系统进行建模(例如,对输出进行排名,然后将排名转换为奖励).而奖励数值的准确性对于RLHF对模型的反馈至关重要,如图所示.

训练RLHF奖励模型的流程
3.使用强化学习进行微调
长期以来,出于工程和算法的原因,人们认为用强化学习训练LM是不可能的.但是强化学习策略PPO(Proximal Policy Optimization,近端策略优化)算法的出现改变了这种情况.PPO算法确定的奖励函数的具体计算步骤说明如下:
(1)将提示输入初始语言模型和当前微调的LM,分别得到输出文本,将来自当前策略的文本传递给RM得到一个标量的奖励.
(2)将两个模型生成的文本进行比较,计算差异的惩罚项,这被设计为输出词分布序列之间的KL(KullbackLeibler)散度的缩放,之后将其用于惩罚RL策略,在每个训练批次中大幅偏离初始模型,以确保模型输出合理连贯的文本.
(3)最后根据PPO算法,按当前批次数据的奖励指标进行优化(来自PPO算法on-policy的特性),其使用梯度约束确保更新步骤不会破坏学习过程的稳定性.

基于RLHF的语言模型优化
2.2RLHF中的具体实现------PPO算法
前面介绍了ChatGPT所使用的输出人类反馈行为的RLHF算法,可以看到直接使用人的偏好(或者说人的反馈)来对模型整体的输出结果计算Reward或Loss,显然要比传统的"给定上下文,预测下一个词"的损失函数合理得多.基于这个思想,ChatGPT的创造者提出了使用强化学习的方法,利用人类反馈信号直接优化语言模型.
在前面的文章中,我们完成了火箭降落任务,相信完成的会有一种强烈的自豪感.现在开始进
行一项新的任务,即将PPO算法以RLHF的训练形式对ChatGPT进行微调.
在ChatGPT中,PPO算法用于训练机器人进行对话.通过训练机器人的Actor和Critic神经网络,机器人能够在对话中根据当前状态选择最优的回复,从而提高对话的质量.PPO算法使用一个改进的替代目标函数(Surrogate Objective Function)来更新Actor网络的参数,这个替代目标函数不但更快,而且更可靠,因此比其他基于梯度的强化学习算法更容易实现.此外,PPO算法还可以在训练过程中使用信任区域(TrustRegion,对超过区域的值进行裁剪)方法来限制每次更新的幅度,以确保更新的稳定性,如图所示.

RLHF对大语言模型训练的三个阶段
ChatGPT对大语言模型的训练可以具体分成以下几个步骤.
1.定义环境和动作空间
ChatGPT算法的环境包括用户输入和机器人回复.对于PPO算法,我们需要定义机器人的动作空间,即机器人可以采取的所有可能的操作.在这种情况下,机器人的动作可以是不同的回复,每个回复都有一个概率,这些概率可以表示为Softmax输出.
2.定义策略网络和价值网络
在PPO算法中,我们需要定义两个神经网络:一个是Actor网络,用于确定机器人的行为;另一个是Critic网络,用于评估Actor的性能.在ChatGPT中,我们可以使用预先训练的语言模型作为Actor和Critic网络.
3.定义PPO的损失函数
PPO算法使用一个改进的替代目标函数来更新Actor网络的参数.这个surrogate objective
function包括两部分:一部分是ratio;另一部分是clipped surrogateobjective.ratio是Actor网络新旧策略的比率,而clipped surrogateobjective通过对ratio进行剪裁来确保更新的稳定性.
4.使用PPO算法训练机器人
在每个训练周期中,ChatGPT算法会根据当前的状态选择一个动作,并且根据选择的动作获取一个奖励.然后,使用PPO算法更新Actor和Critic网络的参数,以最大化累计奖励.更新过程中还需要使用信赖域(Trust Region)方法来限制每次更新的幅度,以确保更新的稳定性.
5.重复训练直到收敛
ChatGPT算法会一直重复训练机器人,直到机器人的性能收敛.在每个训练周期结束后,算法会评估机器人的性能,并将机器人的性能与之前的性能进行比较,以确定是否需要继续训练.
2.3RLHF三阶段:示范、比较与策略优化
RLHF Three Stage Pipeline The language model is supervised on demonstrations, a reward model learns from ranked responses, and PPO improves the policy while a frozen reference model limits drift #mermaid-svg-RQjWW0kMz2hfPi9u{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RQjWW0kMz2hfPi9u .error-icon{fill:#552222;}#mermaid-svg-RQjWW0kMz2hfPi9u .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RQjWW0kMz2hfPi9u .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RQjWW0kMz2hfPi9u .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RQjWW0kMz2hfPi9u .marker.cross{stroke:#333333;}#mermaid-svg-RQjWW0kMz2hfPi9u svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RQjWW0kMz2hfPi9u p{margin:0;}#mermaid-svg-RQjWW0kMz2hfPi9u .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u .cluster-label text{fill:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u .cluster-label span{color:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u .cluster-label span p{background-color:transparent;}#mermaid-svg-RQjWW0kMz2hfPi9u .label text,#mermaid-svg-RQjWW0kMz2hfPi9u span{fill:#333;color:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u .node rect,#mermaid-svg-RQjWW0kMz2hfPi9u .node circle,#mermaid-svg-RQjWW0kMz2hfPi9u .node ellipse,#mermaid-svg-RQjWW0kMz2hfPi9u .node polygon,#mermaid-svg-RQjWW0kMz2hfPi9u .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RQjWW0kMz2hfPi9u .rough-node .label text,#mermaid-svg-RQjWW0kMz2hfPi9u .node .label text,#mermaid-svg-RQjWW0kMz2hfPi9u .image-shape .label,#mermaid-svg-RQjWW0kMz2hfPi9u .icon-shape .label{text-anchor:middle;}#mermaid-svg-RQjWW0kMz2hfPi9u .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RQjWW0kMz2hfPi9u .rough-node .label,#mermaid-svg-RQjWW0kMz2hfPi9u .node .label,#mermaid-svg-RQjWW0kMz2hfPi9u .image-shape .label,#mermaid-svg-RQjWW0kMz2hfPi9u .icon-shape .label{text-align:center;}#mermaid-svg-RQjWW0kMz2hfPi9u .node.clickable{cursor:pointer;}#mermaid-svg-RQjWW0kMz2hfPi9u .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RQjWW0kMz2hfPi9u .arrowheadPath{fill:#333333;}#mermaid-svg-RQjWW0kMz2hfPi9u .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RQjWW0kMz2hfPi9u .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RQjWW0kMz2hfPi9u .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RQjWW0kMz2hfPi9u .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RQjWW0kMz2hfPi9u .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RQjWW0kMz2hfPi9u .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RQjWW0kMz2hfPi9u .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RQjWW0kMz2hfPi9u .cluster text{fill:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u .cluster span{color:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RQjWW0kMz2hfPi9u .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RQjWW0kMz2hfPi9u rect.text{fill:none;stroke-width:0;}#mermaid-svg-RQjWW0kMz2hfPi9u .icon-shape,#mermaid-svg-RQjWW0kMz2hfPi9u .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RQjWW0kMz2hfPi9u .icon-shape p,#mermaid-svg-RQjWW0kMz2hfPi9u .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RQjWW0kMz2hfPi9u .icon-shape .label rect,#mermaid-svg-RQjWW0kMz2hfPi9u .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RQjWW0kMz2hfPi9u .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RQjWW0kMz2hfPi9u .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RQjWW0kMz2hfPi9u :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-RQjWW0kMz2hfPi9u .model>*{fill:#dbeafe!important;stroke:#2563eb!important;stroke-width:2px!important;color:#1e3a5f!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .model span{fill:#dbeafe!important;stroke:#2563eb!important;stroke-width:2px!important;color:#1e3a5f!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .model tspan{fill:#1e3a5f!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .data>*{fill:#f3f4f6!important;stroke:#6b7280!important;stroke-width:2px!important;color:#1f2937!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .data span{fill:#f3f4f6!important;stroke:#6b7280!important;stroke-width:2px!important;color:#1f2937!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .data tspan{fill:#1f2937!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .process>*{fill:#ede9fe!important;stroke:#7c3aed!important;stroke-width:2px!important;color:#3b0764!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .process span{fill:#ede9fe!important;stroke:#7c3aed!important;stroke-width:2px!important;color:#3b0764!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .process tspan{fill:#3b0764!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .success>*{fill:#dcfce7!important;stroke:#16a34a!important;stroke-width:2px!important;color:#14532d!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .success span{fill:#dcfce7!important;stroke:#16a34a!important;stroke-width:2px!important;color:#14532d!important;}#mermaid-svg-RQjWW0kMz2hfPi9u .success tspan{fill:#14532d!important;} KL 锚定
继续采样
🧠 基础语言模型
⚙️ 监督微调
📝 人工示范
🤖 SFT 策略
📤 采样多个回答
📥 Prompt 集
👥 人工排序
⚙️ 训练奖励模型
🧠 奖励模型
🔄 PPO 更新
🔒 冻结参考模型
✅ 对齐策略
阶段一:监督微调
人工标注者为 Prompt 写出高质量回答,策略对这些示范做标准因果语言模型训练:
L S F T = − E ( x , y ∗ ) ∼ D d e m o ∑ t log π θ ( y t ∗ ∣ x , y < t ∗ ) \mathcal L_{SFT} =-\mathbb E_{(x,y^*)\sim\mathcal D_{demo}} \sum_t\log\pi_\theta(y_t^*\mid x,y_{<t}^*) LSFT=−E(x,y∗)∼Ddemot∑logπθ(yt∗∣x,y<t∗)
SFT 的作用不是学习一个标量偏好,而是建立指令格式、对话角色、任务模式与基本拒答行为.没有足够好的 SFT 起点,后续 PPO 会在一个质量差、分布宽的策略上采样,奖励模型更容易被钻空子.
阶段二:偏好数据与奖励建模
对同一个 Prompt 采样多个候选回答,再让标注者比较或排序.最常用的两两数据形态是:
text
(prompt, chosen_response, rejected_response)
奖励模型 r ϕ ( x , y ) r_\phi(x,y) rϕ(x,y) 输出一个标量.它并不需要知道绝对"满分"是多少,只需让被选中的回答比分数较低的回答更高.
阶段三:受约束的策略优化
PPO 从当前策略采样回答,奖励模型打分,价值模型估计回报,再用优势更新策略.与此同时,冻结的参考模型提供语言分布锚点.其宏观目标可以写成:
max θ E x ∼ D , y ∼ π θ r ϕ ( x , y ) − β D K L ( π θ ( ⋅ ∣ x ) ∥ π r e f ( ⋅ ∣ x ) ) \max_\theta\; \mathbb E_{x\sim\mathcal D,\,y\sim\pi_\theta} \leftr_\\phi(x,y) -\\beta D_{KL}\\!\\left( \\pi_\\theta(\\cdot\\mid x)\\,\\\|\\,\\pi_{ref}(\\cdot\\mid x) \\right)\\right θmaxEx∼D,y∼πθrϕ(x,y)−βDKL(πθ(⋅∣x)∥πref(⋅∣x))
第一项鼓励偏好,第二项阻止策略为了奖励走得太远. β \beta β 不是越大越好:太小容易奖励投机,太大则几乎不允许策略改变.
2.4奖励模型:把偏好变成标量,但不是把价值观变成真理
成对偏好损失
给定同一 Prompt x x x 的优选回答 y w y_w yw 与落选回答 y l y_l yl,Bradley-Terry 形式把偏好概率写成:
P ( y w ≻ y l ∣ x ) = σ ( r ϕ ( x , y w ) − r ϕ ( x , y l ) ) P(y_w\succ y_l\mid x) =\sigma\left(r_\phi(x,y_w)-r_\phi(x,y_l)\right) P(yw≻yl∣x)=σ(rϕ(x,yw)−rϕ(x,yl))
对应负对数似然为:
L R M = − E ( x , y w , y l ) ∼ D p r e f log σ ( r ϕ ( x , y w ) − r ϕ ( x , y l ) ) \mathcal L_{RM} =-\mathbb E_{(x,y_w,y_l)\sim\mathcal D_{pref}} \log\sigma\left(r_\phi(x,y_w)-r_\phi(x,y_l)\right) LRM=−E(x,yw,yl)∼Dpreflogσ(rϕ(x,yw)−rϕ(x,yl))
一个最小 PyTorch 实现如下.这里故意只表达稳定的数学接口,不绑定某个 Transformer 类:
python
import torch
import torch.nn.functional as F
from torch import Tensor
def pairwise_reward_loss(
chosen_scores: Tensor,
rejected_scores: Tensor,
) -> Tensor:
"""Return Bradley-Terry loss for two equally shaped score tensors."""
if chosen_scores.shape != rejected_scores.shape:
raise ValueError("chosen and rejected scores must have identical shapes")
score_margin = chosen_scores - rejected_scores
return -F.logsigmoid(score_margin).mean()
只要所有回答的分数同时加上同一个与 Prompt 有关的常数,偏好概率就不会改变.因此奖励模型学到的是相对排序结构,不是天然有绝对物理意义的"人类满意度".
奖励模型与价值模型不是同一个模型

| 角色 | 是否更新 | 输入 | 输出 | 主要用途 |
|---|---|---|---|---|
| 当前策略 π θ \pi_\theta πθ | 是 | Prompt + 响应前缀 | 下一个 Token 分布 | 生成并接受策略梯度 |
| 旧策略 π o l d \pi_{old} πold | 批次内冻结 | 同一状态与动作 | 动作 log-prob | 构造 PPO 重要性比率 |
| 参考策略 π r e f \pi_{ref} πref | 通常冻结 | 同一状态与动作 | 参考 log-prob | 计算 KL 偏移惩罚 |
| 奖励模型 r ϕ r_\phi rϕ | PPO 阶段冻结 | Prompt + 完整回答 | 序列标量 | 近似人类或任务偏好 |
| 价值模型 V ψ V_\psi Vψ | 是 | 每个响应前缀 | 每步价值 | GAE 与价值损失 |
给 GPT-2 隐藏状态接上 Linear(768, 1) + Tanh + Dropout,并将它称为评分功能.更准确地说,这一层是价值头:它为每个 Token 位置预测期望回报.外部中文情感分类器才是代理奖励模型.把两者都叫"评分模型",会直接导致 PPO 数据流理解错误.
此外,Tanh 会把价值输出限制在 − 1 , 1 -1,1 −1,1.当奖励未经归一化或回报范围更大时,这可能造成饱和;现代实现常用不带 Tanh 的线性价值头,再配合奖励缩放、价值裁剪或白化.是否保留 Tanh 应由奖励范围决定,而不是固定模板.
为什么奖励上升不等于模型真的更好
奖励模型只看到了有限偏好样本,它可能利用长度、礼貌措辞、关键词和模板等捷径.OpenAI 的奖励过度优化研究指出,当奖励模型只是目标的有误差代理时,持续提高代理分数可能反而降低真实目标表现.
平均正向分数上升,只能证明策略更会触发该情感分类器.它不能单独证明:
- 评论事实更加真实
- 语言更加自然或多样
- 人类更喜欢生成结果
- 模型对讽刺、否定和领域外文本理解正确
- 原有语言能力没有退化
这就是为什么生产训练必须同时监控奖励、KL、回复长度、EOS 比例、重复度、人工偏好胜率和安全指标。
2.5PPO与KL:两种约束、四类损失信号
PPO-Clip 约束新旧策略更新
PPO 用采样时的旧策略 π o l d \pi_{old} πold 作为分母,定义 Token 级重要性比率:
ρ t ( θ ) = π θ ( a t ∣ s t ) π o l d ( a t ∣ s t ) = exp ( log π θ ( a t ∣ s t ) − log π o l d ( a t ∣ s t ) ) \rho_t(\theta) =\frac{\pi_\theta(a_t\mid s_t)}{\pi_{old}(a_t\mid s_t)} =\exp\left( \log\pi_\theta(a_t\mid s_t) -\log\pi_{old}(a_t\mid s_t) \right) ρt(θ)=πold(at∣st)πθ(at∣st)=exp(logπθ(at∣st)−logπold(at∣st))
裁剪替代目标为:
L C L I P ( θ ) = E t min ( ρ t A \^ t , clip ( ρ t , 1 − ϵ , 1 + ϵ ) A \^ t ) L^{CLIP}(\theta) =\mathbb E_t\left \\min\\left( \\rho_t\\hat A_t, \\operatorname{clip}(\\rho_t,1-\\epsilon,1+\\epsilon)\\hat A_t \\right) \\right LCLIP(θ)=Etmin(ρtA\^t,clip(ρt,1−ϵ,1+ϵ)A\^t)
当优势为正,策略想增加该 Token 概率;当优势为负,策略想降低它.裁剪阻止单个批次把概率比推得过远.PPO 原论文的关键价值是允许对同一批 on-policy 数据做多轮 minibatch 优化,同时保持更新相对稳定.
KL正则约束当前策略与参考模型
参考模型 KL 处理的是另一件事:它限制优化后的策略偏离一个固定语言模型锚点.实践中常对实际采样 Token 使用 log-ratio 估计:
k t = log π θ ( a t ∣ s t ) − log π r e f ( a t ∣ s t ) k_t =\log\pi_\theta(a_t\mid s_t) -\log\pi_{ref}(a_t\mid s_t) kt=logπθ(at∣st)−logπref(at∣st)
然后构造 shaped reward:
r t s h a p e = { − β k t , t < T r ϕ ( x , y ) − β k t , t = T r_t^{shape}= \begin{cases} -\beta k_t,&t<T\\ r_\phi(x,y)-\beta k_t,&t=T \end{cases} rtshape={−βkt,rϕ(x,y)−βkt,t<Tt=T
下面的代码展示 mask 如何确保只有 Response Token 参与奖励.它表达数学结构,不替代 TRL 的完整训练器:
python
import torch
from torch import Tensor
def build_token_rewards(
policy_logprobs: Tensor,
reference_logprobs: Tensor,
sequence_scores: Tensor,
response_mask: Tensor,
kl_coef: float,
) -> Tensor:
"""Combine sampled-token KL penalties with terminal sequence rewards."""
if policy_logprobs.shape != reference_logprobs.shape:
raise ValueError("policy and reference log-prob shapes must match")
if response_mask.shape != policy_logprobs.shape:
raise ValueError("response_mask must match the log-prob tensors")
if sequence_scores.shape != policy_logprobs.shape[:1]:
raise ValueError("sequence_scores must contain one value per sample")
if kl_coef < 0:
raise ValueError("kl_coef must be non-negative")
mask = response_mask.to(dtype=torch.bool)
sampled_kl = policy_logprobs - reference_logprobs
rewards = (-kl_coef * sampled_kl).masked_fill(~mask, 0.0)
if torch.any(mask.sum(dim=1) == 0):
raise ValueError("each sample must contain at least one response token")
positions = torch.arange(mask.size(1), device=mask.device).expand_as(mask)
terminal_indices = positions.masked_fill(~mask, -1).max(dim=1).values
batch_indices = torch.arange(rewards.size(0), device=rewards.device)
rewards[batch_indices, terminal_indices] += sequence_scores
return rewards
⚠️ 实现提醒: 终局位置必须从真实 Response mask 求得,不能简单使用序列长度减一;否则右侧 Padding、不同 Prompt 长度和截断样本会把序列奖励加到错误位置.

GAE与价值损失
从响应末端反向计算 lastgaelam,对应广义优势估计:
δ t = r t + γ V ψ ( s t + 1 ) − V ψ ( s t ) \delta_t=r_t+\gamma V_\psi(s_{t+1})-V_\psi(s_t) δt=rt+γVψ(st+1)−Vψ(st)
A ^ t = δ t + γ λ A ^ t + 1 \hat A_t =\delta_t+\gamma\lambda\hat A_{t+1} A^t=δt+γλA^t+1
回报目标为 R ^ t = A ^ t + V o l d ( s t ) \hat R_t=\hat A_t+V_{old}(s_t) R^t=A^t+Vold(st),价值损失可写成:
L V ( ψ ) = 1 2 E t V ψ ( s t ) − R \^ t 2 L_V(\psi) =\frac{1}{2}\mathbb E_t \leftV_\\psi(s_t)-\\hat R_t\\right^2 LV(ψ)=21EtVψ(st)−R\^t2
策略实际最小化 − L C L I P -L^{CLIP} −LCLIP,再叠加价值损失与可选熵项:
L t o t a l = − L C L I P + c v L V − c e H ( π θ ) \mathcal L_{total} =-L^{CLIP} +c_vL_V -c_e\mathcal H(\pi_\theta) Ltotal=−LCLIP+cvLV−ceH(πθ)
需要注意:KL 惩罚已经进入 shaped reward 时,不应在没有推导的情况下又重复加入同等 KL 损失,否则约束会被重复计算.
AdaptiveKLController 到底裁剪了什么
控制器大致执行:
e = clip ( K L c u r r e n t K L t a r g e t − 1 , − 0.2 , 0.2 ) e=\operatorname{clip}\left( \frac{KL_{current}}{KL_{target}}-1, -0.2,0.2 \right) e=clip(KLtargetKLcurrent−1,−0.2,0.2)
β ← β ( 1 + e n s t e p s h o r i z o n ) \beta\leftarrow\beta \left(1+e\frac{n_{steps}}{horizon}\right) β←β(1+ehorizonnsteps)
这里裁剪的是 KL 比例误差 ,用于平滑调整惩罚系数 β \beta β;它不是 PPO 目标里对策略比率 ρ t \rho_t ρt 的裁剪.两处代码都出现 clip,但作用对象、数学目的和梯度路径完全不同.
当前 TRL 官方 PPOTrainer 也将策略模型、参考模型、奖励模型和价值模型作为独立角色,并分别暴露 kl_coef、cliprange、gamma、lam 等配置.这进一步说明:理解角色和公式比记忆某个版本的构造函数更重要.
单个PPO批次的数据流
Language Model PPO Batch A single PPO batch generates responses, evaluates reference and reward signals, computes values and advantages, then updates policy and value parameters ⚙️ 优化器 📊 价值模型 🧠 奖励模型 🔒 参考模型 🤖 当前策略 📥 Prompt 批次 ⚙️ 优化器 📊 价值模型 🧠 奖励模型 🔒 参考模型 🤖 当前策略 📥 Prompt 批次 #mermaid-svg-0TvI6yVwbnL2S6Hk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0TvI6yVwbnL2S6Hk .error-icon{fill:#552222;}#mermaid-svg-0TvI6yVwbnL2S6Hk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0TvI6yVwbnL2S6Hk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0TvI6yVwbnL2S6Hk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0TvI6yVwbnL2S6Hk .marker.cross{stroke:#333333;}#mermaid-svg-0TvI6yVwbnL2S6Hk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0TvI6yVwbnL2S6Hk p{margin:0;}#mermaid-svg-0TvI6yVwbnL2S6Hk .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0TvI6yVwbnL2S6Hk text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0TvI6yVwbnL2S6Hk .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0TvI6yVwbnL2S6Hk .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0TvI6yVwbnL2S6Hk #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0TvI6yVwbnL2S6Hk .sequenceNumber{fill:white;}#mermaid-svg-0TvI6yVwbnL2S6Hk #sequencenumber{fill:#333;}#mermaid-svg-0TvI6yVwbnL2S6Hk #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0TvI6yVwbnL2S6Hk .messageText{fill:#333;stroke:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0TvI6yVwbnL2S6Hk .labelText,#mermaid-svg-0TvI6yVwbnL2S6Hk .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .loopText,#mermaid-svg-0TvI6yVwbnL2S6Hk .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0TvI6yVwbnL2S6Hk .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0TvI6yVwbnL2S6Hk .noteText,#mermaid-svg-0TvI6yVwbnL2S6Hk .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0TvI6yVwbnL2S6Hk .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0TvI6yVwbnL2S6Hk .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0TvI6yVwbnL2S6Hk .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0TvI6yVwbnL2S6Hk .actorPopupMenu{position:absolute;}#mermaid-svg-0TvI6yVwbnL2S6Hk .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-0TvI6yVwbnL2S6Hk .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0TvI6yVwbnL2S6Hk .actor-man circle,#mermaid-svg-0TvI6yVwbnL2S6Hk line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0TvI6yVwbnL2S6Hk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🔒 PPO 阶段通常保持冻结 采样响应 Token 响应与旧 log-prob 计算参考 log-prob KL 锚点 评分完整回答 序列奖励 预测每步价值 V(s_t) 组合 KL 与终局奖励 计算 GAE 与 returns 重算当前 log-prob PPO-Clip 策略损失 价值损失 更新策略参数 更新价值参数
3.基于RLHF实战的ChatGPT正向评论的生成
前面介绍过了,RLHF算法实际上是一种利用人类的反馈进行增强学习的方法,旨在使机器智能能够在不需要大量训练数据的情况下,从人类专家那里获得指导和改进.而PPO算法在RLHF中被广泛应用,本节进入RLHF的实战部分,实现我们自己的基于PPO算法的正向评论生成机器人.
在这里需要复习前面的内容,本节将重复使用和组合以往讲解过的知识,并依托GPT-2语言模型作为我们的ChatGPT语言生成模型,这是因为如果选用更大的模型,可能性能会好一些,但是一般家用计算机没有足够的运行空间,而我们是以学习为主,需要照顾更多的人,因此这里采用较小的语言模型.有兴趣的可以在学完本文后自行尝试更大的语言模型.
3.1RLHF模型进化的总体讲解
在前面已经详细介绍了PPO算法,并且完成了一个GPT-2模型,可以自由生成对关键词prompt的描述文本.下面我们基于前面讲解的内容,实现一个基于中文情感识别模型的正向评论生成机器人.
这里需要说明的是,对于任何GPT系列的模型,其文本的生成形式都是相通的.可以自行替换
合适的语言模型.
回忆前面的算法模型GPT-2,通过对其进行评论训练,使用一小段文本提示(prompt),模型就能
够继续生成一段文字,如图所示.

使用文本提示继续生成一段文字
但是这段评论生成的只是简单的文本描述,当前的GPT模型是不具备情绪识别能力的,如上面的生成结果都不符合正面情绪.这不能够达到我们所需要的既定目标,即通过一定的训练使得模型生成具有正向情感评论的功能.对此的解决办法就是通过RLHF的方法来进化现有GPT模型,使其学会尽可能生成正向情感的评论.
具体而言,就是在每个模型根据文本提示生成一个结果时,我们需要反馈这个模型输出结果的得分是多少,即为模型的每个生成结果打分,下图展示了生成过程.

可以看到,随着模型的输出,为了简单起见,这里计算了评价均值作为反馈的分值,将训练评价的
结果以图形的形式展示出来,评分结果如图所示.

从图可以看到,随着训练的进行,正向评价分数也随之增加,基本上可以认为我们的训练是正确
的.
3.2ChatGPT评分模块简介
前面介绍了ChatGPT的基本内容,本小节介绍所使用的评分模块.在这里我们使用Huggingface提供的中文二分类情感分类模型,基于网络评论数据集训练,能够对句子的评论情感进行判别,如图所示.

可以看到,在这里输入评论,其下方会输出对该评论的评分值,其中的positive为正向评论得分,而negative是负向评论得分.
既然使用的是基于Huggingface的评论模型,下面直接采用本地化的方法将模型部署在本地机器上,代码如下:
python
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
pipe_device = 0 if torch.cuda.is_available() else -1
# 情感分类模型
senti_tokenizer = AutoTokenizer.from_pretrained(
'uer/roberta-base-finetuned-jd-binary-chinese'
)
senti_model = AutoModelForSequenceClassification.from_pretrained(
'uer/roberta-base-finetuned-jd-binary-chinese'
)
sentiment_pipe = pipeline(
'sentiment-analysis',
model=senti_model,
tokenizer=senti_tokenizer,
device=pipe_device
)
text = [
"这家店东西很好吃,但是饮料不怎么样。",
"这家店的东西很好吃,我很喜欢,推荐!"
]
result = sentiment_pipe(text[0])
print(text[0], result)
print("--------------------------------")
result = sentiment_pipe(text[1])
print(text[1], result)
输出结果如图所示,

从结果中可以看到,此时的输出只显示正向情感评分,而score就是具体的分值.
这里有个提示,关于反馈函数的设定并不是唯一的,在有条件的情况下,可以直接使用OpenAI提供的ChatGPT接口,通过拼接合适的提示词来获取更准确的评分.
3.3带有评分函数的ChatGPT模型的构建
本小节回到GPT模型,回忆实现的可进行再训练的GPT模型,其中forward部分只输出了模型预测的logits,但是根据前面的讲解,相对于一般的GPT模型,还需要一个评分网络来接收对模型的评价反馈.
在这里可以简单地使用一个全连接层来完成此项评分功能,代码如下:
python
value_layer =
torch.nn.Sequential(torch.nn.Linear(768,1),torch.nn.Tanh(),torch.nn.Dropout(0.1))
...
output = embedding
value = self.value_layer(output)
value = torch.squeeze(value,dim=-1)
return logits,value
可以看到,此时通过对模型的输出进行反馈,从而调整模型的整体输出,而此时的输入embedding就是由GPT-2模型的主体计算得到的.完整的GPT-2模型如下:
python
import torch
from torch.nn.parameter import Parameter
from transformers import BertTokenizer, GPT2Model
tokenizer = BertTokenizer.from_pretrained(
"uer/gpt2-chinese-cluecorpussmall"
)
class GPT2(torch.nn.Module):
def __init__(self, use_rlhf=False):
super().__init__()
self.use_rlhf = use_rlhf
# with torch.no_grad():
self.model = GPT2Model.from_pretrained(
"uer/gpt2-chinese-cluecorpussmall"
)
self.lm_head = torch.nn.Linear(768, 21128, bias=False)
weight = torch.load("./dataset/lm_weight.pth")
self.lm_head.weight = Parameter(weight)
self.value_layer = torch.nn.Sequential(
torch.nn.Linear(768, 1),
torch.nn.Tanh(),
torch.nn.Dropout(0.1)
)
def forward(self, token_inputs):
embedding = self.model(token_inputs)
embedding = embedding["last_hidden_state"]
embedding = torch.nn.Dropout(0.1)(embedding)
logits = self.lm_head(embedding)
if not self.use_rlhf:
return logits
else:
output = embedding
value = self.value_layer(output)
value = torch.squeeze(value, dim=-1)
return logits, value
@torch.no_grad()
def generate(
self,
continue_buildingsample_num,
prompt_token=None,
temperature=1.,
top_p=0.95
):
"""
:param continue_buildingsample_num:
这个参数指的是在输入的prompt_token后再输出多少个字符
:param prompt_token:
这是需要转换成Token的内容,这里需要输入一个list
:param temperature:
:param top_x:
:return: 输出一个Token序列
"""
prompt_token_new = list(prompt_token) # 使用这行代码,在生成的Token里面有102个分隔符
for i in range(continue_buildingsample_num):
_token_inp = torch.tensor([prompt_token_new]).to("cuda")
if self.use_rlhf:
result, _ = self.forward(_token_inp)
else:
result = self.forward(_token_inp)
logits = result[:, -1, :]
probs = torch.softmax(logits / temperature, dim=-1)
next_token = self.sample_top_p(
probs,
top_p
) # 预设的top_p = 0.95
next_token = next_token.reshape(-1)
prompt_token_new.append(next_token.item())
return prompt_token_new
def sample_top_p(self, probs, p):
probs_sort, probs_idx = torch.sort(
probs,
dim=-1,
descending=True
)
probs_sum = torch.cumsum(probs_sort, dim=-1)
mask = probs_sum - probs_sort > p
probs_sort[mask] = 0.0
probs_sort.div_(
probs_sort.sum(dim=-1, keepdim=True)
)
next_token = torch.multinomial(
probs_sort,
num_samples=1
)
next_token = torch.gather(
probs_idx,
-1,
next_token
)
return next_token
3.4RLHF中的PPO算法------KL散度
本小节依次讲解在训练时使用的PPO2模型,相对于讲解的PPO算法,实际上还需要active与re
ward方法.因此,在具体使用时,我们采用两个相同的GPT-2模型分别作为算法的实施与更新模块,代码如下:
python
from moudle import model
gpt2_model = model.GPT2(use_rlhf=True)
gpt2_model_ref = model.GPT2(use_rlhf=True)
这是我们已定义好的GPT-2模型.为了简单起见,我们使用的均为带有评分函数的GPT-2模型.
下面对PPO整体模型进行介绍,在这里我们采用自定义的PPOTrainer类来对模型进行整体操作,简单的代码如下:
python
ppo_trainer = PPOTrainer(gpt2_model, gpt2_model_ref, gpt2_tokenizer, **config)
下面对相对简单的散度计算函数AdaptiveKLController进行讲解(PPO2算法).需要注意的是,无论是在经典的PPO算法还是我们自定义的PPO算法中,KL散度的计算都是一项重要的内容,它是一种用来描述两个分布之间距离的性能指标.
这里使用AdaptiveKLController来实现模型计算,这种方法会在梯度函数中添加clip操作,称为PPO2算法.
其实现原理是,当优势函数的值为正,即需要加强对当前动作的选择概率时,将会对两分布在当前状态和动作下的比值的最大值进行约束.如果最大值超过阈值,则停止对策略的更新;当优势函数的值为负,即需要减小对当前动作的选择概率时,将会对两分布在当前状态和动作下的比值的最小值进行约束,如果最小值超过阈值,也会停止对策略的更新.通过这种方式,可以实现在参数更新的同时保证两分布之间的距离在设定的范围内,如图所示.

这种方法使得模型能够通过动态调整KL约束项的惩罚系数,来达到约束参数更新幅度的目的,即参数的更新应尽可能小以保证训练的稳定性,但同时应在分布空间更新得足够大以使策略分布发生改变.
如图所示的算法,对于更新前后的KL距离,我们设定一个目标约束值target(一个可调整的超
参数),直接设置KL散度的最大更新约束值.
但是,和使用KL散度的约束值不同的是,该方法对优势函数做了限制.其中,当重要性采样的系数
大于或小于一个固定值(一般设置区间范围为-0.2,0.2,见下面的代码部分)时,该更新会被忽略,即裁剪后的损失不依赖于参数,所以不产生任何梯度信息.本质上是忽略了差异过大的新策略所产生的优势函数值,保证了训练的稳定性和梯度更新的单调递增所需的步幅小的要求.

在模型中的具体实现如下,可以对照验证:
python
class AdaptiveKLController:
def __init__(self, init_kl_coef, target, horizon):
self.value = init_kl_coef
self.target = target
self.horizon = horizon
def update(self, current, n_steps):
target = self.target
proportional_error = np.clip(current / target - 1, -0.2, 0.2)
mult = 1 + proportional_error * n_steps / self.horizon
self.value *= mult
3.5RLHF中的PPO算法------损失函数
应用RLHF的目的是最大限度反馈生成模型的奖励值,但同时希望生成模型的输出在经过PPO算法的反馈后,不要距离原本的模型生成结果太远.因此,需要使用不同的损失函数来对反馈结果进行约束.
完成此项工作的是PPO算法中的损失函数,如同我们在前面介绍的一样,PPO算法中的损失函数是通过比较当前策略与旧策略之间的差异来计算的,以确保更新不会太大,从而避免策略迭代过程中的过度拟合问题.
在此处损失函数的实现如下:
python
def loss(self, old_logprobs, values, rewards, query, response, model_input):
"""Calculate policy and value losses."""
lastgaelam = 0
advantages_reversed = []
gen_len = response.shape[1]
for t in reversed(range(gen_len)):
nextvalues = values[:, t + 1] if t < gen_len - 1 else 0.0
delta = (
rewards[:, t]
+ self.ppo_params['gamma'] * nextvalues
- values[:, t]
)
lastgaelam = (
delta
+ self.ppo_params['gamma']
* self.ppo_params['lam']
* lastgaelam
)
advantages_reversed.append(lastgaelam)
advantages = torch.stack(
advantages_reversed[::-1]
).transpose(0, 1)
returns = advantages + values # (batch, generated_seq_len)
advantages = whiten(advantages)
advantages = advantages.detach()
logits, vpred = self.model(model_input)
# logits -> (batch, all_seq_len, vocab_size)
# vpred -> (batch, all_seq_len)
logprob = logprobs_from_logits(
logits[:, :-1, :],
model_input[:, 1:]
)
# only the generation part of the values/logprobs is needed
logprob, vpred = (
logprob[:, -gen_len:],
vpred[:, -gen_len-1:-1]
)
# logprob -> (batch, generated_seq_len)
# vpred -> (batch, generated_seq_len)
vpredclipped = clip_by_value(
vpred,
values - self.ppo_params["cliprange_value"],
values + self.ppo_params["cliprange_value"]
)
vf_losses1 = (vpred - returns) ** 2
# value loss = V - (r + gamma * V_next)
vf_losses2 = (vpredclipped - returns) ** 2
# value loss clipped
vf_loss = .5 * torch.mean(
torch.max(vf_losses1, vf_losses2)
)
vf_clipfrac = torch.mean(
torch.gt(vf_losses2, vf_losses1).double()
)
ratio = torch.exp(logprob - old_logprobs)
pg_losses = -advantages * ratio
# importance sampling
pg_losses2 = -advantages * torch.clamp(
ratio,
1.0 - self.ppo_params['cliprange'],
1.0 + self.ppo_params['cliprange']
)
pg_loss = torch.mean(
torch.max(pg_losses, pg_losses2)
)
pg_clipfrac = torch.mean(
torch.gt(pg_losses2, pg_losses).double()
)
loss = pg_loss + self.ppo_params['vf_coef'] * vf_loss
entropy = torch.mean(entropy_from_logits(logits))
approxkl = .5 * torch.mean(
(logprob - old_logprobs) ** 2
)
policykl = torch.mean(
logprob - old_logprobs
)
return_mean, return_var = (
torch.mean(returns),
torch.var(returns)
)
value_mean, value_var = (
torch.mean(values),
torch.var(values)
)
stats = dict(
loss=dict(
policy=pg_loss,
value=vf_loss,
total=loss
),
policy=dict(
entropy=entropy,
approxkl=approxkl,
policykl=policykl,
clipfrac=pg_clipfrac,
advantages=advantages,
advantages_mean=torch.mean(advantages),
ratio=ratio
),
returns=dict(
mean=return_mean,
var=return_var
),
val=dict(
vpred=torch.mean(vpred),
error=torch.mean((vpred - returns) ** 2),
clipfrac=vf_clipfrac,
mean=value_mean,
var=value_var
),
)
return (
pg_loss,
self.ppo_params['vf_coef'] * vf_loss,
flatten_dict(stats)
)
4现代偏好优化:DPO、RLAIF 与PPO的适用边界
4.1DPO如何绕过显式奖励模型与在线PPO
DPO 从 KL 约束的奖励最大化问题出发,把隐式奖励写成策略与参考策略的 log-ratio:
r ^ θ ( x , y ) = β log π θ ( y ∣ x ) π r e f ( y ∣ x ) \hat r_\theta(x,y) =\beta\log\frac{\pi_\theta(y\mid x)}{\pi_{ref}(y\mid x)} r^θ(x,y)=βlogπref(y∣x)πθ(y∣x)
对偏好对 ( y w , y l ) (y_w,y_l) (yw,yl),DPO 最小化:
L D P O = − E log σ ( β log π θ ( y w ∣ x ) π r e f ( y w ∣ x ) − log π θ ( y l ∣ x ) π r e f ( y l ∣ x ) ) \mathcal L_{DPO} =-\mathbb E\log\sigma\left( \beta\left \\log\\frac{\\pi_\\theta(y_w\\mid x)}{\\pi_{ref}(y_w\\mid x)} -\\log\\frac{\\pi_\\theta(y_l\\mid x)}{\\pi_{ref}(y_l\\mid x)} \\right \right) LDPO=−Elogσ(βlogπref(yw∣x)πθ(yw∣x)−logπref(yl∣x)πθ(yl∣x))
这样不需要显式训练奖励模型,也不需要在每个更新批次在线 rollout 和训练价值模型.将其描述为更简单、稳定且计算更轻的偏好优化方法.当前 TRL 的 DPO 数据接口也直接采用 prompt、chosen、rejected 结构.
但 DPO 并没有让偏好数据质量问题消失.偏好标签仍可能偏长、偏风格、缺乏事实核验;参考模型选择、 β \beta β、序列截断和 chosen/rejected 长度差也都会影响结果.
4.2RLAIF与"让更强模型打分"
用 ChatGPT 接口作为反馈模型.如果候选回答主要由 AI 依据原则或提示进行比较,这更接近 Reinforcement Learning from AI Feedback.Constitutional AI 展示了用模型产生比较标签、训练偏好模型,再以该模型作为 RL 奖励信号的路线.
RLAIF 能降低人工标注成本并扩大覆盖,但它会继承评审模型的偏差、盲点和提示敏感性.工程上应保留人工金标准集,并定期测量 AI Judge 与人类的一致率,而不是把自动评分当作绝对真值.
4.3三种路线如何选择
| 路线 | 训练数据 | 是否显式奖励模型 | 是否在线采样 | 优势 | 主要风险 |
|---|---|---|---|---|---|
| 分类器代理奖励 + PPO | Prompt + 任务分数 | 固定代理 | 是 | 教学直观、目标清晰 | 奖励投机、目标过窄 |
| 标准 RLHF + PPO | 人类示范与偏好比较 | 是 | 是 | 可持续在线探索,适合复杂奖励组合 | 系统复杂、显存大、训练不稳 |
| DPO | 离线偏好对 | 否 | 否 | 实现简单、训练稳定、资源较省 | 依赖离线覆盖,难利用在线环境反馈 |
| RLAIF | AI 生成或判断的偏好 | 可选 | 取决于算法 | 标注扩展快、规则易迭代 | Judge 偏差被放大 |
选择原则不是"新算法一定替代旧算法":
- 已有高质量离线偏好对、计算预算有限:先试 DPO
- 奖励来自可执行验证器、环境反馈或需要在线探索:PPO 仍有价值
- 反馈规则明确但人工标注昂贵:可用 RLAIF 扩充,再以人工集校准
- 只是验证强化学习链路:情感代理实验仍是合适起点
5.总结
本文展示了使用RLHF进行自己的ChatGPT实战训练,限于目前只是进行讲解和演示,使用了GPT-2模型进行主模型的调配,同时使用了Huggingface的中文二分类情感分类模型对结果进行评判.
这种方式的好处是可以很简易地进行模型训练,但是难点在于创建的反馈模型无法较好地反映人类的真实情感.此时还有一种较好的且具有一定可行性的训练方案,就是使用OpenAI提供的ChatGPT作为反馈模型,设定专业的关键词Prompt进行打分测试,从而完成模型的训练.

🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容
每日心灵鸡汤: 后悔,不等于当初选错了!
人的任何重要选择,都可能后悔.因为选择本身就意味着放弃另一种可能,而人的认知、需求和评价标准又会随着位置不断变化.今天认为最重要的东西,未来未必还重要;今天主动放弃的东西,未来也可能重新觉得珍贵.所以,成熟不是找到一个永远不会后悔的选择,而是在当下的信息、资源和目标下,做出相对最优的判断,并愿意承担它的机会成本.后悔不一定代表当初选错了,很多时候只是后来站在了不同的位置.
