通俗理解 RLHF,以及如何实现一个 PPO 算法

本文基于 huggingface 源码,对 RLHF 的实现过程做一个比较通俗的讲解。我将尽量避免使用过多的强化学习专业术语,重点在于解析如何实现一个 PPO 算法。

如果大家更好奇为何这么做,可以查看 PPO 原论文,或者在评论区告诉我,我后面再专门解析。

一、RLHF基础知识

RLHF的核心就是4个模型之间的交互过程

  • Actor model:传统的语言模型,最后一层网络是 nn.Linear(hidden_size, vocab_size)

  • Reference model (不参与训练):Actor_model的一个复制

  • Reward model (不参与训练)
    将传统语言模型的最后一层网络,由 nn.Linear(hidden_size, vocab_size) 替换成 nn.Linear(hidden_size, 1),也就是说该模型输出的是当前token的得分,而不是对下一个token的预测
    输入是prompt + answer, 输出是answer中每个token对应的值,answer中最后一个token对应的值即为这条语料的reward

  • Critic model:Reward_model 的一个复制

二、强化学习基础知识

在这里插入图片描述

很多NLP出身的同学(比如我),经常会因为强化学习的基础概念模糊,导致长期对 RLHF 一知半解,这里我用几个例子来做帮助大家更好的认知。

1.大模型生成完整answer的过程,视为PPO的一次完整的交互,reward_model的打分便是这次交互的reward;
2.大模型每生成一个token,视为PPO中的一步;
3.假设一个汉字等价为一个token。

prompt:中国的首都是哪里? answer:首都是南京

  • Reward = Reward_model(‘首都是南京’),如果我们有一个较好的reward_model,这里大概率会输出一个负数,例如-10;
  • Q(2, ‘是’) = Q(‘首都’,‘是’) ,意思是在’首都’这个state下,下一步action选择’是’这个token,所能获得的reward,显然这会是一个较大的值;
  • V(4) = V(‘首都是南’),意思是在’首都是南’这个state下,能获得的reward,显然这会是一个较小的值。

上面的例子也告诉我们,语言模型的reward,只有看到结束才能确定。有时候一个token预测错误,整个句子的reward都不会很大。

三、RLHF完整流程

有了RLHF 和 RL 的基础知识后,我们来介绍每个模型的作用:

  • Reward_model 负责给 LLM 生成的句子打分
  • Actor_model 就是我们要优化的 LLM
  • Critic_model 负责评估Actor_model的策略,计算状态值函数,也就是上面提到的V函数(Reward模型只负责给最后一个token或者说整个句子打分,给之前token打分的重任靠Critic_model 完成)
  • Reference_model 是一个标杆,为的是让我们的Actor_model在训练时不要偏离原始模型太远,保证其不会失去原本的说话能力

RLHF的第一个环节:让模型生成答案,并对其打分

  • 给定 batch_size 条 prompt
  • 调用actor_model生成answer,并进行token化,得到一个 B * L 的矩阵;
  • reward_model 对answer进行打分,得到一个 B * 1 的矩阵;
  • critic_model 对每个token进行打分,得到一个 B * L 的矩阵;
  • actor_model 和 reference_model 对生成的句子进行一遍正向传播,保存output.logits,得到两个 B * L * V 的矩阵
  • 利用gather_log_probs() 函数,只保存目标token的logit值,得到两个 B * L 的矩阵
{
    'prompts': prompts,
    'input_ids': seq,
    "attention_mask": attention_mask
    'logprobs': gather_log_probs(logits[:, :-1, :], seq[:, 1:]),        # batch_size  * (seq_len - 1)
    'ref_logprobs': gather_log_probs(logits_ref[:, :-1, :], seq[:,1:]), # batch_size  * (seq_len - 1)
    'value': values,                                                    # batch_size * seq_len
    'rewards': reward_score,                                            # torch.Size([batch_size])
}

def gather_log_probs(logits, labels):
    log_probs = F.log_softmax(logits, dim=-1)
    log_probs_labels = log_probs.gather(dim=-1, index=labels.unsqueeze(-1))
    return log_probs_labels.squeeze(-1)

RLHF的第二个环节:修正reward

前面提到,我们不能让 actor_model 偏离 reference_model 太远,因此我们要给rewards矩阵添加一个惩罚项,compute_rewards() 函数的返回是:每个token修正后的rewards:

  • 最后一个token的计算方法是 Reward_score + KL_penalty
  • 前面的所有的token 的计算方法是 0 + KL_penalty (除了最后一个token,前置token的reward初始值都是0,但是要加上惩罚项)

在这里插入图片描述

prompts = inputs['prompts']
log_probs = inputs['logprobs']
ref_log_probs = inputs['ref_logprobs']
reward_score = inputs['rewards']
values = inputs['value']
attention_mask = inputs['attention_mask']
seq = inputs['input_ids']
start = prompts.size()[-1] - 1
action_mask = attention_mask[:, 1:]
old_values = values
old_rewards = self.compute_rewards(prompts, log_probs, ref_log_probs, reward_score, action_mask)
ends = start + action_mask[:, start:].sum(1) + 1

# 计算reward
def compute_rewards(self, prompts, log_probs, ref_log_probs, reward_score, action_mask):
    kl_divergence_estimate = -self.kl_ctl * (log_probs - ref_log_probs)
    rewards = kl_divergence_estimate
    start = prompts.shape[1] - 1
    ends = start + action_mask[:, start:].sum(1) + 1
    reward_clip = torch.clamp(reward_score, -self.clip_reward_value, self.clip_reward_value)
    batch_size = log_probs.shape[0]
    for j inrange(batch_size):
        rewards[j, start:ends[j]][-1] += reward_clip[j]

    return rewards

RLHF的第三个环节:计算优势函数和Q函数

在这里插入图片描述

优势函数(Advantage Function)在强化学习中是一个非常关键的概念,通常用于评估在特定状态下采取某个动作比遵循当前策略(Policy)更好或更差的程度。优势函数的主要用途是优化策略,帮助模型明确地了解哪些动作(哪个Token)在当前状态(已生成的token)下是有利的

get_advantages_and_returns() 函数根据第二个环节修正后的 rewards 和 values 计算优势函数,有两个返回值:

  • advantages矩阵
  • returns矩阵,在计算方式上为 advantages + values。returns 尽管在计算上等同于Q函数,但在实际应用中常备视为回报 ,作为 的目标

PPO论文中Advantage函数的计算公式

batch = {'input_ids': seq, "attention_mask": attention_mask}
advantages, returns = self.get_advantages_and_returns(old_values, old_rewards, start)

## 优势函数的返回
def get_advantages_and_returns(self, values, rewards, start):
    # Adoptedfromhttps://github.com/CarperAI/trlx/blob/main/trlx/models/modeling_ppo.py#L134
    lastgaelam = 0
    advantages_reversed = []
    length = rewards.size()[-1]
    for t inreversed(range(start, length)):
        nextvalues = values[:, t + 1] if t < length - 1else0.0
        delta = rewards[:, t] + self.gamma * nextvalues - values[:, t]
        lastgaelam = delta + self.gamma * self.lam * lastgaelam
        advantages_reversed.append(lastgaelam)
    advantages = torch.stack(advantages_reversed[::-1], dim=1)
    returns = advantages + values[:, start:]
    return advantages.detach(), returns

RLHF的第四个环节:更新Actor模型

利用最新的actor模型,重新估算一遍语言模型目标token的logits,然后利用advantages矩阵进行loss计算:

  • 输入是新的actor模型的语言模型logits,旧的actor模型的语言模型logits,advantages矩阵
  • 在clip_loss,和原始loss之间,选择一个最小的loss进行返回

【重要性采样】


【PPO论文中Actor模型loss】

注意我上文的一句话,“利用最新的actor模型”,这里涉及到一个重要的概念:重要性采样!

简单来说,我们的 Actor_model 只要训了一条语料,就会变成一个新的模型,那也就是说:我们在第一个环节所构造的语料都无法使用了,因为现在的 actor_model 已经无法生成出之前的answer

在这里插入图片描述

因此,我们是在用另外一个模型的模拟轨迹,来优化我们当前的模型。利用上述公式,我们可以完整这样的近似转化操作,这就是重要性采样的简单理解。

这里不懂也无所谓,就当是引入了一个新的系数来修正 reward 即可。 log_ratio = (logprobs - old_logprobs) * mask 这一行代码对应着重要性采样的修正实现。

batch = {'input_ids': seq, "attention_mask": attention_mask}
actor_prob = self.actor_model(**batch, use_cache=False).logits
actor_log_prob = gather_log_probs(actor_prob[:, :-1, :], seq[:, 1:])
actor_loss = self.actor_loss_fn(actor_log_prob[:, start:], log_probs[:, start:], advantages, action_mask[:, start:])
self.actor_model.backward(actor_loss)
self.actor_model.step()

## loss的计算
def actor_loss_fn(self, logprobs, old_logprobs, advantages, mask):
    ## policy gradient loss
    log_ratio = (logprobs - old_logprobs) * mask
    ratio = torch.exp(log_ratio)
    pg_loss1 = -advantages * ratio
    pg_loss2 = -advantages * torch.clamp(ratio, 1.0 - self.cliprange, 1.0 + self.cliprange)
    pg_loss = torch.sum(torch.max(pg_loss1, pg_loss2) * mask) / mask.sum()
    return pg_loss

RLHF的第五个环节:更新Critic模型

同理,利用最新的critic模型,重新估算一遍V矩阵,现使用旧的V矩阵进行裁剪,限制更新幅度,再利用returns矩阵进行loss计算:

  • 输入是新的critic模型计算的Q矩阵,旧的critic模型计算的Q矩阵,returns矩阵
  • 在clip_loss,和原始loss之间,选择一个最小的loss进行返回
value = self.critic_model.forward_value(**batch, return_value_only=True, use_cache=False)[:, :-1]
critic_loss = self.critic_loss_fn(value[:, start:], old_values[:,start:], returns, action_mask[:, start:])
self.critic_model.backward(critic_loss)
self.critic_model.step()

## loss的计算
def critic_loss_fn(self, values, old_values, returns, mask):
    values_clipped = torch.clamp(values, old_values - self.cliprange_value, old_values + self.cliprange_value)
    if self.compute_fp32_loss:
        values = values.float()
        values_clipped = values_clipped.float()
    vf_loss1 = (values - returns)**2
    vf_loss2 = (values_clipped - returns)**2
    vf_loss = 0.5 * torch.sum(torch.max(vf_loss1, vf_loss2) * mask) / mask.sum()
    return vf_loss

其他细节

1.PPO 算法中充斥着大量的 clip 操作,几乎是从头 clip 到尾
2.Reward 模型的打分也要进行 clip 操作:reward_clip = torch.clamp(reward_score,-self.clip_reward_value, self.clip_reward_value)
3.Reward 模型的训练时通过偏好数据对完成的,这里不做赘述,感兴趣的话后面再讲
4.huggingface的代码很好,但很长;deepspeed的代码很丑,但很短。
5.想透彻了的同学,还是应该看一下PPO论文和A2C论文

https://github.com/huggingface/trl/blob/main/trl/trainer/ppo_trainer.py
https://github.com/microsoft/De

四、如何系统学习掌握AI大模型?

AI大模型作为人工智能领域的重要技术突破,正成为推动各行各业创新和转型的关键力量。抓住AI大模型的风口,掌握AI大模型的知识和技能将变得越来越重要。

学习AI大模型是一个系统的过程,需要从基础开始,逐步深入到更高级的技术。

这里给大家精心整理了一份全面的AI大模型学习资源,包括:AI大模型全套学习路线图(从入门到实战)、精品AI大模型学习书籍手册、视频教程、实战学习、面试题等,资料免费分享

1. 成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图方向不对,努力白费

这里,我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。可以说是最科学最系统的学习成长路线。
在这里插入图片描述

2. 大模型经典PDF书籍

书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础(书籍含电子版PDF)

在这里插入图片描述

3. 大模型视频教程

对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识

在这里插入图片描述

4. 2024行业报告

行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

在这里插入图片描述

5. 大模型项目实战

学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

在这里插入图片描述

6. 大模型面试题

面试不仅是技术的较量,更需要充分的准备。

在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

在这里插入图片描述

全套的AI大模型学习资源已经整理打包,有需要的小伙伴可以微信扫描下方优快云官方认证二维码,免费领取【保证100%免费

<think>好的,我需要帮助用户理解PPO算法的详细内容、实现方式以及源码解析。首先,用户提到了参考中的两篇资料,其中一篇是通俗讲解RLHFPPO实现的,另一篇是结合Python代码的PPO示例。我需要结合这些资料的信息,同时遵循用户给出的格式要求。 首先,我应该明确PPO的基本概念。PPO(Proximal Policy Optimization)是强化学习中的一种策略优化方法,属于策略梯度算法的一种改进。它的核心目标是提高训练的稳定性和样本效率,通过限制策略更新的幅度来避免训练过程中的剧烈波动。 接下来,用户要求详细介绍实现方式。根据参考中的资料,PPO有两种主要形式:PPO-Clip和PPO-Penalty。其中,PPO-Clip更常用,它通过引入一个剪切函数来限制新旧策略之间的差异,确保更新后的策略不会偏离旧策略太远。而PPO-Penalty则通过KL散度惩罚项来实现类似的限制。 然后,源码解析部分需要结合具体的代码示例。用户提供的参考中有使用PyTorch和Gym环境的代码示例,我需要解释代码中的关键部分,比如策略网络的结构、经验收集、优势估计、损失函数计算等。例如,在策略网络中,通常会使用Actor-Critic结构,Actor负责生成动作的概率分布,Critic评估状态的价值。 在实现步骤部分,我需要分步骤说明PPO的工作流程。首先是经验收集,使用当前策略与环境交互,收集状态、动作、奖励等数据。接着是优势估计,通常使用广义优势估计(GAE)来计算每个时间步的优势值。然后计算重要性采样比率,用于调整旧策略和新策略之间的差异。之后是损失函数的构建,包括策略损失、价值函数损失和熵正则项。最后进行梯度更新,并多次执行这些步骤直到收敛。 在代码解析中,我需要指出关键部分,比如剪切函数的应用,优势值的归一化处理,以及损失函数的具体实现。例如,在计算策略损失时,新旧策略的概率比会被剪切在1±ε的范围内,以防止过大的更新。 最后,根据用户的要求,生成相关问题。这些问题应该围绕PPO的改进方法、参数调整、与其他算法的比较以及实际应用中的挑战等方面展开。 需要确保整个回答符合格式要求:行内数学公式用$...$,独立公式用$$...$$,使用中文,结构清晰,引用标识自然添加。同时,避免使用过多的专业术语,保持通俗易懂,让用户能够逐步理解PPO实现细节。</think>### PPO算法详解与实现解析 #### 一、算法基础 PPO(Proximal Policy Optimization)是一种基于策略梯度的强化学习算法,核心思想是通过约束策略更新的幅度来保证训练稳定性。其核心公式为: $$L^{CLIP}(\theta) = \mathbb{E}_t \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)}$是重要性采样比率,$\epsilon$是剪切系数(通常取0.1-0.3)[^1]。 #### 二、实现步骤 1. **经验收集**:使用当前策略$\pi_{\theta_{old}}$与环境交互,收集轨迹数据$(s_t,a_t,r_t,s_{t+1})$ 2. **优势估计**:计算广义优势估计(GAE): $$A_t = \delta_t + (\gamma\lambda)\delta_{t+1} + \cdots + (\gamma\lambda)^{T-t+1}\delta_{T-1}$$ 其中$\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)$ 3. **重要性采样**:计算新旧策略概率比$r_t(\theta)$ 4. **损失计算**: - 策略损失:$L^{CLIP}$ - 价值损失:$(V_\theta(s_t) - R_t)^2$ - 熵正则项:$-\beta H(\pi_\theta(\cdot|s_t))$ 5. **梯度更新**:使用Adam优化器联合优化总损失$L_{total} = L^{CLIP} + c_1 L^{VF} + c_2 L^{Entropy}$ #### 三、代码解析(基于PyTorch) ```python import torch import torch.nn as nn from torch.optim import Adam class ActorCritic(nn.Module): def __init__(self, obs_dim, act_dim): super().__init__() self.actor = nn.Sequential( # 策略网络 nn.Linear(obs_dim, 64), nn.Tanh(), nn.Linear(64, act_dim)) self.critic = nn.Sequential( # 价值网络 nn.Linear(obs_dim, 64), nn.Tanh(), nn.Linear(64, 1)) def forward(self, x): return torch.distributions.Categorical(logits=self.actor(x)), self.critic(x) ``` #### 四、训练流程关键代码 ```python def update(self, obs, acts, advs, returns, old_log_probs): # 计算新策略的概率分布 dist, values = self.ac(obs) new_log_probs = dist.log_prob(acts) # 重要性采样比率 ratio = (new_log_probs - old_log_probs).exp() # 剪切策略损失 surr1 = ratio * advs surr2 = torch.clamp(ratio, 1.0 - self.eps, 1.0 + self.eps) * advs policy_loss = -torch.min(surr1, surr2).mean() # 价值损失 value_loss = 0.5 * (returns - values).pow(2).mean() # 熵正则项 entropy_loss = -dist.entropy().mean() # 总损失 loss = policy_loss + 0.5*value_loss + 0.01*entropy_loss # 梯度更新 self.optimizer.zero_grad() loss.backward() self.optimizer.step() ``` 关键参数说明: - `advs`:经过归一化的优势估计值 - `returns`:实际累积回报 - `self.eps`:剪切系数(默认0.2) - 优化器通常设置学习率3e-4[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值