聊城网站设计,安徽网站优化怎么做,首页百度,成都市建设局权益卡网站241207_基于MindNLP的大模型高效微调
现在的大模型体量非常庞大#xff0c;全量微调所需要的算力也特别庞大#xff0c;个人开发者没有条件微调。参数量达到7B的模型才刚刚有涌现能力#xff0c;但是我们要微调7B的模型的话#xff0c;就需要328G的显存#xff0c;至少需…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-onlyprefix只加在句首模型的输入表示为 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(ftrainable params: {trainable_params:,d} || all params: {all_param:,d} || trainable%: {100 * trainable_params / all_param})Reparameterization PEFT
LoRA LoRA同样不修改PLMPretrained 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中的QK做了旁路调整。 使用LoRA时会对主干模型做int8甚至int4的量化使得主干模型的前向传播和反向传播耗时减少。 多卡训练数据并行时卡间通信只需要同步LoRA模型部分的梯度大大减小通信的压力也会使总训练速度变快。 秩的选取对于一般的任务rank1248就够了对于领域差距比较大的任务需要更大的rank。 因为旁路的两个矩阵计算完成后会和主干的权重做叠加所以不会增加推理成本
在MindNLP中的步骤 1初始化低秩矩阵 2计算原始矩阵结果 3计算低秩矩阵结果 4汇总得到LoRA的结果 在MindNLP中的使用
# creating model
peft_config LoraConfig(task_typeTaskType.SEQ_2_SEQ_LM, inference_modeFalse, r8, lora_alpha32, lora_dropout0.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_typeTaskType.SEQ_2_SEQ_LM, inference_modeFalse)model AutoModelForSeq2SeqLM.from_pretrained(model_name_or_path)model get_peft_model(model, peft_config)
model.print_trainable_parameters()IA3实战
本次实验环境 mindspore2.3.0mindnlp0.4.1python3.9.10cann8.0 首先我们来捋一下MRPC和GLUE的关系。 GLUE是一个多任务的自然语言理解基准和分析平台他有九大任务其中有一个任务就是MRPC语义相似任务他即可以说是一个任务又可以说是数据集。 接下来我们要使用IA3对Roberta-Large模型进行微调训练
使用roberta-large模型数据集使用mrpc MRPC数据集是一个用于文本相似分析的数据集用于自动识别两个句子是否表达相同的意思 我们加载该数据集并进行可视化。
from mindnlp.dataset import load_dataset
taskmrpc
datasetsload_dataset(glue,task)
datasets这里我们就可以发现每组数据中有两个英文句子label意思是是否相似idx是索引
既然我们要使用IA3微调那就先把IA3加载进来配置一个用于序列分类任务的IA3模型任务类型选的SEQ_CLS是序列分类任务。
from mindnlp.peft import get_peft_model,PeftType,IA3Config
peft_config IA3Config(task_typeSEQ_CLS, inference_modeFalse)
lr 1e-3然后配置我们一些参数
batch_size32 # 批次数
model_nameroberta-large # 模型
taskmrpc # 任务类型
peft_typePeftType.IA3 # 微调类型
num_epochs20 # 训练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_sidepadding_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, truncationTrue, max_lengthNone)# 返回编码后的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_dictTrue)
model get_peft_model(model, peft_config)然后我们就可以打印可以训练的参数数量
model.print_trainable_parameters()这里可以看到ia3微调的参数量非常小只占总参数量的0.3% 然后定义我们的优化器、学习率调度器
optimizer AdamW(paramsmodel.parameters(), lrlr)# Instantiate scheduler
lr_scheduler get_linear_schedule_with_warmup(optimizeroptimizer,num_warmup_steps0.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(), totaltrain_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(), totaleval_total_size)):outputs model(**batch)predictions outputs.logits.argmax(axis-1)predictions, references predictions, batch[labels]metric.add_batch(predictionspredictions,referencesreferences,)eval_metric metric.compute()print(fepoch {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,
)