收集比较数据#
本指南将帮助您设置工作流程,以收集比较数据来训练奖励模型。正如 “大型语言模型数据收集”指南 中所述,RLHF 涉及训练一个奖励模型,以根据人类偏好对回复进行评分。随后,使用强化学习 (RL) 对 LLM 进行微调,以生成符合奖励模型的高分回复。虽然奖励模型对提示-回复对进行评分,但比较数据的收集方式通常不同。这通常涉及人类将针对特定提示的多个回复从最佳到最差进行排序。
使用 Argilla 实现比较数据收集过程的步骤是:创建数据集、添加记录、标注者对回复进行排序、准备数据集和训练奖励模型。
注意
对于实践操作入门,您可以直接前往操作指南或示例部分。本指南侧重于提供该过程的详细概念性描述。
创建数据集#
在此阶段,您将设置一个数据集,以收集针对每个 prompt
的排序回复。
首先,让我们使用 Argilla Python SDK 配置一个数据集。此数据集将包含问题,供标注者回答。在本例中,我们希望从标注者那里收集排序后的回复。我们将定义一个 RankingQuestion
import argilla as rg
questions = [
rg.RankingQuestion(
name="response_ranking",
title="Order the responses based on their accuracy and helpfulness:",
required=True,
values={"response-1": "Response 1", "response-2": "Response 2"} # or ["response-1", "response-2"]
)
]
提示
如果您只有 2 个选项,您也可以使用 RatingQuestion
来完成此操作
import argilla as rg
questions = [
rg.RatingQuestion(
name="response_ranking",
title="Select the most accurate and helpful response (1) or (2). If both are equal select (0):",
required=True,
values=[0, 1, 2]
)
]
数据集由记录组成。每个记录是一个数据点,可以由一个或多个标注者进行标注。一个记录有一个或多个字段。对于此任务,我们需要向标注者展示一个提示和两个要排序的回复。我们将定义一个文本字段
fields = [
rg.TextField(name="prompt", required=True),
rg.TextField(name="response-1", required=True),
rg.TextField(name="response-2", required=True)
]
接下来,为标注者定义指南。这些说明帮助标注者理解并一致地回答问题
dataset = rg.FeedbackDataset(
guidelines="Please, read the prompt carefully and...",
questions=questions,
fields=fields
)
添加记录#
此阶段旨在创建包含 prompt
和两个生成的 responses
的记录,以便推送到 Argilla 中以收集人类 rankings
。
配置数据集后,您需要添加记录并将其发布给标注者。在本例中,记录将包含三个字段:prompt、response 1 和 response 2。prompt 和两个 responses 将在 UI 中显示给标注者,我们将要求他们对两个回复进行排序。此步骤中最重要的的问题是:如何生成两个回复,以最大限度地提高结果 LLM 的质量和多样性。
提示
结果数据集的重要特征包括多样性、一致的回复风格和质量。在设计数据选择和收集过程时,必须考虑这些特征。
回复可以使用预训练的 LLM 生成,该 LLM 在先前的数据集上进行了微调。您可以使用不同的策略来生成回复,例如生成多个回复并选择两个,或使用不同的参数(例如,温度)生成两个回复。
假设您有一个预训练的 LLM,以下是如何使用指令跟随模型 Falcon-7B-instruct 生成回复
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
import torch
# Load the model and tokenizer
model = AutoModelForCausalLM.from_pretrained("tiiuae/falcon-7b-instruct")
tokenizer = AutoTokenizer.from_pretrained("tiiuae/falcon-7b-instruct")
# Create a pipeline for text generation
gen_pipeline = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
torch_dtype=torch.bfloat16,
device_map="auto",
)
# Load your dataset of prompts
prompts = load_dataset("your_prompts_dataset", split=["train"])
records = []
for record in prompts:
prompt = record["prompt"]
# Generate two responses in one call
outputs = gen_pipeline(
prompt,
max_length=100,
do_sample=True,
top_k=10,
num_return_sequences=2,
eos_token_id=tokenizer.eos_token_id,
)
responses = [output["generated_text"] for output in outputs]
record = rg.FeedbackRecord(fields={"prompt": prompt, "response 1": responses[0], "response 2": responses[1]})
records.append(record)
# Add records to the dataset
dataset.add_records(records)
# This publishes the dataset with its records to Argilla and returns the dataset in Argilla
remote_dataset = dataset.push_to_argilla(name="my-dataset", workspace="my-workspace")
# This publishes the dataset with its records to Argilla and turns the dataset object into a dataset in Argilla
dataset.push_to_argilla(name="my-dataset", workspace="my-workspace")
上面的代码将为每个提示生成两个回复,并将这些记录推送到 Argilla。标注者将看到这些提示和回复,并根据数据集中提供的说明对回复进行排序。
以下是使用上述代码生成的记录示例
提示 |
回复 1 |
回复 2 |
---|---|---|
为销售电子邮件撰写跟进 |
尊敬的 [客户姓名], |
尊敬的 [客户姓名], |
注意
如果两个回复非常相似,则排序任务对于标注者来说可能具有挑战性。考虑调整生成过程的参数(例如,温度)以生成更多样化的回复。
提示
由于该领域发展迅速且缺乏共识,我们建议从最高质量的小型数据集开始。Argilla Feedback 专为迭代而构建。从小处着手可以加快迭代速度:训练成本更低、速度更快,反馈循环的长度也缩短了。
标注者对回复进行排序#
此阶段旨在为给定 prompt
的回复对提供人工排序,使用 Argilla UI。
将数据集上传到 Argilla 后,即可通过 Argilla UI 访问。Argilla Feedback 允许从多个用户同时收集反馈,从而增强质量控制。每个有权访问数据集的用户都可以提供反馈。
但是,当资源有限时,建议在不同的标注者之间分配工作负载。此策略需要为每个标注者分配要排序的记录子集。此操作指南提供了有关有效设置这些工作负载分配选项的详细说明。
有关 Argilla UI 主要功能的全面了解,请参阅此操作指南。
准备数据集#
此阶段的目标是将数据集组织成提示、首选回复和较不首选回复三元组。此数据结构将用于训练奖励模型。
分配标注任务并收集标注者的回复后,可以使用 Python SDK 检索这些回复,如下所示
# Assume we distribute the workload in one dataset across multiple labelers
feedback = rg.FeedbackDataset.from_argilla(
name="my-dataset",
workspace="my-workspace"
)
如果您的工作分配策略需要从多个数据集和工作区收集回复,则需要从每个来源检索和整合回复。例如,如果任务在四个标注者之间分配,以下是如何检索他们的回复
# Assume the workload has been divided across the following workspaces
user_workspaces = ["natalia", "amelie", "tom", "dani"]
# This will hold each user's subsets
feedback_datasets = []
for workspace in user_workspaces:
feedback = rg.FeedbackDataset.from_argilla(
name="my-dataset",
workspace=workspace
)
feedback_datasets.append(feedback)
feedback.records
中的每个记录都包含一个 responses
属性,该属性包含为该记录提供的反馈。每个回复的结构包括
user_id
:标注者的 Argilla 用户 ID。values
:标注者提供的反馈。格式为字典,键为每个问题,值为相应的答案。status
:回复的状态,可以是已提交或已丢弃。对于我们的目的,我们只对已提交的回复感兴趣。
对于每个记录只有一个回复的数据集(没有注释重叠),后期处理很简单,因为不需要解决不同注释之间的冲突。但是,如果存在注释重叠,则冲突解决变得必要。有关冲突解决的策略,请参阅本指南。
提示
在多个标注者之间共享比较数据收集可能是有益的。每个标注者都确定他们对特定提示的首选回复。通过汇总这些选择,我们可以得出所有回复的集体排名。
此外,每个记录都包含一个 fields
属性,其中包括在数据集创建期间设置的所有字段。在本例中,prompt、response 1 和 response 2 是奖励模型的输入,并附加了偏好排名。
解决冲突后,目标是获得一个记录列表,每个记录都表明对提示的一个回复优于另一个回复的偏好。这些将用于训练奖励模型。
为了演示目的,这是一个逐步代码片段,用于从单个数据集创建三元组数据集 (prompt, preferred response, less preferred response)
,而没有重叠注释
# Define an empty list to store the triplets
triplets = []
# Loop over all records in the dataset
for record in feedback.records:
# Ensure that the record has responses
if record.responses is None or len(record.responses) == 0:
continue
# Ensure the response has been submitted (not discarded)
response = record.responses[0]
if response.status == 'submitted':
# Get the ranking value from the response for the preferred and least preferred
# responses, assuming there are no ties
preferred_rank = response.values["response_ranking"].value[0]["value"]
least_preferred_rank = response.values["response_ranking"].value[1]["value"]
# Construct the triplet and append to the list
triplets.append({
"prompt": record.fields["prompt"],
"preferred_response": record.fields[preferred_rank],
"least_preferred_response": record.fields[least_preferred_rank],
})
# Now, "triplets" is a list of dictionaries, each containing a prompt and the associated
# preferred and less preferred responses
提示
如果您使用了 RatingQuestion
,则这是相应的代码片段
# Define an empty list to store the triplets
triplets = []
# Loop over all records in the dataset
for record in feedback.records:
# Ensure that the record has responses
if record.responses is None or len(record.responses) == 0:
continue
# Ensure the response has been submitted (not discarded)
response = record.responses[0]
if response.status == 'submitted':
# Get the preferred response index from the feedback
preferred_index = response.values["response_ranking"].value
# Append the non-preferred response index
less_preferred_index = 1 if preferred_index == 2 else 2
# Construct the triplet and append to the list
triplets.append({
"prompt": record.fields["prompt"],
"preferred_response": record.fields[f"response {preferred_index}"],
"less_preferred_response": record.fields[f"response {less_preferred_index}"],
})
# Now, "triplets" is a list of dictionaries, each containing a prompt and the associated
# preferred and less preferred responses
最后一步是准备数据集以训练奖励模型。此准备工作取决于所选框架。本指南全面概述了选项和相应的数据准备方法。