Llm 微调指南:您是否需要以及如何进行微调
在使用LLM时,我们最常收到的问题之一就是关于微调。每第二位客户都会问他们是否应该对他们的模型进行额外的训练。
在大多数情况下,答案是否定的,他们不需要。现代LLM在许多商业应用中已经足够好,无需微调,比如帮助客户从花店订购鲜花的机器人。此外,他们没有数据来进行微调,而他们拥有的20个对话样本并不算数(200个也是如此)。
训练和微调模型是一项昂贵的工作,如果可以的话,你真的应该避免它,并把节省下来的钱花在去阿鲁巴的旅行上,或者任何你喜欢的度假胜地。
但是,有些情况下你确实需要微调。例如,如果你希望LLM遵循非常特定的聊天格式,或者在某个非常特定的领域拥有知识,或者你想通过训练一个小模型来完成非常专业的任务,从而节省成本,而不是使用具有数百亿参数的大型LLM。这些都是通过微调创建定制模型的有效案例。
所以让我们看看如何做到这一点。
何时进行微调
如上所述,您只有在必要时才应该进行微调。首先尝试通过提示工程来解决任务,或者构建一个 RAG 系统。如果这失败了——考虑进行微调。
微调有以下缺点:
- 它需要花费金钱和时间
- 您需要良好的训练数据,否则将无法工作
- 即使正确执行,它也可能导致更频繁的幻觉,因为我们正在为一个最初并未针对该行为进行定制的模型添加新行为。如果您对模型进行重复更新,某个时候几乎可以保证会出现这种情况,这被称为漂移,因此您需要对此评估您的模型。
一旦您考虑了以上所有因素,仍然认为通用 LLM 不够好——您就需要进行微调。
数据
要进行微调,您需要特定格式的数据,称为指令数据集。
数据获取来源
有很多开放数据集可以使用,例如,Anthropic HH-RLHF 数据集用于模型对齐,MIMIC-III 用于医疗,CodeSearchNet 用于编码。具体包括:
- 特定领域的数据集:医学、法律、编码等
- 任务特定的数据集,适用于训练模型执行特定任务并制作 RPAs
- 通用数据集,包含一般知识,通常是从互联网上抓取的数据创建的
- 对齐数据集:用于格式、风格和安全性对齐
Hugging Face Hub 有很多指令 数据集,可以用于不同领域,我建议从那里开始。
但是既然你决定进行微调,你可能已经拥有数据,因此你需要创建自己的数据集。否则,你为什么要这么做呢?
如果样本不足,你可以使用大型 LLM(如 ChatGTP)通过从已有数据中推断来生成合成数据。我稍后会谈到这个。
数据需求
数据集的大小取决于模型的大小、任务的复杂性和训练方法。像 OpenAI 这样的公司使用了数百万项的庞大数据集,这对于大多数公司来说由于成本原因是不可行的,因此实际上我们将会有几千个样本。
对于简单的变化,比如沟通风格的对齐,您不需要太多样本,几百个就足够了;而对于特定领域知识的训练,您将需要几千到几十万个样本,具体取决于领域。一般来说,数量越多越好,最好至少有几千个样本。
数据的质量意味着不仅仅是数量,甚至可能比数量更重要。您需要确保数据正确反映您希望模型学习的行为,包括意义和格式。我想强调格式——您希望模型以用户可以理解的方式输出信息,包括清晰度和风格。如果模型以说唱歌词的形式讲真话,除非您想创造一个埃米纳姆的双胞胎,否则是没有用的。
数据准备
数据准备是一个关键步骤,因为数据的质量直接影响模型的性能和准确性。准备数据涉及多个过程,以确保数据干净、相关,并适合训练:
1. 去重
重复的数据点会增加训练成本,引入不必要的噪声,并导致模型过拟合或偏差。以下是常见的方法:
文本标准化:
- 将文本转换为小写。
- 删除特殊字符、额外空格和标点符号,以标准化内容。
基于哈希的去重:
- 生成标准化文本的哈希值。常用的技术是MinHash,它捕捉项目的本质或“语义指纹”,而不是其确切文本。这允许识别重复项,即使它们的格式或小细节有所不同。您可以使用像datasketch这样的库来实现。
- 比较哈希并删除匹配的条目。
基于向量的去重:
- 将项目转换为向量表示(嵌入),以测量它们的语义相似性。
- 使用像Quadrant、Pinecone或Weaviate这样的向量数据库高效查找相似项目。
- 在检索到的项目上应用交叉编码器,以更准确地计算它们的相似性评分。这一步帮助您自信地识别和消除近似重复项。
2. 个人信息去除
您需要对数据进行去标识化,因为您不希望模型学习(然后告诉每个人)个人信息(除非这是您想要的)。这可能会带来严重的法律和伦理影响,尤其是在GDPR等法规下。此外,通常个人数据与领域知识无关。
去标识化:
- 使用正则表达式模式检测常见格式(例如,电子邮件或电话号码)。
- 利用预训练的NLP模型进行命名实体识别(NER),以识别和删除个人数据。
领域特定过滤:
- 您可以根据数据的上下文创建自己的过滤器。例如,医疗数据可能需要根据HIPAA删除与健康相关的标识符。
3. 去污
您的数据集可能包含会对模型行为产生负面影响的内容:
恶意内容:
- 检测并过滤针对大型语言模型的嵌入命令(例如,提示注入)、脚本、XSS、SQL注入代码等。
- 自动扫描工具或专门的基于LLM的分类器可以帮助识别此类模式。
不当语言:
- 过滤脏话、侮辱性词汇、冒犯性内容、俚语。
4. 基于规则的过滤
您的数据集中并非所有数据都与您的领域或任务相关。基于规则的过滤有助于消除不相关或有害的内容:
- 根据任务定义排除标准。例如,如果您正在训练一个金融模型,请排除非金融数据。
- 使用关键词搜索、短语或主题建模来识别不相关内容。
我建议使用混合方法:
- 首先使用简单工具:
- 正则表达式或 基于关键词的搜索来识别模式,例如识别电子邮件地址或电话号码。
- 对于剩余项目使用高级技术:
- LLM作为评判者 来评估数据的相关性或质量。例如,可以要求LLM标记某个项目是否适合训练任务。
- 使用专门的机器学习模型进行复杂的清理任务,例如检测和过滤有毒语言。HuggingFace上有许多预训练模型可供使用。
数据评估
在完成所有这些步骤后,我建议有一个单独的流程来检查数据质量。这可以由人工完成,如果你只有几百个样本——你可以做到。但如果你有成千上万的样本,那就不太可能。因此,你可以使用 LLM 作为评判者 的方法,或者使用更简单的分类模型进行自动评估。例如,可以参考 HuggingFaceFW/fineweb-edu-classifier。
对于 LLM,你可以使用如下提示:
你是一个数据质量评估员。你的目标是评估一条指令及其相应答案的质量。判断答案在清晰、准确和完整的方式上,如何有效地解决给定的任务。
评估标准:
- 相关性:答案是否直接回应了指令?
- 清晰度:答案是否清晰易懂?
- 完整性:答案是否提供了完成指令所需的所有必要信息?
- 准确性:答案中的信息是否事实正确?
指令:
- 仔细阅读提供的指令和答案。
- 为上述每个评估标准提供一个分数(1–5\)。
- 1 = 非常差
- 5 = 优秀
3. 用具体的例子或观察为每个标准辩护你的分数。
评估示例:
Instruction: Explain the water cycle.
Answer: The water cycle involves evaporation, condensation, and precipitation, moving water between the Earth's surface and atmosphere.
Your Evaluation:
<Relevance>: 5 - The answer directly explains the water cycle.
<Clarity>: 4 - The answer is clear but could elaborate on each step.
<Completeness>: 3 - Missing details on processes like runoff or groundwater flow.
<Accuracy>: 5 - The provided information is correct.
Now, evaluate the following instruction-answer pair:
Instruction: [Insert instruction here]
Answer: [Insert answer here]
这里的可接受阈值由你决定,通常我会从 80–90% 开始。
同时要注意你使用的 LLM,以及 LLM 存在某些偏见(几乎和人类一样):
- 它们更倾向于冗长、长篇大论和论证性的答案,而不是简洁的答案,即使较短的答案更正确。
- 列表中排在前面的项目通常比其他项目更受模型的偏爱。这也被称为 小鸭综合症。如果你要创建偏好数据集,这一点很重要(稍后会详细介绍)。
- 模型偏见——同一家族的 LLM 更可能偏好由同一家族的模型生成的数据。如果你打算生成用于训练的合成数据,这一点很重要。
数据集格式
有几种流行的格式,它们都比较小并使用 JSON,因此您可以使用它们中的任何一种。
OpenAI 格式
OpenAI 的微调过程利用 JSONL(JSON Lines)格式,其中每一行代表一个独特的训练示例。
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Can you explain the concept of photosynthesis?"},
{"role": "assistant", "content": "Photosynthesis is the process by which green plants convert sunlight into chemical energy."}
]
}
Alpaca 数据集格式
由斯坦福大学基础模型研究中心开发。该数据集中的每个条目结构如下:
{
"instruction": "Describe the structure of an atom.",
"input": "",
"output": "An atom consists of a nucleus containing protons and neutrons, with electrons orbiting this nucleus."
}
ShareGPT
ShareGPT 数据集格式旨在捕捉用户与 AI 助手之间的多轮对话,适应各种角色,如“human”、“gpt”、“observation”和“function”。这种结构能够表示复杂的对话,包括工具交互和函数调用。
每个对话表示为一个 JSON 对象,包含以下组件:
{
"conversations": [
{"from": "human", "value": "What is the capital of France?"},
{"from": "gpt", "value": "The capital of France is Paris."},
{"from": "human", "value": "Show me a map of Paris."},
{"from": "function_call", "value": "map_search('Paris')"},
{"from": "observation", "value": "<image of Paris map>"},
{"from": "gpt", "value": "Here is a map of Paris."}
],
"system": "You are a helpful assistant.",
"tools": "map_search"
}
还有 OASST 等其他格式,您明白了。
微调技术
现在您已经拥有了训练数据,让我们看看我们可以用它做些什么。主要技术包括:
- 完全重训练
- Lora
- QLoRA
- 直接偏好优化 (DPO)
完全再训练
这是在特定数据集上训练整个模型(所有层)的过程,以优化其用于特定任务或领域。理论上最有效,但需要大量计算能力,因为它需要通过整个模型进行反向传播。
由于我们直接修改模型权重,这带来了某些风险:
- 过拟合风险:由于所有权重都被更新,因此在微调数据集上过拟合的风险更高,尤其是当数据集较小时。
- 泛化能力丧失:微调后的模型可能会失去其通用能力和先前的知识。
那么,我们需要多少内存来进行完全再训练呢?我们至少需要加载以下内容进行训练:
模型参数 + 梯度 + 激活 + 优化器状态
- 模型参数和梯度:
- 7B模型:大约70亿个参数,
- 12B模型:大约120亿个参数,12 *10⁹*4 = 48GB
每个参数通常需要4个字节(FP32精度)或2个字节(FP16精度)。假设为2个字节,则
- 对于7B模型 7*10⁹ * 2 = 14GB
- 对于12B模型 12*10⁹ * 2 = 24G
梯度每个参数再增加2个字节,因此额外:
- 对于7B模型 7*10⁹ * 2 = 14GB
- 对于12B模型 12*10⁹ * 2 = 24G
2. 激活:
更大的批量大小以及更高的序列长度会增加内存需求。对于典型的批量大小为8–32和序列长度为512个标记,激活内存可能增加:
- 7B模型:10–20 GB。
- 12B模型:15–30 GB。
3. 优化器状态:
像Adam这样的优化器需要额外状态的内存(例如,梯度和动量估计)。Adam需要两个额外参数,每个参数有3个状态,因此:
- 7B模型:14GB * 2 * 3 = 42GB
- 12B模型:24GB * 2 * 3 = 72GB
还有一些其他的东西会消耗内存,因此我们需要至少14 + 14 + 10 + 42 = 80GB用于7B模型。
对于一个小模型来说,这已经是很多内存了,你可以想象对于任何大型模型你需要多少内存。因此,完全再训练并不实用,且很少使用。那么,还有什么替代方案呢?
LoRa
假设您想改变模型的行为,但又不想改变整个模型。改变模型行为意味着改变其权重,以便改变输出。这里有个窍门——如果我们能以某种方式修改模型输出而不改变它们的权重就好了……
当然是有办法的。在一种粗暴的解决方案中,我们可以将模型的输出输入另一个模型进行转换。这是可行的……只是,我们现在有两个模型,并且复杂性增加了很多。
但如果我们可以在模型上添加一个过滤器,保持原始模型层不变并改变它们的输出呢?这有点像戴上增强现实眼镜。您看到的世界不同,但世界并没有改变。
这基本上就是LORA的概念。我们冻结原始模型的权重,并通过添加一个称为Lora矩阵的附加权重矩阵来应用变换,从而形成一个额外的可训练层,大小要小得多。
其中:
- Wnew — 新权重
- Wpre-trained — 原始模型权重
- ΔW — 可训练的权重调整
我们如何计算这个Lora矩阵?我们对那个附加矩阵进行微调/训练,而不是对原始模型进行,使用标准方法使其学习如何预测所需结果与原始模型结果之间的差异。
而且美妙的是,Lora矩阵可以远小于原始模型权重矩阵。这就是为什么它被称为低秩适应,因为该矩阵的秩低于原始矩阵。
假设您有一个大小为d的权重矩阵:
它将有d*d个元素。如果d是100万,那么它将有一万亿个元素。
现在这是LoRa的矩阵:
它将有d*r + r*d个元素。如果d是100万,秩(r)是8,那么它将有1600万个元素。
它的工作原理如下:
y = x * (W + ΔW) = x * W + x*(A*B)
- y: 应用权重后的输出。
- x: 输入到层
- ΔW=A * B
其中:
- A: 形状为d*r的矩阵,其中r是秩(为LoRA微调选择的小维度),d与原始权重矩阵的维度相同
- B: 形状为r*d的矩阵
或者以可视化形式表示:
秩的一个常见起始点是8。某些情况下,使用高达256的值也取得了良好的效果,但您需要进行实验以找出适合您的方案。使用更大的秩可以提高某些任务的性能,特别是那些需要更强表达能力以捕捉复杂模式的任务。然而,这也增加了过拟合的风险,尤其是在较小的数据集上。当模型容量超过数据复杂性时,这种风险在机器学习中是众所周知的。
在训练过程中,我们需要在内存中存储原始模型的权重W和微调模型的ΔW,同时仅计算“新”的小矩阵A和B的梯度。这大大减少了所需的内存和计算能力。训练将会更快,7b模型可以轻松在配备桌面GPU的PC上进行微调。
更重要的是,我们可以有几个不同的“透镜”,像这样,可以放在基础模型上,而无需更改它。
LoRA微调通常能达到与完全微调相当的性能,特别是当低秩近似非常适合任务时,LoRA适配器可以在不影响基础模型的情况下进行测试或应用。
QLoRA
与 LoRa 相同,但为了降低内存占用,我们将基础模型量化为自定义数据类型,通常为 NF4(Normal Float 4-位)。常规模型使用 32-位或 16-位浮点数作为存储权重的基础数据类型。
NF4 使 QLoRA 能够保留基础模型的 大部分 准确性,同时显著减少内存使用和计算需求。
量化的想法是:
- 网络中的大多数权重反正都是 0
- NF4 根据实际数据统计优化值的分布,而不是使用浮点值的线性分布
对于 LoRa 传递,我们仍然会使用常规模型,使用 32-位或 16-位浮点数,以便有更大的学习范围。
使用 QLoRa 可以将 GPU 内存使用减少 40–70%。然而,这也带来了代价——在训练时,QLoRA 的速度大约比 LoRA 慢 30%,并且略微降低量化模型的质量。
它在非常大的模型(例如 LLaMA 或基于 GPT 的架构)中表现良好。
微调与(人类)偏好对齐
微调在训练模型以执行特定任务方面效果很好,但不仅仅是模型执行什么,还要关注它如何与人类互动。如果我们想创建一个语言模型助手,就不能直接使用预训练模型——即使它具备所需的知识,也无法智能地回答用户查询。
教会模型与人类沟通被称为对齐。对此有不同的定义,我将使用Antropic的3H定义:
- 有帮助的 — 响应应该解决用户的问题。
- 无害的 — 响应不应对用户造成伤害。
- 诚实的 — 响应应事实准确。
传统方法在这里帮助不大,因此开发了一套新的技术。
任何此类技术的思路是拥有一个类似于我们上面讨论的数据集,此外人类偏好或价值观被清晰地指示。这可能包括对文本质量、语气、风格或事实正确性的反馈。通常,数据集项有多个响应选项,每个选项按偏好排名。
我敢打赌你见过ChatGPT在生成答案时提供多个选项供你选择——他们这样做是为了收集类似的数据集。许多问答网站通常有点赞或投票/反对票系统,这也可以用作训练数据。如果你从互联网上抓取数据——之后进行清理非常重要,数据集可能包含大量无用信息。
例如:
用户:“我现在感到工作和生活压力很大,很难继续下去。”
响应选项:
- 选项 A:“我很抱歉你有这样的感觉。你考虑过和你信任的人或专业顾问谈谈吗?”
- 选项 B:“你是什么人,抱怨成这样?喝点伏特加——你会好的。”
人类提供的偏好:
- 优选响应:选项 A(因同情和清晰度排名最高)。
- 排名:选项 A > 选项 B。
理由:
- 选项 A 表现出同情,承认用户的感受,并提供可行的建议。
- 选项 B 忽视用户的感受,未提供任何建设性帮助。
或以JSON格式表示:
{
"context": "I'm feeling overwhelmed with work and life right now. It's hard to keep going.",
"responses": [
{
"text": "I'm sorry you're feeling this way. Have you thought about talking to someone you trust or a professional counselor? It might help to share your feelings.",
"rank": 1
},
{
"text": "What kind of man are you, complaining like that? Just drink some vodka - you’ll be fine.",
"rank": 2
}
]
}
一旦你拥有这些数据,就可以使用以下技术:
人类反馈强化学习 (RLHF)
这是偏好对齐的基石。这个想法与训练狗非常相似,即通过奖励狗做正确的事情和惩罚它做错误的事情来进行多次迭代。在这种情况下,你扮演奖励模型的角色,而狗则扮演基础模型的角色。
因此,有一个单独的奖励模型,它被训练用来通过成对比较来预测人类的偏好(例如,“响应 A 比响应 B 更好”)。基本上,我们训练一个奖励模型来预测响应的排名。
这样做是为了在我们拥有奖励模型后不必再使用人类——它在进一步的训练过程中作为人类反馈的代理。
然后,主模型使用强化学习进一步微调,其中奖励信号来自于经过训练的奖励模型,通常经过多次迭代。基础模型在这个过程中并不会获得新的知识,而是学习使用和传达它已经拥有的知识。研究表明,使用一个小而高质量的数据集比使用大量低质量的数据集要好得多(参见 LIMA 研究:Less Is More for Alignment)。
这种方法允许奖励模型产生复杂的奖励信号,包括正确性、相关性、安全性,以及各种政治审查的无稽之谈。它还允许我们使用奖励模型训练多个基础模型以进行偏好对齐。
缺点也很明显。现在我们必须训练两个模型而不是一个,然后对基础模型进行多次微调。这在计算上是昂贵的,复杂的,并且耗时。
此外,还有过拟合奖励模型和降低基础模型性能的风险。
因此,为了避免复杂情况,提出了另一种方法:
直接偏好优化 (DPO)
这可能是你能实现“有蛋糕又能吃蛋糕”的最接近状态。
它是在论文“直接偏好优化:你的语言模型秘密地是一种奖励模型”中提出的,作者是Rafael Rafailov和其他一些人。他们有一个天才的想法:如果我们跳过中间的奖励模型输出,直接使用标准监督学习使模型与人类偏好对齐,会怎样?
所以这里的区别在于,我们没有单独的奖励模型,也不使用强化学习,而是直接使用标准监督学习方法更新基础模型。如果你想知道区别是什么,可以在这里阅读。
监督学习通常使用基于梯度的优化(例如,随机梯度下降)直接根据标记数据调整基础模型的权重。与RLFH相比,DPO在时间和成本方面要好得多,因为它不需要多次迭代和单独的模型,但在许多情况下提供类似的性能和基础模型的对齐,尽管是在某些条件下。
这种方法需要高质量的细粒度数据,它对质量的敏感性超过了RLHF。数据集中偏好数据必须足够且简单明了。如果你有这样的数据集或能够创建一个——DPO可能是最佳选择。
用于微调实验和托管的选择
当然,如果您有相应的硬件,您可以选择自我托管并在本地进行训练/部署。您的设置将取决于您使用的硬件类型、模型和虚拟化,因此我不打算深入讨论。
编排
一般来说,我建议使用像 ZenML 这样的编排工具进行模型部署,这样您可以切换基础设施提供商,避免供应商锁定。然后,您可以从一个提供商的免费套餐开始构建原型,如果需要,可以切换到可扩展的云版本或本地部署。
对于实验,我建议坚持使用云平台的免费套餐,具体如下:
微调基础设施
AWS SageMaker:一个完全托管的服务,用于在AWS上构建、训练和部署机器学习模型。非常方便,因此您无需构建自己的基础设施和购买GPU。他们提供免费套餐以开始实验。
替代方案:
- Google Vertex AI
- Azure Machine Learning
- Databricks ML
- MLflow — 这个是开源的,可以自托管
模型托管
对于实验和协作,最佳选择是 HuggingFace——一个用于共享和发现机器学习模型、数据集和演示的协作平台。它就像模型的 GitHub。他们还有免费的 层级。
替代方案:我认为没有好的替代品,这就是他们如此受欢迎的原因。所有主要参与者(Google、Azure AI Playground)都有类似的东西,但没有那么好。
对于生产环境,您可以使用
- AWS SageMaker
- Google Vertex AI
- Microsoft Azure Machine Learning
- MLflow(可以在本地部署)
玩得开心!