资讯动态

Python+neo4j构建肝病问答系统:知识图谱与规则匹配实现

发布时间:2026/9/11 10:13:59 来源:尧图企业网站定制
简介基于neo4j知识图谱和规则匹配的肝病问答系统是一份可直接运行的Python毕业设计项目源码并附带博客说明文档。项目面向需要完成毕业设计、课程设计或期末大作业的高校学生同时适合对知识图谱问答感兴趣的初学者解决从零搭建医疗问答系统时缺乏完整示例的问题。资源包共27个文件以py源码为主9个辅以json数据文件、txt词典数据、xml工程文件及md说明文档压缩包约15.76MB内部按问题分类、答案搜索、知识图谱构建等功能模块划分代码含详细注释便于阅读和二次开发。资源内置疾病、药品、症状、食物等知识数据与测试样例部署简单运行稳定已有101人学习下载适合作为高分毕业设计项目参考。1. 用 Python 和 neo4j 做肝病问答系统时规则匹配比纯查询更难同一个题目放在课程设计里很多人会先扑向 Neo4j装好数据库、写几条 Cypher 查出肝病和症状的相邻关系就以为系统完成了一大半。真正回答用户问题时才发现用户说“最近经常乏力、胃口不好不会是肝硬化吧”知识图谱能给出一大堆节点和边却给不出一条能直接读的答复。这个落差说明基于 neo4j 知识图谱和规则匹配的肝病问答系统核心难点不是图数据库怎么查而是怎么把临床经验拆成可执行、可解释的规则链。这套方案适合课程设计、医学信息作业和临床辅助演示不需要 GPU不依赖外部大模型规则写在哪、为什么这样判每一步都能在源码里找到对应位置。2. neo4j 知识图谱怎么建模肝病实体、关系权重和两条建图路径2.1 节点标签和关系类型先定小别怕多肝病知识图谱没必要套用复杂本体。我做这套系统时只保留六个实体标签标签实例在问答里的用途Disease肝硬化、慢性乙型肝炎问题主语Symptom腹水、黄疸、乏力症状查询Test肝功能、腹部超声检查建议Drug恩替卡韦、甘草酸制剂治疗推荐Diet低盐饮食、戒酒生活建议Population肝炎病史人群风险提示关系则围绕“疾病”展开HAS_SYMPTOM、NEEDS_TEST、TREATS、SUGGEST_DIET、RISK_FOR。关系上带属性比如HAS_SYMPTOM.weight表示症状对疾病的区分度数值范围 0 到 1后续的规则匹配和查询排序会用到。所有节点统一用name做唯一键避免同一条“肝硬化-腹水”边被重复创建。注意neo4j Browser 可视化时默认只显示前 25 个标签看到“只显示 25 个标签”的提示不代表数据写丢了用MATCH (n) RETURN labels(n), count(*) ORDER BY count(*) DESC查标签统计即可。2.2 用 Cypher 的 LOAD CSV 把知识图谱灌进 neo4j如果知识表维护在 CSV 中推荐用LOAD CSV做第一次全量导入。假设 CSV 有三列disease、symptom、weight// CSV 文件必须放在 $NEO4J_HOME/import 目录下列名要和 row.xxx 对应 LOAD CSV WITH HEADERS FROM file:///liver_symptom.csv AS row MERGE (d:Disease {name: trim(row.disease)}) MERGE (s:Symptom {name: trim(row.symptom)}) MERGE (d)-[:HAS_SYMPTOM {weight: toFloat(row.weight)}]-(s);file:///指向 neo4j 安装目录下的import目录文件名里不要带中文和空格。MERGE保证幂等重复执行不会产生重复节点trim去掉数据文件里常见的首尾空格toFloat把字符串列转成数值属性。如果 CSV 体积很大先用neo4j-admin import做初始装载再配合MERGE增量补边比在 Python 里逐条CREATE快很多。2.3 用 py2neo 在 Python 里建边权重要保留问答系统需要响应式更新比如“根据新文献把某疾病的 weight 调高 0.2”这时候不要在 Cypher 里手动改。用 py2neo 的事务接口维护更方便也贴合项目的 Python 技术栈import csv from py2neo import Graph, Node, Relationship graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) tx graph.begin() with open(liver_kb.csv, encodingutf-8) as f: for row in csv.DictReader(f): disease_node Node(Disease, namerow[disease]) symptom_node Node(Symptom, namerow[symptom]) # 以 name 作为唯一键合并节点重复执行不会产生重复节点 tx.merge(disease_node, Disease, name) tx.merge(symptom_node, Symptom, name) tx.merge(Relationship(disease_node, HAS_SYMPTOM, symptom_node, weightfloat(row[weight])), Disease, name) tx.commit()Graph(bolt://localhost:7687, auth(neo4j, your_password))里的地址要取 neo4j 配置里的 Bolt 端口不能用浏览器界面的http://localhost:7474做 Python 直连。tx.begin()开启事务循环里的merge按Disease.name和Symptom.name匹配已有节点关系merge指定(Disease, name)表示“如果 Disease 存在就更新关系属性不存在就创建”。整个循环结束后一次commit()网络往返次数比逐条写入低很多。调整 weight 时可以单独写一个 update 函数函数签名接收 disease、symptom、new_weight内部只执行最后两行merge。补充一个参数约定auth 元组第一项是用户名第二项是密码初次部署时 neo4j 会强制修改默认密码代码里建议从环境变量读取。若 CSV 行数上万把graph.begin()移出循环每 500 条执行一次commit()再重新begin()避免单事务日志过大。3. 规则匹配把临床判断转成可解释的条件链3.1 为什么知识图谱查完还要再跑规则如果直接问图谱“肝硬化有什么症状”Cypher 能回答。但“腹水加黄疸要不要立刻去医院”这种组合判断图数据库里没有显式边需要推理。规则匹配在这里的价值是确定性同样的输入得到同样的输出并且可以直接把命中的症状和检查原样返回给用户。大模型问答系统需要 API、显存和 prompt 调优规则匹配只依赖规则文件和 Python 逻辑部署成本低一个数量级也更容易在项目评审时讲清判断依据。3.2 规则的数据结构JSON 规则文件把每条规则写成一条 JSON维护规则的人不用碰 Python。项目里我会统一用 rule_id、name、priority、conditions、action 五个字段{ rule_id: R001, name: 疑似肝硬化紧急转诊, priority: 90, conditions: { combine: any, symptom_any: [腹水, 呕血, 黄疸], test_any: [血小板100, 凝血酶原时间延长] }, action: { type: referral, level: urgent, advice: 不要自行服药尽快到消化内科查腹部超声和胃镜。 } }combine: any表示集合内任意一个命中即触发需要“所有条件都满足”就改成all。action 里先写type触发后 Python 只根据 type 分发到下游动作避免规则层直接拼接 SQL 或 Cypher这条隔离很重要。3.3 一个 30 行内的规则匹配器规则匹配的核心是优先级排序加集合交集判断。把规则按 priority 降序排列先匹配到哪条就用哪条除非业务要求多规则叠加class RuleMatcher: def __init__(self, rules): self.rules sorted(rules, keylambda r: r[priority], reverseTrue) def match(self, symptoms, tests): symptom_hit set(symptoms) test_hit set(tests) for rule in self.rules: cond rule[conditions] hit_s set(cond.get(symptom_any, [])) symptom_hit hit_t set(cond.get(test_any, [])) test_hit if cond.get(combine, any) any: triggered bool(hit_s or hit_t) else: triggered bool(hit_s and hit_t) # 记录命中词便于把触发原因拼进回答 if triggered: return rule, hit_s, hit_t return Nonehit_s和hit_t用来告诉用户“到底是哪几个词触发了规则”这也是答辩时最好讲清楚的部分不是黑箱给建议而是把触发条件逐条展示。下面这个表是项目里默认的优先级区间紧急转诊类规则最优先饮食提醒次之常规症状确认最后规则类型条件写法优先级建议输出动作紧急转诊symptom_anytest_any90转诊建议饮食禁忌combine: all70饮食警告症状确认symptom_any50疾病列表常识兜底条件为空0通用提示代码里combine只在 conditions 的键里有效action 不参与匹配。如果同一患者命中多条规则可以全部收集按 priority 分两步走先给转诊再给饮食建议避免只输出一条就把其他信息丢掉。4. 问答系统主流程把输入问题拆成语义单元再拼成 Cypher 查询4.1 用词典和正则做意图识别不先上模型肝病问答的问题形态相对固定优先上 jieba 分词加模型不是性价比选择。一般我会建一个意图模式表用正则匹配意图再用词典抽实体import re INTENT_PATTERNS [ (r什么.{0,4}症状|有哪些表现, symptom), (r能(吃|喝).*|不能吃.*|吃什么, diet), (r需要(做|查).*|做什么检查, test), ] DICTS { Disease: [肝硬化, 慢性乙型肝炎, 酒精性肝病, 脂肪肝], Symptom: [腹水, 黄疸, 乏力, 肝区疼痛], Drug: [恩替卡韦, 甘草酸制剂], } def parse_question(question): intent general for pattern, label in INTENT_PATTERNS: if re.search(pattern, question): intent label break entities {} for label, terms in DICTS.items(): hit [term for term in terms if term in question] if hit: entities[label] hit[0] return intent, entities这段代码有两个参数要特别注意正则里的.{0,4}限制两个词之间最多间隔四个字避免“什么症状”和“症状有什么”两类问法被同一条规则误吞实体抽取按“第一个命中”赋值遇到“乙肝会不会发展成肝硬化”这种双疾病问题时只保留第一个实体宁缺毋滥。4.2 固定模板拼 Cypher参数用$占位很多新手在 Python 里用 f-string 拼接 Cypher中文问句带引号时要么报语法错误要么被注入自定义查询条件。应该把查询做成模板实体值用参数字典传入QUERY_TEMPLATES { symptom: MATCH (d:Disease)-[r:HAS_SYMPTOM]-(s:Symptom) WHERE d.name $disease RETURN s.name AS symptom, r.weight AS weight ORDER BY r.weight DESC LIMIT 10 , diet: MATCH (d:Disease)-[:SUGGEST_DIET]-(diet:Diet) WHERE d.name $disease RETURN diet.name AS diet , } def answer_disease(intent, entities, graph): if Disease not in entities: return None query QUERY_TEMPLATES[intent] # $disease 占位符由 graph.run 的第二个参数做安全转义 result graph.run(query, {disease: entities[Disease]}) return [record.data() for record in result]graph.run(query, {...})的参数由 py2neo 驱动做转义避免了手工拼字符串。LIMIT 10是硬上限防止大量关系让响应变慢。如果查询结果为空不要直接返回“没有”先检查entities词典里是不是只抽到了症状没抽到疾病再决定走澄清还是兜底。下表是意图到查询模板的对照用户意图查询模板返回字段symptomMATCHHAS_SYMPTOMsymptom, weightdietMATCHSUGGEST_DIETdiet.nametestMATCHNEEDS_TESTtest.nametreatmentMATCHTREATSdrug.name, usage4.3 多轮澄清未知对象先反问不硬答当一个问句既没有疾病也没有症状时系统拿到空实体。正确做法是把最近一次确认过的疾病放进会话上下文再补一轮反问def handle_question(question, session_state, graph): intent, entities parse_question(question) if not entities: last session_state.get(last_disease) if last: entities[Disease] last else: return 请先告诉我您想了解的是哪种肝病例如肝硬化、慢性乙型肝炎或脂肪肝。 # 记住上一轮疾病支持“那它能吃什么”这类省略主语问法 session_state[last_disease] entities[Disease] return answer_disease(intent, entities, graph)session_state是一个普通字典键用用户会话 id 区分正式部署时换到 Redis。规则匹配结果也可以挂在这个 JSON 里例如session_state[referral] rule[action]前端在回答末尾单独渲染提示框。开发调试阶段建议装一个 neo4j for VS Code 插件直接在编辑器里跑 Cypher省去在浏览器和 IDE 间反复切换。5. neo4j 运行细节、三个高频报错和规则评估5.1 neo4j 安装后第一件事确认端口和密码熟悉 neo4j 安装与配置的人都知道neo4j 社区版启动后有两个端口7474是浏览器 HTTP 端口7687是 Bolt 驱动端口py2neo 连的是 Bolt。启动命令建议用bin/neo4j console前台运行日志直接刷在终端比桌面端启动更容易看到某条 Cypher 为什么超时。参数默认值说明Browser URIhttp://localhost:7474打开可视化界面Bolt URIbolt://localhost:7687Python 连接用默认数据库neo4j连接参数 name 保持一致初始账号neo4j / neo4j首次登录强制修改密码5.2 三个高频报错第一个ModuleNotFoundError: py2neo。先确认当前 Python 环境是否激活了项目虚拟环境然后执行pip install py2neo。不要照搬网帖里“要安装缺失的包”时的pip install -u --pre这串升级命令缺依赖只装项目 requirements 里列出的包。第二个ServiceUnavailable: Failed to connect to localhost:7687。原因是 neo4j 没启动或连接串写成了http://localhost:7474。在 Python 里显式写bolt://localhost:7687然后再确认bin/neo4j status。第三个Neo4jError: No such property。常见于 Cypher 里节点别名拼错比如查询用了(s:Symptom)却在RETURN里写d.name。先在浏览器里执行MATCH (n) RETURN n LIMIT 25确认标签和属性名完全一致再粘贴回 Python。5.3 快速验证规则匹配的精度要体现“高分项目”的完成度可以准备一组带标准答案的测试用例跑一遍匹配器统计 precision 和 recallcases [ {symptoms: {腹水}, tests: {血小板100}, expect: referral}, {symptoms: {乏力}, tests: set(), expect: symptom}, ] def evaluate(matcher, cases): tp fp fn 0 for c in cases: got matcher.match(c[symptoms], c[tests]) pred got[0][action][type] if got else None if pred c[expect]: tp 1 elif pred is None: fn 1 else: fp 1 precision tp / (tp fp) if tp fp else 0 recall tp / (tp fn) if tp fn else 0 return precision, recallprecision 低说明规则命中过度把对应规则的 priority 调高或把条件集合收紧recall 低说明条件词没覆盖用户说法回到 3.2 节的 JSON 规则文件里补symptom_any或test_any。调优时先保 recall保证该给的转诊建议没漏再逐步收紧误报这样比单纯看准确率更能发现规则边界在哪。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价