赞
踩
本文总结几种主流的微调方法,主要包括Freeze方法、P-tuning方法、Lora方法和Qlora方法。
Freeze是冻结的意思,Freeze方法指的是参数冻结,对原始模型的大部分参数进行冻结,仅训练少部分的参数,这样就可以大大减少显存的占用,从而完成对大模型的微调。
这是一种比较简单微调方法,由于冻结的参数是大部分,微调的参数是少部分,因此在代码中只需要设置需要微调的层的参数即可,核心部分如下:
for name, param in model.named_parameters():
if not any(nd in name for nd in ["layers.27", "layers.26", "layers.25", "layers.24", "layers.23"]):
param.requires_grad = False
P-tuning目前有两个版本。
P-Tuning v1 论文: https://arxiv.org/pdf/2103.10385.pdf
P-Tuning v2论文: https://arxiv.org/abs/2110.07602
P-tuning v1 github代码:https://github.com/THUDM/P-tuning
P-Tuning v2 github代码:https://github.com/THUDM/P-tuning-v2
prefix-tuning:Optimizing Continuous Prompts for Generation
论文地址:https://arxiv.org/abs/2101.00190
代码地址:https://github.com/XiangLi1999/PrefixTuning
在学习P-tuning之前,需要先了解下prefix-tuning,它指的是在微调模型的过程中只优化加入的一小段可学习的向量(virtual tokens)作为Prefix,而不需要优化整个模型的参数(训练的时候只更新Prefix部分的参数,而PLM中的其他部分参数固定)。
如下图所示:Fine-tuning是会更新Transformer中所有的参数,而P-tuning只需要更新Prefix处的参数。
对于不同的任务和模型结构需要不同的prefix:
在autoregressive LM 前添加prefix:
z
=
[
P
R
E
F
I
X
;
x
;
y
]
z=[P R E F I X ; x ; y]
z=[PREFIX;x;y]
在encoder和decoder之前添加prefixs:
z
=
[
PREFIX;
x
;
PREFIX
;
y
]
z=[\text { PREFIX; } x ; \text { PREFIX } ; y]
z=[ PREFIX; x; PREFIX ;y]
Encoder端增加前缀是为了引导输入部分的编码,Decoder 端增加前缀是为了引导后续token的生成。
对于prefix tuning可能还需要一些前置知识,soft prompt和hard prompt的概念。
prompt综述参考:
https://arxiv.org/pdf/2107.13586.pdf
https://zhuanlan.zhihu.com/p/524383554
hard prompt等同于discrete prompt;soft prompt等同于continuous prompt。
离散prompt是一个实际的文本字符串(自然语言,人工可读),通常由中文或英文词汇组成;
连续prompt通常是在向量空间优化出来的提示,通过梯度搜索之类的方式进行优化。
离散的prompts中,提示语的变化对模型最终的性能特别敏感,加一个词、少一个词或者变动位置都会造成比较大的变化。成本比较高,并且效果不太好。
显然:Prefix Tuning属于Soft prompt。
论文:The Power of Scale for Parameter-Efficient Prompt Tuning
论文地址:https://arxiv.org/pdf/2104.08691.pdf
该方法可以看做是Prefix Tuning的简化版本,它给每个任务都定义了自己的Prompt,拼接到数据上作为输出,但只在输入层加入prompt tokens。
通过实验发现,随着预训练模型参数量的增加,Prompt Tuning的方法会逼近全参数微调的结果。
论文:GPT Understands, Too
论文地址:https://arxiv.org/abs/2103.10385
该方法的核心是使用可微的virtual token替换了原来的discrete tokens,且仅加入到输入层,并使用prompt encoder(BiLSTM+MLP)对virtual token进行编码学习。
效果:相同参数规模,如果进行全参数微调,Bert在NLU任务上的效果,超过GPT很多;但是在P-Tuning下,GPT可以取得超越Bert的效果。
之前的Prompt Tuning和P-Tuning等方法存在两个主要的问题:
第一,缺乏模型参数规模和任务通用性。
第二,缺少深度提示优化,在Prompt Tuning和P-tuning中,连续提示只被插入transformer第一层的输入embedding序列中,在接下来的transformer层中,插入连续提示的位置的embedding是由之前的transformer层计算出来的,这可能导致两个可能的优化挑战。
由于序列长度的限制,可调参数的数量是有限的。
输入embedding对模型预测只有相对间接的影响。
这些问题在P-tuning v2得到了改进。
论文:P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks
论文地址:https://arxiv.org/abs/2110.07602
P-Tuning v2主要是基于P-tuning和prefix-tuning技术,引入Deep Prompt Encoding和Multi-task Learning等策略进行优化的。
Deep Prompt Encoding
P-Tuning v2在每一层都加入了Prompts tokens作为输入,而不是仅仅加在输入层,这带来两个方面的好处:
P-Tuning v2是一种在不同规模和任务中都可与微调相媲美的提示方法。P-Tuning v2对从330M到10B的模型显示出一致的改进,并在序列标注等困难的序列任务上以很大的幅度超过了Prompt Tuning和P-Tuning。
论文:LoRA: Low-Rank Adaptation of Large Language Models
论文地址:https://arxiv.org/abs/2106.09685
官方代码github:https://github.com/microsoft/LoRA
HuggingFace封装的peft库:https://github.com/huggingface/peft
微软实现的lora:https://github.com/microsoft/LoRA
LoRA的本质是在原模型的基础上插入若干新的参数,称之为adapter。在训练时,冻结原始模型的参数,只更新adapter的参数。对于不同的基座模型,adapter的参数量一般为几百万~几千万。
具体来讲,Lora方法指的是在大型语言模型上对指定参数增加额外的低秩矩阵,也就是在原始PLM旁边增加一个旁路,做一个降维再升维的操作。并在模型训练过程中,固定PLM的参数,只训练降维矩阵A与升维矩阵B。而模型的输入输出维度不变,输出时将BA与PLM的参数叠加。用随机高斯分布初始化A,用0矩阵初始化B,保证训练的开始此旁路矩阵依然是0矩阵。
具体来看,假设预训练的矩阵为
W
0
∈
R
d
×
k
W_0 \in \mathbb{R}^{d \times k}
W0∈Rd×k ,它的更新可表示为:
W
0
+
Δ
W
=
W
0
+
B
A
,
B
∈
R
d
×
r
,
A
∈
R
r
×
k
W_0+\Delta W=W_0+B A, B \in \mathbb{R}^{d \times r}, A \in \mathbb{R}^{r \times k}
W0+ΔW=W0+BA,B∈Rd×r,A∈Rr×k其中秩
r
<
<
min
(
d
,
k
)
r<<\min (d, k)
r<<min(d,k) 。
对于
h
=
W
0
x
h=W_0 x
h=W0x ,它的前向计算变为:
h
=
W
0
x
+
Δ
W
x
=
W
0
x
+
B
A
x
=
(
W
0
+
B
A
)
x
h=W_0 x+\Delta W x=W_0 x+B A x=\left(W_0+B A\right) x
h=W0x+ΔWx=W0x+BAx=(W0+BA)xLora的这种思想有点类似于残差连接,同时使用这个旁路的更新来模拟full finetuning的过程。
为什么矩阵B被初始化为0,而矩阵A正常高斯初始化?
如果B,A全都初始化为0,那么缺点与深度网络全0初始化一样,很容易导致梯度消失(因为此时初始所有神经元的功能都是等价的)。
如果B,A全部高斯初始化,那么在网络训练刚开始就会有概率得到一个过大的偏移值Δ W,从而引入太多噪声,导致难以收敛。
因此,一部分初始为0,一部分正常初始化是为了在训练开始时维持网络的原有输出(初始偏移为0),但同时也保证在真正开始学习后能够更好的收敛。
训练与推理
为什么一般会选用Q、V矩阵添加LoRA Layer?
看效果,如在一篇医疗图片分割的论文中,有实验表明,有一些实验[1]表明,只添加在Q和V上会更好。
LoRA 的最大优势是速度更快,使用的内存更少,因此可以在消费级硬件上运行。
在多卡训练时,Lora也是效率很高的,在多卡训练中,LoRA的速度优势主要体现在两个方面:
论文:QLORA: Efficient Finetuning of Quantized LLMs
论文地址:https://arxiv.org/abs/2305.14314
QLoRA是华盛顿大学提出的一种高效微调大模型的方法,可以在不降低任何性能的情况下微调量化为 4 bit模型的方法。
核心点如下:
参考:https://github.com/wp931120/baichuan_sft_lora/blob/main/sft_lora.py
lora_rank(int,optional): LoRA 微调中的秩大小。
lora_alpha(float,optional): LoRA 微调中的缩放系数。
lora_dropout(float,optional): LoRA 微调中的 Dropout 系数。
目前主流的可微调的模型有ChatGLM,Baichuan,LLAMA,RWKV等。
中文较好的为ChatGLM和Baichuan;
潜力较好的为LLAMA;
RWKV有较好的推理效率(RNN架构决定的,随输入长度内存占比线性增加,而transformer是指数增长)和长度外推性。
前面已经介绍了几种常见的微调方法,比较推荐采用 Lora 或 QLora两种方法。
对于像LLaMA模型的词表大小是32K,其主要针对英语进行训练(具体详见LLaMA论文),对多语种支持不是特别理想(可以对比一下多语言经典模型XLM-R的词表大小为250K)。通过初步统计发现,LLaMA词表中仅包含很少的中文字符,所以在切词时会把中文切地更碎,需要多个byte token才能拼成一个完整的汉字,进而导致信息密度降低。比如,在扩展词表后的模型中,单个汉字倾向于被切成1个token,而在LLaMA中可能就需要2-3个才能组合成一个汉字,显著降低模型的推理效率。
通常有以下方式,可以减少或避免灾难性遗忘问题
参考
幻觉问题:当模型生成的内容不遵循原文(与给定的输入或源内容不一致)(Faithfulness)或者和事实不符(Factualness),我们就可以认为模型出现了幻觉的问题。
解决思路:
1、加大数据的清洗、筛选粒度,去除低质量的数据,去除不准确信息的数据。2、预训练时给高可信度的来源数据加权。
3、通过Prompt进行控制,例如Langchain中,在Prompt中提出模型不知道的话可以直接回复不知道。
4、选择合适的解码方式。例如top-p更适合创造性的任务,greedy/beam更适合确定性任务等。
5、检索增强,维护高质量的外部知识库,匹配和问题相关的内容,添加到Prompt中供模型学习。
复读机问题可能出现的原因包括:
为了解决复读机问题,可以采取以下策略:
https://finisky.github.io/lora/
大模型参数高效微调技术原理综述(二)-BitFit、Prefix Tuning、Prompt Tuning
大模型参数高效微调技术原理综述(三)-P-Tuning、P-Tuning v2
大模型参数高效微调技术原理综述(五)-LoRA、AdaLoRA、QLoRA
QLoRA实战
LLM - finetuning - 踩坑经验之谈
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。