当前位置: 首页 > news >正文

241207_MindNLP中的大模型微调

241207_基于MindNLP的大模型高效微调

现在的大模型体量非常庞大,全量微调所需要的算力也特别庞大,个人开发者没有条件微调。参数量达到7B的模型才刚刚有涌现能力,但是我们要微调7B的模型的话,就需要3×28G的显存,至少需要2张A100的卡,更何况我们连一张也没有。

近年来研究者提出了各种各样的参数高效微调方法,即固定住预训练模型的大部分参数,仅调整模型的一小部分参数实现微调。

为什么使用高效微调

大模型在下游任务泛化性不够好

高效参数微调相比全量微调训练参数更少

高效参数微调忘得少,相比全量微调不容易过拟合

更适合online training

Additive PEFT(加性微调):在模型的特定位置添加可学习的模块或参数。

Selective PEFT(选择性微调):在微调过程中只更新模型中的一部分参数,而保持其余参数固定。

Reparameterization PEFT(重参数化微调):通过构建原始模型参数的低秩表示,在训练中增加可学习参数,以实现参数高效微调。

在这里插入图片描述

Additive PEFT

Prompt Tuning

Prompt Tuning的本质是往模型添加一些额外的信息,使得模型在生成预测值y的时候能获得一些条件(提示),一般是在我们的输入层面解决的,Prompt Tuning并不改变模型参数,相当于就是一个提示文本。

比如:

输入:这部电影真是太好看了

我们单纯给这样的输入模型并不知道我们要干啥,此时我们就要拼接一个提示的prompt

提示:我感觉很

这个时候模型才知道他的任务可能是续写或者情绪分类,这样他才会预测“很”字后面的情绪。

在这个过程中,模型的所有参数都是冻结的,只有embedding得到了学习。

Prefix Tuning

传统的微调是利用预训练模型针对不同的下游任务进行微调,微调完成之后每个下游任务都要去保存一份模型权重,此时修改整个模型的权重,训练耗时长,同时整个模型的权重保存下来所占据的存储空间也大。

Prefix Tuning是为大模型添加可训练任务的特定前缀,他不是训练整个模型,只是训练一个前缀,就解决了训练耗时以及存储空间的问题。

在这里插入图片描述

针对不同的模型结构需要构造不同的Prefix。prefix需要加在每一个transformer结构前面。

在仅有解码器的GPT中(decoder-only),prefix只加在句首,模型的输入表示为:
z = [ P R E F I X ; x ; y ] z=[PREFIX;x;y] z=[PREFIX;x;y]
在编解码器的结构中(encoder-decoder),编码器和解码器前面都需要添加prefix

在这里插入图片描述

在MindSpore NLP里面的实现步骤:

1.为防止直接更新Prefix参数导致训练不稳定,在Prefix层前面加上MLP结果,训练完成只保留Prefix参数。

2.得到past_key_values

3.在每一层的transformer block的key value前面,拼接past_key_value

在这里插入图片描述

Selective PEFT

BitFit

BitFit在微调的时候仅仅调整预训练模型的bias项,训练时冻结除bias外的所有参数,只更新bias参数。

在mindnlp中的实现:

# creating modelmodel = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)all_param = sum(param.numel() for param in model.parameters())trainable_params = 0
for name, param in model.named_parameters():if "bias" not in name:param.requires_grad = False else:print(name)trainable_params += param.numel()  print(f"trainable params: {trainable_params:,d} || all params: {all_param:,d} || trainable%: {100 * trainable_params / all_param}")

在这里插入图片描述

Reparameterization PEFT

LoRA

在这里插入图片描述

LoRA同样不修改PLM(Pretrained Language Model),是在预训练的权重旁边搭建一个旁路,添加可训练的参数,具体是搭建两个低秩矩阵,相乘后做生维,在和原来的预训练模型的权重相叠加

假设模型有一个具有 1,000 行和 2,000 列的矩阵。这就是要在模型文件中存储的 2,000,000 个数字(1,000 x 2,000)。LoRA 将该矩阵分解为一个 1,000x2 的矩阵和一个 2x2,000 的矩阵。这只需要 6,000 个数字(1,000 x 2 + 2 x 2,000),是原始矩阵大小的 333 倍。这就是 LoRA 参数数量小的原因。

针对transformer的mutil-head attention中的一些矩阵做调整。比如mutil-head attention中的Q,K做了旁路调整。

在这里插入图片描述

使用LoRA时会对主干模型做int8甚至int4的量化,使得主干模型的前向传播和反向传播耗时减少。

多卡训练(数据并行)时,卡间通信只需要同步LoRA模型部分的梯度,大大减小通信的压力,也会使总训练速度变快。

秩的选取,对于一般的任务,rank=1,2,4,8就够了,对于领域差距比较大的任务,需要更大的rank。

因为旁路的两个矩阵计算完成后会和主干的权重做叠加,所以不会增加推理成本

在MindNLP中的步骤:

(1)初始化低秩矩阵

(2)计算原始矩阵结果

(3)计算低秩矩阵结果

(4)汇总得到LoRA的结果

在这里插入图片描述

在MindNLP中的使用:

# creating model
peft_config = LoraConfig(task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1)model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

在这里插入图片描述

( I A ) 3 (IA)^3 IA3

IA3是给每个激活层学习一个系数向量,训练和推断时,激活单元与向量中对应的分量相乘,参数量更少。

就是通过点乘一个向量的形式对模型的一部分参数进行加权。

在这里插入图片描述

和其他PEFT的使用方法差不多,使用步骤如下:

实例化基本模型。

创建一个配置 (IA3Config),在其中定义 IA3 特定的参数。

使用 get_peft_model() 包装基础模型以获得可训练的 PeftModel。

像平常训练基础模型一样训练 PeftModel。

在MindNLP中的实现:

# creating model
peft_config = IA3Config(task_type=TaskType.SEQ_2_SEQ_LM, inference_mode=False)model = AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

IA3实战

本次实验环境

mindspore=2.3.0,mindnlp=0.4.1,python=3.9.10,cann=8.0

首先我们来捋一下MRPC和GLUE的关系。

GLUE是一个多任务的自然语言理解基准和分析平台,他有九大任务,其中有一个任务就是MRPC(语义相似任务),他即可以说是一个任务,又可以说是数据集。

接下来我们要使用IA3对Roberta-Large模型进行微调训练

使用roberta-large模型,数据集使用mrpc

MRPC数据集是一个用于文本相似分析的数据集,用于自动识别两个句子是否表达相同的意思

我们加载该数据集并进行可视化。

from mindnlp.dataset import load_dataset
task="mrpc"
datasets=load_dataset("glue",task)
datasets

在这里插入图片描述

这里我们就可以发现,每组数据中有两个英文句子,label意思是是否相似,idx是索引

既然我们要使用IA3微调,那就先把IA3加载进来(配置一个用于序列分类任务的IA3模型),任务类型选的SEQ_CLS是序列分类任务。

from mindnlp.peft import get_peft_model,PeftType,IA3Config
peft_config = IA3Config(task_type="SEQ_CLS", inference_mode=False)
lr = 1e-3

然后配置我们一些参数

batch_size=32 # 批次数
model_name="roberta-large" # 模型
task="mrpc" # 任务类型
peft_type=PeftType.IA3 # 微调类型
num_epochs=20 # 训练epoch

不同的预训练模型在处理输入时要求不同,例如GPT、OPT、Bloom等模型通常是基于自回归架构,需要在左侧填充,以便在生成过程中保持上下文一致。其他模型如 RoBERTa、BERT 等通常是基于双向编码器,通常需要右侧填充(right),以确保上下文信息的对称性。

if any(k in model_name for k in ("gpt", "opt", "bloom")):padding_side = "left"
else:padding_side = "right"

然后我们再定义一个分词器并进行检查

from mindnlp.transformers import AutoModelForSequenceClassification, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side=padding_side)
if getattr(tokenizer, "pad_token_id") is None:tokenizer.pad_token_id = tokenizer.eos_token_id

为了适配mindnlp输入,我们需要做一些转换。

from mindnlp.dataset import BaseMapFunctionclass MapFunc(BaseMapFunction):"""自定义映射函数类,继承自BaseMapFunction。该类用于处理数据集中的每一项,将文本句子转换为模型所需的输入格式。调用时,它会使用传入的tokenizer对两个句子进行编码,并保持标签不变。参数:sentence1 -- 第一个输入句子sentence2 -- 第二个输入句子label -- 句子对的标签idx -- 数据项的索引返回:input_ids -- 编码后的输入ID序列attention_mask -- 注意力掩码序列label -- 原始标签"""def __call__(self, sentence1, sentence2, label, idx):# 使用tokenizer对句子对进行编码,启用truncation以确保不超过最大长度outputs = tokenizer(sentence1, sentence2, truncation=True, max_length=None)# 返回编码后的input_ids和attention_mask,以及原始标签return outputs['input_ids'], outputs['attention_mask'], labeldef get_dataset(dataset, tokenizer):"""获取经过处理的数据集。该函数将输入的数据集与tokenizer作为参数,使用MapFunc处理每个数据项,然后将处理后的数据项批处理,并对不足批次大小的数据进行填充。参数:dataset -- 输入的数据集tokenizer -- 用于编码文本的tokenizer返回:dataset -- 经过映射和批处理的数据集"""# 定义输入和输出列名input_colums=['sentence1', 'sentence2', 'label', 'idx']output_columns=['input_ids', 'attention_mask', 'labels']# 使用MapFunc处理数据集的每一项dataset = dataset.map(MapFunc(input_colums, output_columns),input_colums, output_columns)# 对处理后的数据集进行批处理和填充dataset = dataset.padded_batch(batch_size, pad_info={'input_ids': (None, tokenizer.pad_token_id),'attention_mask': (None, 0)})# 返回处理后的数据集return dataset# 使用训练数据集和tokenizer获取经过处理的训练数据集
train_dataset = get_dataset(datasets['train'], tokenizer)
# 使用验证数据集和tokenizer获取经过处理的验证数据集
eval_dataset = get_dataset(datasets['validation'], tokenizer)

从 evaluate 库中加载指定任务的 GLUE评估指标

metric = evaluate.load("glue", task)

然后加载预训练的序列分类模型,并把我们刚才配置的ia3模型应用到模型上,使其支持参数高效微调。

model = AutoModelForSequenceClassification.from_pretrained(model_name_or_path, return_dict=True)
model = get_peft_model(model, peft_config)

然后我们就可以打印可以训练的参数数量

model.print_trainable_parameters()

这里可以看到ia3微调的参数量非常小,只占总参数量的0.3%

在这里插入图片描述

然后定义我们的优化器、学习率调度器

optimizer = AdamW(params=model.parameters(), lr=lr)# Instantiate scheduler
lr_scheduler = get_linear_schedule_with_warmup(optimizer=optimizer,num_warmup_steps=0.06 * (len(train_dataset) * num_epochs),num_training_steps=(len(train_dataset) * num_epochs),
)

开始前向训练

from mindnlp.core import value_and_grad
def forward_fn(**batch):outputs = model(**batch)loss = outputs.lossreturn lossgrad_fn = value_and_grad(forward_fn, tuple(model.parameters()))for epoch in range(num_epochs):model.set_train()train_total_size = train_dataset.get_dataset_size()for step, batch in enumerate(tqdm(train_dataset.create_dict_iterator(), total=train_total_size)):optimizer.zero_grad()loss = grad_fn(**batch)optimizer.step()lr_scheduler.step()model.set_train(False)eval_total_size = eval_dataset.get_dataset_size()for step, batch in enumerate(tqdm(eval_dataset.create_dict_iterator(), total=eval_total_size)):outputs = model(**batch)predictions = outputs.logits.argmax(axis=-1)predictions, references = predictions, batch["labels"]metric.add_batch(predictions=predictions,references=references,)eval_metric = metric.compute()print(f"epoch {epoch}:", eval_metric)

如果有没有导入的包,直接用以下代码

import mindspore
from tqdm import tqdm
from mindnlp import evaluate
from mindnlp.dataset import load_dataset
from mindnlp.transformers import AutoModelForSequenceClassification, AutoTokenizer
from mindnlp.core.optim import AdamW
from mindnlp.common.optimization import get_linear_schedule_with_warmup
from mindnlp.peft import (get_peft_model,PeftType,IA3Config,
)
http://www.lryc.cn/news/506787.html

相关文章:

  • MongoDB、Mongoose使用教程
  • 单片机:实现控制步进电机正反转(附带源码)
  • 安装指南|OpenCSG Starship上架GitHub Marketplace
  • Excel设置生日自动智能提醒,公式可直接套用!
  • 同步异步日志系统:前置知识
  • 微服务设计原则——功能设计
  • 低代码软件搭建自学的第一天——熟悉PyQt
  • 基于Python3编写的Golang程序多平台交叉编译自动化脚本
  • 远程桌面连接
  • 网络地址转换NAT
  • 什么是CRM管理软件?CRM的基本概念、功能、选择标准、应用场景
  • Python编程常用的19个经典案例
  • 【Unity基础】AudioSource 常用方法总结
  • CSS系列(25)-- 滚动优化详解
  • CST天线设计的六大核心特点:为天线分析提供完整解决方案!
  • Ubuntu下C语言操作kafka示例
  • 怎么将pdf中的某一个提取出来?介绍几种提取PDF中页面的方法
  • HTTP接口报错详解与解决 200,500,403,408,404
  • 监控IP频繁登录服务器脚本
  • 分布式链路追踪-03-Jaeger、Zipkin、skywalking 中的 span 是如何设计的?
  • 【达梦数据库】获取对象DDL
  • InnoDB和MyISAM引擎优缺点和区别
  • 文件上传知识点汇总
  • 计算机网络技术基础:5.数据通信系统
  • 光谱相机在农业的应用
  • 高考志愿填报:如何制定合理的志愿梯度?
  • Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆角矩形实现,Kotlin(1)
  • webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式
  • 区块链详解
  • 【EXCEL 逻辑函数】AND、OR、XOR、NOT、IF、IFS、IFERROR、IFNA、SWITCH