ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

中文医学文本实体关系抽取Python源码包拆解与实战

中文医学文本实体关系抽取Python源码包拆解与实战 简介本资源为基于Python实现的中文医学文本实体关系抽取完整源码包面向人工智能、自然语言处理方向的高校学生与开发者尤其适合作为期末大作业、课程设计或入门级NLP项目实践参考。包内共13个文件以12个py源码文件与1个txt使用说明为主压缩包约28KB涵盖实体识别、关系抽取、模型定义、评估脚本及Flask接口服务等模块结构清晰便于二次开发。资源围绕中文医学文本这一垂直领域展开涉及数据预处理、模型构建、关系API封装与运行评估等关键环节可帮助读者理解从数据到服务部署的完整流程。目前已有514人学习下载适合需要快速获取可运行代码、对照调试与拓展实验的读者参考使用。1. 中文医学文本实体关系抽取一份能跑通的 Python 源码包拆解医学文本里「阿司匹林」和「胃出血」之间到底是「导致」还是「治疗」机器读错一个字下游的用药提醒、病历质控、知识图谱构建全跟着翻车。这份基于 Python 实现的中文医学文本实体关系抽取源码包解决的就是从非结构化病历、文献里把「实体对 关系类型」结构化抽出来的问题。它包含实体识别与关系分类两条主流程配套 Flask 接口、评估脚本和共享数据结构模块适合做课程设计、期末大作业也适合想快速搭一套医学信息抽取原型的从业者。拿到手先别急着改模型把数据格式和调用链摸清楚后面省一半时间。2. 源码包结构与运行链路从 run_entity.py 到 Flask 接口2.1 目录里每个文件到底管什么先把包解开根目录下能看到这些核心文件run_entity.py、run_relation.py、run_eval.py、relation_api.py、run_relation_api.py、flask_server.py、models.py、utils.py、some_function.py以及shared/目录下的__init__.py、data_structures.py、const.py。这不是一个「一个脚本打天下」的玩具工程而是把实体抽取、关系抽取、评估、服务化拆开了。文件职责什么时候会动它run_entity.py实体识别入口加载模型跑推理换实体模型、调 batch sizerun_relation.py关系分类入口输入实体对输出关系换关系模型、改标签映射run_eval.py评估脚本算 P/R/F1验证自己训的模型models.py模型结构定义换编码器、改分类头utils.py数据加载、分词、指标工具改数据路径、改预处理shared/const.py全局常量、标签集合加关系类型、改实体类型shared/data_structures.py样本、实体、关系的数据类改字段、加元信息relation_api.py关系抽取的 API 封装对外提供服务flask_server.pyHTTP 服务入口部署、调端口常见做法是run_entity.py先跑出实体run_relation.py再基于实体对做关系分类run_eval.py拿标注数据算指标最后flask_server.py把整条链路包成接口。这个分层很清晰改哪一层都不会把别的层带崩。2.2 环境准备与依赖安装源码包没有锁死 Python 版本但这类中文 NLP 工程一般跑在 Python 3.73.9 上最稳。先建虚拟环境别直接往系统 Python 里装不然依赖冲突能折腾一下午。# 建虚拟环境python3.8 兼容性最好 python3.8 -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 升级 pip老版本装包容易卡 pip install --upgrade pip # 装核心依赖版本按实际报错微调 pip install torch1.13.1 pip install transformers4.30.2 pip install flask pip install numpy pandas scikit-learn pip install jieba逻辑说明torch和transformers是模型推理的底座flask给服务化用jieba处理中文分词。参数上torch版本要和 CUDA 对齐——有 GPU 就装对应 cu 版本没 GPU 装 CPU 版也能跑只是慢。transformers别装太新4.30 附近对老模型结构兼容性好装到 4.4x 有时加载权重会报 key 不匹配。提示如果pip install torch卡在下载先确认网络能访问包源或者换国内镜像源别硬等。2.3 跑通实体识别run_entity.py 的调用方式实体识别是第一步输入一段中文医学文本输出实体列表和类型。典型调用长这样# 基本用法指定输入文件和模型目录 python run_entity.py \ --input_file data/sample.txt \ --model_dir checkpoints/entity_model \ --batch_size 16 \ --max_seq_len 128 \ --device cuda:0逻辑说明--input_file是待抽取的文本一行一条--model_dir指向实体模型权重目录--batch_size控制一次喂多少条显存小就调到 8 或 4--max_seq_len是截断长度医学文本句子普遍不长128 够用长病历可以提到 256--device有 GPU 写cuda:0没有写cpu。跑完会在输出目录生成实体结果文件格式一般是「文本 实体 类型 起止位置」。如果报FileNotFoundError先看--model_dir路径对不对如果报CUDA out of memory把batch_size砍半再试。这一步跑通说明环境和模型加载都没问题。2.4 关系抽取与评估run_relation.py 和 run_eval.py实体有了接下来判断实体对之间的关系。run_relation.py的输入是实体识别结果输出是「实体1 - 关系 - 实体2」三元组。# 关系抽取基于实体结果跑关系分类 python run_relation.py \ --entity_file outputs/entities.json \ --model_dir checkpoints/relation_model \ --relation_dict shared/const.py \ --batch_size 32 \ --device cuda:0 # 评估拿标注数据算指标 python run_eval.py \ --pred_file outputs/relations.json \ --gold_file data/gold_relations.json \ --output_file outputs/eval_report.txt逻辑说明--entity_file是上一步的实体输出--relation_dict指定关系标签集合在shared/const.py里定义--batch_size关系分类通常比实体识别能吃更大 batch。run_eval.py的--pred_file是预测结果--gold_file是人工标注输出 P/R/F1。如果 F1 低得离谱先别怀疑模型检查预测和标注的标签名是否一致——标签对不上指标必然崩。3. 数据格式与标签体系shared 目录里的黑匣子3.1 data_structures.py 定义了哪些字段shared/data_structures.py是整个工程的数据契约实体、关系、样本都在这定义。常见结构是Entity文本、类型、起止、Relation头实体、尾实体、关系类型、Sample原文、实体列表、关系列表。改数据格式前先读这个文件不然预处理和模型对不上。# shared/data_structures.py 典型结构示意 from dataclasses import dataclass, field from typing import List dataclass class Entity: text: str # 实体文本如阿司匹林 type: str # 实体类型如药物 start: int # 起始位置 end: int # 结束位置 dataclass class Relation: head: Entity # 头实体 tail: Entity # 尾实体 rel_type: str # 关系类型如导致 dataclass class Sample: text: str # 原始文本 entities: List[Entity] field(default_factorylist) relations: List[Relation] field(default_factorylist)逻辑说明用dataclass定义字段清晰序列化方便。start/end是字符级偏移中文按字符算别按字节算否则位置全错。rel_type直接存字符串和const.py里的标签集合对应。3.2 const.py 里的标签集合怎么改shared/const.py管全局常量最重要的是实体类型和关系类型的枚举。想加一个新关系类型比如「禁忌」就在这里加然后确认模型分类头维度跟着变。# shared/const.py 典型内容示意 ENTITY_TYPES [药物, 疾病, 症状, 检查, 部位] RELATION_TYPES [ 导致, # 药物导致症状 治疗, # 药物治疗疾病 检查, # 检查诊断疾病 位于, # 疾病位于部位 禁忌, # 新增药物禁忌人群 ] REL2ID {r: i for i, r in enumerate(RELATION_TYPES)} ID2REL {i: r for r, i in REL2ID.items()}逻辑说明REL2ID和ID2REL是双向映射模型输出的是 id转回标签靠ID2REL。加关系类型后models.py里分类头的输出维度要同步改成len(RELATION_TYPES)否则加载权重会报维度不匹配。这是最容易踩的坑之一。3.3 数据预处理与分词注意点中文医学文本分词是个玄学。通用分词器会把「非小细胞肺癌」切成「非小细胞 / 肺癌」实体边界就错了。常见做法是实体识别阶段用字符级输入避免分词误差关系分类阶段再把实体文本拼进模板。# utils.py 里常见的文本处理逻辑示意 import jieba def tokenize(text, modechar): if mode char: return list(text) # 字符级实体边界准 else: return list(jieba.cut(text)) # 词级语义更整 def build_relation_input(text, head, tail): # 用特殊标记标出头尾实体喂给关系分类模型 marked text.replace(head.text, f[H]{head.text}[/H]) marked marked.replace(tail.text, f[T]{tail.text}[/T]) return marked逻辑说明tokenize支持字符级和词级两种模式实体识别建议字符级。build_relation_input用[H]、[T]标记头尾实体这是关系分类的常见套路模型靠标记定位实体。注意replace可能误替换——如果实体文本在别处也出现会标错位置严谨做法是按start/end偏移插入标记。4. 服务化与接口调用flask_server.py 怎么用4.1 启动 Flask 服务flask_server.py把整条链路包成 HTTP 接口方便别的系统调用。启动方式# 启动服务默认 5000 端口 python flask_server.py --host 0.0.0.0 --port 5000 --entity_model checkpoints/entity_model --relation_model checkpoints/relation_model逻辑说明--host 0.0.0.0允许外部访问只本机用写127.0.0.1--port改端口被占用就换两个--model参数分别指向实体和关系模型。启动后日志会打印监听地址看到Running on http://...就说明起来了。4.2 接口请求与返回格式服务起来后用 curl 或 Python 请求测试# 发一条文本拿实体和关系 curl -X POST http://127.0.0.1:5000/extract \ -H Content-Type: application/json \ -d {text: 阿司匹林可用于治疗冠心病但可能引起胃出血。}返回结构一般是{ entities: [ {text: 阿司匹林, type: 药物, start: 0, end: 4}, {text: 冠心病, type: 疾病, start: 9, end: 12}, {text: 胃出血, type: 症状, start: 18, end: 21} ], relations: [ {head: 阿司匹林, tail: 冠心病, type: 治疗}, {head: 阿司匹林, tail: 胃出血, type: 导致} ] }逻辑说明entities是实体列表relations是三元组。如果返回空先确认模型加载成功再看输入文本是否为空。接口超时一般是模型推理慢调小batch_size或换 GPU。4.3 relation_api.py 与 run_relation_api.py 的分工relation_api.py是关系抽取的 API 封装层run_relation_api.py是它的启动入口。这种拆法方便单独部署关系服务不和实体服务绑死。常见做法是实体和关系各起一个服务通过内部调用串联这样任一模型更新不影响另一个。# relation_api.py 典型封装示意 from flask import Flask, request, jsonify from models import RelationModel from utils import load_model, build_relation_input app Flask(__name__) model None def init_model(model_dir): global model model load_model(model_dir) app.route(/relation, methods[POST]) def predict_relation(): data request.get_json() text data[text] head data[head] tail data[tail] inp build_relation_input(text, head, tail) rel model.predict(inp) return jsonify({relation: rel})逻辑说明init_model在启动时加载模型避免每次请求都加载。/relation接口接收文本和头尾实体返回关系类型。参数上head、tail要传实体对象或至少传文本和位置只传文本容易定位错。5. 避坑与常见问题排查5.1 模型加载报 key 不匹配现象启动时报Missing key(s) in state_dict或Unexpected key(s)。原因模型结构改了比如加了关系类型但权重还是旧的或者transformers版本差异导致参数名变了。解决确认const.py里标签数量和models.py分类头维度一致换回训练时的transformers版本实在不行重新训一版权重。5.2 实体位置偏移对不上现象关系抽取时头尾实体定位错抽出的关系张冠李戴。原因实体识别输出的是字符偏移但预处理时做了分词或截断偏移没同步更新。解决统一用字符级偏移截断时同步裁剪实体位置build_relation_input按start/end插入标记别用replace。5.3 评估指标异常低现象run_eval.py跑出来 F1 只有零点几。原因预测和标注的标签名不一致比如一个用「导致」一个用「引起」或者数据没对齐。解决先打印几条预测和标注对比确认标签体系一致检查gold_file格式和pred_file是否同构。5.4 Flask 服务启动后请求超时现象接口调不通或等很久才返回。原因模型在 CPU 上跑或者batch_size太大导致单次推理慢。解决换 GPU调小batch_size给 Flask 加超时配置确认没有在请求里重复加载模型。5.5 中文编码问题现象读文件报UnicodeDecodeError或输出乱码。原因文件编码不是 UTF-8Windows 下常见 GBK。解决读写文件统一指定encodingutf-8open时加errorsignore兜底确认终端编码也是 UTF-8。6. 进阶技巧把抽取结果接进知识图谱跑通基础流程后真正有价值的是把三元组接进下游。我一般会加一层后处理把关系结果转成图数据库能吃的格式。以 Neo4j 为例先导出 CSV# 把关系结果转成 Neo4j 导入格式 import json import csv def export_to_neo4j(relation_file, out_nodes, out_edges): with open(relation_file, encodingutf-8) as f: data json.load(f) nodes {} # 去重实体 edges [] # 关系边 for item in data: for ent in item[entities]: key (ent[text], ent[type]) nodes[key] ent[type] for rel in item[relations]: edges.append((rel[head], rel[type], rel[tail])) with open(out_nodes, w, newline, encodingutf-8) as f: w csv.writer(f) w.writerow([name, type]) for (name, typ) in nodes: w.writerow([name, typ]) with open(out_edges, w, newline, encodingutf-8) as f: w csv.writer(f) w.writerow([head, relation, tail]) for e in edges: w.writerow(e) export_to_neo4j(outputs/relations.json, nodes.csv, edges.csv)逻辑说明nodes用(文本, 类型)去重避免同一实体重复建节点edges存头实体、关系、尾实体。导出的 CSV 用 Neo4j 的LOAD CSV导入节点和边分开建。参数上relation_file是上一步输出out_nodes、out_edges是导出路径。验证方法导入后跑一句MATCH (a)-[r]-(b) RETURN a, r, b LIMIT 25看图谱是否连通。如果边大量悬空说明实体名没对齐——实体识别和关系抽取的实体文本必须完全一致差一个空格都连不上。还有个技巧是加置信度过滤。模型输出的关系通常带概率低于阈值的直接丢宁可少抽也别抽错。我一般把阈值设在 0.7 附近医学场景对准确率要求高召回可以牺牲一点。阈值不是拍脑袋定的拿评估集跑几组看 P/R 曲线拐点在哪。从那以后我每次接新模型都强制先跑一遍run_eval.py看指标再拿几条真实文本肉眼过一遍最后才接下游。这套流程帮我挡掉过好几次「指标好看但实际乱抽」的翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表