OpenAI 推出的 ChatGPT 对话模型掀起了新的 AI 热潮,它面对多种多样的问题对答如流,似乎已经打破了机器和人的边界。这一工作的背后是大型语言模型 (Large Language Model,LLM) 生成领域的新训练范式:RLHF (Reinforcement Learning from Human Feedback) ,即以强化学习方式依据人类反馈优化语言模型。
过去几年里各种 LLM 根据人类输入提示 (prompt) 生成多样化文本的能力令人印象深刻。然而,对生成结果的评估是主观和依赖上下文的,例如,
这些结果难以用现有的基于规则的文本生成指标 (如 BLEU 和 ROUGE) 来衡量。
除了评估指标,现有的模型通常以预测下一个单词的方式和简单的损失函数 (如交叉熵) 来建模,没有显式地引入人的偏好和主观意见。
为了解决上述问题,如果我们 用生成文本的人工反馈作为性能衡量标准,或者更进一步用该反馈作为损失来优化模型,那不是更好吗?这就是 RLHF 的思想:使用强化学习的方式直接优化带有人类反馈的语言模型。
RLHF 使得在一般文本数据语料库上训练的语言模型能和复杂的人类价值观对齐。
RLHF 是一项涉及多个模型和不同训练阶段的复杂概念,根据OpenAI的思路,RLHF分为三步:
问题一,为什么不人工直接打分?因为打分是主观的需要归一化,而排序一般大家会有共同的结论:对同一个问题,A和B哪个回答更好。人类反馈的不是标准答案,而是对更好的答案的偏好,这种偏好以排序的形式展现。事实上多数问题没有标准最好的答案。
问题二,有了一组一组的偏序(A>B, A>C, C>B)怎么得到每个回答的奖励分数?这一步在Hug的博客里用了Elo排名系统,打网游排位赛、看足球篮球比赛的可能都知道。把每个偏序当作比赛,把奖励分数看作排位分,这里我们是用Elo得到一个完整排序后,经过归一化得到了奖励分数。
问题三,这个RM用什么模型?只要用Elo系统打分后归一化,然后直接上个LM做回归就行,可以从零训练也可以用老LM做finetune。这里有个有趣的事情在于,做问答和做评分都需要输入所有的文本,实际上两个模型的容量(或者说理解能力)应该是差不多的,而现有的RLHF模型都使用了两个不同大小的模型。
问题四,有没有其他方式训练打分的模型?张俊林老师指出对偏序直接用pairwise learning to rank做打分,大概更符合常规的思路,具体效果如何就需要看实践。
参考链接:
https://zhuanlan.zhihu.com/p/591474085 https://zhuanlan.zhihu.com/p/613315873?utm_id=0
可用于收集人类反馈的模型主要有两类:
对于上述模型产生的结果,由专门的研究人员 labeler 去进行相对好坏的的评价,最终得到”prompt-completions pairs by human feedback“。接下来可以使用经典的微调方法训练一个sft语言模型。对这一步的模型,
这里可以用额外的文本或者条件对这个 LM 进行微调,例如
注意,这个sft-llm的训练只是一个起点,之后我们要训练一个 RM奖励模型,然后用RM奖励模型继续训练这个sft-llm。
当RM奖励模型参与到SFT训练中,会将RM中包含的人类倾向经验注入到SFT反馈中,最终我们得目标是得到一个高质量的 RLHF-LLM。
接下来,我们会基于 sft-llm 来生成训练 奖励模型 (RM,也叫偏好模型) 的数据(prompt对应的completions),并在这一步引入人类的偏好信息(打分和排名)。
下图展示当前GPT技术面向特定任务应用的开发范式,
在一般情况下,SFT已经可以满足大多数场景下的需求(我们要做的主要是数据提纯和数据蒸馏),但如果对模型生成质量有更高的需求,则需要采用基于人类反馈的强化学习(RLHF)。
当SFT Model已经可以较好地生成多种不同风格的响应回答,但出于法律、道德、人类价值观、特定领域任务要求等原因,我们需要引导SFT Model选择某种特定风格的回答。因此,我们需要一种向 LLM 提供反馈的方法,以帮助他们了解什么是有用的,什么是无用的,以便我们可以将其输出与公认的人类价值观(例如诚实、乐于助人和无害)保持一致。
综上,出于以下几种原因,我们需要训练一个RM Model:
以上正是 LLM 对齐中奖励模型的目标。
RM 的训练是 RLHF 区别于旧范式的开端。这一模型接收一系列文本(prompt-completions pairs)并返回一个标量奖励(scores),数值上对应人的偏好。
关于模型选择方面,
例如 Anthropic 提出了一种特殊的预训练方式,即用偏好模型预训练 (Preference Model Pretraining,PMP) 来替换一般预训练后的微调过程。因为前者被认为对样本数据的利用率更高。但对于哪种 RM 更好尚无定论。
关于训练文本方面,RM 的提示(prompt) - 生成(completions)对(prompt-completions pairs)文本是经过人工打标后的包含completions打分或者completions pair排序的增强文。例如下图所示
关于训练奖励数值方面,这里需要人工对 SFT-LM 生成的回答进行打分,
关于刻画文本质量的标量数字,用公式表示如下:
奖励模型接收一系列文本(good or bad prompt-completions pair)并返回一个标量奖励(scores),数值上对应人的偏好。
这个过程中一个有趣的产物是目前成功的 RLHF 系统使用了和生成模型具有 不同 大小的 LM,例如
一种直觉是,偏好模型和生成模型需要具有类似的能力来理解提供给它们的文本,即裁判的能力和运动员要大差不差,才能准确无误地对运动员的表现进行评判。
首先将初始语言模型的微调任务建模为强化学习(RL)问题,因此需要定义策略(policy)、动作空间(action space)和奖励函数(reward function)等基本要素。
整个过程如下所示:
而对于强化学习的算法,常见的可行方案是使用策略梯度强化学习 (Policy Gradient RL) 算法、近端策略优化 (Proximal Policy Optimization,PPO) 微调初始 LM 的部分或全部参数。
设词表为,语言模型为,那么对于长度为 n 的序列的概率分布可表示为
对于输入可能长度为1000的prompt,可能是长度为100的completions。
那么由prompt x 生成completions y 的的概率可表示为:
初始化策略,然后使用PPO算法更新策略π,奖励函数定义为 r,则奖励的期望值可表示为:
接下来,PPO 算法优化奖励函数计算步骤如下:
这一项被用于惩罚 RL 策略在每个训练批次中生成大幅偏离初始模型,以确保模型输出合理连贯的文本。如果去掉这一惩罚项可能导致模型在优化中生成乱码文本来愚弄奖励模型提供高奖励值。
最后根据 PPO 算法,我们按当前批次数据的奖励指标进行优化 (来自 PPO 算法 on-policy 的特性) 。PPO 算法是一种信赖域优化 (Trust Region Optimization,TRO) 算法,它使用梯度约束确保更新步骤不会破坏学习过程的稳定性,另外也可以使用 A2C (synchronous advantage actor-critic) 算法来优化梯度。
让我们首先将微调任务表述为 RL 问题。首先,该 策略 (policy) 是一个接受提示并返回一系列文本 (或文本的概率分布) 的 LM。这个策略的 行动空间 (action space) 是 LM 的词表对应的所有词元 (一般在 50k 数量级) ,观察空间 (observation space) 是可能的输入词元序列,也比较大 (词汇量 ^ 输入标记的数量) 。奖励函数 是偏好模型和策略转变约束 (Policy shift constraint) 的结合。
参考链接:
https://karpathy.ai/stateofgpt.pdf https://zhuanlan.zhihu.com/p/616708590 https://openreview.net/forum?id=10uNUgI5Kl https://huggingface.co/blog/zh/rlhf https://huggingface.co/datasets/CarperAI/openai_summarize_comparisons/viewer/CarperAI--openai_summarize_comparisons/train?row=0 https://zhuanlan.zhihu.com/p/450690041
选择WebGPT数据集作为reward model的语料集,如下所示,每一个prompt都对应了一个completions列表。
( 'The USA entered World War I because Germany attempted to enlist Mexico as an ally, and for what other reason?', [ "The United States entered World War I because of Germany's use of submarine warfare against ships in the Atlantic Ocean, which was hurting American exports to Europe. Additionally, Germany tried to enlist Mexico as an ally against the United States, an event which convinced American businessmen and industrialists that the United States should enter the war.", 'The USA entered World War I because Germany attempted to enlist Mexico as an ally and for the Zimmerman Telegram.' ] )
人类反馈强化后的数据集如下:
从数据集中选出人类反馈的最佳回答的处理逻辑如下:
class WebGPT: name = "openai/webgpt_comparisons" def __init__(self, split: str = "train"): super().__init__() self.split = split dataset = load_dataset(self.name, split=self.split) self.dataset_dict = defaultdict(dict) for item in dataset: post_id = item["question"]["id"] if post_id not in self.dataset_dict.keys(): self.dataset_dict[post_id] = { "full_text": item["question"]["full_text"], "answers": [], } if item["score_0"] > 0: answers = [item["answer_0"], item["answer_1"]] elif item["score_0"] < 0: answers = [item["answer_1"], item["answer_0"]] else: answers = [] answers = [re.sub(r"\[\d+\]", "", answer) for answer in answers] answers = [ ".".join([sent.strip() for sent in answer.split(".")]) for answer in answers ] if answers: self.dataset_dict[post_id]["answers"].extend(answers) else: _ = self.dataset_dict.pop(post_id) self.post_ids = list(self.dataset_dict.keys()) def __len__(self): return len(self.post_ids) def __getitem__(self, idx): question, answers = self.dataset_dict[self.post_ids[idx]].values() return question, answers
然后,在将数据输入模型之前,使用整理功能进行额外的数据准备,例如标记化和填充。 根据数据集,每个提示的完成次数可能会有所不同,因此我将维护一个额外的变量 batch_k_lens 以指示批次中每个提示的可用完成次数。 这将帮助我们计算损失。
@dataclass class RMDataCollator: tokenizer: PreTrainedTokenizer max_length: int = 512 def format_example(self, example, eos, prompt=False): sp_token = SPECIAL_TOKENS["prompter"] if prompt else SPECIAL_TOKENS["assistant"] return "{}{}{}".format(sp_token, example, eos) def process_example(self, example): trunc_len = 0 eos = self.tokenizer.eos_token prefix, outputs = example prefix = self.format_example(example, eos, prompt=True) outputs = [self.format_example(output, eos) for output in outputs] prefix_tokens = self.tokenizer.encode(prefix) input_ids, attention_masks = [], [] for output in outputs: out_tokens = self.tokenizer.encode( output, ) if len(prefix_tokens) + len(out_tokens) > self.max_length: trunc_len = max( 0, len(prefix_tokens) + len(out_tokens) - self.max_length ) prefix_tokens = prefix_tokens[trunc_len:] out_tokens = prefix_tokens + out_tokens out_tokens = out_tokens[: self.max_length] pad_len = self.max_length - len(out_tokens) attn_masks = [1] * len(out_tokens) + [0] * pad_len out_tokens += [self.tokenizer.pad_token_id] * pad_len input_ids.append(out_tokens) attention_masks.append(attn_masks) return input_ids, attention_masks def __call__(self, examples): batch_k_lens = [0] input_ids, attention_masks = [], [] for i, example in enumerate(examples): inp_ids, attn_masks = self.process_example(example) input_ids.extend(inp_ids) attention_masks.extend(attn_masks) batch_k_lens.append(batch_k_lens[i] + len(inp_ids)) return { "input_ids": torch.tensor(input_ids), "attention_mask": torch.tensor(attention_masks), "k_lens": batch_k_lens, }
对于reward model模型架构,有两种选择:
我现在选择 GPTNeoXModel,我将对最后一个隐藏层进行平均池化,并添加顶部的自定义头部以生成标量输出。
@dataclass class GPTNeoxRMOuptput(ModelOutput): """ Reward Model Output """ logits: torch.FloatTensor = None class GPTNeoXRM(GPTNeoXPreTrainedModel): """ """ def __init__( self, config, ): super().__init__(config) self.gpt_neox = GPTNeoXModel(config) self.out_layer = nn.Linear(config.hidden_size, 1) def forward( self, input_ids, attention_mask, **kwargs, ): return_dict = ( kwargs.get("return_dict") if kwargs.get("return_dict") is not None else self.config.use_return_dict ) outputs = self.gpt_neox( input_ids, attention_mask, return_dict=return_dict, **kwargs, ) hidden_states = outputs[0] if attention_mask is None: hidden_states = hidden_states.mean(dim=1) else: hidden_states = (hidden_states * attention_mask.unsqueeze(-1)).sum( dim=1 ) / attention_mask.sum(dim=1).unsqueeze(-1) lm_logits = self.out_layer(hidden_states) if not return_dict: return (lm_logits,) + outputs[1:] return GPTNeoxRMOuptput(logits=lm_logits)
对于损失函数,我将使用额外的 L2 归一化因子,以防止过度拟合。 对于每个提示prompt的k个响应回答completions,存在个两两比较。
损失是针对每个提示单独计算的,并取平均值以获得批量平均损失。
class RMLoss(nn.Module): """ """ def __init__( self, reduction=None, beta=0.001, ): super().__init__() self.reduction = reduction self.beta = beta def forward( self, logits, k_lens=None, ): total_loss = [] indices = list(zip(k_lens[:-1], k_lens[1:])) for start, end in indices: combinations = torch.combinations( torch.arange(start, end, device=logits.device), 2 ) positive = logits[combinations[:, 0]] negative = logits[combinations[:, 1]] l2 = 0.5 * (positive**2 + negative**2) loss = ( -1 * nn.functional.logsigmoid(positive - negative) + self.beta * l2 ).mean() total_loss.append(loss) total_loss = torch.stack(total_loss) if self.reduction == "mean": total_loss = total_loss.mean() return total_loss view raw
最后,我们会将所有这些连同训练参数一起传递给自定义训练器来训练和评估我们的模型。
记住!我们最终的目标是训练出一个”裁判“,这个”裁判“代表了人类反馈的倾向,它可以对prompt的completions进行打分和排序(本质上是实现训练集蒸馏)。
一旦训练出一个好的”裁判“,LLM SFT的开发就可以进入一个正向循环,一个整体的开发流程如下:
参考链接:
https://explodinggradients.com/reward-modeling-for-large-language-models-with-code https://huggingface.co/datasets/openai/summarize_from_feedback/viewer/axis/test?row=0
以trlx的 rlhf案例 为例,深入了解整个过程。
对于大多数特定领域任务LLM的开发来说,项目的初期基本都是从零样本冷启动开始的。因此,task-LLM的第一步就是数据准备工作。
我们分两种情况讨论零样本启动流程。
当处于这种情况时,我们需要分别采取prompt engining、样本提纯蒸馏等过程,循环迭代不断扩展我们的基础样本。
当处于这种情况时,sample distillation(样本蒸馏/提纯)这一步可以基本省略,其他步骤保持不变。
基础大模型生成的completions基本都满足目标任务领域的最低质量要求,重点的工作就要放在强化奖励模型的开发和RLHF微调训练上。
我们使用”CarperAI/openai_summarize_tldr“,基于”EleutherAI/gpt-j-6B“进行SFT,
# 单GPU cd sft/ && CUDA_VISIBLE_DEVICES=0 python3 train_gptj_summarize.py # 多GPU cd sft/ && deepspeed train_gptj_summarize.py
通过sft,得到了一个和summarize任务对齐了的sft-llm。
在一般的项目开发中,我们需要雇佣数据承包商或者外包人员,对base-llm、sft-llm、人工等方式生成的completions进行排序(rank)。这一步非常消耗时间,但对最终模型的效果来说又非常重要。
这里我们使用hugeface上开源的”CarperAI/openai_summarize_comparisons“进行演示。
使用开源数据集,创建一个由字典的组成的列表,每一个字典有3个key,
def create_comparison_dataset(path="CarperAI/openai_summarize_comparisons", split="train"): dataset = load_dataset(path, split=split) pairs = [] for sample in tqdm(dataset): pair = {} prompt = sample["prompt"] chosen_summary = sample["chosen"] rejected_summary = sample["rejected"] if chosen_summary == rejected_summary: continue if len(chosen_summary.split()) < 5 or len(rejected_summary.split()) < 5: continue pair["chosen"] = prompt + "\n" + chosen_summary pair["rejected"] = prompt + "\n" + rejected_summary pairs.append(pair) return pairs
拼接prompt-completions pair对,
针对处理完的 pair 对,进行分词处理,并构造成可供训练的数据集形式
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|>", truncation=True, max_length=max_length, padding="max_length", return_tensors="pt", ) rejected_encodings_dict = tokenizer( "<|startoftext|>" + rejected + "<|endoftext|>", truncation=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], )
上述数据不便于同时输入模型进行训练,需要进一步整理数据,构造成如下形式:
需要说明的是,经过上述处理,batch size 变为原来的2倍
class DataCollatorReward: def __call__(self, data): batch = {} batch["input_ids"] = torch.cat([f[0] for f in data] + [f[2] for f in data]) batch["attention_mask"] = torch.cat([f[1] for f in data] + [f[3] for f in data]) batch["labels"] = torch.tensor([0] * len(data) + [1] * len(data)) return batch
RM 的结构相对简单,即 transformer 结构+线性分类头。
定义loss公式,
class GPTRewardModel(nn.Module): def __init__(self, model_path): super().__init__() model = AutoModelForCausalLM.from_pretrained(model_path) self.config = model.config # `gpt-neo(x)` models use `hidden_size` attribute names instead of `n_embd`` self.config.n_embd = self.config.hidden_size if hasattr(self.config, "hidden_size") else self.config.n_embd self.transformer = model.transformer self.v_head = nn.Linear(self.config.n_embd, 1, bias=False) self.tokenizer = AutoTokenizer.from_pretrained("EleutherAI/gpt-j-6B") self.tokenizer.pad_token = self.tokenizer.eos_token self.PAD_ID = self.tokenizer(self.tokenizer.pad_token)["input_ids"][0] def forward( self, input_ids=None, past_key_values=None, attention_mask=None, token_type_ids=None, position_ids=None, head_mask=None, inputs_embeds=None, mc_token_ids=None, labels=None, return_dict=False, output_attentions=False, output_hidden_states=False, ): loss = None transformer_outputs = self.transformer( input_ids, past_key_values=past_key_values, attention_mask=attention_mask, token_type_ids=token_type_ids, position_ids=position_ids, head_mask=head_mask, inputs_embeds=inputs_embeds, ) hidden_states = transformer_outputs[0] rewards = self.v_head(hidden_states).squeeze(-1) chosen_end_scores = [] rejected_end_scores = [] # Split the inputs and rewards into two parts, chosen and rejected assert len(input_ids.shape) == 2 bs = input_ids.shape[0] // 2 chosen = input_ids[:bs] rejected = input_ids[bs:] chosen_rewards = rewards[:bs] rejected_rewards = rewards[bs:] loss = 0 inference = False for i in range(bs): if torch.all(torch.eq(chosen[i], rejected[i])).item(): c_inds = (chosen[i] == self.PAD_ID).nonzero() c_ind = c_inds[0].item() if len(c_inds) > 0 else chosen.shape[1] chosen_end_scores.append(chosen_rewards[i, c_ind - 1]) inference = True continue # Check if there is any padding otherwise take length of sequence c_inds = (chosen[i] == self.PAD_ID).nonzero() c_ind = c_inds[0].item() if len(c_inds) > 0 else chosen.shape[1] r_inds = (rejected[i] == self.PAD_ID).nonzero() r_ind = r_inds[0].item() if len(r_inds) > 0 else rejected.shape[1] end_ind = max(c_ind, r_ind) # Retrieve first index where trajectories diverge divergence_ind = (chosen[i] != rejected[i]).nonzero()[0] assert divergence_ind > 0 # Index into the correct rewards c_truncated_reward = chosen_rewards[i][divergence_ind:end_ind] r_truncated_reward = rejected_rewards[i][divergence_ind:end_ind] # Append the last rewards to the list of end scores chosen_end_scores.append(c_truncated_reward[-1]) rejected_end_scores.append(r_truncated_reward[-1]) # Compute loss based on truncated rewards (ignore padding) loss += -torch.log(torch.sigmoid(c_truncated_reward - r_truncated_reward)).mean() loss = loss / bs if not inference: chosen_end_scores = torch.stack(chosen_end_scores) rejected_end_scores = torch.stack(rejected_end_scores) if inference: chosen_end_scores = torch.stack(chosen_end_scores) return {"chosen_end_scores": chosen_end_scores} return { "loss": loss, "chosen_end_scores": chosen_end_scores, "rejected_end_scores": rejected_end_scores, }
将以上部分组合起来,即可以训练RM
# Initialize the reward model from the (supervised) fine-tuned GPT-J model = GPTRewardModel("CarperAI/openai_summarize_tldr_sft") # Freeze the first 70% of the hidden layers of the reward model backbone layers = model.transformer.h num_layers = len(layers) num_unfrozen = int(0.3 * num_layers) for layer in layers[:-num_unfrozen]: layer.requires_grad_(False) # Create the comparisons datasets data_path = "CarperAI/openai_summarize_comparisons" train_pairs = create_comparison_dataset(data_path, "train") val_pairs = create_comparison_dataset(data_path, "test") # Make pairwise datasets for training max_length = 550 train_dataset = PairwiseDataset(train_pairs, tokenizer, max_length=max_length) val_dataset = PairwiseDataset(val_pairs, tokenizer, max_length=max_length) # Create the collator to gather batches of pairwise comparisons data_collator = DataCollatorReward() Trainer( model=model, args=training_args, train_dataset=train_dataset, compute_metrics=compute_metrics, eval_dataset=val_dataset, data_collator=data_collator, ).train()
cd reward_model/ && deepspeed train_reward_model_gptj.py
如果想要加快时间,也可以直接下载hugeface上已经开源的训练好的reward model,
mkdir reward_model/rm_checkpoint wget https://huggingface.co/CarperAI/openai_summarize_tldr_rm_checkpoint/resolve/main/pytorch_model.bin -O reward_model/rm_checkpoint/pytorch_model.bin
由于PPO算法的值函数可以是一个深度学习模型,在本例中则是一个 transformer 模型,策略梯度方法的基本思想将值函数表示为策略参数的某个函数,然后可以根据RM的反馈值进行更新。
由于 reward scores 方差较大,因此需要根据人为结果做差以实现标准化,即
其中分别表示模型得分和人为得分。代码实现如下:
def reward_fn(samples: List[str]): # get humans summarizes posts = [sample.split('TL;DR')] for sample in samples] ref_samples = [post + 'TL;DR' + post_summ_dict[post] for post in post] samples_encodings = reward_tokenizer(samples) samples_scores = reward_model(**samples_encodings) # get scores from reward model for samples ref_samples_encodings = reward_tokenizer(ref_samples) # get scores from reward model corresponding references samples ref_samples_scores = reward_model(**ref_samples_encodings) norms_rewards = samples_scores - ref_samples_scores return norms_rewards
当使用 PPO 做 fine-tuning 的时候,summary 由策略(LLM)生成。生成的 summary传到奖励模型生成奖励分,进而更新策略。 由于上述操作是 batch-wise 的,同时由于 RL 训练的噪声很大,特别是在初始阶段,这些可能会导致策略偏移过大。为防止这个问题,引进KL散度作为惩罚项,以避免策略模型偏差过大。
其中表示奖励模型的输出分数,表示系数,表示策略模型,表示监督模型。
accelerate launch --config_file configs/default_accelerate_config.yaml trlx_gptj_text_summarization.py
SFT vs PPO
Model | Rouge-1 | Rouge-2 | Rouge-L | Average |
---|---|---|---|---|
SFT | 0.334 | 0.125 | 0.261 | 0.240 |
PPO | 0.323 | 0.109 | 0.238 | 0.223 |
ROUGE scores
Model | Average Reward | Reward Δ |
---|---|---|
SFT | 2.729 | -0.181 |
PPO | 3.291 | +0.411 |
Reward scores
参考链接:
https://huggingface.co/datasets/CarperAI/openai_summarize_comparisons/viewer/CarperAI--openai_summarize_comparisons/train?row=0 https://link.zhihu.com/?target=https%3A//github.com/CarperAI/trlx/tree/main/examples/summarize_rlhf https://github.com/CarperAI/trlx https://github.com/CarperAI/trlx/tree/main/examples/summarize_rlhf
参考资料:
https://github.com/allenai/RL4LMs
RM总共有两种作用场景:
如果场景1,其实还可以有另一种范式,即通过构造prompt template实现一个“prompt-completions pair文本质量推理链”,prompt template包含如下几个元素:
一个例如如下:
You are a fair AI assistant for checking the quality of the answers of other two AI assistants. [Question] {data['query']} [The Start of Assistant 1's Answer] llama chains: {data['llama_chains']} llama answer: {data['llama_answer']} [The End of Assistant 1's Answer] [The Start of Assistant 2's Answer] chatgpt chains: {data['chatgpt_chains']} chatgpt answer: {data['chatgpt_answer']} [The End of Assistant 2's Answer] We would like to request your feedback on the performance of two AI assistants in response to the user question displayed above. Please first judge if the answer is correct based on the question, if an assistant gives a wrong answer, the score should be low. Please rate the quality, correctness, helpfulness of their responses based on the question. Each assistant receives an overall score on a scale of 1 to 10, where a higher score indicates better overall performance, your scores should be supported by reasonable reasons. Please first output a single line containing only two values indicating the scores for Assistant 1 and 2, respectively. The two scores are separated by a space. In the subsequent line, please provide a comprehensive explanation of your evaluation, avoiding any potential bias, and the order in which the responses were presented does not affect your judgement. If the two assistants perform equally well, please output the same score for both of them.