收集比较数据#

本指南将帮助您设置工作流程,以收集比较数据来训练奖励模型。正如 “大型语言模型数据收集”指南 中所述,RLHF 涉及训练一个奖励模型,以根据人类偏好对回复进行评分。随后,使用强化学习 (RL) 对 LLM 进行微调,以生成符合奖励模型的高分回复。虽然奖励模型对提示-回复对进行评分,但比较数据的收集方式通常不同。这通常涉及人类将针对特定提示的多个回复从最佳到最差进行排序

使用 Argilla 实现比较数据收集过程的步骤是:创建数据集添加记录标注者对回复进行排序准备数据集训练奖励模型

Comparison collection for Reward Modeling

注意

对于实践操作入门,您可以直接前往操作指南或示例部分。本指南侧重于提供该过程的详细概念性描述。

创建数据集#

在此阶段,您将设置一个数据集,以收集针对每个 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

配置数据集后,您需要添加记录并将其发布给标注者。在本例中,记录将包含三个字段:promptresponse 1response 2prompt 和两个 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

最后一步是准备数据集以训练奖励模型。此准备工作取决于所选框架。本指南全面概述了选项和相应的数据准备方法。