参数高效微调PEFT(四)快速入门(IA)3

参数高效微调PEFT(四)快速入门(IA)3

  • 我们已经了解了HuggingFace中peft库的几种高效微调方法。

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning

参数高效微调PEFT(二)快速入门P-Tuning、P-Tuning V2

参数高效微调PEFT(三)快速入门LoRA、AdaLoRA

  • 今天我们继续了解高效微调方法(IA)3。

1 (IA)3

  • in-context learning(ICL)简单的说就是,在冻结大模型参数的情况下,在输入时,给定一些样本包含数据和标签,同时给一个待预测数据,由模型输出这条数据的预测值。这个过程中模型的参数不发生变化。因此在应用到下游任务时,不需要更新参数,可以扩展到各种各样的任务场景。
    • 但这种模式使得,模型在每次推理过程中,都要处理一次prompts中的示例样本,这也是一种很大的计算消耗。
    • 改变prompts中例子的顺序对最终预测效果的影响很大;
    • 还有的研究发现,prompts中的例子数据和标签在没有正确配对的情况下,对带预测数据的预测结果影响不是很大。
    • 这些现象说明in-context learning 背后的机制,以及如何使in-context learning效果更加鲁棒仍然有待研究。
  • IA3论文主要对ICL和PEFT方法,在少样本场景下进行了严谨的实验对比,发现PEFT方法在取得很高精度的情况下,同时很大降低了计算消耗,可以替代ICL。
    • PEFT极大地减少了训练和保存模型所需的内存和存储需求。PEFT可以显著提高计算效率,并同时实现比ICL更高的准确性。
    • 此外,某些PEFT方法可以直接允许混合任务的批处理
      • 例如,Prompt tuning只需将不同的prompt embeddings连接到批处理中的每个示例即可使单个模型执行多个任务。
      • 混合任务的批处理指的是在一个批量处理的数据集中同时包含多个不同类型的任务或问题。在机器学习领域,通常情况下,一个模型被设计用于解决特定的任务,比如分类、回归等。
      • 而混合任务的批处理则是指在同一个训练批次中包含了多种不同类型的任务,使得模型能够同时学习多种任务之间的相关性,从而提高泛化能力和效率。这种方法可以帮助模型更好地利用数据,加速训练过程,并提高模型在多任务学习和迁移学习方面的性能。
    • 另一方面,重参数化模型的PEFT方法(例如LoRA)对于混合任务的批处理来说是昂贵或繁琐的。
    • 此外,不同的PEFT方法增加了执行推断所需的计算和内存量。例如,adapters实际上在模型中添加了额外的(小型)层,导致计算成本和内存开销的增加。

1.1 (IA)3简介

  • 论文地址:Few-Shot Parameter-Efficient Fine-Tuning is Better and Cheaper than In-Context Learning(2205)

  • 论文创新点主要有两个:

    • 提出了一个新的高效微调方法(IA)3
    • 基于T0模型提出了T-Few,在下游任务中不需要对任务进行额外模型调整,即可进行少样本学习。
  • 新的高效微调方法(IA)3

    • 虽然prompt tuning以及prefix tuning等方法可以满足下游多个任务同批次进行,但是精度不够,而精度够的方法又不允许同批次多任务处理。因此作者开发一个新的PEFT方法—(IA)3。这种PEFT方法是对模型的一些激活层进行抑制或放大,也就是通过点乘一个向量的形式对模型的一部分参数进行加权
    • 下图左侧展示了这些下游任务微调的小参数的添加位置,分别在attention机制中的Key向量和Value向量上,以及前馈神经网络的激活层后。

    在这里插入图片描述

    • 另外,有工作指出预训练这部分参数也可以进一步提高下游任务上的少样本以及零样本性能,因此作者也采纳了预训练的做法。
  • 基于T0模型提出了T-Few

    • 基于团队先前的工作T0,作者修改了损失函数以适应少样本学习的情况,称为T-Few,无需针对特定任务进行调整或修改即可应用于新任务。
    • 所以在模型训练过程,作者使用了不同loss:即上图右侧的语言模型损失 L L M L_{LM} LLM, 负例似然损失 L U L L_{UL} LUL, 长度归一化损失 L L N L_{LN} LLN

1.2 (IA)3源码分析

(IA)3的初始化,和之前LoRA一样,重要的代码就是update_layer。

update_layer中,如果是feedforward,会初始化shape为(1, self.in_features)的向量

如果不是(如query_key_value),就初始化shape为(self.out_features, 1)的向量

    # peft\tuners\ia3.py
    def update_layer(self, adapter_name, init_ia3_weights):
        # Actual trainable parameters
        if self.is_feedforward:
            weight = torch.randn((1, self.in_features))
        else:
            weight = torch.randn((self.out_features, 1))
        self.ia3_l.update(nn.ParameterDict({adapter_name: nn.Parameter(weight)}))
        if init_ia3_weights:
            self.reset_ia3_parameters(adapter_name)
        self.to(self.weight.device)

初始化后,就会将之前的module进行替换,替换后的Model如下:

PeftModelForCausalLM(
  (base_model): IA3Model(
    (model): BloomForCausalLM(
      (transformer): BloomModel(
        (word_embeddings): Embedding(250880, 64)
        (word_embeddings_layernorm): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
        (h): ModuleList(
          (0-1): 2 x BloomBlock(
            (input_layernorm): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
            (self_attention): BloomAttention(
              # 1、query_key_value模块添加IA3
              (query_key_value): Linear(
                in_features=64, out_features=192, bias=True
                (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 192x1])
              )
              (dense): Linear(in_features=64, out_features=64, bias=True)
              (attention_dropout): Dropout(p=0.0, inplace=False)
            )
            (post_attention_layernorm): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
            (mlp): BloomMLP(
              (dense_h_to_4h): Linear(in_features=64, out_features=256, bias=True)
              (gelu_impl): BloomGelu()
              (dense_4h_to_h): Linear(
                # 2、dense_4h_to_h模块添加IA3
                in_features=256, out_features=64, bias=True
                (ia3_l): ParameterDict(  (default): Parameter containing: [torch.FloatTensor of size 1x256])
              )
            )
          )
        )
        (ln_f): LayerNorm((64,), eps=1e-05, elementwise_affine=True)
      )
      (lm_head): Linear(in_features=64, out_features=250880, bias=False)
    )
  )
)

最终会调用peft\tuners\ia3.py中Linear的前向传播(如下代码)。

  • 如果是feedforward,ia3_l向量与feedforward的输入相乘,再进入F.linear

  • 如果不是,可学习向量ia3_l与注意力块的输出result相乘

    def forward(self, x: torch.Tensor):
        previous_dtype = x.dtype

        if self.active_adapter not in self.ia3_l.keys():
            return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)

        if self.disable_adapters:
            if self.merged:
                self.unmerge()
            result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
        elif not self.merged:
            # 1、如果是feedforward,ia3_l向量与feedforward的【输入】相乘,再进入F.linear
            if self.is_feedforward:
                x = x.to(self.ia3_l[self.active_adapter].dtype)
                interm = x * self.ia3_l[self.active_adapter].flatten()
                result = F.linear(
                    interm.to(self.weight.dtype),
                    transpose(self.weight, self.fan_in_fan_out),
                    bias=self.bias,
                )
            else:
                # 2、可学习向量ia3_l与注意力块的【输出】result相乘
                result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
                result = result.to(self.ia3_l[self.active_adapter].dtype) * self.ia3_l[self.active_adapter].flatten()
        else:
            result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)

        result = result.to(previous_dtype)

        return result

1.3 (IA)3轻量微调bloom模型

同样,我们只需要在加载原模型后、配置训练器前加peft的代码即可。

from peft import IA3Config, TaskType, get_peft_model

config = IA3Config(task_type=TaskType.CAUSAL_LM,
                   # bloom默认为["query_key_value", "mlp.dense_4h_to_h"]
                   # 配置在TRANSFORMERS_MODELS_TO_IA3_TARGET_MODULES_MAPPING(peft\utils\other.py)
                   target_modules=["query_key_value", "mlp.dense_4h_to_h"],
                   inference_mode=False, 
                   # bloom默认为["mlp.dense_4h_to_h"]
                   # 配置在TRANSFORMERS_MODELS_TO_IA3_FEEDFORWARD_MODULES_MAPPING(peft\utils\other.py)
                   feedforward_modules=["mlp.dense_4h_to_h"]
)

print(config)

model = get_peft_model(model, config)

# 打印可训练参数
model.print_trainable_parameters()

# trainable params: 172,032 || all params: 345,940,992 || trainable%: 0.04972871211515749
  • IA3Config常用参数如下:
    • task_type:指定任务类型。如:条件生成任务(SEQ_2_SEQ_LM),因果语言建模(CAUSAL_LM)等。
    • inference_mode:是否在推理模式下使用Peft模型。
    • target_modules:要替换为 IA3 的模块名称列表或模块名称的正则表达式
    • feedforward_modules:target_modules 中被视为前馈(feedforward)层的模块名称列表或模块名称的正则表达式。注意:可学习向量与注意力块的输出激活相乘,但与经典前馈层的输入相乘
    • module_to_save:除了 IA3 层之外要设置为可训练并保存在最终检查点中的模块列表。
  • 配置训练器、模型训练及推理和参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning中2.1一样。
  • 显存消耗情况:
(base) root@autodl-container-adbc11ae52-f2ebff02:~# nvidia-smi  
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.89.02    Driver Version: 525.89.02    CUDA Version: 12.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ...  On   | 00000000:B2:00.0 Off |                  N/A |
| 31%   53C    P2   183W / 250W |   2836MiB / 11264MiB |     42%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

2 PEFT多适配器

2.1 自定义模型适配

如何为自定义的模型适配参数高效微调呢

import torch
from torch import nn
from peft import LoraConfig, get_peft_model, PeftModel


# 1、自定义模型
net1 = nn.Sequential(
    nn.Linear(10, 10),
    nn.ReLU(),
    nn.Linear(10, 2)
)

# 2、打印参数名
for name, param in net1.named_parameters():
    print(name)

# 0.weight
# 0.bias
# 2.weight
# 2.bias    

# 3、这里对nn.Linear(10, 10)模块高效微调
# 利用target_modules指定要添加LoRA的目标模块(支持正则)
config = LoraConfig(target_modules=["0"])
model1 = get_peft_model(net1, config)

print(model1)
PeftModel(
  (base_model): LoraModel(
    (model): Sequential(
      (0): Linear(
        # 可以看到第0层,可以已经发生了替换
        in_features=10, out_features=10, bias=True
        (lora_dropout): ModuleDict(
          (default): Identity()
        )
        (lora_A): ModuleDict(
          (default): Linear(in_features=10, out_features=8, bias=False) # 秩默认为8
        )
        (lora_B): ModuleDict(
          (default): Linear(in_features=8, out_features=10, bias=False)
        )
        (lora_embedding_A): ParameterDict()
        (lora_embedding_B): ParameterDict()
      )
      (1): ReLU()
      (2): Linear(in_features=10, out_features=2, bias=True)
    )
  )
)

2.2 多适配器加载与切换

一个主模型,多个适配器的情况如何使用

# 我们定义一个主模型main_net
main_net = nn.Sequential(
    nn.Linear(10, 10),
    nn.ReLU(),
    nn.Linear(10, 2)
)

# 保存第1个适配器lora1
config1 = LoraConfig(target_modules=["0"])
model2 = get_peft_model(main_net, config1)
model2.save_pretrained("./lora1")

# 保存第2个适配器lora2
config2 = LoraConfig(target_modules=["2"])
model2 = get_peft_model(main_net, config2)
model2.save_pretrained("./lora2")
  • 在加载第一个适配器时,可以通过 PeftModel.from_pretrained 方法并指定 adapter_name 参数来给它命名。否则,将使用默认的适配器名称 default
  • 要加载另一个适配器,请使用 PeftModel 的 load_adapter() 方法,例如:model.load_adapter(peft_model_path, adapter_name)
  • 要切换适配器,请使用 PeftModel 的 set_adapter() 方法,例如:model.set_adapter(adapter_name)
# 我们重新创建模型,加载适配器
main_net = nn.Sequential(
    nn.Linear(10, 10),
    nn.ReLU(),
    nn.Linear(10, 2)
)


# 加载第一个适配器
main_model = PeftModel.from_pretrained(main_net, model_id="./lora1/", adapter_name="lora1")

for name, param in main_model.named_parameters():
    if name in ["base_model.model.0.lora_A.lora1.weight", "base_model.model.0.lora_B.lora1.weight"]:
        param.data = torch.ones_like(param) * 10


print(main_model.active_adapter)
print(main_model(torch.arange(0, 10).view(1, 10).float()))
lora1
tensor([[-18845.8906,  39025.6406]])
# 加载另一个适配器
main_model.load_adapter("./lora2/", adapter_name="lora2")

# 切换适配器前
print(main_model.active_adapter)
print(main_model(torch.arange(0, 10).view(1, 10).float()))

# 切换适配器后
main_model.set_adapter("lora2")
print(main_model.active_adapter)
print(main_model(torch.arange(0, 10).view(1, 10).float()))
lora1
tensor([[-18845.8906,  39025.6406]]) # 和lora1结果一致
lora2
tensor([[0.3642, 0.7926]])

2.3 禁用适配器

如何获取原始模型的输出结果

  • 要禁用适配器,请使用上下文管理器 disable_adapter(),例如:with model.disable_adapter()
# 切换适配器
main_model.set_adapter("lora1")

# 还是lora1结果 tensor([[-18845.8906,  39025.6406]])
print(main_model(torch.arange(0, 10).view(1, 10).float()))

# 获取原始结果
with main_model.disable_adapter():
    # tensor([[0.3642, 0.7926]])
    print(main_model(torch.arange(0, 10).view(1, 10).float()))
在实战操作中,PEFT库可以用来微调BERT模型,以进行文本情感分类任务。首先,我们需要准备一个包含大量文本和标签的数据集,用于训练和验证BERT模型。然后,我们需要利用PEFT库中提供的工具和接口,将数据集转换成BERT模型可接受的格式,并进行数据预处理,如分词和填充等操作。 接着,我们可以利用PEFT库中提供的预训练模型,加载BERT模型的参数和网络结构,并在数据集上进行微调微调的过程中,我们可以通过调整学习率、批大小和训练轮数等超参数,来优化模型的性能。在每个训练轮数结束后,我们可以利用PEFT库中的评估工具对模型进行评估,以了解模型在验证集上的性能表现。 最后,当模型在验证集上的性能达到满意的水平后,我们可以使用PEFT库提供的保存模型工具,将微调后的BERT模型保存下来,以备在实际应用中使用。通过PEFT库的实战操作,我们可以有效地利用BERT模型进行文本情感分类任务,提高模型的准确性和泛化能力,从而更好地满足实际应用的需求。 PEFT库的实战操作不仅帮助我们更好地理解和使用BERT模型,也为我们提供了一套完整的工具和流程,使得模型训练和应用变得更加简单和高效。 PEFT库实战(一): lora微调BERT(文本情感分类) 的操作流程清晰,易于上手,为我们在文本情感分类任务中的应用提供了有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值