大语言模型的指令微调(Instruct)解析

2024年03月28日 由 alex 发表 942 0

目的

让现代聊天机器人在你自己的数据上维护自己的功能仍然是一项复杂的任务。随着 Gemini 1.5 Pro 和 Claude 3 等领先产品容量跃升至 100 万个令牌,上下文窗口的大小也在迅速增加。


Cognition Labs 最近发布的 Devin 很可能使用了巧妙的 RAG 技术来完成任务,但依赖于将所有信息注入上下文窗口可能会有问题。社区的共识似乎是,GPT-4 128k 可以在大约 60K 标记(这并不算多)的情况下保持出色的性能。即便如此,随着代币数量的增加,要保持出色的性能还需要更好、更棘手的提示。由于这些限制,在不久的将来,能力最强的模型很可能会将良好的提示、RAG 和微调结合起来使用。例如,对于代码助手工具来说,可以通过 RAG 管道检索最新代码。然后,经过微调的模型可以比未经微调的模型更有效地分析和推理这些代码,指出它可能从其他地方学到的任何边缘案例和风险。此外,微调模型将采用组织的编码惯例和最佳实践,从而为员工提供更具洞察力的指导。


我在网上找到的有关在较小数据集上微调高性能聊天机器人的资源非常有限。相反,大多数研究都引入了像 BioMistral 这样的模型,它们使用 30 亿个大型代币数据集取得了成功,需要大量预算和专业知识。


本实验试图发现一种更轻便的方法,它能在 128K 上下文窗口的限制和在数十亿代币(也许更多是数千万代币)上微调模型的复杂性之间游刃有余。


本文的目的是创建一套可重复的说明,以便使用易于获取的硬件进行经济高效的模型微调。重点在于易于使用、尽量减少试验和错误,以及最大限度地使用原始文本数据而非标记会话数据。


我将概述所使用的数据,重点介绍最佳超参数及其结果,最后从技术上解释其有效性。


训练


A100 40GB

我使用 Colab 提供的 Nvidia A100 40GB 进行所有训练,只有一次使用了 H100 80GB。


Unsloth

我使用 Unsloth 库来提高训练速度和内存效率。


训练方法与最先进的微调模型的区别

BioMistral 和 xFinance 是通过微调向模型传授新的特定领域知识的现代范例。xFinance 继续对 Llama 7B 基础模型(即:非结构化版本)进行预训练。它使用 LoRA。该模型首先在超过 216626 份文件(总计 2.36 亿个标记)上进行训练。然后在 25,000 个金融对话数据样本上对其进行进一步微调。与标准的聊天机器人训练类似,这种方法也是从缺乏指令标记或结构化会话元素的原始文本数据开始训练,然后过渡到完全基于会话数据的训练。BioMistral 采用了类似的方法,但有趣的是,它是从 Mistral 7B Instruct v0.2 模型开始微调的。


我的方法是在同一训练运行中结合原始数据集和注释数据集,因为这种方法产生的结果最好。只进行一次训练运行。


TRL 的 SFTtrainer

我使用了 TRL 库中的 SFTtrainer。它是对默认的 HuggingFace 训练器的封装。我找不到太多关于 SFTtrainer 如何扩展它的文档,而代码显示改动很小。它似乎是通过设置与 input_ids 相同的目标标签来为自我监督训练准备数据集(参见这几行代码)。它将目标标签设置为与 input_ids 相同。下面是一个笔记本使用默认的 HuggingFace 训练器做同样事情的示例。这只是使用 HuggingFace 提供的默认训练器进行下一个标记预测,并没有什么花哨的地方。原始文本数据 "和会话数据在训练中的唯一区别是添加了特殊指令标记"[INST]"和"[/INST]",而 Mistral Instruct 已被训练为可以识别这两个标记。


创建原始数据集

我的原始数据集由 repo 的 wiki、12 月份的主分支快照以及最近 100 次的拉取请求(包括注释和代码更改)组成。我对其进行了分块,因此每个样本最多只有 8192 个标记。


抓取维基百科

我只是将每个页面复制并粘贴到一个文本文件中。


抓取代码库

我编写了一个本地运行的 Python 脚本,并将所有文件按以下格式写入文本文件:


- File: productSwitchTypes.ts
  Content:
export type ProductSwitchType =
 | 'to-recurring-contribution'
 | 'recurring-contribution-to-supporter-plus';
export interface PreviewResponse {
 amountPayableToday: number;
 supporterPlusPurchaseAmount: number;
 contributionRefundAmount: number;
 nextPaymentDate: string;
 checkChargeAmountBeforeUpdate: boolean;
}

- File: productTypes.ts
  Content:
...
...
...


抓取 PR 数据

Colab 笔记本中的相应单元格将为该 PR 生成类似的输出结果:


PR #2989: Create devcontainer.json
URL: https://github.com/octocat/Hello-World/pull/2989
Description: None
Created at: 2024-02-26T11:39:03Z
Merged at: None
File: .devcontainer/devcontainer.json, Status: added
Changes: @@ -0,0 +1,5 @@
+{
+  "image": "mcr.microsoft.com/devcontainers/universal:2",
+  "features": {
+  }
+}


生成对话数据

尽管这篇文章的标题是 "对话数据",但我确实使用了一些标签对话数据,不过这些数据都是很容易生成的合成数据。这与精心策划的数据集质量不符,但合成数据正变得越来越普遍(我在某处读到,HuggingFace 上的数据集约有 50% 是合成数据)。虽然它不会带来惊人的聊天机器人性能,但直觉告诉我们,它可能有助于减轻任何灾难性的遗忘和性能下降,而且它也是增强我们数据集的一种简单方法。我使用了三种方法生成合成数据:


  1. 对于每个维基页面,我都使用 GPT-4 Turbo API 根据提供的文本生成一些 QA 样本。这样就产生了大约 300 个 QA 对。
  2. 对于每个维基页面,我都创建了一个特定的指令或问题。例如,在 "Fastly 和缓存 "页面上,指令可能是 "告诉我 Fastly 如何在`manage-frontend`中使用"。然后,回复就是该维基页面的内容。
  3. 与上一步类似,我为代码库中的每个文件都创建了一个问题。例如 package.json文件在manage-frontend存储库中是什么样子?”然后,我在每个代码文件前面加上用于训练的代码库快照的日期作为前缀,即:“截至 2023 年 12 月,该package.json文件如下所示:<package.json code here>”


QA 数据已导出为 JSONL 文件,建议使用以下格式,因为许多标记符号化器都有一个名为 apply_chat_template 的函数,可在每一行中接收消息属性内的列表。下面是一个格式示例:


{"messages":[{"role":"user","content":"What is the capital of France?"},{"role":"assistant","content":"The capital of France is Paris."}]}
{"messages":[{"role":"user","content":"What is the capital of England?"},{"role":"assistant","content":"The capital of England is London."}]}


我使用其中 10% 的对话数据作为验证数据集。


训练模型


超参数扫描

我使用了手动搜索。我的直觉是,LoRA 等级、批量大小和学习率对模型性能的影响最大。因此,我一开始就使用了范围较广的超参数,然后根据初始扫描的性能迭代缩小搜索空间。2e-5 的学习率似乎是最佳的,这似乎也是微调 Mistral 的标准。BioMistral 继续微调指导模型 v0.2,预热时间为 0,余弦调度器和学习率为 2e-5。随着等级的提高和批量规模的减小,评估损失有所改善。不过,值得注意的是,由于一次验证的样本较少,降低评估批量大小自然会改善验证损失,因此最好在训练完成后手动检查模型!


下图中的扫描都使用了 512 或 768 的等级,字母也各不相同:等级的 1 倍、1.5 倍或 2 倍。批次大小为 1、2 或 4。


找到最佳超参数后,我重新运行了训练,包括所有数据,以充分利用我所拥有的少量数据,这也是常见的做法。这些运行在扫描名称的末尾标注了 "所有数据"(All-Data)。


2


结果


结果比预期的要好:


鉴于维基或公关描述中缺乏任何自然语言参考,以下对 "产品切换 "问题的回答令人印象深刻。这里的大部分变量名和条件式都是正确的:


3


像下面这样的问题同样没有自然语言参考,实际上需要深入研究代码才能知道我们不允许切换到 Paypal,只允许切换到银行卡和 DD。它几乎说对了。


4


在明确要求的情况下,它可以完美地回忆起一些代码:


5


我们数据集中的信息相互冲突怎么办?

维基中的一些信息已经过时(例如),包括提及我们的旧 CI 平台 TeamCity 和使用 Reach Router 的旧路由解决方案。在向聊天机器人询问这些信息时,它确实回答正确,但需要注意的是,这些信息更为常见,预训练模型可能更倾向于建议使用这些信息:


6


灾难性遗忘

灾难性遗忘比预期的要轻一些,但微调模型与基础模型之间仍有明显差异:


当提问涉及 JavaScript 和 Typescript(manage-frontend 中普遍使用的语言,例如:"给我写一个执行 x 和 y 的 Typescript 函数")时,模型可能会在回答中添加manage-frontend 代码库中使用的一些模式。例如


7


在收到编写 Python 代码的指令后,我们不会从 "manage-frontend "获得这种知识注入到响应中:


8


对于与代码无关的问题,会有细微的差别,性能也会下降。请注意下面回答中的错误,是 "每小时 229792 公里",而不是 "每秒"。使用相同推理设置的 16 位原始模型不会出现这种错误。


9


文本生成策略

我将 do_sample 设置为 "false"(假),因此模型会在引擎盖下使用贪婪搜索的确定性方法生成文本。它会根据模型预测的概率选取最有可能的下一个单词或最有可能的单词序列。因此,温度和 top_p 等参数并不重要,因为模型并没有从下一个单词的概率分布中采样。相反,它是直接选择概率最高的标记。这里有一篇很好的文章,可以了解更多文本生成中的确定性方法。我发现使用这种方法得到的回复稍好一些,而使用概率方法并将 temperature 和 top_p 设置为更极端的值会导致性能大大降低。


结论

这些结果令人鼓舞,尤其是考虑到训练数据的规模和质量有限。如果有更好的训练数据,我们就能看到显著的改进。公司的消息工具、票据和问题管理系统以及电子邮件中都有大量高质量的文本数据。此外,开发人员还可以投入时间创建高质量的对话数据。



文章来源:https://towardsdatascience.com/fine-tune-an-instruct-model-over-raw-text-data-6db654e7e2ed
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消