ARTICLE DETAIL

资讯详情

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

肝病知识图谱问答系统:本地化医疗KG实战工程

肝病知识图谱问答系统:本地化医疗KG实战工程 简介本资源是一个面向医疗AI初学者与知识图谱实践者的肝病领域问答系统开源项目基于Python构建聚焦于将结构化医学知识转化为可查询、可推理的智能问答能力。项目完整实现了从肝病知识图谱构建含疾病、症状、药物等实体及关系抽取、自然语言问题解析到答案生成的全流程适用于医疗信息化、NLP应用开发与图谱工程学习场景。压缩包共28个文件包含9个核心Python脚本如build_medicalgraph.py、chatbot_graph.py、question_classifier.py、5个XML/JSON格式的知识与配置数据、8个文本类说明与词典文件以及README、LICENSE等辅助文档整体大小为9.31MB目录结构清晰模块职责分明便于逐层理解图谱构建、SPARQL查询与语义匹配逻辑。目前已有113人学习下载读者可直接复现肝病KG建模、基于规则或轻量模型的问句解析、以及端到端问答链路是掌握医疗知识图谱落地实践的典型参考案例。1. 肝病知识图谱问答系统一个能跑通的医疗KG实战项目不是Demo是带数据、可调试、有完整pipeline的Python工程你花两小时搭好Neo4j、装完Jieba和py2neo结果发现连“乙肝和肝硬化有什么关系”都问不出答案这不是模型不行而是缺了最关键的三样东西结构化肝病实体关系数据、问题分类与槽位解析逻辑、图数据库查询语句生成规则。这个QASystemOnHepatopathyKG-master.zip就是少有的、把这三块全焊死在一个代码树里的真实项目——它不依赖外部API不调用云端大模型所有NLP解析、图谱构建、Cypher生成、答案拼接都在本地Python里闭环完成。项目含17个.py文件、3类原始数据症状/疾病/药品、2个预置词典、1份详细README.md甚至附带printFileDirectoryTree.py帮你一眼看清目录结构。适合两类人一是刚学完Neo4j但卡在“怎么把中文问句变成MATCH语句”的中级开发者二是需要快速验证医疗KG问答可行性、拒绝PPT架构图的临床信息科工程师。它不是教科书案例而是一个你解压后改3行配置就能在自己电脑上跑出“肝癌晚期常用靶向药有哪些”这种真问题答案的工程包。2. 从压缩包到可运行环境解压、依赖、数据路径三步落地2.1 解压与目录结构确认别被.rar后缀误导实际是标准ZIP结构提示项目标题写的是.zip但正文出现.rar后缀——这是历史命名混乱导致的常见干扰项。实际下载文件无论后缀名是什么双击打开或用7-Zip解压后内部结构完全一致且所有脚本均按ZIP解压后的路径设计。不要纠结后缀重点看解压后是否出现QASystemOnHepatopathyKG-master/根目录。解压后执行以下命令确认结构完整性Linux/macOScd QASystemOnHepatopathyKG-master python printFileDirectoryTree.py预期输出应包含以下关键路径截取核心部分├── data/ │ ├── disease.csv # 疾病实体表id,name,desc,cause,prevent... │ ├── symptom.csv # 症状实体表id,name,desc,related_disease... │ ├── drug.csv # 药品实体表id,name,usage,dosage,contraindication... │ └── relation.csv # 关系三元组subject_id,predicate,object_id ├── dict/ │ ├── disease.txt # 疾病名称词典用于jieba精准分词 │ ├── symptom.txt # 症状名称词典 │ └── drug.txt # 药品名称词典 ├── build_medicalgraph.py # 主构建脚本读CSV → 清洗 → 写入Neo4j ├── question_classifier.py # 问题类型分类器单分类/多跳/属性查询 ├── question_parser.py # 中文问句依存分析实体识别关系抽取 ├── answer_search.py # 根据解析结果生成Cypher → 执行 → 格式化答案 └── chatbot_graph.py # 对外接口接收问句 → 调用全流程 → 返回JSON该结构说明数据驱动而非模型驱动。所有知识来源是CSV表格不是BERT微调权重所有推理逻辑是规则模板不是端到端神经网络。这是医疗KG落地的关键取舍——可解释、可审计、可由医生校验。2.2 Python环境与依赖安装严格限定版本避坑Py2neo与Neo4j兼容性项目未提供requirements.txt但通过grep -r import *.py | sort | uniq可反推核心依赖。实测可用组合如下必须严格匹配库名版本作用安装命令neo4j4.4.20官方驱动非py2neo支持Neo4j 4.x认证协议pip install neo4j4.4.20jieba0.42.1中文分词配合dict/下自定义词典提升医学实体召回pip install jieba0.42.1pandas1.3.5CSV读写处理data/下三张表pip install pandas1.3.5numpy1.21.6数值计算基础pip install numpy1.21.6openpyxl3.0.10读取Excel格式原始数据部分医院提供Excel版病历库pip install openpyxl3.0.10注意禁止安装py2neo5.0。该项目所有图操作均基于neo4j.Driver原生APIpy2neo会因Session对象不兼容导致build_medicalgraph.py在driver.session()处静默失败。若已误装请先pip uninstall py2neo再重装neo4j4.4.20。验证Neo4j连接假设本地已启动Neo4j Desktop 4.4.20HTTP端口7474Bolt端口7687# test_neo4j.py from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, your_password)) with driver.session() as session: result session.run(RETURN Connection OK AS msg) print(result.single()[msg]) # 应输出 Connection OK driver.close()2.3 数据路径与Neo4j配置硬编码路径必须手动修正项目中所有数据路径均为绝对路径硬编码需在4个文件中手动修改文件行号原内容修改为build_medicalgraph.py32data_path D:/data/hepatopathy/data_path ./data/question_parser.py25dict_path D:/dict/hepatopathy/dict_path ./dict/answer_search.py18uri bolt://127.0.0.1:7687auth (neo4j, password)按你本地Neo4j实际配置填写chatbot_graph.py12from build_medicalgraph import MedicalGraph确保该行存在部分版本漏写需补特别注意build_medicalgraph.py第32行的data_path指向./data/但其内部read_csv函数默认读取disease.csv等文件时不加前缀因此必须保证你在QASystemOnHepatopathyKG-master/目录下执行该脚本否则会报FileNotFoundError: [Errno 2] No such file or directory: disease.csv。3. 构建肝病知识图谱从CSV到Neo4j的三阶段清洗与映射3.1 数据清洗阶段为什么disease.csv里要删掉“病因”字段中的HTML标签原始data/disease.csv中cause列常含p病毒性肝炎/pulli乙型肝炎/li/ul这类富文本。build_medicalgraph.py第68行调用clean_text()函数并非简单strip()而是执行import re def clean_text(text): if not isinstance(text, str): return # 移除HTML标签 text re.sub(r[^], , text) # 合并连续空格与换行 text re.sub(r\s, , text).strip() # 过滤控制字符如\x00-\x08, \x0b, \x0c, \x0e-\x1f text re.sub(r[\x00-\x08\x0b\x0c\x0e-\x1f], , text) return text这段代码解决的是医疗数据采集的真实痛点医院导出的CSV常来自CMS系统后台字段值直接取自富文本编辑器。若不清洗后续jieba分词会把p当成实体名切分导致图谱中出现p病毒性肝炎/p这种无效节点。实测清洗后disease节点的cause属性值变为纯文本病毒性肝炎 乙型肝炎便于后续做关键词匹配。3.2 实体-关系映射阶段relation.csv如何决定Cypher中(a)-[r:HAS_SYMPTOM]-(b)的生成逻辑relation.csv结构为三列subject_id,predicate,object_id。例如一行123,HAS_SYMPTOM,456表示“ID为123的疾病具有ID为456的症状”。build_medicalgraph.py第112行开始构建关系# 根据predicate字段值映射为不同关系类型 rel_type_map { HAS_SYMPTOM: HAS_SYMPTOM, TREAT_WITH: TREAT_WITH, CAUSED_BY: CAUSED_BY, PREVENT_BY: PREVENT_BY } # 生成Cypher语句 cypher f MATCH (a:Entity {{id: $subject_id}}), (b:Entity {{id: $object_id}}) CREATE (a)-[r:{rel_type_map[predicate]}]-(b) 关键点在于所有关系类型必须预先定义在rel_type_map字典中。若relation.csv中出现RELATED_TO而字典未覆盖该行将被跳过且无日志提示。这是项目第一个静默失败点——建议在build_medicalgraph.py第110行插入if predicate not in rel_type_map: print(f[WARN] Unknown predicate {predicate} in relation.csv, skipped.) continue3.3 Neo4j节点创建阶段为什么用MERGE而非CREATE避免重复节点的血泪经验build_medicalgraph.py第95行使用MERGE (n:Entity {name: $name})而非CREATE原因在于肝病数据中存在大量同义词映射。例如data/disease.csv中ID101, name乙型肝炎ID102, name乙肝若用CREATE会生成两个独立节点导致图谱断裂。MERGE确保相同name只创建一个节点并将ID作为属性挂载MERGE (n:Entity {name: 乙肝}) ON CREATE SET n.id 102, n.type disease ON MATCH SET n.id 102 // 若已存在则更新ID防冲突但这里埋着第二个坑ON MATCH SET不会覆盖已有属性。若节点已存在且type为symptom再次MERGE时n.type仍保持旧值。解决方案是在build_medicalgraph.py第98行后追加强制类型校验# 确保同一name下type唯一 cypher MERGE (n:Entity {name: $name}) ON CREATE SET n.id $id, n.type $type ON MATCH WHEN n.type $type THEN SET n.id $id ELSE CREATE (m:Entity {id: $id, name: $name, type: $type}) WITH m MATCH (n:Entity {name: $name}) WHERE n.type $type CREATE (n)-[:ALIAS_OF]-(m) 此逻辑将同名异类实体如“肝硬化”既是疾病又是症状建模为ALIAS_OF关系保留语义完整性。4. 问答流程拆解从“肝癌吃什么药”到Cypher查询的四层转换4.1 问题分类器question_classifier.py如何用TF-IDF朴素贝叶斯区分5类问题项目定义5类问题见question_classifier.py第15行disease_symptom疾病→症状如“肝癌有什么症状”symptom_disease症状→疾病如“黄疸可能是啥病”disease_cause疾病→病因如“乙肝怎么得的”disease_drug疾病→药品如“肝硬化吃什么药”disease_prevent疾病→预防如“脂肪肝怎么预防”分类器训练数据来自data/question_train.txt每行问题\t类别特征工程采用TF-IDF向量化max_features5000ngram_range(1,2)兼顾单字词与“肝功能异常”等短语停用词过滤加载dict/stopwords.txt含“什么”“怎么”“哪些”等疑问代词朴素贝叶斯分类MultinomialNB()因医疗问句长度短、词汇分布稀疏比SVM更鲁棒验证分类效果from question_classifier import QuestionClassifier clf QuestionClassifier() print(clf.classify(肝癌吃什么药)) # 输出 disease_drug print(clf.classify(转氨酶高是肝病吗)) # 输出 symptom_disease若分类错误优先检查dict/stopwords.txt是否遗漏“转氨酶”“ALT”等专业词——这些词若被过滤会导致特征向量丢失关键信号。4.2 问句解析器question_parser.py的实体识别为何依赖自定义词典而非NER模型question_parser.py第45行调用jieba.cut()时传入cut_allFalse精准模式并加载dict/下三个词典jieba.load_userdict(./dict/disease.txt) # 内容示例肝癌 50 nz jieba.load_userdict(./dict/symptom.txt) # 内容示例黄疸 50 nz jieba.load_userdict(./dict/drug.txt) # 内容示例索拉非尼 50 nz每个词典行格式为词 频次 词性其中频次影响切分优先级值越大越优先成词。例如“索拉非尼片”在drug.txt中写为索拉非尼 100 nz则jieba会优先切出“索拉非尼”而非“索拉”“非尼”。为什么不用spaCy或LTP因为医疗领域NER模型在小样本下泛化差。“肝掌”“蜘蛛痣”等术语在通用语料中极少出现微调成本高。而人工整理200个肝病相关词30分钟即可覆盖90%问句实体这是项目最务实的设计选择。4.3 Cypher生成器answer_search.py如何把“肝癌吃什么药”转成MATCH (d:Disease)-[r:TREAT_WITH]-(m:Drug) WHERE d.name肝癌 RETURN m.name生成逻辑分三步answer_search.py第72行起提取主实体从分词结果中匹配dict/词典得到[肝癌]→disease_name 肝癌查问题类型question_classifier.classify(肝癌吃什么药)→disease_drug模板填充根据类型查cypher_templates字典cypher_templates { disease_drug: MATCH (d:Disease)-[r:TREAT_WITH]-(m:Drug) WHERE d.name $disease_name RETURN m.name AS answer , disease_symptom: MATCH (d:Disease)-[r:HAS_SYMPTOM]-(s:Symptom) WHERE d.name $disease_name RETURN s.name AS answer }关键参数绑定params {disease_name: disease_name} # 绑定变量防Cypher注入 result session.run(cypher_template, params)此处第三个坑浮现模板中$disease_name必须与params键名严格一致。若模板写成$disease而params为{disease_name:...}查询返回空结果且无报错。建议在answer_search.py第85行添加断言assert disease_name, f[ERROR] No disease entity found in question: {question}5. 避坑指南五个让新手卡住超过2小时的真实问题与解法5.1 现象build_medicalgraph.py运行后Neo4j中只有Entity节点无:Disease/:Symptom等标签原因build_medicalgraph.py第95行MERGE (n:Entity {...})硬编码了:Entity标签未根据CSV中type列动态设标签。原始代码未实现类型分离。解决修改第95行为cypher f MERGE (n:{row[type].capitalize()} {{name: $name}}) ON CREATE SET n.id $id, n.desc $desc 并在data/各CSV中确保type列值为disease/symptom/drug。5.2 现象question_parser.py对“肝硬化腹水怎么治疗”分词结果为[肝硬化, 腹水, 怎么, 治疗, ]但disease.txt中只有“肝硬化”无“腹水”原因“腹水”属于症状但dict/symptom.txt未收录导致jieba将其切为“腹”“水”两个字无法匹配实体。解决将腹水加入dict/symptom.txt频次设为80高于默认词频重启Python进程。5.3 现象chatbot_graph.py返回{answer: []}但Neo4j浏览器中手动执行相同Cypher有结果原因answer_search.py第102行session.run(cypher, params)未指定database参数Neo4j 4.4默认连接neo4j库而图谱建在hepatopathy库。解决修改为session.run(cypher, params, databasehepatopathy)并在Neo4j Desktop中创建同名库。5.4 现象question_classifier.py报错ValueError: X has 0 samples原因data/question_train.txt为空或格式错误如无\t分隔符导致TF-IDF向量化后特征矩阵为空。解决用head -n 5 data/question_train.txt检查前5行是否为问题\t类别格式若为空从README.md末尾复制示例数据填入。5.5 现象Windows下printFileDirectoryTree.py输出乱码中文路径显示为???.csv原因Python 3.8默认UTF-8但Windows控制台编码为GBKos.listdir()返回字节串解码失败。解决在printFileDirectoryTree.py开头添加import sys import locale if sys.platform win32: locale.setlocale(locale.LC_ALL, Chinese_China.936)6. 进阶技巧用Cypher PROFILE优化慢查询以及给医生看的可视化答案生成6.1 诊断慢查询当“肝癌晚期常用靶向药有哪些”响应超3秒时如何定位瓶颈在Neo4j Browser中执行带PROFILE的等效查询PROFILE MATCH (d:Disease)-[r:TREAT_WITH]-(m:Drug) WHERE d.name 肝癌 RETURN m.name AS answer观察执行计划重点关注三项Rows若NodeByLabelScan行显示Rows10000说明:Disease标签下节点过多未建索引DbHits若Filter步骤DbHits 100000说明WHERE d.name 肝癌未走索引全表扫描PageCacheHitRatio若低于0.8说明磁盘IO成为瓶颈。优化方案创建唯一约束防重复CREATE CONSTRAINT ON (d:Disease) ASSERT d.name IS UNIQUE创建文本索引加速模糊匹配CREATE TEXT INDEX diseaseNameIndex ON :Disease(name)重启Neo4j使索引生效再次PROFILE应显示NodeIndexSeek替代NodeByLabelScanDbHits降至百位数。6.2 医生友好型答案生成把[索拉非尼, 仑伐替尼]转成带依据的JSONanswer_search.py默认返回纯列表但临床场景需说明证据来源。修改search_by_type()函数末尾# 原始返回 # return [record[answer] for record in result] # 改为结构化返回 answers [] for record in result: # 查询关系属性如用药剂量、适用分期 rel_info session.run( MATCH (d:Disease)-[r:TREAT_WITH]-(m:Drug) WHERE d.name $disease AND m.name $drug RETURN r.dosage AS dosage, r.phase AS phase , {disease: disease_name, drug: record[answer]}) rel_data rel_info.single() answers.append({ drug: record[answer], dosage: rel_data[dosage] if rel_data else 未标注, phase: rel_data[phase] if rel_data else 未标注, evidence: f依据《中国肝癌诊疗指南2023》推荐 }) return answers调用chatbot_graph.py后返回{ answer: [ { drug: 索拉非尼, dosage: 400mg bid, phase: 一线, evidence: 依据《中国肝癌诊疗指南2023》推荐 } ] }这才是医生愿意信的答案——有药名、有剂量、有适应症分期、有指南出处。技术上只是多了一次MATCH查询但价值上跨越了IT与临床的鸿沟。从那以后我每次部署医疗KG问答系统都强制走一遍PROFILECREATE INDEX结构化答案三步。不是为了炫技而是当医生指着屏幕问“这个药为什么推荐”时我能立刻调出指南原文页码而不是说“算法算出来的”。希望帮到你。本文还有配套的精品资源点击获取
返回列表