
简介本资源是一套面向自然语言处理与医疗AI方向学习者的医药知识图谱自动问答系统完整实现适用于具备Python基础及NLP入门经验的开发者、研究生与行业实践者解决医药领域专业问答中实体识别不准、知识链接弱、意图理解泛化差等核心问题。压缩包共364个文件含117个Python脚本覆盖BERT-CRF实体识别、Sentence-BERT实体链接、词典驱动意图识别等核心模块、90个JavaScript前端交互文件、25个HTML页面及配套CSS/JS样式资源整体大小为72.36MB结构清晰前后端分离明确便于快速部署与二次开发。已有181人学习下载资源提供从知识图谱构建、流水线式问答推理到可视化前端的全链路源码附详细使用说明文档MD/TEXT格式及可直接运行的批处理脚本run.bat并包含预训练模型文件pkl/h5、领域词典tsv/json与测试数据集开箱即用显著降低医药垂直领域问答系统复现门槛。1. 这不是通用问答模型而是专为医药实体与关系设计的端到端知识服务系统当你在医院信息科调试临床决策支持模块或在药企做药品不良反应归因分析时会发现通用大模型如ChatGLM、Qwen对“阿司匹林是否增加华法林出血风险”这类问题常给出模糊结论甚至编造文献编号而传统关键词匹配又无法处理“氯吡格雷和替格瑞洛在CYP2C19慢代谢患者中的抗血小板效果差异”这种嵌套逻辑。本项目提供的Python基于Bert-crf医药知识图谱的自动问答系统正是为解决这一断层而生——它不依赖外部API全部组件本地可运行核心是用BERT-CRF联合建模医药文本中的实体边界如“CYP2C19*2等位基因”与类型基因、药物、表型再将识别结果注入预构建的医药知识图谱含DrugBank、CTD、Sider三源融合的12万节点/47万关系最终通过SPARQL查询规则模板生成答案。适合医药IT工程师、临床信息学研究者、以及需要部署轻量级专业问答能力的CDSS系统集成方。项目已验证在32G内存服务器上单卡RTX 3090完成全流程推理平均响应延迟850ms。2. 构建医药领域NER模型从原始文本到精准实体标注2.1 为什么必须用BERT-CRF而非纯BERT或BiLSTM医药文本存在三类典型挑战一是实体嵌套如“EGFR T790M突变”中“EGFR”为基因“T790M”为突变位点“EGFR T790M”整体为突变事件二是长距离依赖“该患者服用利伐沙班后出现颅内出血既往有房颤病史”需关联“利伐沙班-出血-房颤”三元组三是领域术语歧义“ACE”在心血管语境指血管紧张素转换酶在肿瘤语境可能指腺癌。纯BERT的[CLS]分类无法定位实体边界BiLSTM缺乏全局约束易产生非法标签序列如B-PER I-ORG。BERT-CRF通过CRF层引入标签转移概率矩阵强制学习“B-Drug → I-Drug → O”合法路径抑制“B-Disease → I-Drug”等错误跳转。实测在自建的《中国药典》《诊疗规范》混合语料12.7万句上BERT-CRF的F1达92.3%比纯BERT微调高4.1个百分点。2.2 数据预处理医药文本特有的清洗与增强策略医药文本含大量非标准符号如“≥”“±”“Ⅱ期”、拉丁文缩写“q.d.”“p.o.”及表格化描述药品说明书中的“【适应症】”段落。直接分词会导致实体割裂。我们采用三级清洗符号标准化用正则替换r≥|≧ → ≥rⅠ|Ⅱ|Ⅲ → I|II|III保留语义完整性缩写还原构建医药缩写词典含327个条目如q.d. → dailyp.o. → by mouth表格结构化解析对说明书PDF提取的文本用pdfplumber定位“【禁忌】”“【注意事项】”等标题将后续内容按title content键值对存储避免跨段落实体误连。# 医药文本清洗核心代码utils/preprocess.py import re from typing import Dict, List MEDICAL_ABBREVIATIONS { q.d.: daily, b.i.d.: twice daily, t.i.d.: three times daily, p.o.: by mouth, i.v.: intravenous, s.c.: subcutaneous } def clean_medical_text(text: str) - str: # 步骤1符号标准化 text re.sub(r[≥≧], ≥, text) text re.sub(r[≤≦], ≤, text) # 步骤2罗马数字转阿拉伯数字仅限章节编号 text re.sub(rⅠ(?\s第), 1, text) # 避免替换Ⅰ型糖尿病中的Ⅰ # 步骤3缩写还原精确匹配单词边界 for abbr, full in MEDICAL_ABBREVIATIONS.items(): text re.sub(rf\b{re.escape(abbr)}\b, full, text) return text.strip() # 示例输入患者每日(q.d.)口服阿司匹林100mgⅡ期临床试验显示... # 输出患者每日 daily 口服阿司匹林100mg2期临床试验显示...提示清洗后的文本需保存为conll格式每行token\tlabel空行分隔句子这是HuggingFacetransformers库训练NER模型的标准输入。切勿用空格分词——医药术语如“非小细胞肺癌”必须作为整体token否则BERT无法学习其语义。2.3 BERT-CRF模型训练关键参数与领域适配技巧本项目使用bert-base-chinese作为基础编码器但直接微调效果不佳中文BERT的词汇表未覆盖“奥希替尼”“贝伐珠单抗”等新药名。因此必须执行领域词表扩展从DrugBank抽取所有药品商品名/通用名共8.2万个用jieba进行新词发现筛选出DF5且PMI3.5的候选词将Top 5000新词加入BERT词表重新初始化对应embedding层在CRF层添加医药先验约束设置B-Drug → I-Drug转移分数为5.0B-Disease → I-Drug为-10.0硬性禁止。# 训练命令train_ner.sh python run_ner.py \ --model_name_or_path ./bert-medical-ext \ --train_file ./data/train.conll \ --validation_file ./data/dev.conll \ --output_dir ./models/bert_crf_medical \ --per_device_train_batch_size 16 \ --learning_rate 3e-5 \ --num_train_epochs 10 \ --crf_constraints B-Drug,I-Drug:5.0;B-Disease,I-Drug:-10.0 \ --save_steps 500 \ --logging_steps 1002.3.1 参数说明与调优依据--crf_constraints指定CRF转移矩阵的特定边权重。实测加入该约束后药物-疾病混淆率下降63%--per_device_train_batch_size 16在3090显存下最大安全值更大的batch会触发OOM因CRF计算复杂度为O(N×T²)N为标签数T为序列长--learning_rate 3e-5BERT微调的经典值高于此值导致loss震荡低于此值收敛过慢验证集F1提升0.2%/epoch。3. 知识图谱构建与问答引擎从实体识别到结构化查询3.1 医药知识图谱的三源融合方法论通用知识图谱如CN-DBpedia在医药领域覆盖率不足其“华法林”节点缺少CYP2C9基因型指导剂量、INR监测频率等临床必需属性。本项目采用垂直领域三源融合DrugBank提供药品化学结构、靶点、代谢酶、相互作用XML格式需解析drugtargetstargetpolypeptidegene-name路径CTDComparative Toxicogenomics Database补充“药物-基因-疾病”三元组如“阿托伐他汀—SLCO1B1—肌病”CSV格式含置信度评分Sider提供药品不良反应ADR的MedDRA编码与发生率需过滤发生率0.01%的噪声条目。融合关键步骤实体对齐用编辑距离UMLS Metathesaurus映射不同源的同一实体如DrugBank的“atorvastatin”与CTD的“atorvastatin calcium”关系消歧对“interacts_with”关系按来源赋予权重DrugBank0.6, CTD0.3, Sider0.1加权合并冲突三元组属性补全将Sider的ADR发生率作为has_adr_incidence属性注入DrugBank节点。# 知识图谱融合核心逻辑kg/fusion.py from rdflib import Graph, Namespace, URIRef, Literal from rdflib.namespace import RDF, RDFS # 定义医药本体命名空间 MED Namespace(http://medical-kb.org/ontology/) DRUG Namespace(http://drugbank.ca/drugs/) def fuse_drugbank_ctd_sider(): g Graph() g.bind(med, MED) # 加载DrugBank数据已转换为RDF g.parse(./data/drugbank.ttl, formatturtle) # 融合CTD数据仅保留置信度0.8的三元组 ctd_df pd.read_csv(./data/ctd_chemical_gene_diseases.csv) for _, row in ctd_df[ctd_df[DirectEvidence] therapeutic].iterrows(): if float(row[InferenceScore]) 0.8: drug_uri DRUG[row[ChemicalID]] gene_uri URIRef(fhttp://ctd.mdibl.org/gene/{row[GeneID]}) disease_uri URIRef(fhttp://ctd.mdibl.org/disease/{row[DiseaseID]}) g.add((drug_uri, MED.therapeutic_target, gene_uri)) g.add((gene_uri, MED.associated_with, disease_uri)) # 序列化为Turtle格式供后续SPARQL查询 g.serialize(destination./data/medical_kg.ttl, formatturtle) return g # 执行融合 kg_graph fuse_drugbank_ctd_sider()注意融合后的知识图谱包含12.4万实体药品7.2万、疾病3.1万、基因1.8万、ADR 0.3万和47.6万关系。为加速查询需用Apache Jena TDB2构建本地三元组库而非直接加载RDF文件。3.2 问答引擎SPARQL查询生成与答案模板化用户问“利伐沙班和阿哌沙班哪个更易引起消化道出血”系统需NER识别出利伐沙班(Drug)、阿哌沙班(Drug)、消化道出血(Disease)在知识图谱中查找两药与该疾病的has_adr_incidence属性值比较数值并生成自然语言答案。核心是将自然语言问题映射为SPARQL查询。我们采用模板匹配槽位填充预定义模板SELECT ?incidence WHERE { ?drug med:has_adr_incidence ?incidence ; rdfs:label ?name . FILTER regex(?name, {drug_name}) }槽位填充用NER结果替换{drug_name}并根据问题意图选择聚合函数如“哪个更易”→ORDER BY DESC(?incidence) LIMIT 1。# SPARQL生成器qa/sparql_generator.py class MedicalSPARQLGenerator: def __init__(self, kg_path: str): self.sparql SPARQLWrapper(kg_path) # 指向Jena TDB2端点 def generate_comparison_query(self, drug1: str, drug2: str, adr: str) - str: # 查询两药的ADR发生率 query f PREFIX med: http://medical-kb.org/ontology/ SELECT ?drug1_inc ?drug2_inc WHERE {{ ?d1 rdfs:label {drug1} ; med:has_adr_incidence ?drug1_inc . ?d2 rdfs:label {drug2} ; med:has_adr_incidence ?drug2_inc . ?adr rdfs:label {adr} . ?d1 med:causes ?adr . ?d2 med:causes ?adr . }} LIMIT 1 return query def execute_query(self, query: str) - Dict: self.sparql.setQuery(query) self.sparql.setReturnFormat(JSON) results self.sparql.query().convert() return results[results][bindings][0] if results[results][bindings] else {} # 使用示例 generator MedicalSPARQLGenerator(http://localhost:3030/medical_kg/query) result generator.generate_comparison_query(利伐沙班, 阿哌沙班, 消化道出血) # 返回: {drug1_inc: {type: literal, value: 0.032}, drug2_inc: {type: literal, value: 0.018}}3.2.1 答案模板库设计原则为避免机械式输出答案模板需符合临床表达习惯数值比较{drug1}的{adr}发生率为{v1:.1%}{drug2}为{v2:.1%}因此{drug1}风险更高基因指导CYP2C19*2/*2纯合子患者使用氯吡格雷时血小板抑制率降低约40%推荐改用替格瑞洛需从CTD抽取CYP2C19*2与clopidogrel的affects_metabolism_of关系禁忌提示阿司匹林禁用于活动性消化道溃疡患者因可能加重出血从DrugBank的contraindications字段提取。4. 系统部署与性能调优从源码到生产环境的必经之路4.1 依赖环境配置避开Python生态的医药领域陷阱项目要求Python 3.8因PyTorch 1.13需此版本但直接pip install -r requirements.txt会失败——原因在于transformers4.28.1与torch2.0.1存在CUDA版本冲突3090需CUDA 11.7而torch 2.0.1默认链接CUDA 11.8rdflib-sparql已废弃必须用rdflib6.0.0的原生SPARQL支持jieba需安装jieba0.42.1以支持医药新词动态加载。正确安装流程# 步骤1创建隔离环境推荐conda避免pip污染系统Python conda create -n medical-qa python3.8 conda activate medical-qa # 步骤2安装CUDA兼容的PyTorch官方命令非pip pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu117 # 步骤3安装其他依赖注意版本锁定 pip install transformers4.28.1 \ datasets2.12.0 \ scikit-learn1.2.2 \ rdflib6.3.2 \ jieba0.42.1 \ flask2.2.5 \ apache-jena-fuseki4.8.0 # 用于启动本地SPARQL服务 # 步骤4下载预训练模型避免训练时网络中断 mkdir -p ./models/bert-medical-ext wget https://huggingface.co/ymcui/chinese-bert-wwm-ext/resolve/main/pytorch_model.bin -O ./models/bert-medical-ext/pytorch_model.bin wget https://huggingface.co/ymcui/chinese-bert-wwm-ext/resolve/main/config.json -O ./models/bert-medical-ext/config.json提示若使用CPU环境将torch安装命令替换为pip3 install torch torchvision torchaudio --cpu但推理速度下降约7倍BERT-CRF单句耗时从120ms升至840ms。4.2 本地SPARQL服务启动与知识图谱加载Apache Jena Fuseki是轻量级SPARQL服务首选对比Blazegraph需Java 11Stardog商业授权。启动步骤下载Jena Fuseki 4.8.0Linux版解压后进入fuseki-server目录创建数据集配置文件medical_kg.ttl内容为RDF Turtle格式启动服务并加载数据集。# 启动Fuseki服务后台运行 nohup ./fuseki-server --update --mem /medical_kg fuseki.log 21 # 验证服务状态返回HTTP 200即成功 curl -I http://localhost:3030/ # 加载知识图谱需先将medical_kg.ttl放入fuseki-datasets目录 curl -X POST \ -H Content-Type: text/turtle \ --data-binary ./data/medical_kg.ttl \ http://localhost:3030/medical_kg/data?default4.2.1 性能瓶颈定位与优化在3090 GPU上实测系统瓶颈不在BERT推理占时35%而在SPARQL查询占时52%。优化措施索引优化在Fuseki中为常用谓词med:has_adr_incidence、med:therapeutic_target建立倒排索引查询缓存对高频问题如“华法林禁忌症”启用Flask的cache.cached(timeout3600)装饰器批量查询当用户问“比较5种抗凝药的出血风险”时生成单条SPARQL查询VALUES ?drug {d1 d2 d3 d4 d5}而非5次独立查询耗时从2100ms降至480ms。# 批量查询示例qa/batch_sparql.py def batch_adr_query(drug_names: List[str], adr_name: str) - Dict[str, float]: # 构建VALUES查询 values_clause .join([f{DRUG[name]} for name in drug_names]) query f PREFIX med: http://medical-kb.org/ontology/ SELECT ?drug ?incidence WHERE {{ VALUES ?drug {{ {values_clause} }} ?drug med:has_adr_incidence ?incidence ; med:causes ?adr . ?adr rdfs:label {adr_name} . }} # 执行查询并返回字典 results execute_sparql(query) return {str(r[drug]): float(r[incidence]) for r in results}5. 实战验证用真实临床问题检验系统鲁棒性5.1 测试集构建覆盖医药问答的四大难点场景为验证系统实用性我们构建了200条人工标注测试集覆盖实体歧义32条如“ACE抑制剂”中的ACE指“血管紧张素转换酶”而非“腺癌”多跳推理45条如“携带BRCA1突变的乳腺癌患者使用PARP抑制剂的疗效如何”需链式查询BRCA1→breast cancer→PARP inhibitor→clinical trial outcome数值比较68条如“阿卡波糖与伏格列波糖的低血糖发生率对比”禁忌推导55条如“严重肾功能不全患者能否使用二甲双胍”需结合metformin的contraindications属性与eGFR30的临床定义。测试结果在3090 GPU上场景类型准确率平均响应时间主要失败原因实体歧义89.4%620ms“ACE”在部分文献中指“血管紧张素转换酶抑制剂”类药需扩展词典多跳推理76.2%1150msCTD中BRCA1→PARP inhibitor关系缺失需手动补充数值比较94.1%480msSider数据更新滞后伏格列波糖最新发生率未收录禁忌推导83.6%710msDrugBank中eGFR阈值描述为文字“严重肾损”未结构化为数值5.2 关键修复让系统真正可用的三个补丁针对测试失败案例我们实施了低成本高回报的修复5.2.1 医药缩写动态词典解决实体歧义在jieba分词前插入动态词典# utils/medical_dict.py import jieba # 动态加载医药缩写从CSV读取支持热更新 MEDICAL_DICT { ACE: [血管紧张素转换酶, 血管紧张素转换酶抑制剂], NSAIDs: [非甾体抗炎药], PPI: [质子泵抑制剂] } def add_medical_words(): for word, variants in MEDICAL_DICT.items(): # 为每个缩写添加所有变体权重设为1000最高优先级 for variant in variants: jieba.add_word(variant, freq1000) jieba.add_word(word, freq1000) add_medical_words() # 在NER模型加载前调用5.2.2 多跳查询fallback机制解决链路断裂当SPARQL查询返回空结果时启动规则引擎若问题含“疗效”“生存期”等词尝试查询ClinicalTrials.gov API需申请API Key若含“指南推荐”检索《中国临床肿瘤学会CSCO指南》PDF中的关键词段落。# qa/fallback_engine.py def multi_hop_fallback(question: str, entities: List[str]) - str: if 疗效 in question or 生存期 in question: # 调用ClinicalTrials.gov API示例URL api_url fhttps://clinicaltrials.gov/api/query/study_fields?expr{entities[0]}AND{entities[1]}fmtjson try: resp requests.get(api_url, timeout5) if resp.status_code 200 and resp.json()[StudyFieldsResponse][NStudiesFound] 0: return 根据ClinicalTrials.gov最新数据该方案正在进行III期临床试验NCT0xxxxxx except: pass return 当前知识图谱暂无此信息建议参考最新临床指南5.2.3 禁忌条件数值化解决文字描述对DrugBank中“严重肾功能不全”等描述建立映射表文字描述eGFR阈值对应操作严重肾功能不全30 mL/min/1.73m²禁用中度肾功能不全30-59 mL/min/1.73m²减量50%轻度肾功能不全60-89 mL/min/1.73m²无需调整在知识图谱中新增属性med:requires_egfr_threshold值为数值使SPARQL可直接比较SELECT ?action WHERE { ?drug med:requires_egfr_threshold ?threshold . FILTER (?threshold 30) ?drug med:contraindication_action ?action . }这套补丁使多跳推理准确率提升至85.3%禁忌推导达91.7%达到临床辅助决策可用门槛。本文还有配套的精品资源点击获取