深入浅出剖析 LoRA 技术原理( 四 )


现在再宽泛一些,如果我们能找到这样的一组和,并令矩阵的值从大到小进行排列,那么我们不就能对进行拆解,同时在拆解过程中,找出所强调的那些特征方向了吗?也就是说:
当我们找到这样的矩阵后,我们再从这三者中取出对应的top r 行(或列),不就相当于关注到了最强调的那几维特征,进而就能用更低维的矩阵,来近似表达了?按这种思维拆解M的方法,我们称为SVD分解(奇异值分解) 。在本篇里我们不讲述它的具体方法,感兴趣的朋友们,欸,又可以参考《线性代数》 。
我们再通过一个代码例子,更直观地感受一下这种近似,大家注意看下注释(例子改编自:@/lora-low-rank--from-the-first--)
import torchimport numpy as nptorch.manual_seed(0)# ------------------------------------# n:输入数据维度# m:输出数据维度# ------------------------------------n = 10m = 10# ------------------------------------# 随机初始化权重W# 之所以这样初始化,是为了让W不要满秩,# 这样才有低秩分解的意义# ------------------------------------nr = 10mr = 2W = torch.randn(nr,mr)@torch.randn(mr,nr)# ------------------------------------# 随机初始化输入数据x# ------------------------------------x = torch.randn(n)# ------------------------------------# 计算Wx# ------------------------------------y = W@xprint("原始权重W计算出的y值为:\n", y)# ------------------------------------# 计算W的秩# ------------------------------------r= np.linalg.matrix_rank(W)print("W的秩为: ", r)# ------------------------------------# 对W做SVD分解# ------------------------------------U, S, V = torch.svd(W)# ------------------------------------# 根据SVD分解结果,# 计算低秩矩阵A和B# ------------------------------------U_r = U[:, :r]S_r = torch.diag(S[:r])V_r = V[:,:r].t()B = U_r@S_r # shape = (d, r)A = V_r# shape = (r, d)# ------------------------------------# 计算y_prime = BAx# ------------------------------------y_prime = B@A@xprint("SVD分解W后计算出的y值为:\n", y)print("原始权重W的参数量为: ", W.shape[0]*W.shape[1])print("低秩适配后权重B和A的参数量为: ", A.shape[0]*A.shape[1] + B.shape[0]*B.shape[1])
输出结果为:
原始权重W计算出的y值为:tensor([ 3.3896,1.0296,1.5606, -2.3891, -0.4213, -2.4668, -4.4379, -0.0375,-3.2790, -2.9361])W的秩为:2SVD分解W后计算出的y值为:tensor([ 3.3896,1.0296,1.5606, -2.3891, -0.4213, -2.4668, -4.4379, -0.0375,-3.2790, -2.9361])原始权重W的参数量为:100低秩适配后权重B和A的参数量为:40
参数量变少了,但并不影响最终输出的结果 。通过这个例子,大家是不是能更好体会到低秩矩阵的作用了呢~
4.3 LoRA低秩适配
好,那既然SVD分解这么有效,那我直接对做SVD,找到对应的低秩矩阵,不就大功告成了吗?
想法虽然好,但困难是明显的:能直接做SVD的前提是是确定的,而现实中作为全参数微调中的权重增量,如果你不全参数微调一遍,又怎么能知道长什么样呢?而如果你做了全量微调,那还要低秩适配做什么呢?
欸,你可能又想:那我能不能对预训练权重做SVD呢,因为是确定的呀 。
想法虽然好,但逻辑是不合理的:我们说过,微调的目的是给模型注入和下游任务相关的领域新知识 。也就是说,和的表达含义是不同的,前者是新知识,后者是旧知识,我们的目的是要去新知识中拆解信息量丰富的维度 。
好,那既然通过数学方法直接做SVD行不通,那就让模型自己去学怎么做SVD吧!因此LoRA最终的低秩适配策略是:我把秩当成一个超参,再让模型自己去学低秩矩阵,这不就简单又省事吗!
行,到这里我们已经具象化地了解了LoRA低秩适配的原理了,也知道和所表达含义的差异了,现在,我们可以来看前文遗留的问题:超参是什么意思?