AI 文摘

LoRA的工作原理





作者: 吴建明利驰软件 来源: 吴建明利驰软件

背景

在自然语言处理领域,预训练-微调(pretrain-finetune)范式非常流行。

通过指令微调,大语言模型能够更好地学习遵循和执行人类指令。但是,由于大语言模型的参数量巨大, 进行全参数微调。

预训练模型通常需要大量的计算资源和时间,而在具体任务上微调模型时,仍然需要调整大量的参数,这使得微调过程非常耗费资源。

LoRA的目标就是通过低秩适应来减少微调过程中的参数量 ,从而降低计算资源的需求。

什么是LoRA?

LoRA,全称Low-Rank Adaptation,是一种高效的模型适应技术,主要用于对大型预训练模型进行微调。

https://arxiv.org/abs/2106.09685

核心思想

LoRA的核心思想是利用低秩矩阵分解技术,将大型预训练模型的参数矩阵分解为两个低秩矩阵的乘积,从而在微调时只需要调整这两个低秩矩阵。

LoRA微调示意图

重新参数化,只训练A和B

具体来说,LoRA假设原始模型的权重矩阵可以表示为两个低秩矩阵的乘积,即:

W=W0+ΔW

其中, W0 预训练模型的原始权重矩阵, ΔW 是通过低秩分解得到的两个矩阵的乘积。

低秩矩阵分解

在LoRA技术中,秩表示用于分解大矩阵的两个低秩矩阵的维度。

具体来说,假设我们有一个权重矩阵 W ,通过低秩分解,我们将其表示为两个矩阵 A 和 B 的乘积,即:

ΔW=A×B

其中, A 的维度是(m,r),B 的维度是(r,n),这里 r 就是秩(Rank)。

选择较小的 r 可以显著减少参数量,从而降低计算和存储成本。

其中, A 和 B 的秩要比 W0 小得多,这样可以显著减少需要调整的参数数量。具体步骤如下:

  1. 1.预训练模型权重初始化 :使用预训练模型的权重矩阵 W0 初始化。

  2. 2.低秩矩阵初始化 :初始化低秩矩阵 A 和 B 。

  3. 3.微调过程 :在微调过程中,只调整低秩矩阵 A 和 B 的参数,而不改变预训练模型的原始权重矩阵 W0 。

优势

  • 降低计算资源需求 :通过调整低秩矩阵来适应模型,只需微调较少的参数,大大降低了计算成本和内存需求。

  • 提高适应效率 :低秩矩阵分解可以在不显著影响模型性能的情况下,提高微调的效率和速度。

  • 适应不同任务 :LoRA可以轻松适应不同的下游任务,只需调整少量的参数即可实现高效的迁移学习。

示例代码

以下是一个简单的示例代码,展示了如何使用LoRA技术对一个预训练的Transformer模型进行微调:

import torch  
import torch.nn as nn  
from transformers import GPT2Model, GPT2Tokenizer  
  
class LoRAAdapter(nn.Module):  
    def __init__(self, original_weight, rank=4):  
        super(LoRAAdapter, self).__init__()  
        self.rank = rank  
        self.A = nn.Parameter(torch.randn(original_weight.size(0), rank))  
        self.B = nn.Parameter(torch.randn(rank, original_weight.size(1)))  
  
    def forward(self, W0):  
        return W0 + torch.matmul(self.A, self.B)  
  
# 加载预训练模型  
model_name_or_path = "./Model"  
tokenizer = GPT2Tokenizer.from_pretrained(model_name_or_path)  
model = GPT2Model.from_pretrained(model_name_or_path)  
  
  
# 在 PyTorch 中使用这个模型获取给定文本的特征的方法如下:  
text = "替换为您想要的任何文本。"  
encoded_input = tokenizer(text, return_tensors='pt')  
output = model(**encoded_input)  
# print(output)  
  
# 获取模型的权重参数  
for name, param in model.named_parameters():  
    print(f"参数名称: {name}, 形状: {param.shape}")  
  
# 获取原始模型的权重矩阵  
original_weight = None  
for name, param in model.named_parameters():  
    if 'attn.c_attn.weight' in name:  
        original_weight = param  
        break  
  
if original_weight is None:  
    raise ValueError("在模型中找不到注意力权重。")  
  
# 初始化LoRA适配器  
lora_adapter = LoRAAdapter(original_weight)  
  
# 定义优化器,只优化LoRA的参数  
optimizer = torch.optim.Adam(lora_adapter.parameters(), lr=1e-4)  
  
tokenizer.pad_token = tokenizer.eos_token  
  
# 数据加载器 (假设已经有一个数据集)  
def get_dataloader():  
    # 这里使用一个简单的示例数据集  
    texts = ["你好,你好吗?", "我很好,谢谢!", "你叫什么名字?"]  
    encodings = tokenizer(texts, return_tensors='pt', padding=True, truncation=True)  
    dataset = torch.utils.data.TensorDataset(encodings['input_ids'])  
    return torch.utils.data.DataLoader(dataset, batch_size=2)  
  
dataloader = get_dataloader()  
  
# 定义训练过程  
def train(model, lora_adapter, dataloader, optimizer, epochs=3):  
    model.train()  
    for epoch in range(epochs):  
        for batch in dataloader:  
            inputs = batch[0]  
            outputs = model(input_ids=inputs).last_hidden_state  
  
            # 简单的损失函数 (示例)  
            loss = outputs.mean()  # 通常你会有一个更复杂的损失函数  
              
            optimizer.zero_grad()  
            loss.backward()  
            optimizer.step()  
  
            # 更新模型权重  
            with torch.no_grad():  
                updated_weight = lora_adapter(original_weight)  
                for layer in model.h:  
                    layer.attn.c_attn.weight.copy_(updated_weight)  
          
        print(f"第 {epoch+1}/{epochs} 轮,损失: {loss.item()}")  
  
# 执行微调  
train(model, lora_adapter, dataloader, optimizer)

输出

在实际应用中,我们可以根据具体任务和数据集调整参数和训练过程。

LoRA通过低秩矩阵分解技术,有效地减少了微调过程中的参数量,从而大幅降低了计算资源的需求。

最后补充个知识点:什么是秩?

秩(Rank)在数学和线性代数中是一个重要的概念,具体来说,它描述了一个矩阵的某些特性。

秩的定义

  • 行秩 :矩阵中最大线性无关行的数量。

  • 列秩 :矩阵中最大线性无关列的数量。

行秩和列秩在理论上总是相等的 ,因此通常直接称为矩阵的秩 。

换句话说,矩阵的秩是其行(或列)向量空间的维数。更正式地,如果一个矩阵 A 的秩是 r ,这意味着 A 中有 r 个线性无关的行(或列)。

举个例子:

import numpy as np  
  
# 定义矩阵 A  
A = np.array([  
    [1, 2, 3],  
    [4, 5, 6],  
    [7, 8, 9]  
])  
  
# 计算矩阵的秩  
rank = np.linalg.matrix_rank(A)  
print(f"矩阵A的秩: {rank}")

可以看出:

对于矩阵 A:

其秩为 2 ,因为只有两行是线性无关的,第三行可以由前两行线性组合得到。

更多AI工具,参考Github-AiBard123国内AiBard123

可关注我们的公众号:每天AI新工具