当前位置:   article > 正文

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 并训练自己的数据集_lora微调和pturning

lora微调和pturning

大模型主流微调训练方法总结

LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning

概述

大模型微调(finetuning)以适应特定任务是一个复杂且计算密集型的过程。本文训练测试主要是基于主流的的微调方法:LoRA、Adapter、Prefix-tuning、P-tuning和Prompt-tuning,并对它们进行总结。
LoRA (Learned Representations for Finetuning)
LoRA是一种新型的微调方法,旨在解决预训练模型微调过程中存在的两大问题,模型调整过程中对初始模型过度依赖以及微调过程中存在的过拟合问题。LoRA通过在预训练模型中引入一个额外的线性层,并使用特定任务的训练数据来微调这个线性层。这种方法使模型能够更好地适应特定任务,同时减少了对初始模型的过度依赖。
Adapter
Adapter是一种简单而有效的微调方法,它通过在预训练模型的特定层上添加一个可学习的附加层来适应特定任务。这个附加层可以是线性层、非线性层或其他类型的层,其目的是对预训练模型的输出进行微调,使其更好地适应特定任务。Adapter具有较低的计算成本和较好的性能,使其成为处理小数据集的理想选择。
Prefix-tuning
Prefix-tuning方法通过微调预训练模型的特定部分(称为“前缀”)以适应特定任务。这种方法只微调前缀,而不是整个模型,从而减少了计算成本和过拟合的风险。Prefix-tuning的性能通常优于传统的微调方法,但不及完整的模型微调。
P-tuning
P-tuning是一种改进的微调方法,通过引入一个参数化转换矩阵来调整预训练模型的权重。这个矩阵可以学习地改变预训练模型的权重分布,使其更好地适应特定任务。P-tuning在保持良好性能的同时,减少了微调过程中对初始模型的过度依赖。
Prompt-tuning
Prompt-tuning是一种新颖的微调方法,利用了近年来自然语言处理领域的prompting技术。该方法通过修改预训练模型的输入来适应特定任务,使其在输入阶段就考虑到任务的特定需求。Prompt-tuning可以显著提高模型的性能,同时减少了对初始模型的过度依赖和过拟合的风险。

总结:

这五种微调方法在处理大型预训练模型以适应特定任务方面都具有各自的优点和适用场景。LoRA通过引入额外的线性层来减少对初始模型的过度依赖和过拟合问题;Adapter具有较低的计算成本和较好的性能,适用于小数据集;Prefix-tuning只微调预训练模型的前缀,减少了计算成本和过拟合的风险;P-tuning通过引入参数化转换矩阵来调整预训练模型的权重,减少了过度依赖;Prompt-tuning利用prompting技术修改预训练模型的输入,显著提高性能并减少过度依赖和过拟合的风险。在实际应用中,应根据具体任务和数据集选择合适的微调方法。

详述

在这里插入图片描述

LoRA

paper:(https://arxiv.org/pdf/2106.09685.pdf)

简介:

自然语言处理目前存在一个重要范式:一般领域数据的大规模预训练,对特定任务或领域的适应(finetune)。但是随着预训练语言模型越来越大,这个范式存在以下问题:
● 当我们finetune大模型时,由于训练成本太高,不太可能重新训练所有模型参数
● 以前的方法(论文发表于2021年)都或多或少有其它性能问题,如adapter增加了模型层数,引入了额外的推理延迟;prefix-tuning比较难训练,效果不如直接finetune。
基于上述背景,论文作者得益于前人的一些关于内在维度(intrinsic dimension)的发现:模型是过参数化的,它们有更小的内在维度,模型主要依赖于这个低的内在维度(low intrinsic dimension)去做任务适配。假设模型在任务适配过程中权重的改变量是低秩(low rank)的,由此提出低秩自适应(LoRA)方法,LoRA通过优化适应过程中密集层变化的秩分解矩阵来间接训练神经网络中的一些密集层,同时保持预先训练的权重不变。

方法

LoRA的实现思想很简单,如下图所示,就是冻结一个预训练模型的矩阵参数,并选择用A和B矩阵来替代,在下游任务时只更新A和B。

在这里插入图片描述
结合图片来看,LoRA的实现流程如下:
● 在原始预训练语言模型(PLM)旁边增加一个旁路,做一个降维再升维的操作,来模拟所谓的内在秩。
● 训练的时候固定PLM的参数,只训练降维矩阵A与升维矩阵B。
● 模型的输入输出维度不变,输出时将BA与PLM的参数叠加。
● 用随机高斯分布初始化A,用0矩阵初始化B,保证训练的开始此旁路矩阵依然是0矩阵。

实现

从公式上解释LoRA的实现。假设要在下游任务微调一个预训练语言模型(如GPT3),则需要更新预训练模型参数,公式表示如下:
W0是预训练模型初始化的参数,ΔW就是需要更新的参数。如果是全参数微调,则它的参数量=W0参数量(如果是GPT3,则ΔW≈175B)。从这可以看出要全参数微调大语言模型,没有超级好的显卡群是没法实现的。
由于前人的工作发现预训练的语言模型具有较低的“内部维度(intrinsic dimension)”,在任务适配过程中,即使随机投影到较小的子空间,仍然可以有效地学习。因此,LoRA做的就是增加小参数模块去学习改变量ΔW。

在这里插入图片描述

在训练过程中,W0是固定不变的,只有A和B包含训练参数,是变化的。而在推理的过程中,只需要把改变量放回原模型,就不会有任何延迟。如果想切换任务,只需要切换任务的过程中,减去BA,然后换上用其它任务训练好的BʹAʹ就可以了。

总结

基于大模型的内在低秩特性,增加旁路矩阵来模拟full finetuning,LoRA是一个能达成lightweight finetuning的简单有效的方案。目前该技术已经广泛应用于大模型的微调,如Alpaca,stable diffusion+LoRA,而且能和其它参数高效微调方法有效结合

2. Adapter

paper:(https://arxiv.org/pdf/1902.00751.pdf)

简介

2019年,Houlsby N等人将Adapter引入NLP领域,作为全模型微调的一种替代方案。

方法

Adapter主体架构下图所示。
在这里插入图片描述
AdapterFusion将学习过程分为两个阶段:
● 1.「知识提取阶段」:训练Adapter模块学习下游任务的特定知识,将知识封装在Adapter模块参数中。
● 2.「知识组合阶段」:将预训练模型参数与特定于任务的Adapter参数固定,引入新参数学习组合多个Adapter中的知识,提高模型在目标任务中的表现。

实现

其中对于N的不同的下游任务训练N个Adapter模块。然后使用AdapterFusion组合N个适配器中的知识,将预训练参数Θ和全部的Adapter参数Φ固定,引入新的参数Ψ,使用N个下游任务的数据集训练,让AdapterFusion学习如何组合N个适配器解决特定任务。参数Ψ在每一层中包含Key、Value和Query(上图右侧架构所示)。
在Transformer每一层中将前馈网络子层的输出作为Query,Value和Key的输入是各自适配器的输出,将Query和Key做点积传入SoftMax函数中,根据上下文学习对适配器进行加权。在给定的上下文中,AdapterFusion学习经过训练的适配器的参数混合,根据给定的输入识别和激活最有用的适配器。「作者通过将适配器的训练分为知识提取和知识组合两部分,解决了灾难性遗忘、任务间干扰和训练不稳定的问题。Adapter模块的添加也导致模型整体参数量的增加,降低了模型推理时的性能」。

总结

Adapter Fusion 在 Adapter 的基础上进行优化,通过将学习过程分为两阶段来提升下游任务表现。作者对全模型微调(Full)、Adapter、AdapterFusion三种方法在各个数据集上进行和对比试验。AdapterFusion在大多数情况下性能优于全模型微调和Adapter,特别在MRPC(相似性和释义任务数据集)与RTE(识别文本蕴含数据集)中性能显著优于另外两种方法

3. Prefix-tuning

Paper:(https://arxiv.org/pdf/2101.00190.pdf)

简介

前缀微调(prefix-tunning),用于生成任务的轻量微调。前缀微调将一个连续的特定于任务的向量序列添加到输入,称之为前缀,如下图中的红色块所示。与提示(prompt)不同的是,前缀完全由自由参数组成,与真正的token不对应。相比于传统的微调,前缀微调只优化了前缀。因此,我们只需要存储一个大型Transformer和已知任务特定前缀的副本,对每个额外任务产生非常小的开销。

在这里插入图片描述

方法

本文考虑两个生成任务:table-to-text 和摘要任务。
在这里插入图片描述
对于table-to-text任务,本文使用自回归语言模型GPT-2,输入为source( x )和target( y )的拼接,模型自回归地生成 y~\tilde{y} :
在这里插入图片描述

实现

在传统微调方法中,模型使用预训练参数进行初始化,然后用对数似然函数进行参数更新。

在这里插入图片描述

4. P-tuning

Paper:https://arxiv.org/pdf/2103.10385.pdf

简介

P-tuning是稍晚些的工作,主要针对NLU任务。对于BERT类双向语言模型采用模版(P1, x, P2, [MASK], P3),对于单向语言模型采用(P1, x, P2, [MASK]):

在这里插入图片描述

方法

加了两个改动:

  1. 考虑到预训练模型本身的embedding就比较离散了(随机初始化+梯度传回来小,最后只是小范围优化),同时prompt本身也是互相关联的,所以作者先用LSTM对prompt进行编码。
  2. 在输入上加入了anchor,比如对于RTE任务,加上一个问号变成[PRE][prompt tokens][HYP]?[prompt tokens][MASK]后效果会更好。
总结

p-tuning的效果很好,之前的Prompt模型都是主打小样本效果,而P-tuning终于在整个数据集上超越了精调的效果:

在这里插入图片描述

5. prompt-tuning

Paper:https://arxiv.org/pdf/2110.07602.pdf

简介

Prompt-tuning给每个任务定义了自己的Prompt,拼接到数据上作为输入,同时freeze预训练模型进行训练,在没有加额外层的情况下,可以看到随着模型体积增大效果越来越好,最终追上了精调的效果:
在这里插入图片描述
同时,Prompt-tuning还提出了Prompt-ensembling,也就是在一个batch里同时训练同一个任务的不同prompt,这样相当于训练了不同「模型」,比模型集成的成本小。

方法总结:

全参数微调太贵,Adapter Tuning存在训练和推理延迟,Prefix Tuning难训且会减少原始训练数据中的有效文字长度。LORA训练过程中,固定住预训练权重,只对低秩矩阵和进行训练。在保存权重时,只需保存低秩矩阵的部分即可。因此对于本次应用,利用LORA训练微调模型更具有可用性。

LoRA整体架构

在这里插入图片描述

图中左侧表示“全参数finetune”的场景。我们将参数分成了两个部分:
 预训练权重
 finetune增量权重
之所以这么拆分,是因为全参数finetune可以理解成“冻住的预训练权重”+“微调过程中产生的权重更新量”。 设输入为,输出为,则有:图中右侧表示“LoRA finetune”的场景。在LoRA中,我们用矩阵A和B来近似表达:

  1. 低秩矩阵,其中被称为“秩”,对采用高斯初始化。
  2. 低秩矩阵,对B采用零初始化。

LoRA优势

  • LoRA能从整体上降低显存使用;
  • LoRA并不是作用在模型的每一层,例如论文里的LoRA只作用在attention部分;
  • LoRA虽然会导致某一层的峰值显存高于全量微调,但计算完梯度后,这个中间结果就可以被清掉了,不会一致保存;
  • 当待训练权重从dd降为2r*d时,需要保存的optimizer states也减少了(尤其是fp32)。

超参

在这里插入图片描述

  1. 论文中采用Adam做优化器时,调整的作用就相当于调整learning rate。一般而言,把它设置为第一次做实验时设置的超参数,然后固定下来,之后只调整即可,这样做的好处是当尝试不同的时,就不需要再去调整别的超参了。
  2. 预训练权重(旧知识),增量权重的近似(新知识)。理论上说,当较小时,我们提取的是信息含量最丰富的维度,此时信息精炼,但不全面;当较大时,我们的低秩近似越逼近,此时信息更加全面,但带来的噪声也越多(含有很多冗余无效的信息)。

基于此因,做实验时,我们会尽量把调得大些,例如32、64,在这个秩下,低秩权重已经非常近似了,意味着我们假定LoRA低秩微调的效果和全参数微调持平。然后往小的进行尝试,当越小时,低秩矩阵表示的信息精炼,但不全面。通过调大,来放大forward过程中新知识对模型的影响。通过调大,适当增加梯度下降的步伐,也就相当于调整learning rate了。

显卡性能需求

    • SFT 全量微调: 4张显卡平均分配,每张显卡占用48346MiB显存。 4X48G
    • P-TuningV2 微调: 1张显卡,占用 18426MiB 显存。18G
    • LORA 微调: 1张显卡,占用 14082MiB 显存。14G

实验

数据集

(1)文件名后缀:json或jsonl,格式如下

[{“conversations”:[{“role”:“user”,“content”:“问题1},{“role”:“assistant”,“content”:“答案1}]},
{“conversations”:[{“role”:“user”,“content”:“问题2},{“role”:“assistant”,“content”:“答案2}]},
{“conversations”:[{“role”:“user”,“content”:“问题3},{“role”:“assistant”,“content”:“答案3}]}

  • 1
  • 2
  • 3
  • 4
数据制作代码
import json
from typing import Union
from pathlib import Path

def _resolve_path(path: Union[str, Path]) -> Path:
return Path(path).expanduser().resolve()

def _mkdir(dir_name: Union[str, Path]):
    dir_name = _resolve_path(dir_name)
    if not dir_name.is_dir():
        dir_name.mkdir(parents=True, exist_ok=False)

def convert_adgen(data_dir: Union[str, Path], save_dir: Union[str, Path]):
    def _convert(in_file: Path, out_file: Path):
        _mkdir(out_file.parent)
        with open(in_file, encoding='utf-8') as fin:
            with open(out_file, 'wt', encoding='utf-8') as fout:
                for line in fin:
                    dct = json.loads(line)
                    sample = {'conversations': [{'role': 'user', 'content': dct['content']},
                                                {'role': 'assistant', 'content': dct['summary']}]}
                    fout.write(json.dumps(sample, ensure_ascii=False) + '\n')

    data_dir = _resolve_path(data_dir)
    save_dir = _resolve_path(save_dir)

    train_file = data_dir / 'train.json'
    if train_file.is_file():
        out_file = save_dir / train_file.relative_to(data_dir)
        _convert(train_file, out_file)

    dev_file = data_dir / 'dev.json'
    if dev_file.is_file():
        out_file = save_dir / dev_file.relative_to(data_dir)
        _convert(dev_file, out_file)

print("convert data starting ......")
convert_adgen('formatted_data/AdvertiseGen', 'formatted_data/AdvertiseGen_fix')
print("convert data finishing ......")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

LORA训练接入配置

Lora.Yaml
 data_config:
 train_file: my_data_qa.json
 val_file: my_data_qa.json
 test_file: my_data_qa.json
 num_proc: 16 #16
 max_input_length: 128
 max_output_length: 256 #256
 training_args:
 # see transformers.Seq2SeqTrainingArguments
 output_dir: ./output_boshi
 max_steps: 100000
 # settings for data loading
 per_device_train_batch_size: 16
 dataloader_num_workers: 16
 remove_unused_columns: false
 # settings for saving checkpoints
 save_strategy: steps
 save_steps: 10000
 # settings for logging
 log_level: info
 logging_strategy: steps
 logging_steps: 10
 # settings for evaluation
 per_device_eval_batch_size: 16 #@16
 evaluation_strategy: steps
 eval_steps: 10000
 # settings for optimizer
 # adam_epsilon: 1e-6
 # uncomment the following line to detect nan or inf values
 # debug: underflow_overflow
 predict_with_generate: true
 # see transformers.GenerationConfig
 generation_config:
 max_new_tokens: 256
 # set your absolute deepspeed path here
 #deepspeed: ds_zero_2.json
 # set to true if train with cpu.
 use_cpu: false
 peft_config:
 peft_type: LORA
 task_type: CAUSAL_LM
 r: 8
 lora_alpha: 32
 lora_dropout: 0.1

关键代码

        if peft_config.peft_type.name == "LORA":
            model = AutoModelForCausalLM.from_pretrained(
                model_dir,
                trust_remote_code=True,
                empty_init=False,
                use_cache=False
            )
            model = get_peft_model(model, peft_config)
            model.print_trainable_parameters()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

训练过程

在这里插入图片描述
在这里插入图片描述

训练结果生成

在这里插入图片描述
在这里插入图片描述

权重合并

权重合并代码:

import torch
from peft import PeftModel
from transformers import AutoTokenizer, AutoModel
#加载原模型
base_model = '/media/DATA/XXX/large_model/weights'
base_model = AutoModel.from_pretrained(base_model, trust_remote_code=True).cuda(3)
#加载微调的模型
lora_model_path = '/media/DATA/XXX/large_model/Chat_weitiao/ChatGLM3/finetune_demo/output/checkpoint-30000'
lora_model = PeftModel.from_pretrained(base_model,lora_model_path, torch_dtype=torch.float16)
lora_model.to("cpu")
#合并
merged_model = lora_model.merge_and_unload()
#合并的模型存储
new_model_directory = '/media/DATA/XXX/large_model/Chat_weitiao/ChatGLM3/finetune_demo/output/merge'
merged_model.save_pretrained(new_model_directory, max_shard_size="2048MB", safe_serialization=True)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

合并结果
在这里插入图片描述

测试结果

在这里插入图片描述
注*:部分图片资源来源于网络

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/926425
推荐阅读
相关标签
  

闽ICP备14008679号