LoRA微调中文版Llama3模型

概要

LoRA(Low-Rank Adaptation)是一种微调大模型的方法,通过引入低秩矩阵来减少参数量和计算复杂度,主要应用于大型预训练语言模型的微调过程。本文章讲解的是使用LoRA技术微调Llama3中文版模型。实验环境为kaggle、GPU环境为2块T4.

整体微调流程

1.准备数据:收集并预处理用于微调的训练数据。
2.模型初始化:加载预训练的大模型Llama3。
3.引入LoRA层:在特定层引入低秩矩阵,使其参数可学习,而不改变原始模型的大部分参数。
4.微调训练:使用准备好的数据进行微调训练,只更新低秩矩阵的参数。
5.评估与调优:在验证集上评估微调后的模型效果,并根据需要调整超参数。
接下来详细介绍微调流程

  1. 导入所需要的库
# 基本都是些常见库,有些可能没用到,但无伤大雅
import torch
import shutil
from torch import cuda
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
torch.backends.cuda.enable_mem_efficient_sdp(False)
torch.backends.cuda.enable_flash_sdp(False)
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments,AutoConfig
from transformers import GenerationConfig,AutoModelForSequenceClassification,DataCollatorWithPadding
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ['CUDA_LAUNCH_BLOCKING'] = '1'
!pip install -q peft==0.3.0
from peft import TaskType,LoraConfig,get_peft_model,set_peft_model_state_dict
from peft.utils import TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING
import pandas as pd
from datasets import Dataset
import json
from sklearn.model_selection import train_test_split
from transformers import TrainerCallback
  1. 将所需要的模型下载下来

    model_id为模型版本,由于环境限制只采用了16位计算,device_map="auto"会自动将模型分布在两张GPU上
    
model_id = 'hfl/llama-3-chinese-8b-instruct-v3'
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float16, device_map="auto")
print('模型下载好了')
  1. 准备微调的数据集,本次实验数据集链接为https://github.com/SCIR-HI/Huatuo-Llama-Med-Chinese/tree/main/data
#数据集样例
"instruction":"一名年龄在70岁的女性,出现了晕厥、不自主颤抖、情绪不稳等症状,请详细说明其手术治疗和术前准备。"
"input":""
"output":"该病需要进行电极导线、脉冲发生器和永久心脏起搏器置入术,并需要使用镇静药物和局麻对病人进行手术治疗。术前准备包括1-3天的时间进行术前检查和生活方式的调整。"
#计算一下数据集的长度
flag = 0
for entry in data_list:
    instruction = entry.get('instruction', '')
    output_text = entry.get('output', '')

    context = f"""下面是一个问题,运用医学知识来正确回答提问.\n问题:\n{instruction}\n"""
    target = f"回答:\n{output_text}"
    combined_input = context + target
    encoded = tokenizer(combined_input)
    input_ids = encoded['input_ids']
    if len(input_ids)>=256:
#        print(combined_input)
        flag+=1
#         print('---------')
print(flag)#按照输入模版编码后长度超过256的仅有26条数据,所以256的max_length是够的
由于原数据不是完全按照json格式,所以要处理一下
input_file = '/kaggle/input/medical/llama_data.json'
output_file = 'data.json'
# 读取文件内容
with open(input_file, 'r', encoding='utf-8') as file:
    lines = file.readlines()
# 处理每一行并转换为字典
data_list = [json.loads(line) for line in lines]
# 将列表保存为JSON格式的文件
with open(output_file, 'w', encoding='utf-8') as file:
    json.dump(data_list, file, ensure_ascii=False, indent=2)
print(f"数据成功保存 {output_file}")

# 创建包含指令、输入和输出的列表
processed_data = []
# 设置pad_token
tokenizer.pad_token_id = (0)

# 1. **`input_ids`** 是模型看到的所有上下文,用于生成输出。
# 2. **`labels`** 是模型应该生成的文本,与 `input_ids` 对应的位置对齐,其他部分填充特殊的忽略标记(如 `-100`),以确保它们不影响损失计算。
max_length = 256
for entry in data_list:
    instruction = entry.get('instruction', '')
    output_text = entry.get('output', '')

    context = f"""下面是一个问题,运用医学知识来正确回答提问.\n问题:\n{instruction}\n"""
    target = f"回答:\n{output_text}"
    combined_input = context + target
    # 对输入文本进行编码
    encoded = tokenizer(combined_input, truncation=True, padding='max_length', max_length=max_length)
    input_ids = encoded['input_ids']
    attention_mask = encoded['attention_mask']
    
    #保证labels的长度跟Input_ids的一致
    labels = [-100] * len(context) 
    target = tokenizer(target, truncation=True, padding='max_length', max_length=max_length-len(labels))
    # 对输出文本进行编码作为labels
    labels = labels + target['input_ids']
#     print(len(labels))
#     print(labels)
    processed_data.append({
        'input_ids': input_ids,
        'attention_mask': attention_mask,
        'labels': labels
    })

# 转换为Dataset
dataset = Dataset.from_list(processed_data)   #
dataset.set_format(type='torch', columns=['input_ids', 'attention_mask', 'labels'])
dataset_dict = dataset.train_test_split(test_size=0.05)
# 获取训练集和测试集
train_dataset = dataset_dict['train']
test_dataset = dataset_dict['test']

print(len(train_dataset))
print(train_dataset)
print('数据集加载好了')
  1. 配置LoRA参数以及训练参数
# 配置lora
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,  
    # 微调哪些模块,可以打印model看看有哪些模块
    target_modules=['q_proj', 'v_proj'], #, 'k_proj', 'o_proj','gate_proj','up_proj','down_proj'
    lora_dropout=0.1,
    bias='none',
    task_type=TaskType.CAUSAL_LM
)
model_lora = get_peft_model(model, lora_config)

training_args = TrainingArguments(
    output_dir='./results',
    #一种按多少步  一种按轮数,前期试验可以用步数,确定之后可以用轮数
    eval_strategy="epoch",#steps  epoch  
#     max_steps=50,
    num_train_epochs=5,#可以看看几轮效果最好,这个调参是个长期的过程
    learning_rate=1e-5,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=4,
    weight_decay=0.01,
    remove_unused_columns=True,
    report_to="none",
    fp16=True,
    ddp_find_unused_parameters=True,
    gradient_accumulation_steps=16,
    save_strategy="no",  # 不保存检查点
    logging_steps=100, 
)
  1. 配置trainer开始训练
# 数据填充器
data_collator = DataCollatorWithPadding(tokenizer)

trainer = Trainer(
    model=model_lora,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=test_dataset,
    tokenizer=tokenizer,
    data_collator=data_collator
)
torch.cuda.empty_cache()
print('开始训练了')
trainer.train()

def count_parameters(model):
    trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
    frozen_params = sum(p.numel() for p in model.parameters() if not p.requires_grad)
    return trainable_params, frozen_params

model_lora = trainer.model

# 计算可训练参数和冻结参数数量
trainable_params, frozen_params = count_parameters(model_lora)

print(f"Total trainable parameters: {trainable_params}")
print(f"Total frozen parameters: {frozen_params}")
print('训练好了')
  1. 训练完了别忘了保存参数

    保存的lora参数很小,用的时候再跟基准模型组合一下就可以了
    
# 保存LoRa参数
model_lora.save_pretrained("save/lora")
print('模型参数保存好了')
  1. 推理看看结果
messages = [
    {
        'role':'user',
        'content':"""一位60岁男性患者由于肝动脉瘤出现肝功能异常,具体表现为黄疸,该如何诊治?"""
    }
]
input_ids = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt").to(model.device)

outputs = model_lora.generate(
    input_ids=input_ids,
    max_new_tokens=256,
    temperature=0.8,
)
response = outputs[0][input_ids.shape[-1]:]
print(tokenizer.decode(response, skip_special_tokens=True))

多训练几轮,可能效果会更好,我训练了3轮,感觉没多大变化。接下来尝试5-10轮。平均一轮要1h+。
在这里插入图片描述

疑难问题有哪些

例如:

  • 模型的选择,选择一个适合的,比如我一开始想用原版的llama3,但这个微调效果肯定不会太好,所以在中文的版本下微调
  • 输入模板的问题,刚开始以为就是输入:问,输出:答,但这样会导致模型输出有问题,如果还有问题就看链接
  • 参数的问题,微调模块越多,那么batchsize或者max_length就要降低,因为只有2块T4毕竟免费的
  • 有个问题自己还没弄懂,就是微调保存参数很小,都几十MB左右,看别人说微调后2、3、4个G(原模型16G),这是量化了吗?还不懂

小结

LoRA的优势在于其减少了需要微调的参数量,从而降低了计算成本和内存需求,同时保持了模型性能。现在还有QLoRA,但个人感觉这个参数不是很巨大的话LoRA已经够用了。只是个人见解,如有错误请大家指正,有问题可以在评论区联系我。

  • 40
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值