大模型微调1——使用LoRA微调qwen模型优化推理效果

任务

使用LoRA微调技术微调qwen大模型,优化大模型在逻辑推理上的回答效果。任务详情可参考第二届世界科学智能大赛逻辑推理赛道:复杂推理能力评估

LoRA微调步骤

首先是数据集大模型准备,然后加载tokenizer和model,之后需要将文本数据转为模型能够读取的token,并配置微调训练的各项参数,然后就可以开启训练了,训练完将LoRA层参数与原模型参数合并(其原理详见大模型LoRA微调过程)。

补充说明: 本文所作微调非常基础,可以让初学者对模型微调框架有个大概认知。完整代码已上传至我的资源`,其中包含数据集、运行环境搭建教程、LoRA微调代码

1、数据集准备

官方给出数据集round1_train_data.jsonl,一条数据的样例如下:

{
  'problem': 
        '有一群人和一些食物类型。下列是关于这些个体和食物的已知信息:\n\n1. 鸡肉是一种食物。\n2. 苹果是一种食物。\n3. 如果X吃了Y,且X活着,则Y是一种食物。\n4. Bill存活。\n5. Bill吃了花生。\n6. John吃所有食物。\n7. Sue吃所有Bill吃的食物。\n8. John喜欢所有食物。\n\n根据以上信息,回答以下选择题:', 
   'questions': [
        {
        'question': '选择题 1:\n谁喜欢吃花生?', 
        'options': ['Bill', 'Sue', 'John', 'None of the above']
        }
    ], 'id': 'round1_test_data_000'
}

其中,每条数据包含“problem”、“question”、“options”、“id”“question”可以有多个,每个question都需要推理一次,因此不仅要对每个problem转为prompt,还需要将拆分级别进一步到question

设计如下prompt模板:

def get_prompt(problem, question, options):  
    prompt = f"""你是一个逻辑推理专家,擅长解决各个领域不同类型的逻辑推理问题。你能够针对问题已知条件一步一步进行推理,最终得到符合逻辑的结果。即使面对棘手的逻辑推理问题,你也能一步一步慢慢分析,最终得到正确结果。以下是一个逻辑推理的题目,形式为单项选择题。所有的问题都是(close-worldassumption)闭世界假设,即未观测事实都为假。请逐步分析问题并在最后一行输出答案,最后一行的格式为"答案是:A"。题目、问题分别如下:

### 题目:
{problem}

### 问题:
{question}
{options}
""" 
# ### 提示:
# 1、请基于已知条件和逻辑推理得出结论。
# 2、请逐步解释推理过程,确保每一步都有逻辑支撑。
    # print(prompt)
    return prompt

构建的新数据包含“instruction”、“input”、“output”,这里对这三个字段做简略说明:

instruction: 描述模型需要执行的任务或指令。这部分告诉模型它应该完成什么样的操作。

input: 任务的输入内容。如果指令需要具体的上下文或输入信息,这里会包含相关数据。如果任务是开放式的或不需要特定输入,可以将其设置为空字符串。

output: 模型在给定指令和输入的情况下需要生成的预期输出。通常是目标答案或期望的响应。

在构建数据集时,还可能引入其他字段,例如

context: 用来提供额外的上下文信息,这可能是任务的背景、相关的知识点等,帮助模型更好地理解指令。特别是在多轮对话或需要参考更多上下文的任务中,这个字段会非常有用。

metadata: 这个字段可以包含任务的附加信息,比如数据来源、任务类型(如分类、生成、翻译等)、难度级别等。它有助于在微调过程中对不同类型的数据进行筛选或加权。

example_id: 每个数据样本的唯一标识符,有助于数据的跟踪和管理,尤其是用于大型数据集或进行错误分析时。

tags: 可以为每个数据条目添加标签,用于标注数据的特性,例如“对话”、“情感分析”、“问答”等。这有助于在微调过程中根据不同任务类型筛选数据。

output_type: 如果输出有多种格式(如文本、代码、摘要等),这个字段可以标明输出的具体类型,帮助模型在生成时选择合适的策略。

source: 指示数据样本的来源或数据集的创建方式,可能包括数据的来源网站、生成方法(如人工创建或自动生成)等信息。

language: 标识数据的语言,尤其在多语言微调时,这个字段有助于模型识别并使用不同语言的信息。

timestamp: 记录数据创建或收集的时间,在涉及时间相关任务(如时间敏感的新闻分析)时,这个字段可以提供上下文信息。

新数据集构建名为an_train.json,构建的新数据示例如下:

 {
        "instruction": "你是一个逻辑推理专家,擅长解决逻辑推理问题。以下是一个逻辑推理的题目,形式为单项选择题。所有的问题都是(close-world assumption)闭世界假设,即未观测事实都为假。请逐步分析问题并在最后一行输出答案,最后一行的格式为\"答案是:A\"。题目如下:\n\n### 题目:\n假设您需要构建一个二叉搜索树,其中每个节点或者是一个空的节点(称为\"空节点\"),或者是一个包含一个整数值和两个子树的节点(称为\"数值节点\")。以下是构建这棵树的规则:\n\n1. 树中不存在重复的元素。\n2. 对于每个数值节点,其左子树的所有值都小于该节点的值,其右子树的所有值都大于该节点的值。\n3. 插入一个新值到一个\"空节点\"时,该\"空节点\"会被一个包含新值的新的数值节点取代。\n4. 插入一个已存在的数值将不会改变树。\n\n请基于以上规则,回答以下选择题:\n\n### 问题:\n选择题 1:\n给定一个空的二叉搜索树,插入下列数字: [5, 9, 2, 10, 11, 3],下面哪个选项正确描述了结果树的结构?\nA. tree(5, tree(2, tree(3, nil, nil), nil), tree(9, tree(10, nil, nil), tree(11, nil, nil)))\nB. tree(5, tree(2, nil, tree(3, nil, nil)), tree(9, nil, tree(10, nil, tree(11, nil, nil))))\nC. tree(5, tree(3, tree(2, nil, nil), nil), tree(9, nil, tree(10, tree(11, nil, nil), nil)))\nD. tree(5, nil, tree(2, nil, tree(3, nil, nil)), tree(9, tree(11, nil, nil), tree(10, nil, nil)))\n",
        "input": "",
        "output": "B"
    },

2、下载大模型

受显卡资源限制,使用qwen2-1.5b-instruct模型,这里推荐阿里的魔塔社区下载方式,非常快,常见的大模型下载方式可查看超快捷的大模型下载方式

from modelscope import snapshot_download
model_dir = snapshot_download('qwen/Qwen2-1.5B-Instruct', cache_dir='./', revision='master')

3、加载tokenizer和model

tokenizer = AutoTokenizer.from_pretrained('./qwen/Qwen2-1___5B-Instruct', use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained('./qwen/Qwen2-1___5B-Instruct', device_map="auto")#,torch_dtype=torch.bfloat16)

4、训练数据格式化

Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,通常需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:【对process_fun函数的详细说明可参考大模型微调中的process_fun函数是干嘛的?

def process_func(example):
    MAX_LENGTH = 384    # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
    instruction = tokenizer(f"{example['input']}<sep>")
    response = tokenizer(f"{example['output']}<eod>")
    input_ids = instruction["input_ids"] + response["input_ids"]
    attention_mask = [1] * len(input_ids) 
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] # instruction 不计算loss

    if len(input_ids) > MAX_LENGTH:  # 做一个截断
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]

    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }

5、配置LoRA的参数

定义LoraConfig:

LoraConfig这个类中可以设置很多参数,但主要的参数没多少,详细参数说明可参考peft.LoraConfig()参数说明

  • task_type:模型类型
  • target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
  • rlora的秩,具体可以看Lora原理
  • lora_alphaLora alaph,具体作用参见 Lora 原理
config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, 
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    inference_mode=False, # 训练模式
    r=8, # Lora 秩
    lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
    lora_dropout=0.1# Dropout 比例
)

创建PeftModel

使用 get_peft_model() 函数创建一个 PeftModel

model = get_peft_model(model, config)
# 可打印下模型可训练参数情况:
model.print_trainable_parameters()

模型可训练参数情况为:

trainable params: 9,232,384 || all params: 1,552,946,688 || trainable%: 0.5945074657965335

可以看出,LoRA训练的参数仅占模型所有参数的0.59%,其实是非常少的。

自定义 TrainingArguments 参数

TrainingArguments这个类用于设置模型训练参数。

  • output_dir:模型的输出路径
  • per_device_train_batch_size:顾名思义 batch_size
  • gradient_accumulation_steps: 梯度累加,如果你的显存比较小,那可以把 batch_size 设置小一点,梯度累加增大一些。
  • logging_steps:多少步,输出一次log
  • num_train_epochs:顾名思义 epoch
  • gradient_checkpointing:梯度检查,这个一旦开启,模型就必须执行model.enable_input_require_grads(),这个原理大家可以自行探索
args = TrainingArguments(
    output_dir="./output/Qwen2_1___5B_instruct_lora",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=4,
    logging_steps=10,
    num_train_epochs=1,
    save_steps=100, 
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True
)

6、开启训练

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_id,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
torch.backends.cuda.enable_mem_efficient_sdp(False)
trainer.train()

7、训练结束后,合并LoRA和原模型参数

mode_path = './qwen/Qwen2-1___5B-Instruct/'
lora_path = './output/Qwen2_1___5B_instruct_lora/checkpoint-300' # 这里改称你的 lora 输出对应 checkpoint 地址

# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True)

# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.float16, trust_remote_code=True).eval()

# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path)

# 模型合并存储
new_model_directory = "./merged_model_1___5B_1"
merged_model = model.merge_and_unload()    
# 将权重保存为safetensors格式的权重, 且每个权重文件最大不超过2GB(2048MB)
merged_model.save_pretrained(new_model_directory, max_shard_size="2048MB", safe_serialization=True)

import shutil
# 定义源文件路径和目标文件路径
source = './qwen/Qwen2-1___5B-Instruct/tokenizer.json'
destination = './merged_model_1___5B_1/'
# 使用 shutil.copy 将文件从 source 复制到 destination
shutil.copy(source, destination)

至此,得到LoRA微调后的模型,存储在merged_model_1___5B_1文件夹中。

参考文献:

1、‬⁠‌‍‍‍‌‌⁠‍‍‍‍⁠‍‬‍‍‬‍⁠‌⁠Task3 baseline02 微调方案 - 飞书云文档 (feishu.cn)

### 对Qwen2模型使用LoRA技术进行微调 #### 数据准备 为了成功地对Qwen2模型实施LoRA微调,首先需要准备好适合该任务的数据集。这通常意味着收集并清理一批高质量的文本样本,这些样本应该代表目标应用场景中的典型输入[^2]。 #### 安装依赖库 确保安装了必要的Python包来支持LoRA微调流程。可以利用`pip install transformers peft accelerate loralib`命令一次性安装所需软件包[^3]。 #### 配置环境变量与导入模块 在开始之前,还需要适当配置计算资源(如GPU),并通过如下代码片段引入必需的类和函数: ```python import torch from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments from peft import get_peft_model, LoraConfig, TaskType ``` #### 加载预训练的基础模型 接着,加载未经调整过的Qwen2作为起点。这里假设已知具体版本号为"qwen-2": ```python model_name_or_path = "path/to/qwen-2" tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) base_model = AutoModelForCausalLM.from_pretrained(model_name_or_path) ``` #### 设置LoRA配置 定义适用于当前项目的LoRA参数设定,比如选择要修改哪些层以及它们各自的秩大小等细节: ```python peft_config = LoraConfig( task_type=TaskType.CAUSAL_LM, inference_mode=False, r=8, # 秩数 lora_alpha=32, target_modules=["query_key_value"], # 或者其他感兴趣的模块名称列表 ) ``` #### 构建适配器模型结构 基于上述配置创建一个新的带有LoRA组件的模型实例,并将其转换成可用于后续操作的形式: ```python adapter_model = get_peft_model(base_model, peft_config) ``` #### 训练过程定制化 制定详细的训练策略,包括但不限于批次尺寸、学习率衰减计划等方面的内容;同时也要考虑采用何种优化算法最为合适。这部分可以通过设置`TrainingArguments`对象实现: ```python training_args = TrainingArguments( output_dir="./results", num_train_epochs=3, per_device_train_batch_size=4, gradient_accumulation_steps=2, learning_rate=1e-4, logging_dir='./logs', ) trainer = Trainer( model=adapter_model, args=training_args, train_dataset=train_dataset, eval_dataset=val_dataset, tokenizer=tokenizer, ) ``` #### 启动训练循环 最后一步就是启动整个训练周期,在此期间系统会自动处理前向传播、反向传播等一系列内部机制直至达到预定条件为止: ```python trainer.train() ``` #### 模型评估与部署 完成训练之后,应当仔细验证所得成果的质量,并根据实际情况决定是否进一步改进或是直接投入生产环境中去。如果一切顺利的话,则可通过下面的方式保存最终版模型供日后重复利用: ```python adapter_model.save_pretrained("./fine-tuned-qwen-lora") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值