在本教程中,我们将依据“"WizardLM: Empowering Large Language Models to Follow Complex Instructions"”以及“What makes good data for alignment? A comprehensive study of automatic data selection in instruction tuning”中所阐述的方法,构建一个evol-instruct数据集。接下来的章节中,我们将详细描述这一过程。所以,让我们开始吧!
Introduction
《WizardLM》论文提出了一种新的方法——“Evol-Instruct”,利用 gpt-3.5-turbo 生成了包含不同复杂度开放领域指令的合成数据集。将该数据集与原始数据集相结合,用于对 LLaMa 进行微调,从而创建出了 WizardLM 模型。该模型在人工评估和自动评估中均超越了 ChatGPT,其在 29 项技能中的 17 项中展现了超过 ChatGPT 90%的能力。
在本教程中,我们将仅探讨使用 Evol-Instruct 方法来创建更复杂数据集的流程。从作为进化过程起点的初始数据集开始,每个epoch(设定为 M = 4)的step如下:
Intruction Evolving : 使用 gpt-3.5-turbo 并结合预设提示来生成进化后的指令。这些提示分为两种类型: 深度进化 (包括 添加限制 、 深化 、 具体化 、 增强推理 和 使输入复杂化 )和 广度进化 (包括 变异 )。其中使输入复杂化的提示是唯一未应用的,因为它需要上下文示例。然后,从剩余的五个中随机选择一个应用于输入指令。您可以在 此处 查看原始代码。
Elimination Evolving : 该指令生成步骤可能会失败,因此新指令会根据以下标准进行筛选:
- • 经过优化的指令并未带来任何信息上的提升。该结果由 ChatGPT 自动评估得出。
- • 经过改进的指令中包含了“对不起”这几个字,并且其字数少于 80 个单词。
- • 经过优化的指令仅包含标点符号和停用词。
- • 经过优化的指令会从不断变化的提示中复制出词语。
-
- 如果经过优化的指令符合之前的各项标准,那么它就会被添加到新的指令池中,并且还会作为下一轮迭代的输入使用。如果不符合标准,该指令就会被舍弃,而下一轮迭代将使用原始指令。
一旦生成了经过优化的指令,就会使用相同的大型语言模型来生成相应的回复。最后,生成的数据集就是原始指令与每个epoch生成的新指令的组合。
在这里插入图片描述
另一方面,Deita的论文提出了更多用于选择最佳数据以进行对齐的策略。在使用 Evol-Instruct 方法的同时,但没有采用广度进化步骤(他们称之为 Evol-Complexity ),他们还应用了Evol-quality 和 Data selection 策略。
- • Evol-quality 与 Evol-Complexity 类似,尽管它采用了不同的提示方式,但该提示侧重于通过 提升帮助性 、 增强相关性 、 丰富深度 、 激发创造力 以及 提供更多细节 来 提高回复的质量 ,从而生成新的组合。
- • Data Selection 策略通过使用 embedding 和与原始指令的余弦相似度来筛选新的指令,从而选出最佳且最多样化的指令。
在接下来的几节中,我们将了解如何运用这些方法,利用 distilabel 来构建我们的数据集。
Getting started
Install dependencies
首先,我们需要安装运行 distilabel 所需的依赖项。此外,您还可以安装 argilla 以实现更出色的可视化和结果管理功能。
%pip install -q -U "distilabel[openai,argilla]" --upgrade
然后我们可以导入所需的库。
import os
import string
import time
from dataclasses import dataclass
from typing import Dict, List
import pandas as pd
from datasets import Dataset, load\_dataset
from distilabel.dataset import CustomDataset
from distilabel.llm import LLM, OpenAILLM
from distilabel.pipeline import Pipeline
from distilabel.tasks import EvolComplexityTask, Prompt, EvolQualityTask, TextGenerationTask
# Set the OpenAI API Key
os.environ["OPENAI\_API\_KEY"] = 'sk-...'
Prepare the initial dataset
第一步是准备用于演化过程的初始数据集。按照论文中给出的示例所采用的相同思路,我们将使用 HuggingFace 中提供的著名“alpaca”数据集。出于本教程示例的需要,我们将使用 5 个样本。
值得一提的是,还有其他一些数据集,比如distilabel-intel-orca-dpo-pairs,这是“distilabeled”的orca_dpo_pairs 版本,用于偏好调整,包含 12.9K 个样本,也被用作种子数据集。然而,说明内容已经相当复杂了,所以进化过程生成了一些质量不佳或存在幻觉的指令。
# Load the dataset
hf\_dataset = load\_dataset("tatsu-lab/alpaca", split="train")
# Get our initial dataset
initial\_dataset = (
hf\_dataset
.select\_columns(["instruction", "output"])
.rename\_column("instruction", "input")
.rename\_column("output", "response")
)
# Select a subset
initial\_dataset = initial\_dataset.shuffle(seed=5).select(range(5))
initial\_dataset[0]
# {'input': 'Generate a list of three ingredients for a chocolate cake.',
# 'response': '- Flour\n- Cocoa powder\n- Sugar'}
The Evol-Complexity approach
对于我们的案例,我们需要设置两个不同的 LLM 及其对应的任务:一个用于指令进化,另一个用于消除进化步骤 1。
Instruction Evolving LLM
接下来的步骤是定义将用于生成进化指令的语言模型。我们将使用 gpt-3.5-turbo 作为语言模型,并采用 EvolComplexityTask 任务,同时设置一些参数(参考 WizardLM 第 4.3 节)。请注意,EvolComplexity 将执行随机选择进化提示以及在与相同提示相关的消除进化第一步中对进化指令进行筛选。
# Define our LLM
complexity\_llm = OpenAILLM(
task=EvolComplexityTask(),
api\_key=os.getenv("OPENAI\_API\_KEY"),
model= "gpt-3.5-turbo",
num\_threads=4,
max\_new\_tokens=2048,
temperature=1,
frequency\_penalty=0.0,
top\_p=0.9,
)
Elimination Evolving LLM
作为消除步骤的一部分,已说明要询问 ChatGPT 原始提示和当前轮次演进后的提示是否相等。为了做到这一点,我们需要定义一个具有相应任务的大型语言模型。由于该任务不存在,我们将基于 distilabel 中的 TextGenerationTask 自定义一个,说明如何生成提示以及如何解析输出。
# Indicate the prompt (Appendix G from WizardLM)
elimination\_equal\_prompt = """Here are two Instructions, do you think they are equal to each other and meet the following requirements?:
1. They have the same constraints and requirements.
2. They have the same depth and breadth of the inquiry.
The First Prompt: {first\_instruction}
The Second Prompt: {second\_instruction}
Your Judgement (Just answer: Equal or Not Equal. No need to explain the reason):"""
# Define our distilabel class
@dataclass
classEliminationEqualPrompts(TextGenerationTask):
system\_prompt: str = "You are an AI judge in charge of determining the equality of two instructions. "
defgenerate\_prompt(self, input: List[str]) : Prompt:
return Prompt(
system\_prompt=self.system\_prompt,
formatted\_prompt=elimination\_equal\_prompt.format(
first\_instruction=input[0], second\_instruction=input[1]
),
)
defparse\_output(self, output: str) : List[Dict[str, str]]:
"""Remove punctuation from the string and lowercase it."""
return {
"generations": output.translate(
str.maketrans("", "", string.punctuation)).lower()
}
我们将此任务纳入我们的语言模型定义中。与该论文类似,参数将与上一节中所使用的参数相同。
# Define out second LLM
elimination\_llm = OpenAILLM(
task=EliminationEqualPrompts(),
api\_key=os.getenv("OPENAI\_API\_KEY"),
model= "gpt-3.5-turbo",
num\_threads=4,
max\_new\_tokens=2048,
temperature=1,
frequency\_penalty=0.0,
top\_p=0.9,
)
The Evol-quality approach
遵循 Deita 论文中的思路,我们将采用“Evol-quality”方法,从上一节中生成的那些侧重于质量的指令中生成新的回复。同样,我们将定义“LLM”和“EvolQualityTask”以生成新的回复。
# Define our LLM
quality\_llm = OpenAILLM(
task=EvolQualityTask(),
api\_key=os.getenv("OPENAI\_API\_KEY"),
model= "gpt-4-turbo-preview",
num\_threads=4,
max\_new\_tokens=2048,
temperature=1,
frequency\_penalty=0.0,
top\_p=0.9,
)
Run the evolution process
为了启动进化过程,我们将创建一个名为“make_evol_instruct_dataset”的函数,该函数将接收已定义的大型语言模型、初始数据集以及进化步骤的数量。在我们的方法中,我们将遵循 WizardLM 的步骤,但会使用“Evol-Complexity”任务及其轮数(以及“Evol-Quality”)。为了说明清楚,对于每个复杂性步骤,我们都是按照以下流程进行的:
- • 运行复杂度管道以根据之前的指令生成新的指令。Deita:对于每个指令样本
,我们使用深度进化提示 [...]。经过
次迭代后,我们为
获得了不同复杂度的一组指令,即
。
- • 执行筛选管道以过滤新的指令。WizardLM:经过改进的指令并未带来任何信息增益。由聊天机器人自动评估得出。
- • 在当前时期内创建一个循环,为每次成功的指令生成新的响应。生成的样本将被保存以用于最终的数据集。Deita:经过
次迭代后,对于相同的指令
,我们获取了一组涵盖不同质量的
响应,记为
,
,
。
- • 接下来复杂步骤的输入内容将是那些有效的指令及其相应的初始responses。
# Helper functions to generate the evol-instruct dataset
def prepare\_for\_equal\_prompts(example):
""""If the evolved instruction is None, we use the original instruction (to make sure it will be removed)"""
if example["instructions"][0] is None:
return {"input": [example["input"], example["input"]]}
else:
return {"input": [example["input"], example["instructions"][0]]}
def prepare\_for\_evol\_quality(example):
return {"input": example["instructions"][0], "generation": example["response"]}
def make\_evol\_instruct\_dataset(
complexity\_llm: LLM,
elimination\_llm: LLM,
quality\_llm: LLM,
dataset: Dataset,
instruction\_steps: int = 4,
responses\_steps: int = 4
) : "Dataset":
# Set the pipelines
complexity\_pipe = Pipeline(generator=complexity\_llm)
elimination\_pipe = Pipeline(generator=elimination\_llm)
quality\_pipe = Pipeline(generator=quality\_llm)
# Set the initial dataset
input\_complexity = dataset
successful\_samples = []
# Start the evolution process
for step inrange(1, instruction\_steps + 1):
print(f"Evolving instruction step: {step}/{instruction\_steps}")
# Run the complexity pipe to generate new instructions
instruction\_dataset = complexity\_pipe.generate(input\_complexity, batch\_size=8)
# Run the elimination pipe to determine if the instructions are equal
prepared\_dataset = (
instruction\_dataset
.map(prepare\_for\_equal\_prompts)
.select\_columns(["input"])
)
elimination\_dataset=elimination\_pipe.generate(prepared\_dataset, batch\_size=8)
# Save the successful instructions to be used for quality evol and prepare the inputs for the next iteration
new\_instructions = []
responses= []
successful\_instructions = []
for row\_evolved, row\_elimination inzip(instruction\_dataset, elimination\_dataset):
if (row\_evolved['instructions'][0] isnotNone) and (row\_elimination['generations'][0] != "equal"):
new\_instructions.append(row\_evolved['instructions'][0])
responses.append(row\_evolved['response'])
successful\_instructions.append(row\_evolved)
else:
new\_instructions.append(row\_evolved['input'])
responses.append(row\_evolved['response'])
input\_complexity = Dataset.from\_dict({"input": new\_instructions, "response": responses})
# Run the quality pipe to generate new responses
complexity\_dataset = pd.DataFrame(successful\_instructions)
input\_quality = Dataset.from\_pandas(complexity\_dataset).map(prepare\_for\_evol\_quality).select\_columns(["input", "generation"])
for q\_step inrange(1, responses\_steps + 1):
print(f"Evolving response step: {q\_step}/{responses\_steps}")
# Generate new responses
response\_dataset = quality\_pipe.generate(input\_quality, batch\_size=8)
# Save the successful responses in the pool and prepare the inputs for the next iteration
inputs = []
new\_responses = []
for row in response\_dataset:
inputs.append(row['input'])
new\_responses.append(row['generations'][0])
successful\_samples.append(row)
input\_quality = Dataset.from\_dict({"input": inputs, "generation": new\_responses})
# Prepare the final dataset
df\_final\_dataset = pd.DataFrame(successful\_samples)
final\_dataset = Dataset.from\_pandas(df\_final\_dataset)
final\_dataset.\_\_class\_\_ = CustomDataset
final\_dataset.task = TextGenerationTask() #or EvolQualityTask()
return final\_dataset
那么,让我们来创建我们的第一个“evol-instruct”数据集吧!
ds\_evol\_instruct = make\_evol\_instruct\_dataset(
complexity\_llm=complexity\_llm,
elimination\_llm=elimination\_llm,
quality\_llm=quality\_llm,
dataset=initial\_dataset,
instruction\_steps=5,
responses\_steps=5,
)
ds\_evol\_instruct[0]
{'input': 'Provide a selection of three specific ingredients for a decadent dark chocolate raspberry cake.',
'generation': '- Flour\n- Cocoa powder\n- Sugar',
'generation\_model': ['gpt-4-turbo-preview'],
'generation\_prompt': [[{'content': '', 'role': 'system'},
{'content': "I want you to act as a Response Rewriter\nYour goal is to enhance the quality of the response given by an AI assistant\nto the #Given Prompt# through rewriting.\nBut the rewritten response must be reasonable and must be understood by humans.\nYour rewriting cannot omit the non-text parts such as the table and code in\n#Given Prompt# and #Given Response#. Also, please do not omit the input\nin #Given Prompt#.\nYou Should enhance the quality of the response using the following method:\nPlease make the Response more in-depth.\nYou should try your best not to make the #Rewritten Response# become verbose,\n#Rewritten Response# can only add 10 to 20 words into #Given Response#.\n'#Given Response#', '#Rewritten Response#', 'given response' and 'rewritten response'\nare not allowed to appear in #Rewritten Response#\n#Given Prompt#:\nProvide a selection of three specific ingredients for a decadent dark chocolate raspberry cake.\n#Given Response#:\n- Flour\n- Cocoa powder\n- Sugar\n#Rewritten Response#:",
'role': 'user'}]],
'raw\_generation\_responses': ['- High-quality all-purpose flour\n- Unsweetened dark cocoa powder\n- Granulated white sugar'],
'generations': ['- High-quality all-purpose flour\n- Unsweetened dark cocoa powder\n- Granulated white sugar']}
另外,我们还可以通过“push_to_hub”方法将数据集推送到 HuggingFace 上,以便与社区共享。
# Push to Hugging Face
HF\_REPO\_ID = "argilla/distilabel-evol-instruct-dataset"
ds\_evol\_instruct.push\_to\_hub(
HF\_REPO\_ID, # type: ignore
split="train",
private=False,
token=os.getenv("HF\_TOKEN", None),
)
Conclusions
在本教程中,我们按照自己的方法,将 WizardLM 和 Deita 的相关方法结合起来,开发了一个经过改进的指令数据集。利用 distilabel,我们生成并评估了新的指令,通过应用 Evol-Complexity 使其更加复杂,并通过 Evol-Quality 提高其响应的质量,从而创建了一个包含成功指令的数据集。
参考文献
-
• https://distilabel.argilla.io/0.6.0/tutorials/create-evol-instruct-dataset/
-
• https://github.com/nlpxucan/WizardLM/tree/main/Evol\_Instruct
点个「赞」+「在看」❤️
让我们知道这份文字有温暖到你,也是 我们持续 创作的最大动力!
推荐
最新!SpeechLLM 综述:架构、能力、挑战与未来全揭秘
从数量到质量:通过自引导数据选择来提升语言模型性能以实现指令调优
GeForce RTX 3090, 4090, A10, A40, A100, A800, L20, L40 显卡性能对比
基础模型中的新范式:为什么o1是不同的,以及它将如何改变LLM应用
从数量到质量:通过自引导数据选择来提升语言模型性能以实现指令调优
Fully Sharded Data Parallelism (FSDP)
CosyVoice 2:基于大型语言模型的可扩展流式语音合成技术
Mini-Omni2: with Vision, Speech and Duplex Capabilities
亲测有效!如何用 Address Sanitizer 精准定位内存漏洞?附保姆级操作指南
要用 AI 裁员 50% 的千亿独角兽,公开认错,重启招聘!
single codebook和dual codebook在LLM中向量量化上有什么区别?
亲测有效!如何用 Address Sanitizer 精准定位内存漏洞?附保姆级操作指南
CosyVoice:一种基于监督式语义标记的可扩展多语言 Zero-Shot 语音合成器
近日还在想要不要建个群呢?感觉自己是个i人,又懒,打理不来呀。但这个想法不自主的就冒出来了,还是要思考下。天人交战良久,得,一位朋友私我要入群,那就建一个吧,感谢。
欢迎入群,希望能有一个交流的地方。但群主是个i人,没事儿让他想静静,有事儿圈他。
群主不是万能的,不是万能的,不是能的,能的。
