参数高效微调:LoRA、Adapter与Prompt Tuning实战

目录

参数高效微调:LoRA、Adapter与Prompt Tuning实战

1. 为什么需要参数高效的微调方法?

2. LoRA(Low-Rank Adaptation)

2.1 原理

2.2 代码示例(LoRA微调)

3. Adapter

3.1 原理

3.2 代码示例(Adapter微调)

4. Prompt Tuning

4.1 原理

4.2 代码示例(Prompt Tuning)

5. LoRA、Adapter与Prompt Tuning的对比

6. 总结


随着大规模预训练语言模型(如BERT、GPT等)的普及和深度学习计算资源的消耗,如何高效微调这些巨型模型成为了研究的热点。传统的微调方式需要调整大量参数,导致显存消耗和计算开销较大,尤其在多任务学习或在资源受限的环境中,这种方式可能变得不切实际。为了解决这一问题,研究者们提出了几种参数高效的微调方法,如LoRA(Low-Rank Adaptation)、Adapter与Prompt Tuning,这些方法可以有效减少微调时的显存占用,甚至达到减少90%显存的效果。

本文将详细介绍这三种技术,并通过代码示例展示如何在实际应用中实现低成本的微调。

1. 为什么需要参数高效的微调方法?

在传统的微调方法中,通常会直接对预训练模型的所有参数进行更新,这样会导致以下问题:

  • 显存消耗大:大规模模型的参数量非常庞大,更新所有参数会消耗大量显存。
  • 训练效率低:每次训练都需要从头到尾处理整个模型的参数,导致训练时间过长。
  • 计算资源浪费:对于单一任务或少量任务而言,微调整个模型可能是不必要的,浪费了计算资源。

因此,开发参数高效的微调方法,能够减少微调时的显存占用并提高训练效率。


2. LoRA(Low-Rank Adaptation)

LoRA(低秩适应)是一种高效的微调方法,其核心思想是通过在原始模型的层之间引入低秩矩阵,来减少微调时需要更新的参数量。LoRA通过将预训练模型中部分权重矩阵分解为低秩矩阵,使得只需对低秩矩阵进行更新,而不是整个模型。

2.1 原理

  • 传统的微调方法会更新模型的所有权重参数,而LoRA将每一层的权重矩阵$W$分解为两个低秩矩阵$A$$B$,即W = A \cdot B,其中$A$$B$是低秩矩阵,低秩矩阵的维度远小于原始矩阵。
  • 在训练时,仅更新$A$$B$,这大大减少了需要更新的参数数量。

公式:假设原始模型的权重矩阵为W \in \mathbb{R}^{d \times d},LoRA通过低秩矩阵$A \in \mathbb{R}^{d \times r}$$B \in \mathbb{R}^{r \times d}$来近似$W$。其中,$r$表示低秩矩阵的秩,通常远小于$W$的维度$d$

2.2 代码示例(LoRA微调)

以下是使用LoRA对BERT进行微调的简化示例代码:

from transformers import BertTokenizer, BertForSequenceClassification
import torch
import torch.nn as nn

class LoRA_BERT(nn.Module):
    def __init__(self, model_name='bert-base-uncased', rank=4):
        super(LoRA_BERT, self).__init__()
        self.bert = BertForSequenceClassification.from_pretrained(model_name)
        self.rank = rank
        
        # 插入低秩适应层
        self.lora_layers = nn.ModuleList([
            nn.Linear(self.bert.config.hidden_size, rank, bias=False),
            nn.Linear(rank, self.bert.config.hidden_size, bias=False)
        ])
    
    def forward(self, input_ids, attention_mask=None, labels=None):
        # 获取BERT输出
        outputs = self.bert.bert(input_ids, attention_mask=attention_mask)
        hidden_states = outputs[0]
        
        # 低秩适应
        lora_output = self.lora_layers[0](hidden_states)
        lora_output = self.lora_layers[1](lora_output)
        
        # 将LoRA层的输出与BERT输出结合
        final_output = hidden_states + lora_output
        
        # 分类层
        logits = self.bert.classifier(final_output)
        
        return logits

# 初始化LoRA模型
model = LoRA_BERT()

# 示例输入
input_ids = torch.tensor([[101, 2054, 2003, 1996, 2196, 102]])  # 示例输入文本(转化为ID)
attention_mask = torch.tensor([[1, 1, 1, 1, 1, 1]])

# 获取预测
outputs = model(input_ids, attention_mask)
print(outputs)

在这个示例中,LoRA_BERT模型在原始BERT模型的基础上添加了两个低秩矩阵($A $$B$),通过这些矩阵对BERT的输出进行调整,而不会更新整个模型的权重。这样,LoRA显著降低了显存消耗。


3. Adapter

Adapter方法是一种微调技术,它通过在预训练模型的各层之间插入轻量级的适配器(Adapter)模块来减少需要更新的参数量。每个适配器模块通常包含两个全连接层,通过这些模块可以有效地调整模型的行为。

3.1 原理

  • Adapter方法通过在每个Transformer层的输入和输出之间插入一个小型的神经网络(通常是一个简单的全连接层),仅更新这些适配器参数,而不更新原始模型的权重。
  • 适配器的大小通常很小,因此大大减少了需要更新的参数量。

公式:假设一个Transformer层的输出为hh,通过适配器模块的操作可以表示为:

其中,f(h; \theta_{adapter})是适配器模块的变换函数,\theta_{adapter}是适配器的参数。

3.2 代码示例(Adapter微调)

以下是一个基于HuggingFace库实现的Adapter微调代码示例:

from transformers import BertTokenizer, BertForSequenceClassification
import torch
from transformers import AdapterConfig, AdapterType

# 加载预训练BERT
model = BertForSequenceClassification.from_pretrained('bert-base-uncased')

# 添加适配器
adapter_name = "my_adapter"
config = AdapterConfig.load("pfeiffer")  # 使用Pfeiffer适配器策略
model.add_adapter(adapter_name, config=config)

# 启用适配器训练
model.train_adapter(adapter_name)

# 示例输入
input_ids = torch.tensor([[101, 2054, 2003, 1996, 2196, 102]])  # 示例输入文本(转化为ID)
attention_mask = torch.tensor([[1, 1, 1, 1, 1, 1]])

# 获取预测
outputs = model(input_ids, attention_mask)
print(outputs.logits)

在这个示例中,使用了一个适配器来微调BERT模型,适配器的更新仅影响添加的模块,而不需要重新训练整个模型。


4. Prompt Tuning

Prompt Tuning是一种新兴的微调技术,特别适用于像GPT这类生成式模型。其基本思想是在输入文本中加入一些特定的提示(Prompt),并通过学习这些提示来调整模型的行为。与传统微调方法不同,Prompt Tuning只需调整输入的提示词而不涉及模型内部权重的更新。

4.1 原理

  • 在Prompt Tuning中,模型的权重保持不变,仅通过学习如何构造有效的提示(Prompt)来调整模型的输出。
  • 通过优化提示的词向量,可以使模型在特定任务中表现更好。

公式:在Prompt Tuning中,我们将输入文本$S$转化为带有优化过的提示的形式S' = P + S,其中$P$是优化得到的提示。

4.2 代码示例(Prompt Tuning)

以下是一个基于HuggingFace的GPT-2模型的Prompt Tuning代码示例:

from transformers import GPT2Tokenizer, GPT2LMHeadModel
import torch

# 加载GPT2模型和Tokenizer
model = GPT2LMHeadModel.from_pretrained('gpt2')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

# 示例输入和Prompt
prompt = "Translate the following English sentence to French: 'Hello, how are you?'"
inputs = tokenizer(prompt, return_tensors="pt")

# 获取GPT2的输出
outputs = model.generate(inputs["input_ids"], max_length=50)
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)

print(generated_text)

在这个示例中,通过Prompt Tuning,只需要通过设计有效的提示词来引导GPT-2模型进行特定任务的生成,而不需要对模型的权重进行微调。


5. LoRA、Adapter与Prompt Tuning的对比

为了帮助大家更清晰地理解这三种参数高效微调技术,我们通过以下表格对它们进行对比:

特性LoRAAdapterPrompt Tuning
原理引入低秩矩阵进行微调在每个层之间插入适配器模块通过优化提示词(Prompt)调整模型行为
参数更新只更新低秩矩阵(AA和BB)只更新适配器模块的参数只更新提示词(Prompt)的参数
显存占用极大减少显存占用显存占用较少显存占用最少
计算开销计算开销相对较小计算开销较小计算开销最小
适用场景多任务学习,文本分类特定任务微调,尤其是低资源环境生成任务、对话系统
实现复杂度较高,涉及矩阵分解中等,需要添加适配器低,只需要优化提示词

6. 总结

在深度学习模型的微调过程中,LoRA、Adapter和Prompt Tuning是三种有效的参数高效微调方法。这些方法能够显著降低显存消耗,提高训练效率,同时在多任务学习和低资源环境中表现出色。选择哪种方法取决于任务的类型、资源的限制以及模型的特点。希望本文能够帮助大家理解并应用这些技术来优化模型训练。如果有任何问题,欢迎留言讨论!


推荐阅读:

手把手搭建你的第一个大模型:基于HuggingFace的模型微调-CSDN博客

预训练核心技术:掩码语言建模(MLM)与因果语言建模(CLM)-CSDN博客

训练大模型的硬件指南:GPU、TPU与分布式计算-CSDN博客

### 其他模型微调技术 除了 LoRA 技术外,还有多种模型微调方法被广泛应用于降低资源消耗和提高效率的任务中。以下是几种常见的替代方案: #### 1. **P-Tuning 和 P-Tuning v2** 这些方法通过引入连续提示(continuous prompts)来调整预训练语言模型的行为,而无需修改原始模型的权重。这种方法的核心思想是在输入序列前附加一组可学习的嵌入向量作为提示[^5]。 ```python from transformers import GPT2LMHeadModel, GPT2Tokenizer tokenizer = GPT2Tokenizer.from_pretrained('gpt2') model = GPT2LMHeadModel.from_pretrained('gpt2') # 添加可学习的 prompt tokens prompt_tokens = torch.nn.Parameter(torch.randn((num_prompt_tokens, model.config.hidden_size))) ``` #### 2. **Adapter Tuning** 适配器微调是一种轻量级的方法,在不改变原模型结构的情况下,插入小型神经网络模块(即适配器),仅对这些新增加的部分进行优化。这种方式显著减少了需要更新的参数数量[^6]。 ```python from adapters import AdapterConfig, BertModelWithHeads config = AdapterConfig(mh_adapter=True, output_adapter=True, reduction_factor=8) model.add_adapter("task_name", config=config) # 训练期间冻结基础模型并只更新adapter部分 for param in model.base_model.parameters(): param.requires_grad = False ``` #### 3. **BitFit** 此方法主张仅仅调节每一层中的偏置项(bias terms),而不是整个权重矩阵。由于每层只有一个较小维度的bias vector需要重新训练,因此极大地降低了计算成本内存需求[^7]。 ```python import torch.optim as optim def set_bitfit(model): for name, param in model.named_parameters(): if 'bias' not in name: param.requires_grad_(False) set_bitfit(model) optimizer = optim.Adam([p for p in model.parameters() if p.requires_grad], lr=learning_rate) ``` #### 4. **Prefix Tuning** 类似于Prompt Learning的概念,但是更进一步地设计了一套固定的prefix embeddings加入到attention机制里参运算过程之中。相比起完全重写所有的transformer layers weights来说更加高效经济实用得多[^8]。 ```python class PrefixEncoder(nn.Module): def __init__(self, prefix_length, hidden_dim): super().__init__() self.prefix_embedding = nn.Embedding(prefix_length * num_layers, hidden_dim) def forward(self, batch_size): prefixes = self.prefix_embedding.weight.reshape(num_layers, -1).unsqueeze(0).expand(batch_size,-1,-1) return prefixes ``` 以上列举了几种主流且有效的减少大规模预训练模型fine-tune开销的技术手段,它们各自有其特点以及适用场景,请依据具体项目需求选取最合适的策略实施应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一碗黄焖鸡三碗米饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值