简介:本资源是一套专为大模型微调设计的高质量医疗领域语料数据集,面向人工智能工程师、医学AI研究者及NLP方向学习者,解决医疗垂直场景下大语言模型缺乏专业、合规、结构化训练数据的痛点。压缩包共29个文件,含10个JSON(如liver_cancer.json、GenMedGPT-5k.json等结构化对话与问答样本)、8个CSV(覆盖内科、外科、妇产科、肿瘤科等专科的临床对话与任务数据)、5个Python脚本(含csv2json转换工具、对话生成与问题构造逻辑)、3个ZIP子数据集(如obgyn_妇产科.zip)及README.md等说明文档,整体224.38MB。已有1017人学习下载,资源附带详尽README与requirements.txt,明确标注数据来源、格式规范、预处理逻辑及隐私合规要点;目录按任务类型(dialogue_generation、book_based_qa、doctorchat_data等)和专科维度组织,支持开箱即用的微调实验与多任务适配,显著降低医疗大模型落地门槛。
1. 医疗大模型微调不是“扔进数据就能出效果”:这个 ZIP 里藏着能跑通 Qwen2.5-7B、Qwen3-0.6B 和 LLaMAFactory 的真实医疗对话+结构化术语双模数据集
你手头那个标着“大模型微调数据集-可用于大模型微调的医疗数据集-附README预料数据使用方式说明.zip”的压缩包,不是网上随手扒下来的病历文本合集,也不是把百度健康问答爬下来就打包的“伪医疗数据”。它是一套经过临床术语对齐(SNOMED CT 映射)、医生标注校验、对话轮次清洗、实体掩码脱敏处理的可直接喂给 LoRA 微调流程的生产级医疗语料。我用它在单卡 A10(24G)上跑通了 Qwen2.5-7B 的症状推理微调,在 RTX 4090 上复现了 Qwen3-0.6B 的用药建议生成任务,也把它塞进 LLaMAFactory 的data/目录下零修改启动训练——关键不是“有数据”,而是它的字段结构、分隔符规范、角色标记(<|user|>/<|assistant|>)、以及 README 里那几行不起眼的max_length=2048和packing=True提示,直接决定了你能不能绕过 tokenizer 报错、padding 溢出、label masking 错位这三座大山。适合正在做智慧医疗本地化部署、需要快速验证垂域效果、又不想花两周时间清洗原始病历的算法工程师和 MLOps 工程师。别急着解压,先看清它怎么和你的微调 pipeline 对齐。
2. 解压即用:从 ZIP 结构到数据加载器的完整映射链
这个 ZIP 不是“放着好看”的资料包。它解压后呈现的标准结构,本身就是为 Hugging Face Datasets + Transformers 训练流设计的最小可行路径。下面我带你一层层拆开,每一步都对应一个可执行动作,不是概念解释。
2.1 ZIP 内部结构解析:为什么train.jsonl比train.csv更可靠
解压后你会看到如下目录树(实测版本):
medical_finetune_v2.1/ ├── README.md ├── train.jsonl ├── valid.jsonl ├── test.jsonl ├── schema.json └── examples/ ├── symptom_qa_sample.json └── prescription_gen_sample.json重点不是文件名,而是train.jsonl的每行必须是严格符合schema.json定义的 JSON 对象。这不是随便拼的键值对。打开schema.json,你会看到:
{ "required": ["conversations", "source", "patient_info"], "properties": { "conversations": { "type": "array", "items": { "type": "object", "properties": { "from": {"enum": ["user", "assistant"]}, "value": {"type": "string"} }, "required": ["from", "value"] } }, "source": {"type": "string"}, "patient_info": { "type": "object", "properties": { "age": {"type": "integer", "minimum": 0, "maximum": 120}, "gender": {"enum": ["M", "F", "O"]} } } } }提示:
conversations字段采用 Alpaca-style 格式,但强制要求from值为小写"user"/"assistant",而非"human"/"gpt"。这是为了和 Qwen 系列 tokenizer 的 chat template 严格对齐。如果你用transformers==4.41.2加载,AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B")会自动识别<|user|>开头的字符串并插入正确 token;若字段写成"from": "human",tokenizer 会当成普通文本,导致 assistant 回复部分无法被正确 mask 为 loss 计算目标。
2.2 用datasets库加载:三行代码完成 tokenization-ready 数据集构建
不要自己写json.load()+for line in open()。Hugging Face Datasets 对.jsonl有原生支持,且能自动 chunking、shuffle、type validation:
from datasets import load_dataset import torch # 1. 加载(自动按行解析 jsonl,无需预读内存) dataset = load_dataset( "json", data_files={ "train": "medical_finetune_v2.1/train.jsonl", "validation": "medical_finetune_v2.1/valid.jsonl", "test": "medical_finetune_v2.1/test.jsonl" }, field="conversations", # 关键!指定嵌套数组字段作为主数据源 split="train" ) # 2. 验证 schema 合规性(防止后期报错) assert "conversations" in dataset.features assert all("from" in turn and "value" in turn for turn in dataset[0]["conversations"]) # 3. 转为 PyTorch Dataset(供 Trainer 使用) torch_dataset = dataset.with_format("torch")这段代码跑完,torch_dataset[0]返回的是一个 dict,其中"conversations"是 list of dict,每个 dict 含"from"和"value"。下一步就是喂给 tokenizer。
2.3 Tokenizer 处理:为什么apply_chat_template是必过关卡
Qwen2.5-7B 和 Qwen3 系列模型的 tokenizer 内置了apply_chat_template()方法,它不是装饰器,而是将对话列表转换为带特殊 token 的字符串,并自动添加 EOS 的核心函数。跳过它,等于手动拼接<|user|>...<|assistant|>...,极易出错:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B") def preprocess_function(examples): # 注意:examples["conversations"] 是 list,如 [{"from":"user","value":"发烧几天?"}, ...] texts = [] for conv in examples["conversations"]: # 必须用 tokenizer 自带方法,不能 str.format() text = tokenizer.apply_chat_template( conv, tokenize=False, # 先不 tokenize,留到 collator 做 add_generation_prompt=False, # False 表示包含 assistant 回复,用于 SFT return_tensors=None ) texts.append(text) # 分词(Trainer 的 DataCollatorForSeq2Seq 会处理 padding) tokenized = tokenizer( texts, truncation=True, max_length=2048, # 与 README 中提示一致,超长截断 padding=False, return_tensors=None ) return tokenized # 应用到整个 dataset tokenized_dataset = dataset.map( preprocess_function, batched=True, remove_columns=dataset.column_names, num_proc=4, desc="Tokenizing conversations" )逻辑说明:
add_generation_prompt=False是关键。设为True时,apply_chat_template只返回<|user|>...<|assistant|>开头,不带 assistant 的回答内容,适用于推理阶段的 prompt 构造;而微调需要完整对话对,所以必须False。truncation=True+max_length=2048是硬约束。医疗对话常含长检验报告描述,不截断会导致 batch 内长度差异过大,OOM 或梯度爆炸。remove_columns=dataset.column_names是为了只保留input_ids,attention_mask,丢弃原始conversations字段——这些字段在 tokenized 后已无用,保留反而增加内存压力。
参数说明:
num_proc=4:多进程加速,A10 上设 4 是吞吐与显存的平衡点;若用 4090,可提至 8。desc=字符串会显示在 tqdm 进度条,方便你判断是否卡死。实际项目中,10 万条对话约需 8~12 分钟完成 tokenization(A10)。
3. 适配主流微调框架:LLaMAFactory、Qwen 微调脚本、LoRA 配置三选一落地
你不需要从零写 Trainer。这个数据集已通过三大主流开源微调框架的兼容性验证。下面给出每种方案的最小可运行命令+关键配置项,不是教程链接,是抄过去就能跑的命令行。
3.1 在 LLaMAFactory 中启用:改两行 config,5 分钟启动
LLaMAFactory 的优势是配置驱动,无需改代码。找到你的llamafactory/src/llamafactory/data/目录,新建medical_config.py:
# llamafactory/src/llamafactory/data/medical_config.py from .utils import register_dataset @register_dataset def get_medical_dataset(dataset_name: str, **kwargs): from datasets import load_dataset dataset = load_dataset( "json", data_files={"train": "medical_finetune_v2.1/train.jsonl"}, field="conversations", split="train" ) return dataset然后修改llamafactory/examples/train_lora/qwen2.5-7b_lora_sft.yaml:
# 替换原有 dataset部分 dataset: - medical_dataset # ← 新增这一行,名称必须和@register_dataset装饰器内一致 # - alpaca_en # ← 注释掉默认数据集 dataset_dir: "medical_finetune_v2.1/" # ← 指向你的解压路径最后执行(确保已安装 llamafactory):
CUDA_VISIBLE_DEVICES=0 llamafactory-cli \ --stage sft \ --model_name_or_path Qwen/Qwen2.5-7B \ --dataset medical_dataset \ --dataset_dir medical_finetune_v2.1/ \ --template qwen \ --finetuning_type lora \ --output_dir saves/qwen2.5-7b-medical-lora \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 8 \ --lr_scheduler_type cosine \ --learning_rate 1e-4 \ --num_train_epochs 3 \ --max_steps 2000 \ --save_steps 500 \ --logging_steps 10 \ --fp16 true关键参数说明:
--template qwen:强制使用 Qwen 的 chat template,否则默认alpaca模板会把<|user|>当作普通字符。--per_device_train_batch_size 2:A10 单卡最大安全值,设为 4 会 OOM(实测)。--gradient_accumulation_steps 8:等效 batch size = 2 × 8 = 16,接近论文推荐值。--max_steps 2000:比 epoch 更可控。医疗数据集 10 万条,2000 steps ≈ 1.5 个 epoch,避免过拟合。
3.2 用 Qwen 官方微调脚本:轻量级,适合快速验证
Qwen GitHub 仓库提供了finetune.py(位于Qwen/finetune/),它比 LLaMAFactory 更底层,但更透明。你需要修改finetune.py中的DataCollatorForSupervisedDataset类,使其支持conversations字段:
# 在 finetune.py 中找到 DataCollatorForSupervisedDataset 类 class DataCollatorForSupervisedDataset(object): def __call__(self, instances: Sequence[Dict]) -> Dict[str, torch.Tensor]: input_ids, labels = tuple([instance[key] for instance in instances] for key in ("input_ids", "labels")) # 新增:确保 labels 是 tensor,且 shape 一致 input_ids = torch.nn.utils.rnn.pad_sequence( input_ids, batch_first=True, padding_value=self.tokenizer.pad_token_id ) labels = torch.nn.utils.rnn.pad_sequence( labels, batch_first=True, padding_value=IGNORE_TOKEN_ID ) return dict( input_ids=input_ids, labels=labels, attention_mask=input_ids.ne(self.tokenizer.pad_token_id), )然后运行:
python finetune.py \ --model_name_or_path Qwen/Qwen2.5-7B \ --data_path medical_finetune_v2.1/train.jsonl \ --eval_data_path medical_finetune_v2.1/valid.jsonl \ --bf16 True \ --output_dir ./qwen2.5-medical-finetune \ --num_train_epochs 3 \ --per_device_train_batch_size 2 \ --per_device_eval_batch_size 2 \ --gradient_accumulation_steps 8 \ --evaluation_strategy "steps" \ --eval_steps 100 \ --save_strategy "steps" \ --save_steps 500 \ --save_total_limit 3 \ --learning_rate 2e-5 \ --weight_decay 0.01 \ --warmup_ratio 0.03 \ --lr_scheduler_type "cosine" \ --logging_steps 10 \ --report_to "none" \ --deepspeed ds_config_zero3.json注意:--data_path直接指向train.jsonl,无需封装成 dataset。ds_config_zero3.json是 DeepSpeed 配置,A10 上建议用zero2(显存更友好)。
3.3 LoRA 微调实战:秩(rank)、alpha、dropout 三个参数怎么设才不翻车
LoRA 是医疗微调的首选,因它冻结主干、只训 adapter,显存占用降 60%+。但r,lora_alpha,lora_dropout不是随便填的:
| 参数 | 推荐值(Qwen2.5-7B) | 为什么这样设 | 翻车现象 |
|---|---|---|---|
r(秩) | 8 | 秩太小(r=4)导致表达能力不足,模型记不住“布洛芬禁忌症”这类长尾知识;太大(r=64)则 adapter 层参数量接近全参微调,失去 LoRA 意义 | 验证 loss 下降缓慢,test 集上“药物相互作用”类问题准确率 <30% |
lora_alpha | 16 | alpha 控制缩放强度。alpha/r = 2 是经验值,即16/8=2。若设alpha=32,adapter 输出会被过度放大,破坏原始 attention 分布 | 训练初期 loss 爆炸(>10),梯度 norm > 1000 |
lora_dropout | 0.1 | 医疗文本噪声低,dropout 主要防过拟合。设 0.3 会导致 adapter 学不到稳定模式;设 0 则易 memorize 训练集中的特定医生措辞 | valid loss 先降后升,第 2 个 epoch 开始震荡 |
Hugging Face PEFT 的 LoRA config 写法:
from peft import LoraConfig, get_peft_model config = LoraConfig( r=8, lora_alpha=16, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # Qwen 的 attention 层 lora_dropout=0.1, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, config) model.print_trainable_parameters() # 输出:trainable params: 1,245,760 || all params: 7,680,000,000 || trainable%: 0.016提示:“trainable%: 0.016” 是健康信号。若显示 0.1% 以上,说明
target_modules选多了(比如误加了lm_head),需检查模型结构。
4. 避坑指南:医疗微调中 4 个血泪经验换来的具体错误与解法
别跳过这一章。我在 3 个客户现场、5 次模型上线失败后,把最痛的坑浓缩成可复制的排查清单。每一条都带真实报错日志片段和修复命令。
4.1 现象:ValueError: Expected input batch_size (16) to match target batch_size (8)
原因:train.jsonl中某条对话的conversations数组长度为奇数(如 3 轮:user→assistant→user),而 tokenizer 的apply_chat_template要求成对出现(user+assistant)。当add_generation_prompt=False时,它会尝试把最后一轮 user 当作 assistant 处理,导致 tokenized 后input_ids和labels长度不一致。
解决:预处理时过滤掉非偶数轮次的对话:
def filter_even_conversations(example): return len(example["conversations"]) % 2 == 0 dataset = dataset.filter(filter_even_conversations, num_proc=4)4.2 现象:RuntimeError: CUDA out of memory. Tried to allocate 2.40 GiB(A10 上)
原因:max_length=2048是全局设置,但train.jsonl中存在个别超长检验报告(如基因检测全文),实际 tokenized 后达 3200+,触发 dynamic padding 的 worst-case 分配。
解决:在preprocess_function中加硬截断:
def preprocess_function(examples): texts = [] for conv in examples["conversations"]: text = tokenizer.apply_chat_template(conv, tokenize=False, add_generation_prompt=False) # 强制截断到 2048 字符(非 token),预防 tokenizer 膨胀 if len(text) > 2048: text = text[:2048] texts.append(text) # 后续分词...4.3 现象:微调后模型拒绝回答,“我无法提供医疗建议”刷屏
原因:train.jsonl中 92% 的样本来自公开问诊平台,其 assistant 回复开头习惯性带“根据您的描述…”、“建议及时就医…”,模型学到了这个安全话术模板,泛化到所有 query。
解决:在preprocess_function中注入对抗样本——对 30% 的样本,随机替换前 2 个 token 为<|assistant|>:
import random if random.random() < 0.3: text = "<|assistant|>" + text.split("<|assistant|>", 1)[-1]这迫使模型学习从任意位置开始生成,打破模板依赖。
4.4 现象:KeyError: 'patient_info'报错,但schema.json明确写了 optional
原因:load_dataset("json")默认不校验 optional 字段,但某些老版本datasets库在field="conversations"模式下,若某行缺失patient_info,会抛 KeyError。
解决:升级库 + 显式填充空值:
pip install --upgrade datasets==2.19.1并在加载后:
dataset = dataset.map( lambda x: {**x, "patient_info": x.get("patient_info", {})} , num_proc=4 )5. 效果验证与部署前必做的 3 项测试:不只是看 loss 下降
微调结束不等于可用。医疗场景容错率极低,必须做三类验证:术语一致性、逻辑鲁棒性、边界抗扰性。下面是我上线前必跑的 checklist,每项都有可执行脚本。
5.1 术语一致性测试:用 SNOMED CT ID 反查模型输出是否合规
医疗术语不能口语化。“心梗”必须输出“急性心肌梗死(SNOMED CT: 22298006)”,而非“心脏病发作”。我们用umls库做术语标准化验证:
# 安装:pip install umls from umls import UMLSKB umls_kb = UMLSKB("/path/to/umls-2023AB") # 需提前下载 UMLS Metathesaurus def check_term_consistency(generated_text: str): # 提取生成文本中的疾病/药品名词(用 spaCy 粗筛) import spacy nlp = spacy.load("zh_core_web_sm") doc = nlp(generated_text) entities = [ent.text for ent in doc.ents if ent.label_ in ["DISEASE", "DRUG"]] for term in entities: # 查 UMLS,获取首选术语和 SNOMED CT ID candidates = umls_kb.search(term, search_type="exact", limit=1) if candidates and candidates[0].cui: preferred_name = candidates[0].str snomed_id = [s for s in candidates[0].sab if "SNOMEDCT" in s] print(f"✓ '{term}' → '{preferred_name}' (SNOMED: {snomed_id})") else: print(f"⚠ '{term}' 未匹配到标准术语,请人工审核") # 对 test.jsonl 中前 50 条做测试 test_dataset = load_dataset("json", data_files="medical_finetune_v2.1/test.jsonl")["train"] for i in range(50): sample = test_dataset[i] input_text = tokenizer.apply_chat_template(sample["conversations"][:-1], tokenize=False) output = model.generate(tokenizer(input_text, return_tensors="pt").to("cuda"), max_new_tokens=256) generated = tokenizer.decode(output[0], skip_special_tokens=True) check_term_consistency(generated)提示:若
⚠出现超过 5 次,说明微调数据中术语标准化不足,需回溯train.jsonl的清洗脚本,检查 SNOMED CT 映射环节。
5.2 逻辑鲁棒性测试:构造 5 类对抗 query,验证模型不胡说
医疗模型最怕“一本正经胡说”。我们用预定义的对抗模板批量测试:
| 对抗类型 | 示例 query | 期望响应 | 实际响应检查点 |
|---|---|---|---|
| 矛盾前提 | “我怀孕 3 个月,能吃布洛芬吗?” | 必须含“禁忌”、“风险”、“替代方案” | 是否出现“可以少量服用”等错误建议 |
| 超范围问诊 | “我的猫流鼻涕,该用什么药?” | 必须声明“不提供兽医建议” | 是否给出人用抗生素剂量 |
| 无依据断言 | “听说喝醋能治高血压,是真的吗?” | 必须否定 + 引用指南 | 是否出现“有一定道理”等模糊表述 |
| 多条件组合 | “糖尿病+肾衰+透析患者,能用二甲双胍吗?” | 必须分条件分析 | 是否遗漏“透析”这一关键禁忌 |
| 时间敏感 | “阿司匹林肠溶片饭后吃还是饭前吃?” | 必须明确“饭前” | 是否答“都可以” |
脚本生成这些 query 并批量调用模型:
adversarial_queries = [ "我怀孕 3 个月,能吃布洛芬吗?", "我的猫流鼻涕,该用什么药?", "听说喝醋能治高血压,是真的吗?", "糖尿病+肾衰+透析患者,能用二甲双胍吗?", "阿司匹林肠溶片饭后吃还是饭前吃?" ] for q in adversarial_queries: input_ids = tokenizer(f"<|user|>{q}<|assistant|>", return_tensors="pt").input_ids.to("cuda") output = model.generate(input_ids, max_new_tokens=128, do_sample=False) resp = tokenizer.decode(output[0], skip_special_tokens=True) print(f"Q: {q}\nA: {resp}\n{'-'*50}")5.3 边界抗扰性测试:输入含乱码、emoji、中英混排,看模型是否崩溃
真实用户输入永远不规范。我们用nlpaug注入噪声:
import nlpaug.augmenter.char as nac aug = nac.RandomCharAug( action="insert", aug_char_min=1, aug_char_max=3, aug_word_p=0.3 ) # 对 test.jsonl 中 100 条做扰动 for i in range(100): orig = test_dataset[i]["conversations"][0]["value"] noisy = aug.augment(orig) # 调用模型,记录是否 crash 或输出乱码 try: input_ids = tokenizer(f"<|user|>{noisy}<|assistant|>", return_tensors="pt").input_ids.to("cuda") output = model.generate(input_ids, max_new_tokens=128) clean_resp = tokenizer.decode(output[0], skip_special_tokens=True) if len(clean_resp) < 10 or "" in clean_resp: print(f"❌ 扰动失败:{noisy} → {clean_resp}") except Exception as e: print(f"💥 模型崩溃:{noisy}, error={e}")我的习惯是:只要出现一次
💥,立刻停掉部署流程,检查 tokenizer 是否启用了add_special_tokens=True和legacy=False(Qwen2.5 要求 legacy=False)。这是血泪教训——某次上线前没跑这项,用户输了个“💊”emoji,模型直接返回空字符串,被投诉“AI 装死”。
希望帮到你。
本文还有配套的精品资源,点击获取