这是一个医疗领域多模态大模型的微调案例,分享给各位开发者参考。
一、背景
通用的多模态视觉语言模型,用于医疗影像分析时,存在三个明显的技术瓶颈。
(1)细节捕捉能力弱:难以识别高分辨率医学影像(CT、MR)中的微小病灶。
(2)显存占用高:动辄数十GB的显存需求,边缘设备无法运行,难以在临床部署。
(3)专业表述差:生成内容缺乏临床术语,可信度低,无法支撑实时分析需求。
下面是一个例子。患者提问:”请使用中文详细描述这张图像并给出你的诊断结果。”
上图是微调前模型的回答。虽然能够识别出基本病变,但分析存在明显不足:描述过于简略,仅关注单一病灶,忽略了图像中实际存在的双肺多发性结节;诊断结论过于武断,直接定性为”良性肿瘤”,缺乏严谨的鉴别诊断思路,临床参考价值有限。
上图是微调后模型的回答。它成功化身为”严谨的放射科医生”:准确定位双肺病灶,系统分析肺部结构、心脏大血管和骨骼关系,从病灶特征、位置分布和临床意义多个维度进行专业解读,提供完整的鉴别诊断思路,描述精准、逻辑严密、术语规范。
可以看到,经过高质量数据微调后,模型从一位”门外汉”进化为了可靠的”AI放射科医生”。
二、方案设计
医疗场景面临一个两难困境:
(1)要精度:必须看懂高分辨率CT/MR,参数量不能小(30B级别)。
(2)要成本:医院边缘设备显存有限,跑不动庞然大物。
我们选择了Qwen3-VL-30B-A3B-Instruct模型,因为它采用了稀疏激活(Active 3B)架构:拥有300亿参数的知识储备,但推理时仅激活30亿参数,为低成本落地提供了可能。
| 配置项 | 选择 | 说明 |
|---|---|---|
| 模型 | Qwen3-VL-30B-A3B-Instruct | 稀疏激活架构,仅激活3B参数,支持高分辨率动态切换 |
| 数据集 | MedTrinity-25M(16k样本子集) | 当前规模最大的公开医学影像-文本对数据集,涵盖超过2500万张图像,涉及CT、MR、X-Ray等多种模态,为65多种疾病提供多层次注释 |
| GPU | H800 × 4(推荐) | 模型规模较大,建议配置足够显存 |
| 微调方法 | LoRA | 显著降低计算与存储成本,实现高效轻量化微调 |
三、训练实战
3.1 数据加工
高质量、格式规范的数据集是成功的关键。我们通过以下流程将原始医学数据转化为模型可理解的格式:
(1)下载数据:从MedTrinity-25M数据集中精选1.6万条高质量影像-文本对。
(2)格式转换:使用定制Python脚本,将原始数据转换为LLaMA-Factory Online支持的ShareGPT多模态对话格式。
(3)质量验证:通过随机抽样与基线模型测试验证数据有效性。
下面是数据格式转换的核心代码。
#多模态数据格式转换代码
import os
import json
import random
from tqdm import tqdm
import datasets
def save_images_and_json(ds, ratio=0.1, output_dir="mllm_data"):
"""
保存数据集中的图像,并且构建多模态训练集和验证集。
参数:
ds: 数据集对象,包含图像和描述。
ratio: 验证集比例,默认为 0.1。
output_dir: 输出目录,默认为 "mllm_data"。
"""
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
all_train_data = [] # 多模态训练数据
all_val_data = [] # 多模态验证数据
total_samples = len(ds)
val_index = set(random.sample(range(total_samples), int(ratio * total_samples)))
# 遍历数据集中的每个项目
for idx, item in tqdm(enumerate(ds), total=total_samples, desc="Processing"):
img_path = os.path.join(output_dir, f"{item['id']}.jpg")
image = item["image"]
# 保存图像
image.save(img_path)
sample = {
"messages": [
{
"role": "user",
"content": "<image>图片中的诊断结果是怎样?"
},
{
"role": "assistant",
"content": item["caption"] # 从数据集中获取的描述
}
],
"images": [img_path] # 图像文件路径
}
if idx in val_index:
all_val_data.append(sample)
else:
all_train_data.append(sample)
# 将数据保存到 JSON 文件
train_json_path = os.path.join(output_dir, "mllm_train_data.json")
val_json_path = os.path.join(output_dir, "mllm_val_data.json")
with open(train_json_path, "w", encoding="utf-8") as f:
json.dump(all_train_data, f, ensure_ascii=False, indent=2)
with open(val_json_path, "w", encoding="utf-8") as f:
json.dump(all_val_data, f, ensure_ascii=False, indent=2)
if __name__ == "__main__":
# 加载数据集
ds = datasets.load_dataset("parquet", data_files="../dataset/data_sft/train-00000-of-00010.parquet")["train"]
# 保存图像并构建多模态训练/验证集
save_images_and_json(
ds,
ratio=0.2,
output_dir="/workspace/user-data/codelab/Qwen3-VL/dataset/mllm_data"
)
转换后的数据示例如下:
[
{
"messages": [
{
"role": "user",
"content": "<image>图片中的诊断结果是怎样?"
},
{
"role": "assistant",
"content": "这是一张非增强脑部计算机断层扫描(CT)图像,图中未见任何医疗设备。位于图像中央的兴趣区域可见密度改变,提示脑内出血。该异常区域与周围脑组织分界清晰,可能为脑实质内血肿或出血。其位置及表现可能与邻近脑组织存在关联,进而产生占位效应或导致颅内压升高。"
}
],
"images": [
"/workspace/user-data/codelab/Qwen3-VL/dataset/mllm_data/8031efe0-1b5c-11ef-8929-000066532cad.jpg"
]
}
]
3.2 模型训练
训练一个专业模型,不仅是”跑起来”,更要”学得好”。参数调优就是寻找最佳学习方案的过程。
(1)DeepSpeed Stage的选择
微调30B级别大模型时,很多人的第一反应是使用DeepSpeed Stage 3以节省显存。但在医疗影像这种需要极高精度的任务中,我们通过实验发现:
DeepSpeed Stage 3:虽然显存占用低,但Loss下降缓慢。原因在于Stage 3的”参数延迟+梯度噪声”机制,干扰了模型对微小病灶的学习。
DeepSpeed Stage 2:虽然显存占用稍高,但Loss曲线平滑,收敛更彻底。
建议:在LLaMA-Factory Online配置时,若显存允许(如使用H800),请选择Stage 2。如果必须用Stage 3,请配合”放大Global Batch Size + 拉长Warmup”来弥补性能损失。
(2)参数配置对比实验
我们进行了两组微调实验,变量仅为per_device_train_batch_size和DeepSpeed参数,其他条件完全相同。
| 配置参数 | 参数一 | 参数二 |
|---|---|---|
| model | Qwen3-VL-30B-A3B-Instruct | Qwen3-VL-30B-A3B-Instruct |
| per_device_train_batch_size | 32 | 4 |
| DeepSpeed | 3 | 2 |
| Learning Rate | 5e-05 | 5e-05 |
| Epochs | 2 | 2 |
| Gradient Accumulation | 8 | 8 |
| Mixed Precision | bf16 | bf16 |
| LoRA Rank | 8 | 8 |
| LoRA Alpha | 16 | 16 |
从Loss对比结果来看:
(1)DeepSpeed 3(参数一)训练速度更快,但微调阶段Loss显著上升。
(2)DeepSpeed 2(参数二)训练速度略有下降,但能更有效地压低Loss。
选型建议:若显存充足,优先选择DeepSpeed 2以追求更优指标;若显存不足需使用DeepSpeed 3,则需同步放大global batch、拉长warmup时长、降低学习率来弥补收敛性能。
(3)参数调优心法
通过反复实验,我们总结出一套适用于Qwen3-VL医疗微调的参数建议:
LR Scheduler:放弃Linear,选择Cosine + Warmup,它能更好地适配视觉特征的学习节奏。
Epoch:在16k数据场景下,3个Epoch是性能拐点;第4个Epoch起训练Loss仍降,但验证指标不再上升,属于典型过拟合。5k小数据场景下可拉到6~8 Epoch。
LoRA Rank:医疗影像细节极多(如微小结节、毛刺征),低Rank(如8以下)表达能力不足。Rank 32是效果与成本的性价比拐点。
Alpha值:Alpha = Rank × 2,稳定性最佳。
Dropout:数据量≤10k时,设置dropout=0.05可有效防过拟合;数据>10k时可直接设为0。
3.3 效果验证
(1)指标对比
下表展示了模型在微调前后的变化,参数二(DeepSpeed Z2方案)在各项指标上达到了最优水平。
| 评估指标 | 微调前 | 参数一(Z3方案) | 参数二(Z2方案) |
|---|---|---|---|
| BLEU-4 | 0.806 | 27.653 | 92.375 |
| ROUGE-1 | 2.778 | 38.069 | 96.114 |
| ROUGE-2 | 0.006 | 16.363 | 94.036 |
| ROUGE-L | 2.013 | 20.695 | 94.286 |
指标说明:BLEU-4衡量生成文本与参考答案在词组和表达上的匹配度;ROUGE-1/2/L综合评估生成内容的关键词覆盖、短语搭配和句法连贯性。
采用Z2方案微调的模型(参数二),其生成质量远超原生模型和Z3方案,在专业术语、句式结构和临床逻辑上都与标准医学描述高度一致。
(2)效率对比
| 评估指标 | 微调前 | 参数一(Z3方案) | 参数二(Z2方案) |
|---|---|---|---|
| predict_samples_per_second | 0.773 | 0.057 | 0.194 |
| predict_steps_per_second | 0.048 | 0.002 | 0.048 |
| predict_runtime | 4179.834 | 56431.560 | 16668.369 |
微调不仅解决了原生模型生成质量”不可用”的核心问题,更在效率上实现了超越。最终得到的模型在专业性、准确性和响应速度上取得了平衡,可投入医学影像报告生成、辅助诊断等场景。
3.4 实战对话
我们对比了两组参数微调后的模型对同一张胸部CT影像的分析。
参数二(Z2方案)在以下方面表现更优:
(1)观察敏锐度:能够发现图像中的多个病灶,避免漏诊。
(2)分析系统性:提供从解剖结构到病变特征的完整分析框架。
(3)诊断严谨性:基于医学证据进行推理,给出合理的鉴别诊断。
(4)临床实用性:回答具有直接临床参考价值。
这一结果与实验数据高度吻合——Z2方案虽然在训练速度上稍慢,但能够学习到更丰富的医学知识结构和诊断逻辑,最终生成的影像报告更接近资深放射科医生的专业水准。




