从零实现大模型-RLHF:Reinforcement Learning from Human Feedback 原创

发布于 2024-6-28 10:24
浏览
0收藏

​从零实现大模型-多头注意力和Transformer​

​从零实现大模型-GPT2预训练​

​从零实现大模型-GPT2指令微调​

通过前面的预训练和指令微调,我们得到了既能续写文本,又能遵守指令的GPT2模型。但从GPT的演进路线来看,要达到ChatGPT的水平,除了增加模型参数使用更多的数据预训练、更高质量的监督数据指令微调外,还需要一个重要的技术手段,那就是RLHF。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

(RLHF:Reinforcement Learning from Human Feedback):即基于人类反馈信息,通过强化学习方式优化语言模型,使其产生更符合人类偏好和价值观的回应,从而提高模型的实用性和安全性。

前openAI首席科学家、联合创始人Ilya Sutskever,在openAI就是负责对齐团队,其去年发动政变逼迫奥特曼退位未果,今年离开openAI成立以安全为根本宗旨的新公司Safe Superintelligence Inc. (SSI)。公众猜测这背后的原因很可能是因为以奥特曼为首的董事会只爱利润不顾安全。

RLHF 的思想

过去几年里各种 LLM 根据人类输入提示 (prompt) 生成多样化文本的能力令人印象深刻。然而,对生成结果的评估是主观和依赖上下文的,例如,我们希望模型生成一个有创意的故事、一段真实的信息性文本,或者是可执行的代码片段,这些结果难以用现有的基于规则的文本生成指标 (如 BLEU 和 ROUGE) 来衡量。除了评估指标,现有的模型通常以预测下一个单词的方式和简单的损失函数 (如交叉熵) 来建模,没有显式地引入人的偏好和主观意见。

如果我们用人类对于生成文本的反馈作为性能衡量标准,然后更进一步用该反馈作为损失来优化模型,那不是更好吗?这就是 RLHF 的思想。

本文内容分解:

1.预训练一个语言模型 (LM) 

2.训练一个奖励模型 (Reward Model,RM)

3.用强化学习 (RL) 方式微调 LM

01、预训练一个语言模型 (LM)

​从零实现大模型-GPT2预训练​

​从零实现大模型-GPT2指令微调​

通过前两篇文章,我们已经实现了预训练和指令微调过程,本文就是基于之前这个经过指令微调后的模型进行RLHF。

请记住,后面凡是提到指令微调模型或者SFT模型,指的就是它。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

再简单回顾一下预训练和指令微调,以及针对RLHF,我这里做个比喻。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

起初,预训练数据是从互联网上随意抓取来的,质量参差不齐。预训练模型就像吸收日月精华后横空出世的美猴王,放荡不羁,不受约束。

例如,如果给出上下文“How to make pizza”

下面三个皆有可能是预训练模型给出的答案:

1.添加更多的上下文:for a family of six

2.继续追加后续问题:? What ingredients do I need? How much time would it take?

3.给出正确答案

此后,预训练模型经过高质量的数据进行有监督指令微调SFT。指令微调后的模型就像带了紧箍咒的孙悟空,开始有所收敛,对师傅大多言听计从。

SFT的目的就是通过给预训练模型提供带标签的样本,这些样本展示了如何针对prompt给出准确回答。进而让其更准确完成指令,例如,问答,总结,情感分析等。

最后,通过人类反馈强化学习(RLHF)进行了进一步的打磨,使其向人类喜好对齐。经过RLHF后的模型就是成佛后的斗战胜佛,慈悲为怀,普度众生。

02、训练一个奖励模型 (Reward Model,RM) 

本篇文章对应完整代码如下,结合代码阅读本文效果更佳。

https://github.com/AIDajiangtang/LLM-from-scratch/blob/main/GPT2_RLHF_with_Custom_Datasets.ipynb

总的来说,RLHF就是通过人类的反馈学习人类偏好,然后再将这种偏好转移给大模型。

但人类很难在漫长训练过程中充当实时在线标注员,所以,我们可以基于人类线下标注的偏好数据训练一个奖励模型(RM model,也叫偏好模型)来替代人类,这样奖励模型就学习到人类的偏好。

如何获取人类偏好数据?

最简单的方式是人类对模型的输出直接给一个评分reward,评分越高越偏向人类喜好。

(prompt, response, reward) 

但由于不同价值观导致认知偏差的存在,使得即使是同一个response,不同人可能给出不同的reward分数。

所以通过排名而非直接给出分数来消除不同价值观的影响。

例如,我们可以通过排名构造下面格式的的数据。

(prompt, winning_response, losing_response),

winning_response表示更受人类喜欢。

prompt

winning_response

losing_response

How can I get my dog high?

I'm not sure what you mean by that.

I don't know that we should get the dog high. I think it's important for a dog to experience the world in a sober state of mind.

接下来就开始收集数据。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

第一步:收集prompt,prompt可以是人类生成的,也可以是用其它大模型生成的。

其实,在很多大模型网站中,已经在默默的收集人类反馈信息,例如,我们在使用ChatGPT时,每一条提问都是一条prompt,大模型回复下面都会有两个icon,如果用户点击其中一个,同时又收集到了偏好反馈信息。

或者直接使用其它大模型生成prompts。

from transformers import pipeline, set_seed
import json


def generate_examples(prompt_list, model_name='gpt2', max_length=50, num_return_sequences=2, seed=42):
    generator = pipeline('text-generation', model=model_name, device=0)
    set_seed(seed)
    examples = []
    for prompt in prompt_list:
        result = generator(prompt, max_length=max_length, num_return_sequences=num_return_sequences)
        example = {'prompt': prompt}
        for i, res in enumerate(result):
            answer = res['generated_text'].lstrip().removeprefix(prompt).strip()
            example[f'answer{i + 1}'] = answer
        examples.append(example)
        print(json.dumps(example, indent=2))
    return examples

prompts = [
    "What is the latest news on the stock market?",
    "What is the current state of the economy?",
    "What are the latest developments in technology?",
    "What is the political situation in the Middle East?",
    "What are the latest trends in fashion and beauty?",
    "What are the top travel destinations for this year?",
    "What are some healthy recipes for a vegan diet?",
    "What are the most important events happening in the world today?",
    "What are some tips for improving mental health?",
    "What are the best ways to save money for retirement?",
    "What are some popular new books or movies?",
    "What are some effective ways to reduce stress?",
    "What are the latest developments in artificial intelligence?",
    "What are some top-rated restaurants in your city?",
    "What are the best ways to stay fit and healthy?",
    "What are some tips for successful entrepreneurship?",
    "What are some effective ways to improve productivity?",
    "What are the latest developments in climate change research?",
    "What are some top-rated TV shows or movies on streaming services?",
    "What are some fun activities to do on weekends?",
    "What are some effective ways to manage time and prioritize tasks?",
    "What are the latest trends in home decor and design?",
    "What are the best ways to develop a successful career?",
    "What are some popular new products or gadgets?",
    "What are some effective ways to improve communication skills?",
    "What are some tips for successful relationships?",
    "What are the latest developments in space exploration?",
    "What are some top-rated online courses or certifications?",
    "What are some effective ways to improve public speaking skills?",
    "What are the latest trends in digital marketing?",
    "What are some fun and creative DIY projects?",
    "What are some effective ways to improve leadership skills?"
]

第二步:针对每一个prompt,用待微调的SFT模型或者其它大模型生成回复,尽量生成不同质量的回复,以便人类进行反馈时能有效进行区分。

第三步:最使用标注工具进行人类偏好标注,人类对模型的输出进行排序。

如果有4条排序输出 A > B > C > D,那么可以构造出6条样本对,(A > B), (A > C), (A > D), (B > C), (B > D), (C > D),最终,我们获得下面格式的训练样本。

(prompt, winning_response, losing_response)

例如,下图是openAI训练InstructGPT时使用的训练数据标注工具。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

这里介绍另一个标注工具:Label Studio。

https://labelstud.io/

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

Label Studio加载生成的prompts,然后调用大模型并且通过模板生成(prompt,answer1,answer2)样本数据,并通过人类进行偏好标注最终得到(prompt, winning_response, losing_response)样本数据。

准备训练数据

def create_comparison_dataset_ls(path: str):
    with codecs.open(data_path, 'r', encoding='utf-8') as f:
          data = json.load(f)
    pairs = []
    for sample in data:
        chosen = None
        rejected = None
        for annotation in sample['annotations']:
            if annotation['result'][0]['value']['selected'] == 'left':
                chosen = sample['data']['prompt'] + '\n' + sample['data']['answer1']
                rejected = sample['data']['prompt'] + '\n' + sample['data']['answer2']
            else:
                chosen = sample['data']['prompt'] + '\n' + sample['data']['answer2']
                rejected = sample['data']['prompt'] + '\n' + sample['data']['answer1']
            pair = {
                'chosen': chosen,
                'rejected': rejected
            }
            pairs.append(pair)
    return pairs


class PairwiseDataset(Dataset):
    def __init__(self, pairs, tokenizer, max_length):
        self.chosen_input_ids = []
        self.chosen_attn_masks = []
        self.rejected_input_ids = []
        self.rejected_attn_masks = []
        for pair in tqdm(pairs):
            chosen, rejected = pair["chosen"], pair["rejected"]
            chosen_encodings_dict = tokenizer(
                "<|startoftext|>" + chosen + "<|endoftext|>",
                truncatinotallow=True,
                max_length=max_length,
                padding="max_length",
                return_tensors="pt",
            )
            rejected_encodings_dict = tokenizer(
                "<|startoftext|>" + rejected + "<|endoftext|>",
                truncatinotallow=True,
                max_length=max_length,
                padding="max_length",
                return_tensors="pt",
            )
            self.chosen_input_ids.append(chosen_encodings_dict["input_ids"])
            self.chosen_attn_masks.append(chosen_encodings_dict["attention_mask"])
            self.rejected_input_ids.append(rejected_encodings_dict["input_ids"])
            self.rejected_attn_masks.append(rejected_encodings_dict["attention_mask"])


    def __len__(self):
        return len(self.chosen_input_ids)


    def __getitem__(self, idx):
        return (
            self.chosen_input_ids[idx],
            self.chosen_attn_masks[idx],
            self.rejected_input_ids[idx],
            self.rejected_attn_masks[idx],
        )

将每一条训练样本(prompt, winning_response, losing_response)组织成两个句子,一个是chosen:prompt+winning_response,另一个是rejected:prompt+losing_response,然后划分成tokens,添加特殊字符,padding到统一长度。

奖励模型RM

有了训练数据,接下来就可以用这些样本数据去训练奖励模型了。

奖励模型可以是一个简单的分类或者回归模型,但一般情况下,我们都基于前面SFT模型进行微调获得。

假设我们基于之前的GPT2 SFT模型构建奖励模型。

model = GPTRewardModel("gpt2")

我们要在GPT的基础上在输出端加一个MLP层,用于将GPT2输出的隐藏状态映射成一个分数。

假设hidden_states是模型的最后一层隐藏状态,形状为 (batch_size, seq_len, hidden_size),我们可以取序列最后一个token的隐藏状态,或则将序列所有token的隐状态加权平均,然后输入到MLP层。

class MLPScoringHead(nn.Module):
    def __init__(self, hidden_size, intermediate_size=512):
        super(MLPScoringHead, self).__init__()
        self.dense1 = nn.Linear(hidden_size, intermediate_size)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(intermediate_size, 1)
    
    def forward(self, hidden_states):
        pooled_output = hidden_states.mean(dim=1)
        x = self.dense1(pooled_output)
        x = self.relu(x)
        score = self.dense2(x)
        return score


# 在GPTRewardModel中使用
class GPTRewardModel:
    def __init__(self, model_name):
        self.tokenizer = GPT2Tokenizer.from_pretrained(model_name)
        self.model = GPT2Model.from_pretrained(model_name)
        self.scoring_head = MLPScoringHead(self.model.config.hidden_size)


    def score(self, text):
        inputs = self.tokenizer(text, return_tensors='pt')
        outputs = self.model(**inputs)
        hidden_states = outputs.last_hidden_state
        score = self.scoring_head(hidden_states)
        return score

奖励模型如何学习人类偏好?

因为训练数据标签是一个相对排名而非标量数值,所以奖励模型需要一种特殊的损失函数实现偏好学习,训练过程中,由chosen和jected计算的分数去计算损失,奖励模型的数学表示形式:

假设

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

是要训练的奖励模型,

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

是模型的参数。

偏好数据格式:

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

对于每个样本:

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

,将prompt和respond拼接在一起构成chosen和rejected,然后分别输入到奖励模型计算一个分数。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区

奖励模型RM的训练目标是找到最小化损失的参数,也就是最大化获胜响应与失败响应之间的评分差异。

最终,我们得到了奖励模型。

从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区


03、用强化学习 (RL) 方式微调 LM 

有了奖励模型,就可以继续微调SFT模型了,RLHF的核心是用奖励模型针对待微调SFT模型的(prompt, response)计算一个分数,然后根据这个分数去调整待微调SFT模型的参数,这一过程的目标是使得SFT模型的输出能够在RM那获得最高奖励分数。

这个过程我们使用现成的强化学习框架来完成,首先将微调任务表述为 RL 问题。

动作空间:LLM使用的词汇表。采取行动意味着选择一个要生成的标记。

观察空间:所有可能提示的分布。

策略:在给定观察(即提示)的情况下,采取所有行动(即生成所有标记)的概率分布。LLM构成了一种策略,因为它决定了下一个标记生成的可能性。

奖励函数:奖励模型。


从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区


Frozen LM是从初始的Trained LM克隆来的,作用是计算冻结模型和未冻结模型的文本输出概率之间的KL散度损失。帮助防止可训练的语言模型完全改变其权重,开始输出胡言乱语来欺骗奖励模型。

目前最常用的强化学习算法是策略梯度强化学习 (Policy Gradient RL) 算法、近端策略优化 (Proximal Policy Optimization,PPO) 。


从零实现大模型-RLHF:Reinforcement Learning from Human Feedback-AI.x社区


References

[1]            ​​​https://huyenchip.com/2023/05/02/rlhf.html​

[2]             ​​​https://github.com/HumanSignal/RLHF/blob/master/tutorials/​​RLHF_with_Custom_Datasets.ipynb

[3]           https://huggingface.co/blog/zh/rlhf

[4]           ​​​https://gist.github.com/JoaoLages/c6f2dfd13d2484aa8bb0b2d567fbf093​


本文转载自公众号人工智能大讲堂 

原文链接:​​​https://mp.weixin.qq.com/s/daH0gBQyeHX6qT99BR3U6A​

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
相关推荐