利用APE与SELF自动化构建指令数据集
在前文我们探讨了多种指令微调的方法。本章将聚焦于如何显著降低指令数据集的人工标注成本,从而使每个人都能构建自己的专属指令集。当然,我自身也在积极地进行数据集的构建工作。
两种自动化指令生成方案
本章介绍两种旨在降低人工成本的方案:SELF-Instruct 和 Automatic Prompt Engineer (APE)。
- SELF-Instruct:该方法利用多样化的种子指令,借助大语言模型强大的上下文理解和指令生成能力,通过"自举"(Bootstrap)的方式生成更多样化的指令样本。
- Automatic Prompt Engineer (APE):此方法采用"逆向工程"的思路,基于给定的输入-输出对,利用大语言模型来生成和筛选最优的指令提示(prompt)。
我们将这两种方法结合起来,用 APE 将原始任务转化为种子指令,再利用 SELF-Instruct 进行指令集的扩充。我们已在医学和金融领域的 NLP 任务上进行了实践,并将该应用部署在 Hugging Face 平台上,提供了可视化的 Gradio 界面。拥有 API 密钥的用户可以自行尝试:https://huggingface.co/spaces/xl2533/MakeInstruction。请记得先将应用 Fork 到您自己的 Space 中进行体验。
Automatic Prompt Engineer (APE)
论文:2023.3, LARGE LANGUAGE MODELS ARE HUMAN-LEVEL PROMPT ENGINEERS
GitHub:https://github.com/keirp/automatic_prompt_engineer
核心思想:通过输入和输出,让模型逆向生成并寻找最优的指令提示。
指令生成
APE 方法的核心是基于原始的输入-输出样本(部分生成类任务可能仅有输出),让大语言模型预测生成这些样本时可能使用的原始指令。原论文将指令生成模板分为三类,但实践中发现,仅使用一种"前向生成"的类型(即将 Few-shot 样本置于开头,待生成的指令置于末尾)即可满足需求。
原论文使用 text-davinci-002 作为指令生成模型,并为每个样本提供了 5 条 Few-shot 示例作为上下文,引导模型输出可能的指令。在此实现中,我们将生成指令的模型更换为 ChatGPT(主要出于成本考虑),并根据 ChatGPT 的对话特性调整了 Prompt 模板。需要注意的是,作为对话模型的 ChatGPT 相比 davinci-002/003 倾向于生成更多"闲聊"式的内容。若沿用原模板,可能会得到类似"我认为这个朋友收到的指令是……"的回答。
实践中的观察
- 抽象与生成类任务的 Few-shot 样本数量:对于较为抽象或偏向生成的任务,需要提供足够多的 Few-shot 样本,模型才有可能准确推断出"无偏"的指令。例如,在相似新闻标题生成任务中:
- 使用 2 条 Few-shot 样本,模型预测指令为:"将输入中的公司或组织名称规范化为全称"。
- 使用 4 条 Few-shot 样本,模型预测指令为:"将公司公告或新闻标题简化成简短的标题,包括公司名称和主要内容"。
- "说人话"的重要性:在构建指令样本时,清晰、符合语意的表述至关重要。例如,我们将相似标题生成任务简化为判断两个标题是否描述同一事件的分类任务。
- 初始输入(包含分类类输出):模型预测:"我无法确定这个任务指令的具体内容,但它可能与文本分类或者自然语言处理相关。给出输入文本,需要判断该文本是否符合某种特定的模式或标准,从而得出输出结果。"
- 改进输入(包含"相同"/"不相同"输出):模型预测:"判断两个新闻标题是否相同,如果相同输出‘相同’,否则输出‘不相同’。"
- "模型以为"比"你以为"更重要:起初,我对这种机器自动生成指令的方式持保留态度。然而,在医学术语标准化任务中,经过 APE 优化生成的指令,在单测时相比我手动编写的指令,获得了更高的正确率。我大胆推测,模型之间的一致性使得合理使用的机器生成指令,能够提供更精确的上下文任务描述,并且理论上不应逊于人类指令。
指令评分
APE 采用两种评分方式来评估多个候选指令的优劣:
- 准确率 (Accuracy):基于模型在给定样本上,使用不同指令进行预测的正确率来评价指令的效果。例如,对于问答任务,根据模型回答的准确性来评估指令。
- 对数概率 (Log Probability):利用模型预测的对数概率作为评价指标。此指标较为精妙:并非简单地计算模型预测正确 token 的对数概率之和,而是将输入、输出和指令一并喂给模型,然后计算模型生成原始输出的概率。这有效解决了生成类任务因解码随机性导致指令难以比较的问题。
获取 OpenAI 接口的 Logprobs:设置 echo=True 和 logprobs=1,接口会返回所有采样 token 的对数概率。logprobs 的值对应 TopN 返回的 token 数量(OpenAI 最多返回 Top 5,包括实际被采样的 token)。设置 max_tokens=0 可阻止模型生成新文本,仅返回输入及其对应的 token 条件概率。
此外,APE 还引入了随机搜索机制:过滤掉低分指令后,基于特定模板,让模型为高分指令生成相似指令,并与原始指令一同排序,选出最优者。在实际实现中,我们将相似指令生成部分替换为人工干预:针对高分指令,补充我认为缺失的核心信息,然后使用 Log Probability 评分来评估其效果。因此,应用中的"Generated Prompt"窗口被设计为可交互的,允许用户直接修改生成的指令,然后进行评估。
例如,在医疗搜索意图任务中,模型最初无法理解"多问"标签的含义。因此,最初生成的最优指令是图中的第二个。在我人工加入了"多问"的指令后,第一个指令(包含"多问")取得了更好的效果。
效果评估
原论文使用了 REF[1] 中的 24 个指令任务,每类任务选取 5 对样本,通过上述方法生成最优指令。然后,在剩余样本上,与人工指令以及 REF[1] 中未包含搜索和评分排序的 APE 方法(称为 Greedy)进行效果对比。结果显示,APE 在 24 个任务上的表现基本能与人工指令持平,甚至在部分任务上超越了人工指令。在 BigBench 等更具挑战性的数据集上,APE 在 17 个任务(共 21 个)上也优于人工指令。
我们在 4 个医学数据集上,使用 APE 结合人工优化得到的最优指令如下表所示:
| 任务 | 指令 |
|---|---|
| 搜索意图 | 生成医学相关问题的答案。给定一个输入问题,需要根据问题生成相应的输出答案。答案包括临床表现、病因、治疗方法、作用、定义等等,如果有多个问题,返回"多问"。 |
| 医疗术语标准化 | 将医学手术名称的术语表述标准化。输入是医学手术的名称,输出是对该手术的名称进行修正、标准化,以供医学专业人员更好地理解。 |
| 医疗药物功能实体抽取 | 给定药品信息和用途说明,根据用途说明提取出药品的主治功能。 |
| 医疗文献QA生成 | 训练一个问答系统,给定一些医学文本,能够回答用户提问关于该文本内容的问题。每个输入-输出对是一组文本和对应的问题及答案。输出的形式是以下 JSON 格式 {"问题": (问题), "回答": (回答)}。 |
以医学术语标准化为例,我们简化了 APE 的 Gradio 应用,效果如下。
SELF-Instruct
论文:2022.12, SELF-INSTRUCT: Aligning Language Model with Self Generated Instructions
GitHub:https://github.com/yizhongw/self-instruct
Stanford Alpaca 数据生成过程:https://github.com/tatsu-lab/stanford_alpaca#data-generation-process
核心思想:通过大语言模型的生成多样性,利用"自举"方式对种子指令集进行不定向扩充,类似于非线性插值。
在前一步中,我们使用 APE 获得了一个初步的种子指令集,但其丰富度和多样性仍显不足。SELF-Instruct 提出了一种 Bootstrap 方案,允许大语言模型基于种子指令生成新的指令,从而实现指令集的扩充。该方案也被 Alpaca 项目用于生成微调指令集,主要包含以下三个步骤:
1. 新指令生成
最初,作者手动构建了 175 个种子指令,每个指令包含一条指令和对应的样本。在每个迭代步骤中,会从中采样 8 个指令:6 个来自种子指令,2 个来自 LLM 生成的指令(第一步全部使用种子指令)。然后,基于以下 Prompt 模板构建模型输入:
"""
Come up with a series of tasks:
Task 1: {instruction for existing task 1}
Task 2: {instruction for existing task 2}
Task 3: {instruction for existing task 3}
Task 4: {instruction for existing task 4}
Task 5: {instruction for existing task 5}
Task 6: {instruction for existing task 6}
Task 7: {instruction for existing task 7}
Task 8: {instruction for existing task 8}
Task 9:
"""
如果读者觉得该模板与 ChatGPT 的交互方式格格不入,这是正常的,因为该模板是基于 GPT-3(一个"天真"的续写模型)实现的。Alpaca 项目在使用 SELF-Instruct 构建指令样本时,升级了模型至 davinci-003。由于模型对指令的理解能力增强,纯 Few-shot 的 Prompt 模板被修改为(已翻译成中文),并且 Few-shot 样本数量缩减至 3 个:
""" 你需要想出20个不同的任务指令。这些任务指令将输入GPT模型,我们将评估GPT模型完成指令的情况。 以下是要求: 1. 尽量不要在每个指令中重复使用动词,以最大化多样性。 2. 指令的表达形式需要多样化。例如你可以把问题和祈使句结合起来。 3. 指令的类型应该多样化,包括但不限于开放式生成、分类、抽取、问答、文本编辑等等。 4. 指令应该是GPT模型可以完成的任务。例如,指令不能是输出图像或者视频,另一个例子,不要让助手在下午5点叫醒你或设置提醒,因为GPT不能执行任何动作。 5. 指令必须是中文。 6. 指令应该是1到2句话,可以是祈使句或问句。 20个任务的列表: """
初次阅读 SELF-Instruct 的论文时,可能会对指令生成过程感到困惑。但可以理解为,LLM 在执行类似"非线性插值"的工作。每个迭代步骤都会采样不同的指令作为上下文,模型会将这些不同的上下文融合,生成新的指令。
2. 样本生成
仅有指令是不够的,还需要生成对应的输入和输出样本。在 SELF-Instruct 的原始论文中,作者增加了一个分类任务步骤:首先让 LLM 判断指令是否为分类任务。如果是,则先生成输出,再生成输入;否则,先生成输入,再生成输出。这是因为作者发现,对于分类任务,模型倾向于仅生成其中一个标签的输入。
然而,在 Alpaca 项目中,由于模型升级到 davinci-003,上述问题似乎不再显著。同时,随着模型输入长度的增加,Alpaca 项目将样本生成和指令生成步骤合并,一步完成。因此,上述指令生成模板扩展为:
""" 7. 你应该为指令生成一个合适的输入。输入字段应该包含为指令提供的一个具体示例。它应该涉及真实的数据,而不应该包含简单的占位符。输入应该提供足够的内容,使指令具有挑战性,但理想情况下不应超过100个单词。 8. 不是所有的指令都需要输入。例如,当一个指令询问一些一般信息时,"世界上最高的山峰是什么",就不需要提供具体的上下文。在这种情况下,我们只需在输入字段中放置"<无输入>"。 9. 输出应该是对指令和输入的合适回应。确保输出少于100个单词。 20个任务的列表: """
这样,每一步实际上直接生成了指令及其对应的输入和输出样本。对于自由生成类任务,也支持仅有输出而无输入的情况。
3. 过滤与后处理
生成的指令和样本需要经过一系列后处理,主要包括模型预测解析、不合理指令过滤以及相似指令过滤。解析部分建议直接参考代码实现。
不可用指令过滤
- 因输出长度被截断的指令:若响应停止原因是长度限制,则过滤掉最后一个指令。
- 包含不可用关键词的指令:例如图片、音频等 GPT 模型无法完成的任务。
- 过长或过短的指令。
- 指令开头字符非英/中文的指令。
相似指令过滤
为保证指令集的多样性,每轮都会计算新生成指令与已有指令的 Rouge-L 相似度(基于最长公共子串长度计算的 F 值)。只保留 F 值小于 0.7 的新指令。同时,可以通过观察最近几轮生成指令的整体相似度来判断是否应停止生成。
我们使用 APE 生成的 4 个医学 NLP 任务作为种子指令集,每轮采样 2 个人工指令和 1 个机器指令作为 Few-shot 示例,生成 2 个新的指令任务。对上述指令进行调整,限制只生成医学领域的任务,得到了以下结果。看来我需要给账户充值了。
以上是关于自动化构建指令集的主要内容。欢迎感兴趣的朋友们一同探索(数据构建的艰辛,我们共同承担)!
