微调介绍
模型本质
什么是微调
微调在本质上就是学习这些“改动量”,而不是从头学习所有参数,这样既能保证原来的已学到的能力还在,也能学一些新知识在新任务上表现更好一点。但如果,在微调的时候,忘记了原来的能力,反而只在新任务上表现良好,这就是“灾难性遗忘”,这个在迁移学习领域中,是一个重点难题。
微调方法
全量微调
全量微调,可以简单理解一下就是一个已经训练好的模型在新的数据集上再训练一次,也就是所有参数都参与了更新,那么计算成本等不用说,一定是最高的。当然,每个方法一定有它适用的场景,如果说一个新任务的数据集与原任务的数据集相差十分大的时候,那么也许全量微调后的模型性能是最好的,通常来说,就是一个trade-off 取舍的问题。
高效微调
1. 核心思想
PEFT 的核心是:
-
冻结预训练模型的大部分参数
-
注入少量可训练参数(几百万,甚至几十万)
-
在下游任务中只更新这小部分参数
这样做的好处是:
-
显著减少训练开销(显存 & 计算量)
-
减少存储成本(比如 LoRA 只需要存储几 MB 的增量参数)
-
更容易迁移到多个任务(共享同一个基座模型)
2. 常见方法
PEFT 不是单一算法,而是一类方法,常见的包括:
-
LoRA (Low-Rank Adaptation)
-
最流行的 PEFT 方法。
-
在模型的权重矩阵 WWW 上加入一个低秩分解 ΔW=AB\Delta W = A BΔW=AB,其中 A,BA, BA,B 是可训练的低维矩阵。
-
训练时只更新 A,BA, BA,B,原始 WWW 保持不变。
-
通常只增加 0.1%–1% 的参数。
-
-
Prefix Tuning / Prompt Tuning
-
在输入序列前面加上一小段 可训练的虚拟 token embedding。
-
类似“学一个任务专属的提示词”。
-
模型主体完全冻结。
-
-
Adapter
-
在每层 Transformer 之间插入一个小型的瓶颈网络(比如两层 MLP)。
-
只训练这些 Adapter 的参数。
-
-
BitFit
-
极端高效的版本:只训练模型里的 bias 参数。
-
参数量极少,但在一些任务上仍有不错表现。
-
3. 举个例子
比如 LLaMA-7B(70 亿参数),如果做 full fine-tuning:
-
每个任务都要更新并保存 70 亿参数 (~28 GB)。
如果用 LoRA (秩=8):
-
只需更新几百万参数(占比不到 0.1%)。
-
存储开销从几十 GB → 几百 MB。
这就是 PEFT 的高效性。
LoRA微调
算法过程
1. LoRA 的基本思想
我们想给某个大权重矩阵 W(冻结,不更新)加一个小的增量 ΔW,但直接学整个 ΔW(大小和 W一样)太大。
于是,LoRA 强制约束 ΔW的形式:
这样需要学习的参数量只有 O(r(d+k))O(r(d+k))O(r(d+k)),远小于 O(dk)O(dk)O(dk)。
2. 这两个矩阵 A、B 是怎么初始化的?
在实际实现里,它们一开始就是随机初始化的,并没有什么先验的分解操作。
常见的做法(参考 HuggingFace PEFT、原始 LoRA 论文实现):
-
A(降维矩阵):用标准高斯或均匀分布随机初始化;缩放因子通常设得比较小(比如 Xavier 或 Kaiming 初始化)。
-
B(升维矩阵):一开始通常初始化为 零矩阵。
这样做的好处:
-
在训练初期,ΔW=BA≈0,相当于模型刚开始时几乎就是原始大模型 W;
-
随着训练,A 和 B 被梯度更新,慢慢学出对任务有用的“方向”。
3. 为什么不用先对 W 做 SVD 分解再截断?
很多人一开始以为 LoRA 是对大矩阵 W 做低秩近似(像 PCA、SVD 那样),其实不是。
LoRA 不去逼近现有的 W,而是只学“新任务需要的调整”。
-
冻结原始 W ,相当于保留大模型已有知识,缓解“灾难性遗忘”;
-
新学的 A,BA,BA,B 只需表示“少量、低秩的增量”。
所以直接随机初始化比做 SVD 更自然,训练也更稳定。
4. 更新方式
训练时:
-
梯度只更新 A、B;
-
W 不更新。
这等价于在模型的某些投影矩阵上,插入一个“低秩旁路”。
优缺点
LoRA 通常“插”在哪些权重上?这就是所谓的即插即用
Transformer 里最常见是 自注意力的投影矩阵:
-
Wq,Wk,Wv,Wo(很多实践里主要对 q_proj / v_proj 做 LoRA 足够好;有时也会加在 o_proj 或 FFN 的 up_proj/down_proj)
-
对于 LLaMA 类模型:常见 target_modules =
["q_proj", "v_proj"]
(起步配置),也有["q_proj","k_proj","v_proj","o_proj"]
的全注意力适配。 -
很少对 LayerNorm 做 LoRA;嵌入层/输出头是否加 LoRA 视任务而定。
为什么一个低秩更新(参数量远小于原模型)就能带来很好的效果?
1. 线性层权重矩阵的“冗余性”
-
在 Transformer 里,大量权重矩阵(注意力里的 Wq,Wk,Wv,Wo,前馈网络里的 W1,W2)其实都很大。
-
但研究发现,这些大矩阵的 有效秩(rank)通常比它的尺寸小很多。
-
直观理解:虽然矩阵有成千上万参数,但“真正重要的方向/子空间”只有一小部分。
-
所以很多调整可以在一个低秩子空间里完成。
-
2. 新任务的调整往往是“低秩的”
-
一个预训练大模型已经学了大量通用知识(语言结构、世界常识…)。
-
下游任务(比如情感分类、对话风格)往往只需要在现有表示空间上轻微偏移,而不是重学一切。
-
这种“偏移”通常可以用低秩矩阵捕捉到。
-
比如,情感分类可能只需要在语义向量空间里增加/减弱“积极-消极”的方向。
-
这种变化本质上是一种“低维信号”。
-
3. 为什么两个矩阵就够?
ΔW=BA,A∈Rr×k, B∈Rd×r
-
A:把输入投影到一个小维度的“任务相关子空间”;
-
B:再把子空间映射回原始输出维度。
这样就像在原有大矩阵 W旁边加了一个“旁路”,只针对任务相关方向做调整。
即使 r 很小,也能覆盖到“关键方向”,从而大幅提升性能。
4. 直观比喻
可以把大模型 W想象成一个超级大的放大镜,它能看到几乎所有维度的知识。
而微调的任务,只需要在放大镜上贴一个小滤镜(ΔW),改变一点光线的方向。
这个滤镜本身不需要复杂设计,用两个小矩阵(低秩分解)就能表达。
Adapter微调
我介绍一个用于联邦学习场景中的自适应Adapter微调技术。这篇论文是首次将adapter微调技术应用到联邦学习场景中的自然语言处理任务上,但是adapter微调就会引入超参数,需要明确adapter放在模型的哪个位置以及特征变化的维度。而且作者发现,不同的自然语言处理任务,不同的数据集等,只要外部环境改变,可能最优adapter的超参数就会变化,所以当前的问题已经转化为了如何找到当前微调环境下最优的adapter超参数,然后利用最优的adapter来加快模型的微调速度。
这是本论文提出的核心算法。输入包括目标准确率(算法追求的不是更好的准确率,追求的是到达某一目标准确率所耗费的时间更短);试验间隔(模型微调经过了几轮训练或多长时间);adapter配置的初始值(初始depth,adapter初始插入在Bert模型中的第几层/初始width,adapter的特征转换的维度是多少);depth和width的步长(需要更新depth和width值时,更新的大小是多少,比如Bert-base模型有12层,depth取值为1-12/Bert-base内部的特征表示通常为768维,width表示将768-->width-->768)。该算法的输出为经过微调后,最好的adapter参数值(adapter微调,其他层的参数全部冻住,不参与更新)。
其次,主要的算法过程都包含在Cloud_controller()中,其过程如下:服务端选择三组试验组,每组实验组含多个客户端。第0组试验组,使用初始化的adapter配置(depth,width)进行训练;第1组试验组使用更深的adapter配置训练(depth + step,width);第二组试验组使用更宽的adapter配置(depth,width + step)进行训练。然后,服务端将配置信息分发给每组试验组,每组实验组内的客户端根据分配到的adapter配置进行训练。客户端根据adapter配置和自己的私有数据集进行训练,期间只有adapter参数参与更新,然后训练完后,将更新后的adapter参数上传给服务端。
服务端收到来自第0组试验组的n个客户端上传的更新后的adapter参数,然后使用Fedavg(加权平均)算法,整合所有adapter参数,生成本轮训练下,第0组试验组更新的整体adapter参数。同样的流程,服务端会生成第1组和第2组试验组的adapter参数。然后,为每组试验组选择新的客户端,将服务端整合好的三组adapter参数分发给每组的所有客户端,同时adapter配置保持不变。
假如,训练的时长超过了设定的试验间隔,服务端会比较三组试验组的adapter参数的性能,第0组会继承当前性能最好的adapter参数和配置(depth_best,width_best),同时,第1组会更新其adapter配置为(depth_best + step,width_best),第2组会更新其adapter配置为(depth_best,width_best + step)。然后,每组试验组的所有客户端会根据新的adapter配置继续训练。循环往复上述流程,直到找到最好的adapter参数使得服务端的模型性能达到设定的目标准确率。
同时,本文也设计了activation cache机制,减少重复的计算。假设adapter的depth为1,在第12层Transformer和第11层Transformer层之间;而由于adapter微调时,所有层的参数都不参与更新,所以可将第11层Transformer层的输出存储到缓存中,等到下轮计算到同一批数据时,该批次数据可以不用经过前面11层Transformer层的计算,直接从缓存中取出第11层输出的特征表示输入到adapter中,然后再送入第12层Transformer中去计算。
假设,adapter配置更新了,adapter的depth变为了4,处于第9层和第8层之前。那么需要更新缓存内保存的特征表示,更新为第8层Transformer的输出。下轮训练,训练同批数据时,数据不用经过前面8层Transformer的计算,而是去缓存中取出第8层的输出,将特征表示直接输入adapter中,然后再经历后面几层的计算。