首页 / 资讯中心 / 文章详情

肝病知识图谱问答系统实战:从压缩包到KBQA全链路

肝病知识图谱问答系统实战:从压缩包到KBQA全链路 ★ FEATURED ARTICLE
简介这份资源是面向医疗信息化与知识图谱方向的开发者、学生及NLP爱好者的肝病问答系统完整源码包基于Python实现围绕肝病知识图谱完成实体关系构建、自然语言问题解析与答案检索适合作为知识图谱入门到进阶的实战参考。压缩包共28个文件约9.31MB以py脚本为核心涵盖图谱构建、问题分类、问答主流程与答案搜索等模块并配有txt词典与数据文件、xml与json配置及结构化数据、iml工程配置、md说明文档和license授权文件目录按代码、数据、词典分层组织便于按模块阅读与二次开发。目前已有115人学习下载。通过研读可掌握知识图谱节点与关系的建模思路、SPARQL查询与图结构操作、NLP语义解析及预训练模型在问答中的应用同时理解数据清洗、实体识别与关系抽取的工程流程为医疗领域智能问答项目提供可复用的实现范式。1. 肝病知识图谱问答系统从压缩包到能跑通的 KBQA 全链路肝病领域的知识问答有个尴尬现状患者问“转氨酶高是不是一定得肝炎”通用搜索引擎返回的是碎片化科普而医院内部的知识库又不对个人开放。QASystemOnHepatopathyKG-master 这个压缩包恰好卡在中间——它用 Python 把肝病实体、症状、药物、治疗方案串成一张知识图谱再套一层自然语言问答壳子让“乙肝吃什么药”“肝硬化早期什么表现”这类问题能直接命中结构化答案。整个包不大核心就是十来个 py 文件加一份 dict 词典但麻雀虽小从实体抽取、关系建图到问句解析、答案检索的链路全齐了。适合谁正在做医疗 NLP 课程设计的学生、想拿知识图谱练手 KBQA 的 Python 开发者以及需要快速搭一个垂直领域问答原型的工程师。它不依赖 GPU 集群一台普通笔记本就能把图建起来、把问题答出来。2. 拆包先看骨架文件清单与模块职责怎么对应2.1 从目录树反推系统分层拿到压缩包别急着 pip install先把文件按职责分堆。这个项目的文件命名很直白几乎每个 py 文件对应一个处理阶段。我一般会先跑一遍printFileDirectoryTree.py把目录结构打印出来心里有个分层图。python printFileDirectoryTree.py这个脚本逻辑很简单就是递归遍历当前目录用缩进展示层级。跑完你会看到类似这样的结构不同解压路径下名字略有差异QASystemOnHepatopathyKG-master/ ├── build_medicalgraph.py # 建图主脚本 ├── question_classifier.py # 问句分类 ├── question_parser.py # 问句解析 ├── answer_search.py # 答案检索 ├── chatbot_graph.py # 问答入口 ├── prepare_data/ # 原始数据 ├── data/ # 处理后的数据 ├── dict/ # 词典 ├── LICENSE └── README.md逻辑说明build_medicalgraph.py负责把结构化数据灌进 Neo4j是整条链路的起点question_classifier.py和question_parser.py是 NLP 侧的两把刀一个判断问句类型一个把问句拆成可查询的槽位answer_search.py拿着解析结果去图数据库里捞答案chatbot_graph.py是总调度。prepare_data和data的区别在于前者是原始素材后者是清洗后可直接喂给建图脚本的中间态。dict目录里放的是领域词典用于分词和实体匹配。2.2 环境依赖与 Neo4j 的版本选择这个项目对 Neo4j 版本有隐性要求。代码里用的是 py2neo 做图数据库交互而 py2neo 2021.x 之后的版本对 Neo4j 4.x 的兼容性有变化。我实测下来Neo4j 3.5.x 配 py2neo 4.3 最稳Neo4j 4.x 需要改连接串的认证方式。# 建议的依赖安装方式 pip install py2neo4.3.0 pip install jieba0.42.1 pip install pandas1.3.5参数说明py2neo 4.3.0 的Graph对象初始化时password参数在 Neo4j 3.5 默认是neo4j4.x 则强制要求首次登录改密码。如果你装的是 Neo4j 4.x建图脚本里的Graph(http://localhost:7474, usernameneo4j, password你的密码)这行要同步改。jieba 版本别用太新的0.42.1 对自定义词典的加载行为最符合这个项目的预期新版 jieba 在某些边界分词上会多切一刀导致实体匹配失败。提示Neo4j 启动后先访问http://localhost:7474确认能登录再跑建图脚本。图数据库没起来后面所有步骤都是空转。3. 建图实战从 prepare_data 到 Neo4j 的完整灌库流程3.1 数据预处理实体和关系的结构化prepare_data目录里的原始数据通常是 CSV 或 JSON 格式字段包括疾病名、症状、药物、检查项等。建图前需要把这些扁平数据转成“实体-关系-实体”的三元组。项目里build_medicalgraph.py承担了这个转换和写入的职责。# build_medicalgraph.py 核心逻辑节选 from py2neo import Graph, Node, Relationship # 连接图数据库 graph Graph(http://localhost:7474, usernameneo4j, passwordneo4j) # 创建疾病节点 def create_disease_node(name): node Node(Disease, namename) graph.create(node) return node # 创建症状节点并建立关系 def create_symptom_relation(disease_node, symptom_name): symptom_node Node(Symptom, namesymptom_name) rel Relationship(disease_node, HAS_SYMPTOM, symptom_node) graph.create(rel)逻辑说明Node的第一个参数是标签Label相当于 Neo4j 里的“表名”第二个参数是属性键值对。Relationship的第一个参数是起始节点第二个是关系类型第三个是终止节点。这段代码每调用一次就向图里插入一个节点或一条边。实际项目中数据量大的话要改成批量提交否则逐条graph.create会慢得让你怀疑人生。参数说明HAS_SYMPTOM这个关系类型名是自定义的你可以改成出现症状或SYMPTOM_OF只要前后一致就行。但注意 Neo4j 关系类型不支持中文空格用下划线或驼峰命名更稳妥。3.2 批量写入与索引优化逐条写入在数据量超过 500 条时就会明显卡顿。我一般会改成事务批量提交同时给常用查询字段建索引。# 批量写入示例 tx graph.begin() for disease in disease_list: disease_node Node(Disease, namedisease[name]) tx.create(disease_node) for symptom in disease[symptoms]: symptom_node Node(Symptom, namesymptom) tx.create(symptom_node) tx.create(Relationship(disease_node, HAS_SYMPTOM, symptom_node)) tx.commit() # 建索引加速查询 graph.run(CREATE INDEX ON :Disease(name)) graph.run(CREATE INDEX ON :Symptom(name))逻辑说明graph.begin()开启一个事务所有tx.create操作先缓存在事务里最后tx.commit()一次性提交。这样比逐条提交快一个数量级。索引语句用 Cypher 执行CREATE INDEX ON :Disease(name)表示给 Disease 标签的 name 属性建索引后续按疾病名查节点时能走索引而不是全图扫描。参数说明事务批量提交的批次大小建议控制在 1000 条节点以内太大容易内存溢出。索引建完后用:schema命令在 Neo4j 浏览器里确认索引状态变成 ONLINE 才算生效。注意建图脚本跑完后在 Neo4j 浏览器里执行MATCH (n) RETURN count(n)确认节点总数。如果数字是 0说明连接或提交环节出了问题先查 Neo4j 日志再回头改代码。4. 问句解析与答案检索NLP 侧的两个核心模块4.1 question_classifier.py 的问句分类逻辑用户输入“乙肝有哪些症状”和“什么药能治肝硬化”在系统里走的是不同分支。question_classifier.py干的就是这件事——判断问句属于哪一类。项目里用的是基于规则和关键词匹配的方法没有上深度学习模型。# question_classifier.py 分类逻辑示意 import jieba class QuestionClassifier: def __init__(self): self.disease_words set() self.symptom_words set() # 从 dict 目录加载领域词典 self.load_dict() def classify(self, question): # 分词后匹配关键词 words jieba.lcut(question) for word in words: if word in self.disease_words: return disease_symptom # 问疾病症状 if word in self.symptom_words: return symptom_disease # 问症状对应疾病 return unknown逻辑说明jieba.lcut把问句切成词列表然后逐个比对领域词典。如果命中疾病词就归类为“问疾病相关”命中症状词归类为“问症状相关”。这种规则法在垂直领域其实够用因为肝病领域的实体边界相对清晰不像开放域那样歧义丛生。参数说明load_dict方法里加载的词典文件放在dict目录下通常是每行一个词。词典的质量直接决定分类准确率建议把肝病相关的疾病名、症状名、药物名、检查项都收进去同义词用|分隔或建映射表。4.2 answer_search.py 的 Cypher 查询构造分类和解析完成后answer_search.py负责把槽位拼成 Cypher 语句去 Neo4j 里捞答案。# answer_search.py 查询构造示意 class AnswerSearcher: def __init__(self, graph): self.graph graph def search_disease_symptom(self, disease_name): cypher MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE d.name $name RETURN s.name AS symptom result self.graph.run(cypher, namedisease_name).data() return [row[symptom] for row in result]逻辑说明Cypher 语句里$name是参数占位符通过graph.run的第二个参数传入这样能避免字符串拼接带来的注入风险。MATCH模式(d:Disease)-[:HAS_SYMPTOM]-(s:Symptom)表示从疾病节点沿 HAS_SYMPTOM 关系找到症状节点。RETURN s.name只返回症状名减少数据传输量。参数说明d.name $name里的$name是 py2neo 的参数化查询语法不同版本可能用{name}或$name以你装的 py2neo 版本文档为准。如果查询返回空列表先确认图里确实有这个疾病节点再检查关系类型名是否写错。提示调试 Cypher 时直接在 Neo4j 浏览器里跑一遍确认能出结果再写进 Python 代码。浏览器里能跑通、代码里跑不通九成是参数传递格式问题。5. 避坑与排查跑通这个项目最容易翻车的五个地方5.1 现象建图脚本报 ConnectionRefusedError原因Neo4j 服务没启动或者端口不是默认的 7474。有些安装方式会把 HTTP 端口改成 7475 或别的。解决先neo4j status确认服务在跑再检查conf/neo4j.conf里的dbms.connector.http.listen_address配置。代码里的连接串端口要和配置文件一致。5.2 现象问句分类永远返回 unknown原因dict目录下的词典文件没加载成功或者词典编码不是 UTF-8。Windows 下用记事本编辑过的词典文件经常带 BOM 头jieba 加载时会出问题。解决用open(file, encodingutf-8-sig)读取词典或者在 Linux 下用file命令确认编码。词典内容每行一个词不要有多余空格。5.3 现象Cypher 查询返回空结果但图里明明有数据原因关系类型名大小写不一致。Neo4j 的关系类型是大小写敏感的建图时写HAS_SYMPTOM查询时写has_symptom就匹配不上。解决在 Neo4j 浏览器里执行CALL db.relationshipTypes()列出所有关系类型复制粘贴到代码里别手敲。5.4 现象chatbot_graph.py 跑起来后输入问题没反应原因主循环里的输入编码问题或者question_parser.py解析出的槽位为空导致后续查询被跳过。解决在chatbot_graph.py的输入处加一行print(repr(question))确认读到的字符串没有乱码。再在question_parser.py的解析结果处打印槽位字典看是不是空的。5.5 现象Neo4j 内存溢出建图到一半卡死原因默认堆内存配置太小肝病图谱虽然不大但如果你把症状、药物、检查项全展开节点数可能上万默认 512MB 堆内存不够用。解决改conf/neo4j.conf里的dbms.memory.heap.max_size调到 2G 或 4G重启 Neo4j 再跑建图脚本。6. 进阶技巧把规则问答升级成可评估的 KBQA 流水线规则法跑通之后你很快会碰到天花板——用户换个问法就答不上来。比如“我转氨酶偏高可能是什么病”和“转氨酶高提示啥”规则词典覆盖不全就歇菜。我一般会在这个项目基础上加一层轻量级的语义匹配不换框架只加一个相似度兜底。具体做法是把question_classifier.py的分类结果作为主路径当返回unknown时走一个基于字符级 TF-IDF 的相似问句检索。预先准备一批“标准问句-意图”映射用 jieba 分词后算余弦相似度取 top1 且阈值超过 0.6 的作为兜底意图。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # 标准问句库 standard_questions [ 乙肝有哪些症状, 肝硬化怎么治疗, 转氨酶高是什么病 ] intents [disease_symptom, disease_treatment, symptom_disease] vectorizer TfidfVectorizer(tokenizerjieba.lcut, analyzerword) tfidf_matrix vectorizer.fit_transform(standard_questions) def fallback_classify(question): q_vec vectorizer.transform([question]) sims cosine_similarity(q_vec, tfidf_matrix)[0] best_idx sims.argmax() if sims[best_idx] 0.6: return intents[best_idx] return unknown逻辑说明TfidfVectorizer把问句转成 TF-IDF 向量cosine_similarity算用户问句和标准问句的余弦相似度。阈值 0.6 是经验值低于这个值说明问句和标准库里的都不像强行匹配反而会答非所问。tokenizerjieba.lcut让向量化走中文分词而不是默认的字符级切分。参数说明analyzerword配合tokenizer使用确保按词而不是按字计算 TF-IDF。标准问句库不用太大每个意图覆盖 5 到 10 种常见问法就够关键是问法要多样别都是同一个句式。验证方法准备 20 条没在标准库里的问句人工标注意图跑一遍看准确率。如果低于 70%优先扩充标准问句库而不是调阈值。阈值调太高会漏答调太低会错答0.6 到 0.7 之间比较平衡。从那以后我每次拿到这种规则型 KBQA 项目都会先跑一遍全链路确认基线能通再加兜底模块最后用一批“脏问句”做回归测试。规则系统最怕的不是答不上来而是答错还理直气壮。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站