使用LoRA简单微调一个预训练模型,实现中文问答(easy版)

#手把手教学

使用工具:RTX4090 24G VSCode python3.8 cuda11.8

连接上远程服务器之后操作

一、配置环境

我们需要用到datasets、transformers、peft库,直接打开服务器终端,使用清华镜像安装:
清华镜像使用方法

pip install datasets transformers peft -i https://pypi.tuna.tsinghua.edu.cn/simple

下载我们需要用到的数据集(alpaca_gpt4_data_zh.json)和模型(Langboat/bloom-1b4-zh):

使用镜像下载

第一步:

pip install -U huggingface_hub

第二步:

export HF_ENDPOINT=https://hf-mirror.com

第三步:下载模型

huggingface-cli download --resume-download Langboat/bloom-1b4-zh --local-dir Langboat/bloom-1b4-zh

第四步:下载数据集

huggingface-cli download --repo-type dataset --resume-download llm-wizard/alpaca-gpt4-data-zh --local-dir data

至此,前期工作结束

二、Code

1.导入相关包

#导入包
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer
from datasets import Dataset
from peft import LoraConfig, TaskType, get_peft_model

2.加载分词器、模型和数据

#加载分词器、模型、数据集
tokenizer = AutoTokenizer.from_pretrainen("Langboat/bloom-1b4-zh")
model = AutoModelForCausalLM.from_pretrained("Langboat/bloom-1b4-zh", low_cpu_mem_usage=True)
data = Dataset.from_json("data/alpaca_gpt4_data_zh.json")

3.处理数据

#定义数据处理函数
def process_func(example):
    #设置最大长度
    MAX_LENGTH = 256
    #初始化输入ID、注意力掩码和标签列表
    input_ids,attention_mask,lable = [],[],[]
    #对指令和输入进行编码,tokenizer会将句子分成input_ids和attention_mask两行
    instruction = tokenizer("/n".join(["Human:" + example["instruction"],example["input_ids"]]).strip + "/n/nAssistant:")
    #对输出进行编码,tokenizer会将句子分成input_ids和attention_mask两行
    response = tokenizer(example["output_ids"]+tokenizer.eos_token)
    #将指令和响应的input_ids拼接起来
    input_ids = instruction["input_ids"] + response["input_ids"]
    #将指令和响应的attention_mask拼接起来
    attention_mask = instruction["attention_mask"] + response["attention_mask"]
    #将指令的标签设置为-100,表示不计算损失;将响应的输入ID作为标签
    lable = (-100)*len(instruction["input_ids"]) + response["input_ids"]
    #超出长度的进行截断
    if len(input_ids)>MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        lable = lable[:MAX_LENGTH]
    return{
        "input_ids":input_ids,
        "attention_mask":attention_mask,
        "lable":lable
    }
tokenized_data = data.map(process_func,remove_columns=ds.column_names)

处理数据是为了将文本数据转换为机器学习模型可以理解的格式。机器学习模型不能直接处理原始文本,因此需要将文本转换为数字表示。这通常通过分词(Tokenization)完成,其中文本被分割成更小的单元(如单词或子词),并转换为对应的唯一整数标识符。
input_ids:是模型输入的词汇标识符列表。这些标识符对应于输入文本中的词或子词。

attention_mask:是用于指示哪些标记应该被模型关注,哪些不应该。

lable:是用于训练模型的输出。在这个例子中,指令部分被赋予标签 -100,这是因为在某些任务中,我们可能不想对指令部分进行损失计算,只想让模型学习生成响应。

4.设置config(调用LoRA):

其实这个特别短,也特别简单,下面罗列全部的参数:

  1. r: 这是 LoRA 的核心参数之一,表示低秩矩阵的秩。较小的秩意味着添加的参数更少,因此训练更快,但可能会降低模型的表达能力。

  2. target_modules: 这个参数指定了哪些模块应该添加 LoRA 适配层。通常,只会对模型的关键层(如注意力机制的自注意力层和前馈网络)应用 LoRA。

  3. task_type: 这个参数定义了微调任务的类型,例如 TaskType.CAUSAL_LM 表示因果语言建模任务,TaskType.SEQ_2_SEQ_LM 表示序列到序列的语言建模任务等。

  4. inference_mode: 这个参数用于控制模型在推理时的行为。设置为 False 时,模型在推理时使用 LoRA 适配层;设置为 True 时,模型在推理时忽略 LoRA 适配层,使用原始预训练模型的权重。

  5. lora_alpha: 这是一个超参数,用于缩放 LoRA 适配层的输出。在某些情况下,调整这个参数可以帮助提高模型的性能。

  6. lora_dropout: 这是 LoRA 适配层中的 dropout 概率。添加 dropout 可以帮助模型泛化,减少过拟合。

  7. bias: 这个参数指示是否在 LoRA 适配层中添加可训练的偏置项。

  8. modules_to_save: 这个参数用于指定在保存模型时应该包含哪些模块。这通常用于确保 LoRA 适配层被正确保存。

我们这里只设置任务类型,其他的都使用默认值。

config = LoraConfig(tasktype=TaskType.CAUSAL_LM)
model = get_peft_model(model, config)

5.定义训练参数

这里的保存路径需要自己设置,把save_path替换成自己想要保存的路径

args = TrainingArguments(
    output_dir="save_path",# 指定模型训练结果的输出目录。
    per_device_train_batch_size=8, # 指定每个设备(如GPU)上的批次大小
    gradient_accumulation_steps=8,# 指定梯度累积步数。在本例子中,每8个步骤进行一次梯度更新。
    save_steps=1000,#指定模型保存的频率。在本例子中,每1000个步骤保存一次模型
    num_train_epochs=4 #指定训练的总轮数
)

6.初始化训练器

#初始化训练器
trainer = Trainer(
    model=model,#指定训练模型
    args=args, #指定训练参数
    train_dataset=tokenized_ds, #指定数据集
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True) #指定数据收集器。其中tokenizer是分词器,padding=True表示对输入进行填充以保持批次大小一致。
)
trainer.train()

至此,代码已经写完了,直接点击运行开始训练。

三、对比模型

其实可以先对比训练之前和训练之后的模型推理效果

原模型:

微调后的:

测试代码:自己填充一下文件路径,这个会直接输出两个答案,直观对比

from transformers import pipeline
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel

# 加载模型和分词器
model = AutoModelForCausalLM.from_pretrained("原模型路径", low_cpu_mem_usage=True)
tokenizer = AutoTokenizer.from_pretrained("原模型路径")

# 创建生成文本的管道
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device=0,do_sample=True)

# 输入文本
ipt = "Human:早上容易饿怎么办?\n\nAssistant: "

# 生成文本,设置截断,加载PeftModel之前
response_before = pipe(
    ipt, 
    max_length=256, 
    truncation=True,
    num_return_sequences=1,
    do_sample=True,        # 使用采样而不是贪婪解码
    temperature=0.7,       # 控制生成文本的随机性
    top_k=50,              # 保证生成文本的多样性
    top_p=0.95,            # 控制生成文本的质量
    repetition_penalty=2.0 # 减少重复生成的现象
)

print("Before loading PeftModel:", response_before)

# 加载PEFT模型
p_model = PeftModel.from_pretrained(model, model_id="微调之后的模型保存路径")

# 生成文本,设置截断,加载PeftModel之后
response_after = pipe(
    ipt, 
    max_length=256, 
    truncation=True,
    num_return_sequences=1,
    do_sample=True,        # 使用采样而不是贪婪解码
    temperature=0.7,       # 控制生成文本的随机性
    top_k=50,              # 保证生成文本的多样性
    top_p=0.95,            # 控制生成文本的质量
    repetition_penalty=2.0 # 减少重复生成的现象
)

print("After loading PeftModel:", response_after)

四、合并模型

为什么要合并模型?因为LoRA微调是加了额外的层,但是这个层并不是原模型的层,就好像给手机贴了个膜,这个膜并不属于手机的一部分,我们要把这个膜融入手机屏幕上面,成为手机的一部分。其实也可以不合并,但是走完完整的流程。

  1. 简化推理流程:合并后的模型可以作为一个单一的模型进行推理,这样可以简化推理流程,也就是推理的更快,答复更快。

  2. 减少推理时的资源消耗:合并后的模型在推理时不需要额外的LoRA适配层参数,可以减少内存和计算资源的使用。

  3. 提高模型的兼容性:合并后的模型可以更容易地与其他系统或库集成,因为它遵循原始模型的架构和接口。

  4. 便于模型分享和分发:合并后的模型可以作为一个单一的权重文件进行分享和分发,而不需要额外的适配层信息。

    from transformers import pipeline
    from transformers import AutoTokenizer, AutoModelForCausalLM
    from peft import PeftModel
    
    # 加载模型和分词器
    model = AutoModelForCausalLM.from_pretrained("原模型路径", low_cpu_mem_usage=True)
    tokenizer = AutoTokenizer.from_pretrained("原模型路径")
    
    # 加载微调后的模型
    p_model = PeftModel.from_pretrained(model, model_id="微调后的模型路径")
    
    # 合并和卸载微调模型
    merge_model = p_model.merge_and_unload()
    
    # 创建生成文本的管道
    pipe = pipeline("text-generation", model=merge_model, tokenizer=tokenizer, device=0)
    
    # 输入文本
    ipt = "Human:晚上吃什么更有营养?\n\nAssistant: "
    
    # 生成文本,设置截断
    response = pipe(ipt, max_length=256, truncation=True)
    
    print(response)
    

    没有实习,日常焦虑。。。。

  • 35
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LoRA(Large-scale Reinforcement Learning from Image Pixels with Latent Actions)是一种用于微调大型模型的方法,它结合了强化学习和图像像素级别的训练LoRA的目标是通过观察环境中的图像像素,并根据这些像素采取相应的行动来学习一个能够解决复杂任务的模型。 具体来说,LoRA使用一个基于像素的强化学习框架,其中模型通过观察环境中的图像像素来学习如何采取最佳行动。这种方法的一个关键特点是,模型不需要任何先验知识或手工设计的特征,而是直接从原始像素数据中学习。 LoRA训练过程包括两个阶段:预训练微调。在预训练阶段,使用自编码器来学习图像的表示。自编码器是一种无监督学习方法,它通过将输入图像压缩成低维编码,然后再将编码解压缩为重构图像。通过这种方式,自编码器可以学习到图像的有用特征。 在微调阶段,使用强化学习算法(如Proximal Policy Optimization)来优化模型的策略。模型通过观察环境中的图像像素,并根据当前的状态选择最佳的行动。通过与环境进行交互并根据奖励信号进行反馈,模型逐渐优化其策略,以实现更好的性能。 LoRA的优势在于它能够处理高维度的原始输入数据,并且不需要手工设计的特征。通过使用像素级别的训练LoRA可以学习到更丰富和复杂的特征表示,从而提高模型的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值