注:以下的理解只是我自己的理解,如果要看权威的解释,你可以看看论文。当然,权威的解释有时候也不好理解。如有什么问题,欢迎到评论区指正🥰
LoRA 是什么#
简单来说,LoRA 是一种技术,可以微调模型(神经网络)里的参数。可以将模型看成是一个具有很多参数的函数,你输入一个东西,这个模型就会输出一个数字,文本或者图像。拿 ChatGPT 举例来说,你输入的文本会先转换成一个个数字(Word Embedding),然后这些数字放到模型这个函数里面,这个模型会给出一系列输出的数字,每个数字都在之间,而这个每一个数字会对应一个单词,这个数字就代表这个单词出现在这句话后面的概率,ChatGPT 就像这样将单词一个一个 “吐出来”。
既然模型是一个函数,那函数中的每一个参数,比如 其中 和都是这个一次函数的参数,如果你更改其中的任意一个参数,那么这个一次函数代表的直线的位置就会发生改变。对于模型来说也是一样,如果你更改了模型的一个参数,那么这个模型的功能也会发生变化。
所以,如果你在网上下载了一个模型,这个模型用来分类不同水果的,但是你在使用这个模型进行分类任务时,你发现这个模型的性能并不怎么样,比如说,这个模型误把一个葡萄识别成了提子。就说明这个模型里面的参数并不好,导致了模型的性能不好。所以你想要调整以下这个模型里面的参数,让模型顺利把葡萄识别成提子。而调整模型参数的一个方法就是使用 LoRA。简单来说,你觉得当前模型的功能并不能满足你的要求,你就可以使用 LoRA 微调当前的模型。
注:为了直观的感受 LoRA 的能力,我想说说之前我在折腾的 Stable Diffision (AI 绘图软件),其中的模型影响生成的图是二次元还是三次元,而你可以在这个模型中添加 LoRA 来微调这个 AI 画师。我记得非常清楚里面有一个叫做水墨风 LoRA,如果你把这个 LoRA 应用到模型中,那个最后画出的图就有水墨风,但是图像的本质区别 -- 二次元还是三次元,是没有变的。
微调现存的模型,为何不直接训练出一个模型#
当下,大模型的种类越来越多了。基本上比较大的互联网公司都发布了自己公司的大模型。为何我们不自己训练出来一个大模型,而是想要微调别人训练完成的模型呢?其中一个很大的原因就是 “大”,现在的模型的参数动不动上 7B,12B(billion)。修改(训练)这么多的参数,一方面需要电脑运算的快(显卡,CPU 的算力高),另一方面需要电脑有足够的内存存储这么多的参数供计算单元进行计算。我们拿 7B 的模型举个例子,如果模型参数的类型是 float16,一个模型的参数占 2 个字节,那么 7B 个参数占字节,换算成 GB 就是 13GB。也就是说,如果你要在 GPU 上运行这个模型,显卡的显存需要 13GB,而如果你想要调整里面的参数,则电脑还要为其中每一个参数再预留一个参数的空间来放每个模型的梯度,所以要训练这个模型至少要GB。这么大的显存在消费级显卡一般不会出现,比如现在性能最好的消费级 GPU--4090 的显存最多也就 24GB。而专业的计算卡如 A100 可以达到 80GB,但是专业的计算卡价格高昂。
由于训练一个大模型需要很多专业级的显卡,只有大的互联网公司才能训练得起。有些大公司会把它们训练好的模型进行开源,也就是把它们训练好的模型公开,别人就可以在它们的基础上调整模型里面的参数了。
所以,为了能在普通消费级显卡上能够调整模型里面的参数,也就是在普通人的电脑上能够改变模型的性能,我们只能微调里面的一些参数,不可能和大公司训练模型那样调整模型里面的所有参数。可以说,如果我们需要的模型想要训练成 100%,那大公司一开始的训练就占了 80%,然后在我们电脑上微调模型就占其中的 20%。
LoRA#
上面我提到,普通人的电脑上能够改变模型的性能,我们只能微调里面的一些参数,但是微调这些参数的方式也有多种,其中最常见的方式就是 LoRA。
在论文中提到了一个公式,我觉得比较重要:
其中,是模型的原始参数,这是一个矩阵的形式,根据论文,$W_0$ 矩阵中的数字是不变的,而 $\Delta W = BA$ 是另外一个矩阵,LoRA 主要是改变这个矩阵。通过将和相加就得到了一个新的参数,如果我们将这个新的参数作用到模型上时,模型的参数更改,模型的功能也就发生了变化,达到了微调的目的。
那我们就出现了一个问题,我们根据矩阵的加法,如果两个矩阵想要相加,那么这两个矩阵就要有相同的大小。比如,下面中,矩阵的大小就是的,如果要和相加,那么的大小也必须是的。如果我们直接更改这个,也就是更改其中个参数,那我们怎么不直接更改原模型中的参数,反正这两个矩阵的大小是一样的。那我们的目的没有达到啊 -- 普通人的电脑上为了能够改变模型的性能,我们只能微调模型里面的一些参数,因为根据消费级的电脑的算力和内存,是不可能的更改原模型那成千上万的参数的。
为了解决这个问题,论文提出了一个新的方法
LoRA allows us to train some dense layers in a neural network indirectly by optimizing rank decomposition matrices of the dense layers’ change during adaptation instead, while keeping the pre-trained weights frozen
其中,我觉得这句话中,关键词就是rank decomposition,直接翻译是秩分解?
什么是 rank decomposition#
根据矩阵的乘法,如果矩阵的大小是的,矩阵的大小是的,那么他们两相乘的话矩阵的大小就是的。
我还是拿上面的等式举例子,由可知,的大小是的,由矩阵加法,的大小也要是的。关键的地方来了,为了得到,我们可以让,如所示。也就是说,将变成两个矩阵相乘。其中矩阵的大小是的,矩阵是的,其中 r 是一个变量,随便更改,这个 r 也非常重要,我后面会说明。由矩阵乘法可知结果的矩阵大小是的。这样就可以与相加了。
从中可以看出,一个拥有 9 个参数的大矩阵分解成了两个拥有 3 个参数的小矩阵相乘,秩分解可以简单理解为大矩阵分解成小矩阵相乘。这样为了得到,我们只要关注,更改两个小矩阵中的 6 个参数就行了,相比于中的 9 个参数,6 个参数减少许多。
由 LoRA 训练带来的训练参数减少的效果是显著的,论文中提到
When the pre-trained model is GPT-3 175B, the number of trainable parameters can be as small as 0.01% of $W_0$
也就是说,用 LoRA 训练 GPT-3,可训练参数是模型参数的 0.01%。
关于 LoRA 中 r 参数的选取#
由前面可知,被分解成了两个矩阵的相乘,其中矩阵的大小是的,矩阵是的。由线性代数的知识可得,一般来说,两个矩阵相乘的结果矩阵的秩不会超过两个原始矩阵的秩中的最小值。也就是说,如果我们选取的 r 小于 3,那么和相乘得到的矩阵的秩不会超过 r,小于 3。其实,对于这个例子,我们选的 r 也要必须小于 3,如果我们选 r 等于 4,那么要更改(训练)的参数为,比原模型的参数都要多,这就违背我们使用 LoRA 的目的 -- 减少可训练参数的数量。
这里面就存在问题了,如果模型原来的参数矩阵是满秩的,但是我们由 LoRA 得到的的秩是小于的。也就是说,LoRA 只能微调模型参数矩阵的一个子空间。还是以举个例子,的秩是 3。简单来说,这个模型需要拿三维坐标系来进行建模来解决问题,但是如果我们使用 LoRA 并且设,则我们只是在更改模型的一个维度,别的两个维度并没有考虑到,这样模型的参数怎么更改,效果都比较差。
既然 LoRA 只能微调模型参数矩阵的一个子空间,那我们使用 LoRA 干啥呢,无论如何都有一两个维度没考虑到。
幸好,由之前的论文可知:the learned over-parametrized models in fact reside on a low intrinsic dimension. 也就是说,我们给模型三维坐标系进行建模,但是模型最后只是利用了一条线,它只用了其中的一个维度。说明模型参数这个矩阵里的列向量存在线性相关,模型参数的秩其实是 1,这也就是论文所说的low intrinsic dimension。这时,如果我们令时,效果就比较好。我们只要关注,更改两个小矩阵中的 6 个参数就行了,相比于中的 9 个参数,6 个参数减少许多。但是,我们也可以令, 但是这样可训练参数变为 12 了,还起到了副作用。
由于我们并不知道模型用了几个维度进行建模,所以参数 r 需要我们手动尝试取得最优值,这种参数叫做超参数。如果 r 大了,可训练参数变多;r 小了,训练效果不行。
LoRA 优点#
为了形成对比,我先讲讲其他可微调模型参数的方法
-
Adapter Layers:这就相当于改变模型的架构,在模型中添加一些层,然后只是更改这些层的参数。但是这有一个问题,这会使模型推理时间变长,学名好像叫Inference Latency。类比一下,我们将模型想成一条水泥路连接 A 和 B。你的输入是一辆在 A 的车,这个车要经过水泥路到达 B,输出得到结果,车在路上要时间。然后Adapter Layers就是在这个路上加了一小块水泥路,并只对这个小块水泥路进行优化。但是路程变长了,导致汽车路途时间变长,这就是推理时间变长。但是 LoRA 好像就是在不改变路长度的同时,将一部分路升级成高速公路(不是说明速度快,而是效果好,LoRA 并不会加快推理速度)。
-
Prefix tuning: 前缀微调这个方法其实是一种提示词工程,并不直接对模型进行改变。将提示词分成前缀 (prefix) 和后缀。用户输入到后缀,前缀被占领用于微调。这样用户的输入就变少了。这是它的缺点。