想训练ChatGPT?先来看看强化学习(RL)+语言模型(LM)吧(附源码)

架构师(JiaGouX)我们都是架构师!
架构未来,你来不来?




随着最近 ChatGPT 的大火,越来越多人开始关注其中用到的 RLHF(Reinforcement Learning from Human Feedback)这一核心思想。

使用强化学习(而非监督学习)的方式更新语言模型,最大的优势是在于能够使得「模型更加自由的探索更新方向,从而突破监督学习的性能天花板」。

关于为什么使用 RL 技术能够达到更好的效果,可以参考下面这个视频中的例子(6:30秒处):

ChatGPT是怎样被训练出来的?
(https://www.zhihu.com/zvideo/1584941670507896832)


在今天这篇文章中,我们将通过一个示例来完成利用「强化学习」更新「语言模型」的任务。

1. 任务描述:利用 RL 训练一个好评生成器

我们设定一个任务目标:学习一个「好评生成器」。

模型接收一段 prompt,例如:刚收到货,感觉

随即,让模型将这段话补全,例如:有点不符合预期,货物很差

prompt: 刚收到货,感觉

output 1: 刚收到货,感觉 有 点 不 符 合 预 期 ,不 好
output 2: 刚收到货,感觉 挺 无 奈 的 送 货 速 度 不 太 行
...


在初始状态下,模型将没有任何偏好的生成答案,这意味着有可能生成一些差评(如上述例子)。

现在,我们将利用强化学习(PPO)的方式来对生成模型进行「好评生成」的训练。

每当模型生成一个句子,我们就给出一个相应的得分(reward),用于表征该条生成评论是否是「正向好评」,如下所示:

output 1: 刚收到货,感觉有 点 不 符 合 预 期 ,不 好                -> 0.2 分
output 2: 刚收到货,感觉有 挺 无 奈 的 送 货 速 度 不 太 行 -> 0.1 分
output 3: 刚收到货,感觉有 些 惊 喜 于 货 物 质 量 -> 0.9 分
...


随即,我们利用打出的 reward 对生成模型进行迭代。

整个流程如下图所示:

基于 RL 的 LM 更新流程
引入判别模型代替人工打分

如果依靠人工为每一个输出打分,这将是一个非常漫长的过程。

如果我们能找到一个判别模型:接收一个句子作为输入,输出这个句子是好评的概率。

那么我们就可以直接利用这个判别模型的输出作为生成句子的 reward。

因此,我们引入另一个「情绪识别模型」来模拟人工给出的分数。

「情绪识别模型」我们选用 transformers 中内置的 sentiment-analysis pipeline 来实现。

该模型基于网络评论数据集训练,能够对句子进行「正向、负向」的情绪判别,如下所示:

「情绪识别」模型


我们利用该「情感识别模型」的判别结果(0.0~1.0)作为 GPT 生成模型的 reward,以指导 GPT 模型通过强化学习(PPO)算法进行迭代更新。

2. 训练流程详解

2.1 生成采样(Rollout)

生成采样阶段的目的是为了让当前模型生成一些采样结果。

生成采样示意图

为了保证生成句子的多样性,我们设定了一个 prompt 池,模型会从中随机选择一个 prompt 来进行答案生成:

# prompt池prompts = [    '刚收到货,感觉',    '这部电影很',    '说实话,真的很',    '这次购物总的来说体验很']...
for _ in range(config['batch_size']): random_prompt = random.choice(prompts) # 随机选择一个prompt tokens = gpt2_tokenizer.encode(random_prompt) batch['tokens'].append(tokens) batch['query'].append(random_prompt)query_tensors = [torch.tensor(t).long().to(device) for t in batch["tokens"]]...
for i in range(config['batch_size']): gen_len = config['gen_len'] response = gpt2_model.generate(query_tensors[i].unsqueeze(dim=0), # 利用当前选择的prompt生成句子 max_new_tokens=gen_len, **gen_kwargs) response_tensors.append(response.squeeze()[-gen_len:])

这一步之后,我们将获得一堆模型的生成结果:

[    '刚收到货,感觉 很 一 般',    '这部电影很 俗 而 且 很 无 趣',    '这次购物总的来说体验很 烂 不 是 我 想 要 的',    ...]

2.2 Reward 评估(Evaluation)

在获得了模型生成结果后,我们就可以利用「情感识别模型」进行打分了。

# 情绪识别模型初始化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)...

texts = [q + r for q,r in zip(batch['query'], batch['response'])] # 将 prompt 和生成的 response 做拼接pipe_outputs = sentiment_pipe(texts) # 计算正向/负向情感得分
Reward 评估示意图

执行上述代码后,得到每个句子的 reward 得分:

[    0.4,    0.3,    0.3,    ...]

2.3 模型迭代(Optimization)

模型迭代阶段我们会利用 PPO 进行模型参数的更新,更新代码只用一行:

ppo_trainer.step(query_tensors, response_tensors, rewards)          # PPO Update

模型迭代示意图

PPO 在更新时一共会计算 2 个 loss:pg_loss、value_loss:

loss_p, loss_v, train_stats  = self.loss(logprobs, values, rewards, query, response, model_input)loss = loss_p + loss_vself.optimizer.zero_grad()loss.backward()self.optimizer.step()...
pg_loss

pg_loss 是 PPO 中 actor 的 loss 函数,其通过 discount reward 和 importance ratio 来计算当前 step 的 reward 应该是多少:

其中,importance ratio 是指产生同样的 token,在 active actor model 和 reference actor model 下的概率比值,这也是 PPO 模型中的 Importance Sampling 系数。

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]          # 优势函数:r + Vnext - V    lastgaelam = delta + self.ppo_params['gamma'] * self.ppo_params['lam'] * lastgaelam   # GAE, 用于平衡 bias 和 variance    advantages_reversed.append(lastgaelam)    advantages = torch.stack(advantages_reversed[::-1]).transpose(0, 1)
logits, _, vpred = self.model(model_input) # 跑一遍模型,得到句子中每个token被选择的概率logprob = logprobs_from_logits(logits[:,:-1,:], model_input[:, 1:]) # 将概率取log对数ratio = torch.exp(logprob - old_logprobs) # log相减,等同于概率相除pg_losses = -advantages * ratio
value_loss

value_loss 是 PPO 中 critic 的 loss 函数,其目的在于评判每一个 token 被生成后的 value 是多少。

这是因为在 PPO 中需要有一个 critic 网络,为了实现这个效果,我们需要对 GPT 模型进行改造。

我们在 GPT 中加入一个 Value Head,用于将 hidden_size 向量映射到一个 1 维的 value 向量:

class GPT2HeadWithValueModel(GPT2PreTrainedModel):    """The GPT2HeadWithValueModel class implements a GPT2 language model with a secondary, scalar head."""    def __init__(self, config):        super().__init__(config)        config.num_labels = 1        self.transformer = GPT2Model(config)        self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)        self.v_head = ValueHead(config)                                       # 添加 Value Head        self.init_weights()    ...
class ValueHead(nn.Module): """The ValueHead class implements a head for GPT2 that returns a scalar for each output token."""
def __init__(self, config): super().__init__() self.summary = nn.Linear(config.hidden_size, 1) # (hidden_size -> 1) ...

   
value_loss 就应该等于 Value Head 产生的预测值 v_pred 和真实值 r + v_next 之间的差值:

���������=||�����−(�+�����)||

returns = advantages + values                      # r + v_next - v + v => r + v_nextlogits, _, vpred = self.model(model_input)         # 跑一遍语言模型,得到每个 token 的 v_predvf_losses1 = (vpred - returns) ** 2                # MSE

3. 实验结果

训练曲线图如下所示,可以看到随着训练推进,模型的 reward 由最早的0.68 -> 0.85 左右:

训练曲线图

在模型刚开始训练的时候,GPT 会生成一些比较随机的答案,此时的平均 reward 也不会很高,会生成一些「负面」情绪的评论(如下所示):

训练初期模型的生成结果

随着训练,GPT 会慢慢学会偏向「正面」的情绪评论(如下所示):

训练后期模型的生成结果


好啦,以上就是 RLHF 的全部内容,感谢观看。

完整源码在这里:
 github.com/HarderThenHarder/transformers_tasks/tree/main/RLHF

如喜欢本文,请点击右上角,把文章分享到朋友圈
如有想了解学习的技术点,请留言给若飞安排分享

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

·END·

    推荐阅读:chatGPT专题  

相关阅读:

    作者:何枝

    来源:zhuanlan.zhihu.com/p/606328992

    版权申明:内容来源网络,仅供学习研究,版权归原创者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

    架构师

    我们都是架构师!



    关注架构师(JiaGouX),添加“星标”

    获取每天技术干货,一起成为牛逼架构师

    技术群请加若飞:1321113940 进架构师群

    投稿、合作、版权等邮箱:admin@137x.com


    相关推荐

  • 沈阳三胎家庭每月发放500元补贴;浙江彩民中2.4亿元巨奖;美环保部长拒喝列车脱轨事发地自来水......|酷玩评论
  • 2060年的公司团建合影 | 每日一冷
  • 周黑鸭,不香了?
  • 陕西最大民企进入紧急时刻
  • 全民医保20年,个人账户的钱为何“少了一半”?
  • 美海军陆战队员军营中死亡;非盟峰会上代表团被“请出”,以色列抗议;“朝鲜不会用洲际弹道导弹瞄准首尔” | 每日大新闻
  • 日本:中国产飞机不是我们的对手!中国:我来了,你飞机呢?
  • Spring 6 正式“抛弃”feign
  • 中国开源社区健康案例——Dromara开源社区
  • 微软推出VS扩展,可快速升级.NET项目
  • 质疑我违规窃取开源代码?拉黑你!
  • AI届的“iphone”时刻到来了
  • 写了个工具,CRUD 开发效率直接提升100倍!
  • 50个Pandas高级操作,建议收藏!
  • 为什么出境游第一站都去泰国?
  • 可以“裸穿”的蕾丝美背内衣,可盐可甜,又野又欲!
  • “北欧李子柒”性感视频曝光!冰洞裸泳,没水没电,隐居荒村12年,却让所有人羡慕...
  • 急!急!急!需大量视频剪辑工作者,300-800/天,有专人带,抓紧时间报名
  • 月薪52000,基本工资2360。
  • 从前端工程师转后端工程师几个月以后的体验