跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

Qwen2 大模型指令微调入门实战

基于 Qwen2-1.5B-Instruct 模型进行指令微调的完整流程。内容包括环境配置、复旦中文新闻数据集准备、LoRA 参数高效微调方法、使用 SwanLab 监控训练过程以及模型推理测试。通过具体代码示例,展示了如何加载模型、预处理数据、配置训练参数并完成文本分类任务。

静心发布于 2025/2/7更新于 2026/10/2595 浏览
Qwen2 大模型指令微调入门实战

Qwen2 大模型指令微调入门实战

Qwen2 是通义千问团队最近开源的大语言模型,由阿里云通义实验室研发。

以 Qwen2 作为基座大模型,通过指令微调的方式做高精度文本分类,是学习 LLM 微调的入门任务。

在本文中,我们会使用 Qwen2-1.5b-Instruct 模型在复旦中文新闻数据集上做指令微调训练,同时使用 SwanLab 监控训练过程、评估模型效果。

显存要求不高,10GB 左右就可以跑。

知识点:什么是指令微调?

大模型指令微调(Instruction Tuning)是一种针对大型预训练语言模型的微调技术,其核心目的是增强模型理解和执行特定指令的能力,使模型能够根据用户提供的自然语言指令准确、恰当地生成相应的输出或执行相关任务。

指令微调特别关注于提升模型在遵循指令方面的一致性和准确性,从而拓宽模型在各种应用场景中的泛化能力和实用性。

在实际应用中,指令微调更多把 LLM 看作一个更智能、更强大的传统 NLP 模型(比如 Bert),来实现更高精度的文本预测任务。所以这类任务的应用场景覆盖了以往 NLP 模型的场景,甚至很多团队拿它来标注互联网数据。

下面是实战正片:

1. 环境安装

本案例基于 Python>=3.8,请在您的计算机上安装好 Python;

另外,您的计算机上至少要有一张英伟达显卡(显存要求并不高,大概 10GB 左右就可以跑)。

我们需要安装以下这几个 Python 库,在这之前,请确保你的环境内已安装了 pytorch 以及 CUDA:

swanlab modelscope transformers datasets peft accelerate pandas

一键安装命令:

pip install swanlab modelscope transformers datasets peft pandas accelerate

本案例测试于 modelscope1.14.0、transformers4.41.2、datasets2.18.0、peft0.11.1、accelerate0.30.1、swanlab0.3.9

2. 准备数据集

本案例使用的是 zh_cls_fudan-news 数据集,该数据集主要被用于训练文本分类模型。

该数据集由几千条数据组成,每条数据包含 text、category、output 三列:

text 是训练语料,内容是书籍或新闻的文本内容;

category 是 text 的多个备选类型组成的列表;

output 则是 text 唯一真实的类型。

将三者组合成数据集的例子如下:

"""
[PROMPT]
Text: 第四届全国大企业足球赛复赛结束新华社郑州5月3日电(实习生田兆运)上海大隆机器厂队昨天在洛阳进行的第四届牡丹杯全国大企业足球赛复赛中,以5:4力克成都冶金实验厂队,进入前四名。沪蓉之战,双方势均力敌,90分钟不分胜负。最后,双方互射点球,沪队才以一球优势取胜。复赛的其它3场比赛,青海山川机床铸造厂队3:0击败东道主洛阳矿山机器厂队,青岛铸造机械厂队3:1战胜石家庄第一印染厂队,武汉肉联厂队1:0险胜天津市第二冶金机械厂队。在今天进行的决定九至十二名的两场比赛中,包钢无缝钢管厂队和河南平顶山矿务局一矿队分别击败河南平顶山锦纶帘子布厂队和江苏盐城无线电总厂队。4日将进行两场半决赛,由青海山川机床铸造厂队和青岛铸造机械厂队分别与武汉肉联厂队和上海大隆机器厂队交锋。本届比赛将于6日结束。(完)
Category: Sports, Politics
Output:
   
[OUTPUT]
Sports
"""

我们的训练任务,便是希望微调后的大模型能够根据 Text 和 Category 组成的提示词(Prompt),预测出正确的 Output。

我们将数据集下载到本地目录下。下载方式是前往魔搭社区,将 train.jsonl 和 test.jsonl 下载到本地根目录下即可。

3. 加载模型

这里我们使用 modelscope 下载 Qwen2-1.5B-Instruct 模型,然后把它加载到 Transformers 中进行训练:

from modelscope import snapshot_download, AutoTokenizer
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForSeq2Seq
import torch

# 在 modelscope 上下载 Qwen 模型到本地目录下
model_dir = snapshot_download("qwen/Qwen2-1.5B-Instruct", cache_dir="./", revision="master")

# Transformers 加载模型权重
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. 配置训练可视化工具

我们使用 SwanLab 来监控整个训练过程,并评估最终的模型效果。

这里直接使用 SwanLab 和 Transformers 的集成来实现,更多用法可以参考官方文档:

from swanlab.integration.huggingface import SwanLabCallback
from transformers import Trainer

swanlab_callback = SwanLabCallback(...)

trainer = Trainer(
    ...
    callbacks=[swanlab_callback],
)

如果你是第一次使用 SwanLab,那么还需要去官网注册一个账号,在用户设置页面复制你的 API Key,然后在训练开始时粘贴进去即可。

5. 完整代码

开始训练时的目录结构:

|--- train.py
|--- train.jsonl
|--- test.jsonl

train.py:

import json
import pandas as pd
import torch
from datasets import Dataset
from modelscope import snapshot_download, AutoTokenizer
from swanlab.integration.huggingface import SwanLabCallback
from peft import LoraConfig, TaskType, get_peft_model
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForSeq2Seq
import os
import swanlab


def dataset_jsonl_transfer(origin_path, new_path):
    """
    将原始数据集转换为大模型微调所需数据格式的新数据集
    """
    messages = []

    # 读取旧的 JSONL 文件
    with open(origin_path, "r") as file:
        for line in file:
            # 解析每一行的 json 数据
            data = json.loads(line)
            context = data["text"]
            category = data["category"]
            label = data["output"]
            message = {
                "instruction": "你是一个文本分类领域的专家,你会接收到一段文本和几个潜在的分类选项,请输出文本内容的正确类型",
                "input": f"文本:{context},类型选型:{category}",
                "output": label,
            }
            messages.append(message)

    # 保存重构后的 JSONL 文件
    with open(new_path, "w", encoding="utf-8") as file:
        for message in messages:
            file.write(json.dumps(message, ensure_ascii=False) + "\n")


def process_func(example):
    """
    将数据集进行预处理
    """
    MAX_LENGTH = 384
    input_ids, attention_mask, labels = [], [], []
    instruction = tokenizer(
        f"<|im_start|>system\n你是一个文本分类领域的专家,你会接收到一段文本和几个潜在的分类选项,请输出文本内容的正确类型<|im_end|>\n<|im_start|>user\n{example['input']}<|im_end|>\n<|im_start|>assistant\n",
        add_special_tokens=False,
    )
    response = tokenizer(f"{example['output']}", add_special_tokens=False)
    input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
    attention_mask = (
        instruction["attention_mask"] + response["attention_mask"] + [1]
    )
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
    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}
      

def predict(messages, model, tokenizer):
    device = "cuda"
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    model_inputs = tokenizer([text], return_tensors="pt").to(device)

    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=512
    )
    generated_ids = [
        output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
    ]

    response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

    print(response)

    return response

# 在 modelscope 上下载 Qwen 模型到本地目录下
model_dir = snapshot_download("qwen/Qwen2-1.5B-Instruct", cache_dir="./", revision="master")

# Transformers 加载模型权重
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)
model.enable_input_require_grads()  # 开启梯度检查点时,要执行该方法

# 加载、处理数据集和测试集
train_dataset_path = "train.jsonl"
test_dataset_path = "test.jsonl"

train_jsonl_new_path = "new_train.jsonl"
test_jsonl_new_path = "new_test.jsonl"

if not os.path.exists(train_jsonl_new_path):
    dataset_jsonl_transfer(train_dataset_path, train_jsonl_new_path)
if not os.path.exists(test_jsonl_new_path):
    dataset_jsonl_transfer(test_dataset_path, test_jsonl_new_path)

# 得到训练集
train_df = pd.read_json(train_jsonl_new_path, lines=True)
train_ds = Dataset.from_pandas(train_df)
train_dataset = train_ds.map(process_func, remove_columns=train_ds.column_names)

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 比例
)

model = get_peft_model(model, config)

args = TrainingArguments(
    output_dir="./output/Qwen2",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    logging_steps=10,
    num_train_epochs=2,
    save_steps=100,
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True,
    report_to="none",
)

swanlab_callback = SwanLabCallback(
    project="Qwen2-fintune",
    experiment_name="Qwen2-1.5B-Instruct",
    description="使用通义千问 Qwen2-1.5B-Instruct 模型在 zh_cls_fudan-news 数据集上微调。",
    config={
        "model": "qwen/Qwen2-1.5B-Instruct",
        "dataset": "huangjintao/zh_cls_fudan-news",
    }
)

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
    callbacks=[swanlab_callback],
)

trainer.train()

# 用测试集的前 10 条,测试模型
test_df = pd.read_json(test_jsonl_new_path, lines=True)[:10]

test_text_list = []
for index, row in test_df.iterrows():
    instruction = row['instruction']
    input_value = row['input']

    messages = [
        {"role": "system", "content": f"{instruction}"},
        {"role": "user", "content": f"{input_value}"
    }

    response = predict(messages, model, tokenizer)
    messages.append({"role": "assistant", "content": f"{response}"})
    result_text = f"{messages[0]}\n\n{messages[1]}\n\n{messages[2]}"
    test_text_list.append(swanlab.Text(result_text, caption=response))

swanlab.log({"Prediction": test_text_list})
swanlab.finish()

看到下面的进度条即代表训练开始。

6. 训练结果演示

在 SwanLab 上查看最终的训练结果:

可以看到在 2 个 epoch 之后,微调后的 qwen2 的 loss 降低到了不错的水平——当然对于大模型来说,真正的效果评估还得看主观效果。

可以看到在一些测试样例上,微调后的 qwen2 能够给出准确的文本类型。

至此,你已经完成了 qwen2 指令微调的训练!

7. 推理训练好的模型

训好的模型默认被保存在 ./output/Qwen2 文件夹下。

推理模型的代码如下:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

def predict(messages, model, tokenizer):
    device = "cuda"

    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    model_inputs = tokenizer([text], return_tensors="pt").to(device)

    generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
    generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
    response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

    return response


# 加载原下载路径的 tokenizer
tokenizer = AutoTokenizer.from_pretrained("./qwen/Qwen2-1___5B-Instruct/", use_fast=False, trust_remote_code=True)

# 加载训练好的模型路径的 model,将下面的 checkpointXXX 替换为实际的 checkpoint 文件名名称
model = AutoModelForCausalLM.from_pretrained("./output/Qwen2/checkpointXXX", device_map="auto", torch_dtype=torch.bfloat16)

test_texts = {
    'instruction': "你是一个文本分类领域的专家,你会接收到一段文本和几个潜在的分类选项,请输出文本内容的正确类型",
    'input': "文本:航空动力学报 JOURNAL OF AEROSPACE POWER1998 年 第 4 期 No.4 1998 科技期刊管路系统敷设的并行工程模型研究*陈志英* * 马 枚北京航空航天大学【摘要】提出了一种应用于并行工程模型转换研究的标号法,该法是将现行串行设计过程 (As-is) 转换为并行设计过程 (To-be)。本文应用该法将发动机外部管路系统敷设过程模型进行了串并行转换,应用并行工程过程重构的手段,得到了管路敷设并行过程模型。"
}

instruction = test_texts['instruction']
input_value = test_texts['input']

messages = [
    {"role": "system", "content": f"{instruction}"},
    {"role": "user", "content": f"{input_value}"
    ]

response = predict(messages, model, tokenizer)
print(response)

目录

  1. Qwen2 大模型指令微调入门实战
  2. 知识点:什么是指令微调?
  3. 1. 环境安装
  4. 2. 准备数据集
  5. 3. 加载模型
  6. 在 modelscope 上下载 Qwen 模型到本地目录下
  7. Transformers 加载模型权重
  8. 4. 配置训练可视化工具
  9. 5. 完整代码
  10. 在 modelscope 上下载 Qwen 模型到本地目录下
  11. Transformers 加载模型权重
  12. 加载、处理数据集和测试集
  13. 得到训练集
  14. 用测试集的前 10 条,测试模型
  15. 6. 训练结果演示
  16. 7. 推理训练好的模型
  17. 加载原下载路径的 tokenizer
  18. 加载训练好的模型路径的 model,将下面的 checkpointXXX 替换为实际的 checkpoint 文件名名称

更多推荐文章

查看全部
  • CATE 条件平均处理效应估计:五大方法原理详解与模拟数据实战
  • 动态规划专题:回文串问题与区间 DP
  • C++ 核心特性详解:命名空间、重载与引用实战
  • IO 多路复用、网络协议与爬虫抓包
  • uView Pro:基于 Vue3 的 uni-app 跨端 UI 组件库实践
  • Python warnings 库底层机制与企业级 API 演进实战
  • 如何在机器人平台上微调与部署 OpenVLA 模型
  • GitHub Copilot 提示词工程指南:从基础到精通
  • Spring Boot 数据可视化与图表集成实战
  • 企业微信群机器人 Webhook 配置与消息发送流程
  • AI Agent 辅助工具 Superpowers 使用介绍及原理分析
  • GitHub Copilot 学生认证指南:合规申请与常见问题解析
  • 人工智能大模型应用开发:从微调适配到场景落地
  • 微信支付接入密码输入后转圈失败问题排查
  • Java 基础:char、String、StringBuilder 与 StringBuffer 核心解析
  • C++ 内存管理进阶:从裸指针到智能指针
  • 常见排序算法详解与 Java 实现
  • Unidbg 环境下解决 Native 调用 Java 方法报错问题
  • AI 大模型全面概述
  • VSCode Copilot 配置文件提示未知工具警告

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online