通过Unsloth微调Qwen2.5-VL实现复杂数学公式的OCR

【学习目标】

  • 理解Unsloth的核心优化原理与基础实践;
  • 掌握基于Unsloth的高效微调工作流。

【知识储备】

1. Unsloth简介

Unsloth是一个专为大型语言模型(LLM)设计的微调框架,旨在提高微调效率并减少显存占用。 它通过手动推导计算密集型数学步骤并手写 GPU 内核,实现了无需硬件更改即可显著加快训练速度。

主要功能点:

  • 高效微调:Unsloth通过深度优化,使 LLM 的微调速度提高 2-5 倍,显存使用量减少约 80%,且准确度无明显下降。

  • 广泛的模型支持:目前支持的模型包括目前各类主流模型,用户可以根据需求适合的模型进行微

调。

  • 兼容性:Unsloth与HuggingFace生态兼容,用户可以轻松将其与 traformers、peft、trl 等库结合,实现模型的全参微调(full)、监督微调(SFT)和广义强化学习优化(GRPO)、基于人类反馈的奖励建模(包括DPO、ORPO、KTO等方法)、持续预训练(continued pretraining)、文本补全(text completion)以及其他前沿训练方法。

  • 内存优化: 通过 4 位和 16 位的 QLoRA/LoRA 微调,unsloth 显著了显存占用,使得在资源受限的环境中也能大的微调。

Unsloth核心优势:

  • Unsloth简化了整个微调工作流程,包括模型加载、量化、训练、评估、运行、保存、导出,以及与推理引擎(如Ollama、llama.cpp和vLLM)的集成;

  • Unsloth相比传统方法,Unsloth 能够在更短的时间内、更少的显存消耗完成微调任务,节省时间及硬件成本;

  • Unsloth定期与Huggingface、Google和Meta团队合作,以修复LLM训练和模型中的错误(例如,之前有报告有为Gemma 3和Phi-4所做的错误排查工作)。因此,在使用Unsloth进行模型微调时能看到最准确的结果。

  • 开源免费: Unsloth提供开源版本,用户可以在 Google Colab 或 Kaggle Notebooks 上免费试用,方便上手体验。

总的来说,unsloth 为大型语言模型的微调提供了高效、低成本的解决方案,适合希望在有限资源下进行模型微调的开发者和研究人员。

【任务实施】

1. 运行环境要求

1.1、硬件环境

名称建议配置
1CPUIntel I7
2显卡NVIDIA GeForce RTX 4090
3内存16G
4系统Ubuntu20.04 +

注:根据微调的模型参数及量化方法不同,显存要求也会不一样,参考值如下:

参数量QLoRA (4-bit)LoRA (16-bit)
3B3.5 GB8 GB
7B5 GB19 GB
8B6 GB22 GB
9B6.5 GB24 GB
11B7.5 GB29 GB
14B8.5 GB33 GB
27B22 GB64 GB
32B26 GB76 GB
40B30 GB96 GB
70B41 GB164 GB
81B48 GB192 GB
90B53 GB212 GB
405B237 GB950 GB

1.2、软件环境

名称版本
1Python3.10+
2CUDA12.1+
3JupyterLab3.5+
2. Unsloth安装

2.1、创建并配置虚拟环境

打开一个新的命令行终端,创建Conda新环境,名称可自定义,这里以"unsloth"为例:

$ conda create -n unsloth python=3.11 ipykernel -y

激活新建的环境:

$ conda activate unsloth

激活后,终端提示符通常会显示环境名称(unsloth),表示您已在该环境当中。

unsloth虚拟环境加入到Jupyterlab的内核中,以便后续.ipynb文档可以选择该环境运行:

$ python -m ipykernel install --user --name=unsloth --display-name "unsloth"

运行后,点击右上角内核切换按钮,进行内核切换,查看是否有出现unsloth内核,如果没有请在菜单栏重启内核再操作:

2.2、Unsloth安装

In [ ]:

import sys
PYTHON_PATH=sys.executable
print(PYTHON_PATH)

In [ ]:

%%capture
!{PYTHON_PATH} -m pip install unsloth modelscope ipywidgets tensorboard
  • %%capture:隐藏命令的输出,避免安装过程中的冗长日志刷屏。但注意观察右上角的运行状态,显示"忙碌",请耐心等待。

如果是开发环境,可以继续运行以下命令,从 GitHub 仓库安装Unsloth的最新开发版(可能包含未发布的修复或功能)。

In [ ]:

!{PYTHON_PATH} -m pip install \
--force-reinstall \
--no-cache-dir \
--no-deps \
git+https://github.com/unslothai/unsloth.git

2.3、验证Unsloth

运行以下命令查看Unsloth的安装情况 ,如果安装成功,会显示版本号等信息。

In [ ]:

!{PYTHON_PATH} -m pip show unsloth

3. 通过Unsloth进行Qwen2.5-VL模型推理

3.1、Qwen多模态模型下载

通过ModelScope SDK将Qwen2.5-VL多模态模型下载到指定目录,使用的是7B经过指令微调后的模型。

In [ ]:

import os
from modelscope import snapshot_download

# 定义基座模型以及模型存放目录
MODEL_NAME_OR_PATH = "models/Qwen2.5-VL-7B-Instruct"
BASE_MODEL = "unsloth/Qwen2.5-VL-7B-Instruct"


# 如目录不存在,则下载模型
if not os.path.exists(MODEL_NAME_OR_PATH):
    snapshot_download(BASE_MODEL, local_dir=MODEL_NAME_OR_PATH)
# 目录已存在,打印文件列表
else:
    print("模型已存在,跳过下载")
    files = [item for item in os.listdir(MODEL_NAME_OR_PATH) if not item.startswith('.')]
    for file in files:
        print(file)

3.2. 导入相关依赖库

In [ ]:

from unsloth import FastVisionModel  
import torch
from PIL import Image, ImageOps
from IPython.display import display
from transformers import TextStreamer

3.3、加载模型和分词器

In [ ]:

model, tokenizer = FastVisionModel.from_pretrained(
    model_name=MODEL_NAME_OR_PATH,
    max_seq_length=2048,
    dtype=None,
    load_in_4bit=True,
    load_in_8bit=False,
    full_finetuning=False,
)

3.4、微调前的模型推理

将推理过程封装成一个函数,方便后续多次调用,代码如下:

In [ ]:

def inference(text, image_file, system_prompt = None):
    """
    推理函数

    Args
        text: 输入的文本
        image_file: 图片文件路径
        system_prompt: 系统提示语,默认为None
    """
    # 显示图片
    image = Image.open(image_file)
    image = ImageOps.exif_transpose(image) 
    display(image)

    # 将模型切换到推理模式(会关闭 dropout 等训练专用层,优化推理速度)
    FastVisionModel.for_inference(model)

    # 构造符合ChatML风格的输入消息
    messages = []
    if system_prompt: 
        messages.append({"role": "system", "content": [{"type":"text", "text": system_prompt}]})
    messages = [
        {"role": "user", "content":
            [
                {"type": "image"},
                {"type": "text", "text": text}
            ]}
    ]

    # 将messages转换为模型所需的对话格式字符串
    input_text = tokenizer.apply_chat_template(
        messages, 
        tokenize=False,
        add_generation_prompt=True)

    # 图像会被编码为视觉特征向量,文本按正常分词流程处理
    # 输出包含input_ids(文本)、pixel_values(图像)等键的字典
    model_inputs = tokenizer(
        text=input_text,
        images=image,
        padding=True,
        add_special_tokens=False,
        return_tensors="pt"
    )

    model_inputs = model_inputs.to(model.device)

    # 通过TextStreamer实现流式输出
    model.generate(
        **model_inputs,
        max_new_tokens=512,
        use_cache = True, 
        temperature = 1.5, 
        min_p = 0.1,
        streamer=TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True),
    )

函数编写完成后,现在我们来先传递一个问题、一张图片给函数,看看Unsloth框架的模型推理效果。

In [ ]:

inference(text="图片表达了什么?" , image_file="assets/candy.jpg")

那么在进行微调前,我们首先验证下原模型Qwen2.5-VL,对含有数学公式的图片识别效果怎么样,同样调用上面的推理函数inference:

In [ ]:

inference(
    text="为图片生成LaTeX表达式", 
    image_file="assets/demo_pic_1.jpg", 
    system_prompt="你是一个LaText OCR助手,目标是读取用户输入的照片,转换成LaTex公式"
)

观察模型生成的结果,将该结果通过LaTeX公式生成器验证下,看是否正确,同时记录起来供后续做对比。

对比两者,可以发现模型虽然在提示词的作用下,发挥作用,但是回答的并不正确。但下来我们需要对它进行微调,使其更适应处理复杂数学公式。

4. 通过Unsloth微调Qwen2.5-VL实现复杂数学公式的OCR

4.1、微调数据集的准备

通过huggingface datasets库下载数据集:

In [ ]:

from datasets import load_dataset

# 定义数据集名称及保存路径
dataset_name = "unsloth/LaTeX_OCR"
dataset_dir = "datasets/LaTeX_OCR"
exist = os.path.exists(dataset_dir)

dataset = load_dataset(dataset_name, split="train", cache_dir=dataset_dir )

if not exist:
    print(f"数据集已下载保存到{dataset_dir},共 {len(dataset)} 条样本")
else:
    print(f"数据集已存在,已从{dataset_dir}加载数据集")
数据集已存在,已从datasets/LaTeX_OCR加载数据集

让我们来简单了解一下这个数据集。我们看一看第三张图片是什么,以及对应的标题是什么。

In [3]:

dataset[2]["image"]

Out[3]:

In [4]:

dataset[2]["text"]

Out[4]:

'H ^ { \\prime } = \\beta N \\int d \\lambda \\biggl \\{ \\frac { 1 } { 2 \\beta ^ { 2 } N ^ { 2 } } \\partial _ { \\lambda } \\zeta ^ { \\dagger } \\partial _ { \\lambda } \\zeta + V ( \\lambda ) \\zeta ^ { \\dagger } \\zeta \\biggr \\} \\ .'

我们运行下一行代码,直接在JupyterLab中渲染上述dataset[2]["text"]的LaTeX表达式,看是否与图片一致:

In [5]:

from IPython.display import display, Math

latex = dataset[2]["text"]
display(Math(latex))

H′=βN∫dλ{12β2N2∂λζ†∂λζ+V(λ)ζ†ζ} .H′=βN∫dλ{12β2N2∂λζ†∂λζ+V(λ)ζ†ζ} .

可以发现与原图的公式一模型一样。

那么了解完数据集结构之后,我们需要将这些数据格式化成Qwen2.5-VL需要的Json格式(本质上所有视觉微调任务都是类似ChatML格式,ChatML格式仅仅是sharegpt格式的一种特殊情况),如下所示:

[
    { "role": "user",
    "content": [{"type": "text",  "text": Q}, {"type": "image", "image": image} ]
    },
    { "role": "assistant",
    "content": [{"type": "text",  "text": A} ]
    },
]

定义数据预处理函数data_process,目的是处理数据集的每条数据,将其格式化成Qwen2.5-VL需要的Json格式:

In [ ]:

instruction = "为图片生成LaTeX表达式"
def data_process(sample):
    conversation = [
        { "role": "user", "content" : [
            {"type" : "text",  "text"  : instruction},
            {"type" : "image", "image" : sample["image"]} ]
        },
        { "role" : "assistant",
          "content" : [
            {"type" : "text",  "text"  : sample["text"]} ]
        },
    ]
    return { "messages" : conversation }

调用数据处理预函数data_process,批量将所有数据格式化为微调输入格式,返回给新的变量converted_dataset

In [ ]:

converted_dataset = [data_process(sample) for sample in dataset]

我们展示下经过格式化后的首条数据内容:

In [ ]:

converted_dataset[0]

4.2、LoRA微调配置

In [ ]:

model = FastVisionModel.get_peft_model(
    model,
    finetune_vision_layers     = True,
    finetune_language_layers   = True,
    finetune_attention_modules = True,
    finetune_mlp_modules       = True,
    # target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],

    r = 16,
    lora_alpha = 16,
    lora_dropout = 0,
    bias = "none",
    use_gradient_checkpointing="unsloth",
    random_state = 3407,
    use_rslora = False,
    loftq_config = None,
)

4.3、训练参数配置

In [ ]:

from trl import SFTTrainer, SFTConfig
from unsloth.trainer import UnslothVisionDataCollator
from unsloth import is_bf16_supported
from datetime import datetime

output_dir = f"outputs/exp_{datetime.now().strftime('%Y%m%d_%H%M')}"


# 将模型切换到训练模式
FastVisionModel.for_training(model)

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    data_collator = UnslothVisionDataCollator(model, tokenizer),
    train_dataset = converted_dataset,
    args = SFTConfig(
        output_dir = output_dir,
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        max_steps = 10,
        # num_train_epochs = 2,
        learning_rate = 2e-4,
        fp16 = not is_bf16_supported(),
        bf16 = is_bf16_supported(),

        report_to = "tensorboard",
        logging_steps = 5,
        logging_dir=output_dir,

        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 3407,
        
        remove_unused_columns = False,
        dataset_text_field = "",
        dataset_kwargs = {"skip_prepare_dataset": True},
        dataset_num_proc = 4,
        max_seq_length = 2048,
    ),
)

4.4、启动训练

打印当前GPU显存信息:

In [ ]:

# 获取索引为0的GPU设备的详细属性
gpu_stats = torch.cuda.get_device_properties(0)
# 返回PyTorch当前预留的显存峰值
start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
# GPU的物理显存总量
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
print(f"GPU = {gpu_stats.name}.")
print(f"1)最大显存 = {max_memory} GB.")
print(f"2)预留 {start_gpu_memory} GB 的显存.")

调用train()开始训练:

In [ ]:

trainer_stats = trainer.train()

显示最终内存和时间统计:

In [ ]:

used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
used_memory_for_lora = round(used_memory - start_gpu_memory, 3)
used_percentage = round(used_memory / max_memory * 100, 3)
lora_percentage = round(used_memory_for_lora / max_memory * 100, 3)
print(f"训练耗时:{trainer_stats.metrics['train_runtime']}秒.")
print(
    f"训练耗时:{round(trainer_stats.metrics['train_runtime']/60, 2)}分钟."
)
print(f"峰值预留显存 = {used_memory} GB.")
print(f"LoRA训练专用显存峰值 = {used_memory_for_lora} GB.")
print(f"峰值预留显存占总显存比例 = {used_percentage} %.")
print(f"LoRA训练显存占总显存比例 = {lora_percentage} %.")

4.5、微调后结果分析

指定训练日志所在目录,调用tensorboard命令启动,会在--port指定的端口启动一个可视化WEB服务,在浏览器中打开 http://localhost:6006(如果是云服务器的话,根据IP或映射访问) 即可查看可视化结果。

In [ ]:

!tensorboard --logdir {output_dir} --port 6006

运行以上命令后,打开浏览器访问,如果损失率没有稳定下降,需要调整训练参数重新开始训练。

4.6、模型微调后的推理

现在开始运行微调后的模型,使用相同的推理函数、相同的图片以及提示词:

In [ ]:

inference(
    text="为图片生成LaTeX表达式",
    image_file="assets/demo_pic_1.jpg",
    system_prompt="你是一个LaText OCR助手,目标是读取用户输入的照片,转换成LaTex公式"
)

将输出的结果拷贝到LaTeX公式生成器验证下:

继续与推理前的结果对比,可以发现经过微调后的模型,生成的结果更加接近、符合预期。但由于训练步数/轮次太少,因此生成的结果还并不能完全正确,感兴趣的大家可以继续增大训练轮次,但时间会久些。

4.7、保存微调模型

将最终模型保存为LoRA适配器,可以使用Huggingface的save_pretrained方法进行本地保存,同时也要把分词器保存。

In [ ]:

model.save_pretrained(output_dir) 
tokenizer.save_pretrained(output_dir)
print(f"LoRA权重文件已保存在:{output_dir}")

但上述代码只是保存了LoRA适配器,而不是完整的模型,通过以下代码保存为完整的float16精度模型。该精度的模型可以使用vLLM、transformers等工具进行加载推理。

In [ ]:

new_model_dir = "models/Qwen2.5-VL-7B-LaTeXOCR"
model.save_pretrained_merged(
    new_model_dir,
    tokenizer, 
    save_method="merged_16bit",
    )
print(f"模型已合并并保存到:{new_model_dir}")

model.save_pretrained_merged方法会逐层检查基础模型,并去huggingface下载相应的基础模型,所以尽量开启HF国内镜像源或代理,不然会抵账,下载也需要点时间。

合并保存完成后,观察models/Qwen2.5-VL-7B-LaTeXOCR目录,生成了以下文件:

到此,我们使用Qwen2.5-VL多模态基座模型,通过Unsloth的QLoRA微调方法,成功训练了第一个模型,让其可以识别LaTeX公式。让你对Unsloth有个初始的认识,更多其它模型的训练方法,请继续往下实战。

<think>好的,我需要比较Qwen2.5-VL和PaddleOCR的性能、功能和适用场景。首先,我要明确两者的定位和核心功能。 Qwen2.5-VL是通义千问的多模态模型,专注于视觉语言任务,比如图像理解、图文问答等。根据引用[2],它通过预训练和多个阶段的微调,支持复杂的多模态交互,比如多图推理和数学解题。这说明它在处理需要结合图像和文本信息的场景中表现较好。 而PaddleOCR是百度的OCR工具,主要用于文本检测和识别,包括多语言、表格、手写体等。它的功能集中在从图像中提取文字信息,可能更偏向于工业应用,比如文档数字化、车牌识别等。 接下来比较性能。Qwen2.5-VL作为大模型,可能在复杂任务上更灵活,但需要较高的计算资源。PaddleOCR作为专用工具,可能在文本识别速度和准确率上更优化,尤其是在处理大量文档时效率更高。 适用场景方面,Qwen2.5-VL适合需要深度理解图像内容的场景,如智能客服中的图文问答,教育领域的解题辅助。PaddleOCR则更适合需要快速准确提取文本的场景,如扫描文件转换、票据处理等。 需要确认两者是否在功能上有重叠。比如,Qwen2.5-VL是否具备OCR能力?根据资料,虽然多模态模型可能包含基础OCR,但专用工具如PaddleOCR在精度和速度上更优。因此,在纯文本提取任务中,PaddleOCR更合适;而需要进一步分析或推理时,Qwen2.5-VL更有优势。 最后,总结两者的对比,强调用户应根据具体需求选择工具。如果主要是文字提取,选PaddleOCR;如需复杂的图文交互,选Qwen2.5-VL。</think>### Qwen2.5-VL 与 PaddleOCR 功能对比及适用场景分析 #### 一、核心功能定位 1. **Qwen2.5-VL** - **多模态交互**:基于视觉语言模型(VLM),支持图像理解、图文问答、多图推理、数学解题等复杂任务[^2]。 - **语义关联**:可结合上下文进行跨模态推理,例如分析图像中的场景并生成描述,或根据图表回答数学问题。 - **训练目标**:通过多阶段训练(预训练+指令微调+安全对齐)实现通用多模态能力。 2. **PaddleOCR** - **文本识别工具**:专注于OCR(光学字符识别)技术,支持多语言文本检测、识别、版面分析、表格提取、手写体识别等。 - **工业级部署**:提供轻量化模型和端到端解决方案,适用于文档扫描、票据处理、车牌识别等场景。 - **垂直优化**:针对长文本、模糊图像、复杂背景等场景有专项优化。 --- #### 二、性能与功能对比 | **维度** | **Qwen2.5-VL** | **PaddleOCR** | |----------------|-----------------------------------------|-----------------------------------------| | **核心能力** | 多模态语义理解与推理 | 高精度文本检测与识别 | | **输入类型** | 图像+文本混合输入 | 纯图像输入 | | **输出形式** | 自然语言回答、结构化推理结果 | 文本坐标框、识别文字、表格/版面结构 | | **计算需求** | 需GPU推理,资源消耗较高[^1] | 支持CPU/移动端部署,资源消耗低 | | **典型场景** | 教育解题、图文问答、多图分析 | 文档数字化、车牌识别、票据信息提取 | --- #### 三、适用场景选择建议 1. **选择 Qwen2.5-VL 的场景** - 需要结合图像内容进行**语义推理**(如根据医学影像生成报告) - 多模态交互需求(如用户上传图片并提问“图中化学实验的潜在风险是什么”) - 复杂数学公式识别与解题(支持 LaTeX 输出)[^2] 2. **选择 PaddleOCR 的场景** - **纯文本提取**(如古籍数字化、营业执照信息录入) - 实时性要求高的场景(如移动端拍照翻译) - 需要处理**特殊格式文本**(如弯曲文本、手写体、多语言混合排版) --- #### 四、协同应用案例 两者可结合使用: 1. 用 PaddleOCR 提取图像中的文字和表格数据 2. 将提取结果输入 Qwen2.5-VL 进行语义分析 例如: - **财务报表分析**:OCR提取数字 → Qwen2.5-VL生成趋势解读 - **学术论文处理**:OCR识别公式 → Qwen2.5-VL解释物理含义
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智驭导师授AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值