如何用 Transformers-Tutorials 里的 LayoutLMv3 自动抽取病历关键字段:手把手完整指南
【免费下载链接】Transformers-TutorialsThis repository contains demos I made with the Transformers library by HuggingFace.项目地址: https://gitcode.com/GitHub_Trending/tr/Transformers-Tutorials
医院信息科要把出院小结、处方单、检验报告录入系统时,人工逐页抄写一天能处理两三百页已是极限,而且剂量、药名一旦抄错一个字符就可能引发用药安全问题。Transformers-Tutorials 项目里的 LayoutLMv3 教程正是为这类文档设计的:模型能同时"读"文档图像里的文字和它所在的位置,再配合一份现成的微调 Notebook,三步就能搭起一条病历信息抽取流水线。
为什么这类任务难做
先说三个绕不开的低效点,你大概率都遇到过:
- OCR 只认字,不懂字段。同一页上出现两个
2023-05-01,一个是出生日期、一个是入院日期,只靠文字内容根本分不出谁是谁,必须结合坐标位置才能判断。 - 版面不规整,规则写不牢。病历扫描件有倾斜、有印章压字、字体大小不一,用"固定坐标框切区域"的方式一碰到字段错位就全盘失效。
- 纯文本模型看不见位置。同一个词在"诊断"栏和"用药"栏里含义完全不同,只看 token 序列会猜错,模型需要知道每个词"在哪"。
LayoutLMv3 的思路就是把图像、文本、边界框三种信号一起喂进 Transformer,让位置信息直接参与理解。
在项目的哪里做
打开仓库根目录下的 LayoutLMv3/,里面就两个文件:
- 主教程 Fine_tune_LayoutLMv3_on_FUNSD_(HuggingFace_Trainer).ipynb.ipynb):完整的"数据准备 → 微调 → 推理"流程,基于 HuggingFace Trainer 实现;
- README.md:讲了一个很关键的性能细节(segment 位置编码,后面调优部分会用到)。
这个示例做的是 FUNSD 数据集(一批带标注的表单文档)上的命名实体识别——把每个词标成"字段头""地址""其他"之类的类别。这跟病历场景几乎同构:病历本质上也是"带固定字段的表单",所以这套流程不用改架构,只需把 FUNSD 换成你的病历数据、把标签集换成"患者姓名/诊断/用药"即可。
🛠️ 三步跑起来
第一步:把原始数据喂给模型
先明确数据格式要求。每条样本需要四样东西:image(PIL 图像)、tokens(OCR 切出的词列表)、bboxes(每个词的坐标[x1, y1, x2, y2],归一化到 0–1000)、ner_tags(每个词的标签编号)。OCR 这一步可以交给 Tesseract 这类引擎,它能给出带坐标的词级结果。
拿到这四样后,加载处理器(注意apply_ocr=False表示 OCR 结果由你外部提供,模型内部不再跑 OCR):
from transformers import AutoProcessor processor = AutoProcessor.from_pretrained("microsoft/layoutlmv3-base", apply_ocr=False)然后写一个批量处理函数,把原始样本变成张量。这一步 processor 会自动完成两件事:把图像缩放归一化成 RGB 通道的pixel_values(224×224),把词和坐标转成 token 级的input_ids、bbox和labels:
def prepare_examples(examples): encoding = processor(examples["image"], examples["tokens"], boxes=examples["bboxes"], word_labels=examples["ner_tags"], truncation=True, padding="max_length") return encoding注意一个容易踩的坑:LayoutLMv3 的图像通道要求 RGB 格式,而上一代 LayoutLMv2 用的是 BGR,直接从旧代码迁移时颜色通道不对会白白损失精度。
第二步:微调,参数怎么设
在预训练底座上挂一个随机初始化的 token 分类头,加载方式如下:
from transformers import LayoutLMv3ForTokenClassification model = LayoutLMv3ForTokenClassification.from_pretrained( "microsoft/layoutlmv3-base", id2label=id2label, label2id=label2id)核心超参照抄教程即可,每个参数的取值理由整理在这里:
| 参数 | 取值 | 为什么这么设 |
|---|---|---|
| learning_rate | 1e-5 | 底座是预训练权重,小学习率微调不易破坏已有知识 |
| max_steps | 1000 | FUNSD 样本少,按步数控制比按 epoch 好掌握 |
| per_device_train_batch_size | 2 | base 模型 + 224×224 图像较吃显存,显存富裕可上调 |
| evaluation_strategy / eval_steps | "steps" / 100 | 每 100 步看一次 F1,及时发现问题 |
| load_best_model_at_end / metric_for_best_model | True / f1 | 训练结束自动回退到验证 F1 最高的 checkpoint |
组装 Trainer 并启动训练(指标函数用seqeval算实体级 F1):
from transformers import TrainingArguments, Trainer training_args = TrainingArguments(output_dir="test", max_steps=1000, per_device_train_batch_size=2, learning_rate=1e-5, evaluation_strategy="steps", eval_steps=100, load_best_model_at_end=True, metric_for_best_model="f1") trainer = Trainer(model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, tokenizer=processor, compute_metrics=compute_metrics) trainer.train()按教程跑完,验证集 F1 可以稳定到 90% 上下,对表单类 NER 是个相当可用的水平。
第三步:推理,拿到结构化结果
训练完的 checkpoint 加载方式跟加载底座完全一样,推理时把新病历的图像、词和坐标交给 processor,再取 logits 的 argmax 就是每个词的预测类别:
inputs = processor(image, words, boxes=boxes, return_tensors="pt") predictions = model(**inputs).logits.argmax(-1).squeeze().tolist() # 把 token 编号映射回实体名,非 O 的位置就是抽出的字段 for tid, pred in zip(inputs.input_ids[0].tolist(), predictions): if model.config.id2label[pred] != "O": print(processor.tokenizer.decode([tid]), "->", model.config.id2label[pred])一句话解释:模型输出形状为(batch, seq_len, num_labels)的 logits,逐 token 取最大值编号,再用id2label反查成"患者姓名""诊断"这类名称,配合该词自带的 bbox 就能把结构化字段连同位置一起拿到。注意真实推理时你手上没有标签,教程末尾演示了如何用 tokenizer 返回的offset_mapping把 token 对齐回原始词,需要时翻到那一段。
📈 效果调优与常见坑
- 改用 segment 级位置编码。让同一个字段里的词(比如一整个地址、一整个日期)共享同一个 bbox,从而获得相同的 2D 位置嵌入。README 明确指出这是 FUNSD 上 F1 突破 90% 的关键,Tesseract 这类 OCR 引擎本身就能识别出 segment。
- 图像增强扩样本。对病历图做旋转、缩放、对比度抖动,直接扩大有效训练量,降低扫描件质量差异带来的掉点。
- 后处理加规则校验。对日期、ICD 编码、剂量单位做正则校验,明显不合法的结果直接丢弃或标记人工复核,端到端精度会有一截提升。
- 多任务辅助。并行加一个"病历类型分类"头,与实体抽取共享底座,能给小样本场景提供额外监督信号。
- 小数据压住学习率。病历标注数据通常有限,保持 1e-5 到 5e-5 量级并配合早停,比激进的大学习率更不容易把验证集 F1 冲垮。
一句话收尾
把 FUNSD 换成你的病历数据、把标签集改成临床字段,这条从数据到推理的链路就通了——剩下的工作量主要在标注。接下来可以顺着这几个方向走:
- 手写病历场景:上游接一个手写识别(OCR)流水线,把识别出的词和坐标按本教程格式灌入;
- 结果可信度:把抽出的诊断、用药字段接入 ICD 编码库做交叉校验,明显矛盾的输出转人工;
- 落地部署:对微调后的 checkpoint 做量化或蒸馏,让单页推理成本降到可以支撑批量扫描件的量级。
【免费下载链接】Transformers-TutorialsThis repository contains demos I made with the Transformers library by HuggingFace.项目地址: https://gitcode.com/GitHub_Trending/tr/Transformers-Tutorials
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考