深度学习:基于MindSpore的极简风大模型微调

瑞星  金牌会员 | 2024-12-14 20:42:28 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 802|帖子 802|积分 2406

什么是PEFT?What is PEFT?

PEFT(Parameter Efficient Fine-Tuning)是一系列让大规模预练习模型高效顺应于新使命或新数据集的技能。
PEFT在保持大部分模型权重冻结,只修改或添加一小部份参数。这种方法极大得减少了盘算量和存储开销,但包管了大模型在多个使命上的复用性。
为什么需要PEFT?Why do we need PEFT?

扩展性挑衅

大规模预练习模型如GPT、BERT或ViT拥有大量参数。为每个具体使命全参微调这些模型不仅耗费大量盘算量,同时需要巨大的存储资源,这些资源每每难以承担。
提升迁移学习效率

PEFT很好地利用了预练习模型在通用使命上的本领,同时提升了模型在具体使命上的表现。同时PEFT能减少过拟归并提供更好的通用型。
PEFT如何工作?How does PEFT work?

1. 冻结大部人预练习模型的参数
2. 修改或添加小部份参数
3. 模型练习时,只修改小部份参数即可
PEFT方法分类

Additive PEFT(加性微调):在模型特定位置添加可学习的模块或参数。如:Adapters、Prompt-Tuning 
Selective PEFT(选择性微调):在微调过程只更新模型中的一部份参数,保持其余参数固定。如:BitFit、HyperNetworks
Reparameterization PEFT(重参数化微调):构建原始模型参数的低秩表现,在练习过程中增长可学习参数以实现高效微调。如:LoRA (Low-Rank Adaptation)、Prefix-Tuning

Prefix Tuning

Prefix Tuning在每个Transformer Block层参加Prefix Learnable Parameter(Embedding层),这些前缀作为特定使命的上下文,预练习模型的参数保持冻结。相当于在seq_len维度中,加上特定个数的token。
  1. class LoRA(nn.Module):
  2.     def __init__(self, original_dim, low_rank):
  3.         super().__init__()
  4.         self.low_rank_A = nn.Parameter(torch.randn(original_dim, low_rank))  # Low-rank matrix A
  5.         self.low_rank_B = nn.Parameter(torch.randn(low_rank, original_dim))  # Low-rank matrix B
  6.     def forward(self, x, original_weight):
  7.         # x: Input tensor [batch_size, seq_len, original_dim]
  8.         # original_weight: The frozen weight matrix [original_dim, original_dim]
  9.         
  10.         # LoRA weight update
  11.         lora_update = torch.matmul(self.low_rank_A, self.low_rank_B)  # [original_dim, original_dim]
  12.         
  13.         # Combined weight: frozen + LoRA update
  14.         adapted_weight = original_weight + lora_update
  15.         # Forward pass
  16.         output = torch.matmul(x, adapted_weight)  # [batch_size, seq_len, original_dim]
  17.         return output
复制代码
但Prefix Tuning在需要更深层次模型调整的使命上表现较差。

Adapters 

Adapters是较小的,可练习的,插入在预练习模型层之间的模块。每个Adapter由一个下采样模块,一个非线性激活和一个上采样模块组层。预练习模型参数保持冻结,adapters用于捕捉具体使命的知识。 

基于MindSpore的模型微调

环境需求:2.3.0-cann 8.0.rc1-py 3.9-euler 2.10.7-aarch64-snt9b-20240525100222-259922e
Prefix-Tuning 

 安装mindNLP
  1. pip install mindnlp
复制代码
加载依赖
  1. # 模块导入 and 参数初始化
  2. import os
  3. import mindspore
  4. from mindnlp.transformers import AutoModelForSeq2SeqLM
  5. # peft相关依赖
  6. from mindnlp.peft import get_peft_config, get_peft_model, get_peft_model_state_dict, PrefixTuningConfig, TaskType
  7. from mindnlp.dataset import load_dataset
  8. from mindnlp.core import ops
  9. from mindnlp.transformers import AutoTokenizer
  10. from mindnlp.common.optimization import get_linear_schedule_with_warmup
  11. from tqdm import tqdm
  12. # 演示模型 t5-small
  13. model_name_or_path = "t5-small"
  14. tokenizer_name_or_path = "t5-small"
  15. checkpoint_name = "financial_sentiment_analysis_prefix_tuning_v1.ckpt"
  16. max_length = 128
  17. lr = 1e-2
  18. num_epochs = 5
  19. batch_size = 8
复制代码
 通过mindnlp.peft库加载模型并进行prefix配置
  1. # Prefix-Tuning参数设置以及配置模型
  2. peft_config = PrefixTuningConfig(task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, num_virtual_tokens=20)
  3. # 加载预训练模型
  4. model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
  5. # 加载加入prefix后的模型
  6. model = get_peft_model(model, peft_config)
  7. model.print_trainable_parameters()
复制代码
加载、预处置惩罚数据集
  1. # 微调 t5 for 金融情感分析
  2. # input: 金融短句
  3. # output: 情感类别
  4. # 由于华为云无法连接huggingface,因此需要先本地下载,再上传至华为云
  5. mindspore.dataset.config.set_seed(123)
  6. # loading dataset
  7. dataset = load_dataset("financial_phrasebank", cache_dir='/home/ma-user/work/financial_phrasebank/')
  8. train_dataset, validation_dataset = dataset.shuffle(64).split([0.9, 0.1])
  9. classes = dataset.source.ds.features["label"].names
  10. # 将标签号映射为文本
  11. def add_text_label(sentence, label):
  12.     return sentence, label, classes[label.item()]
  13. # 输入为两列,输出为三列
  14. train_dataset = train_dataset.map(add_text_label, ['sentence', 'label'], ['sentence', 'label', 'text_label'])
  15. validation_dataset = validation_dataset.map(add_text_label, ['sentence', 'label'], ['sentence', 'label', 'text_label'])
  16. # 加载t5模型的分词器
  17. tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
  18. # tokenize 输入和text_label
  19. import numpy as np
  20. from mindnlp.dataset import BaseMapFunction
  21. from threading import Lock
  22. # 线程锁?
  23. lock = Lock()
  24. class MapFunc(BaseMapFunction):
  25.     def __call__(self, sentence, label, text_label):
  26.         lock.acquire()
  27.         model_inputs = tokenizer(sentence, max_length=max_length, padding="max_length", truncation=True)
  28.         labels = tokenizer(text_label, max_length=2, padding="max_length", truncation=True)
  29.         lock.release()
  30.         # 提取 labels 中的 input_ids
  31.         # 这些 ID 实际上是模型词汇表中相应单词或子词单元的位置索引。
  32.         # 因此,input_ids 是一个整数列表,代表了输入文本序列经过分词和编码后的结果,它可以直接作为模型的输入。
  33.         labels = labels['input_ids']
  34.         # 将 labels 中的填充标记替换为 -100,这是常见的做法,用于告诉损失函数忽略这些位置。
  35.         labels = np.where(np.equal(labels, tokenizer.pad_token_id), -100, lables)
  36.         return model_inputs['input_ids'], model_inputs['attention_mask'], labels
  37.    
  38. def get_dataset(dataset, tokenizer, shuffle=True):
  39.     input_colums=['sentence', 'label', 'text_label']
  40.     output_columns=['input_ids', 'attention_mask', 'labels']
  41.     dataset = dataset.map(MapFunc(input_colums, output_columns),
  42.                           input_colums, output_columns)
  43.     if shuffle:
  44.         dataset = dataset.shuffle(64)
  45.     dataset = dataset.batch(batch_size)
  46.     return dataset
  47. train_dataset = get_dataset(train_dataset, tokenizer)
  48. eval_dataset = get_dataset(validation_dataset, tokenizer, shuffle=False)
复制代码
进行微调练习 
  1. # 初始化优化器和学习策略
  2. from mindnlp.core import optim
  3. optimizer = optim.AdamW(model.trainable_params(), lr=lr)
  4. # 动态学习率
  5. lr_scheduler = get_linear_schedule_with_warmup(
  6.     optimizer=optimizer,
  7.     num_warmup_steps=0,
  8.     num_training_steps=(len(train_dataset) * num_epochs),
  9. )
  10. from mindnlp.core import value_and_grad
  11. def forward_fn(**batch):
  12.     outputs = model(**batch)
  13.     loss = outputs.loss
  14.     return loss
  15. grad_fn = value_and_grad(forward_fn, model.trainable_params())
  16. for epoch in range(num_epochs):
  17.     model.set_train()
  18.     total_loss = 0
  19.     train_total_size = train_dataset.get_dataset_size()
  20.    
  21.     for step, batch in enumerate(tqdm(train_dataset.create_dict_iterator(), total=train_total_size)):
  22.         optimizer.zero_grad()
  23.         loss = grad_fn(**batch)
  24.         optimizer.step()
  25.         total_loss += loss.float()
  26.         lr_scheduler.step()
  27.    
  28.     model.set_train(False)
  29.     eval_loss = 0
  30.     eval_preds = []
  31.     eval_total_size = eval_dataset.get_dataset_size()
  32.     for step, batch in enumerate(tqdm(eval_dataset.create_dict_iterator(), total=eval_total_size)):
  33.         with mindspore._no_grad():
  34.             outputs = model(**batch)
  35.         loss = outputs.loss
  36.         eval_loss += loss.float()
  37.         eval_preds.extend(
  38.             tokenizer.batch_decode(ops.argmax(outputs.logits, -1).asnumpy(), skip_special_tokens=True)
  39.         )
  40.     # 验证集loss
  41.     eval_epoch_loss = eval_loss / len(eval_dataset)
  42.     eval_ppl = ops.exp(eval_epoch_loss)
  43.     # 测试集loss
  44.     train_epoch_loss = total_loss / len(train_dataset)
  45.     train_ppl = ops.exp(train_epoch_loss)
  46.     print(f"{epoch=}: {train_ppl=} {train_epoch_loss=} {eval_ppl=} {eval_epoch_loss=}")
复制代码
模型评估
  1. # 模型评估
  2. correct = 0
  3. total = 0
  4. ground_truth = []
  5. correct = 0
  6. total = 0
  7. ground_truth = []
  8. for pred, data in zip(eval_preds, validation_dataset.create_dict_iterator(output_numpy=True)):
  9.     true = str(data['text_label'])
  10.     ground_truth.append(true)
  11.     if pred.strip() == true.strip():
  12.         correct += 1
  13.     total += 1
  14. accuracy = correct / total * 100
  15. print(f"{accuracy=} % on the evaluation dataset")
  16. print(f"{eval_preds[:10]=}")
  17. print(f"{ground_truth[:10]=}")
复制代码
模型保存
  1. # 模型保存
  2. # saving model
  3. peft_model_id = f"{model_name_or_path}_{peft_config.peft_type}_{peft_config.task_type}"
  4. model.save_pretrained(peft_model_id)
复制代码
加载模型进行推理
  1. # 加载模型并推理
  2. from mindnlp.peft import PeftModel, PeftConfig
  3. config = PeftConfig.from_pretrained(peft_model_id)
  4. model = AutoModelForSeq2SeqLM.from_pretrained(config.base_model_name_or_path)
  5. model = PeftModel.from_pretrained(model, peft_model_id)
  6. model.set_train(False)
  7. example = next(validation_dataset.create_dict_iterator(output_numpy=True))
  8. print("input", example["sentence"])
  9. print(example["text_label"])
  10. inputs = tokenizer(example['text_label'], return_tensors="ms")
  11. with mindspore._no_grad():
  12.     outputs = model.generate(input_ids=inputs["input_ids"], max_new_tokens=10)
  13.     print(tokenizer.batch_decode(outputs.numpy(), skip_special_tokens=True))
复制代码
 BitFit

BitFit需要冻结除Bias外的全部参数,只练习Bias参数。
  1. for n, p in model.named_parameters():
  2.     if "bias" not in n:
  3.         p.requires_grad = False
  4.     else:
  5.         p.requires_grad = True
复制代码
其余数据预处置惩罚代码和练习代码与上述相同。 
LoRA

LoRA(Low Rank Adaptation)专注于学习一个低秩矩阵。通过在冻结的预练习权重中添加可学习的低秩矩阵。在前向通报过程中,冻结的权重和新的低秩矩阵参与盘算。
低秩矩阵指的是相较于原矩阵,秩更低的矩阵。参加一个矩阵的形状为m x n,矩阵的秩最多为min(m, n),低秩矩阵的秩数远远小于原本的m和n。
LoRA微调不更新原本m x n的权重矩阵,转而更新更小的低秩矩阵A(m, r), B(r, n)。假设W0为512x512,低秩矩阵的r则可以为16,这样需要更新的数据只需要(512x16+16x512)=16384,相较于原来的512x512=262144,少了93.75%。
LoRA实现的基本思路代码
  1. class LoRA(nn.Module):
  2.     def __init__(self, original_dim, low_rank):
  3.         super().__init__()
  4.         self.low_rank_A = nn.Parameter(torch.randn(original_dim, low_rank))  # Low-rank matrix A
  5.         self.low_rank_B = nn.Parameter(torch.randn(low_rank, original_dim))  # Low-rank matrix B
  6.     def forward(self, x, original_weight):
  7.         # x: Input tensor [batch_size, seq_len, original_dim]
  8.         # original_weight: The frozen weight matrix [original_dim, original_dim]
  9.         
  10.         # LoRA weight update
  11.         lora_update = torch.matmul(self.low_rank_A, self.low_rank_B)  # [original_dim, original_dim]
  12.         
  13.         # Combined weight: frozen + LoRA update
  14.         adapted_weight = original_weight + lora_update
  15.         # Forward pass
  16.         output = torch.matmul(x, adapted_weight)  # [batch_size, seq_len, original_dim]
  17.         return output
复制代码

LoRA的MindSpore实现 
  1. # creating model
  2. # r 控制适应层的秩,lora_alpha 是缩放因子,而 lora_dropout 定义了在训练期间应用于 LoRA 参数的 dropout 率。
  3. # 缩放因子用于控制低秩矩阵对模型参数更新的影响程度。
  4. peft_config = LoraConfig(task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1)
  5. model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
  6. model = get_peft_model(model, peft_config)
  7. model.print_trainable_parameters()
复制代码
其余数据预处置惩罚代码和练习代码与上述相同。 
更多内容可以参考mindspore的官方视频:
【第二课】昇腾+MindSpore+MindSpore NLP:极简风的大模型微调实战_哔哩哔哩_bilibili

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

瑞星

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表