简介:基于Python的医疗知识图谱问答系统毕业设计项目,面向计算机相关专业毕业生和有项目实战需求的开发者,覆盖从医疗知识图谱构建到自然语言问答的完整技术链路。源码均在本地编译调试通过,可直接运行,难度适中,适合课程设计、毕业设计以及Python与NLP方向的技术巩固。资源包共70个文件,以32个py源码脚本为核心,辅以配置文件、JSON数据、pkl序列化模型、项目文档、启动脚本及其他说明文件,压缩包约51.61MB,目录结构清晰,便于快速部署与二次开发。已有58人浏览学习。从中可重点学习BiLSTM命名实体识别、意图识别与知识图谱查询等模块的设计思路,并参考一套从数据预处理到问答交互的完整可运行实现,有助于深入理解医疗知识图谱问答系统的工程落地方法。
1. 基于 Python 的医疗知识图谱问答系统:毕业设计从哪里开始
毕业设计做医疗知识图谱问答系统,最怕的往往不是写不出代码,而是把一个图谱问答做成“病历关键词搜索器”:用户输入“感冒吃什么药”,系统先是匹配不到症状实体,再是回一句笼统的“请咨询医生”。我接触这类 Python 医疗知识图谱问答系统源码与数据的方向有几年,真正决定项目质量的核心不是 Flask 和 Py2neo 这些框架,而是三件事:医疗数据怎么整理成三元组、自然语言问句怎么翻译成图谱查询、数据不好时系统怎么兜底。这套方案适合做毕设、课设,也适合想快速搭一个领域问答 Demo 的开发者参考。它在工程上不算难,难在实体对齐、同义词合并、否定词处理这些细节,而这些恰好是答辩评委最喜欢追问的地方。照着本文的路径,你完全可以在本地跑通一个最小可用的医疗问答系统。
2. 医疗知识图谱问答系统的选型理由:为什么是 Neo4j + Flask 而不是文件存储
2.1 四类实体、四类关系的本体设计,先画清楚再写代码
知识图谱问答系统的上限由本体设计决定,而不是由算法决定。毕设规模不需要仿照医学知识库做成几百类实体,我一般建议控制在四类实体、四到六类关系:疾病、症状、药物、科室。关系围绕疾病展开,最常见的四组是:疾病到症状的HAS_SYMPTOM、症状到疾病的SYMPTOM_OF、疾病到药物的USE_DRUG、疾病到科室的BELONGS_TO。如果数据里还有食物禁忌,可以再加一组EAT_AVOID,但不要贪多。
设计本体的顺序是先列实体属性,再画关系方向。实体属性在 Neo4j 里就是节点的 key,至少要留name和alias两个字段,alias用来存同义词。关系方向要和问句习惯一致,比如用户习惯说“感冒会发烧”,所以图谱里存感冒 -[:HAS_SYMPTOM]-> 发烧,回答时顺着这个方向查询最自然。下面这张表是毕设里最常用的一套设计,可以直接照抄。
| 实体/关系 | 类型 | 方向 | 语义 |
|---|---|---|---|
| Disease | 实体 | 无 | 疾病,属性 name 唯一 |
| Symptom | 实体 | 无 | 症状,属性 name 唯一 |
| Drug | 实体 | 无 | 药物,属性 name 唯一 |
| Department | 实体 | 无 | 科室,属性 name 唯一 |
| HAS_SYMPTOM | 关系 | 疾病→症状 | 该疾病有哪些症状 |
| SYMPTOM_OF | 关系 | 症状→疾病 | 该症状对应哪些疾病 |
| USE_DRUG | 关系 | 疾病→药物 | 该疾病常用什么药 |
| BELONGS_TO | 关系 | 疾病→科室 | 该疾病挂哪个科室 |
这里有一个很容易犯的错误:把关系建成双向,即同时导入HAS_SYMPTOM和SYMPTOM_OF,然后在问答里不确定该查哪条。不是不行,但会让 Cypher 模板翻倍。更省事的做法是只建一条关系,查询时通过箭头方向反过来的写法拿到反向语义,比如查“发烧可能是什么病”时写MATCH (s:Symptom {name:'发烧'})<-[:HAS_SYMPTOM]-(d:Disease)。一条关系,正反都能用。
2.2 三种存储方案对比:Neo4j、NetworkX 和 CSV 三元组
中介面到底要不要上 Neo4j,是很多人纠结的地方。毕设的数据量往往只有几千到几万条三元组,用 CSV 也能折腾出答案,但后续麻烦很多:所有“查症状对应的疾病”这类查询都要自己写 Python 遍历,答一个问句就得扫描全表,而且答辩现场很难向评委展示“我做的确实是图”。做这个方向的常见做法是选 Neo4j 社区版,配合 Py2neo 操作,数据规模完全够用。
| 存储方案 | 适合规模 | 查询表达 | 答辩/演示效果 | 我的建议 |
|---|---|---|---|---|
| Neo4j 社区版 | 几千到几百万条关系 | Cypher 表达图查询很自然 | 高,浏览器能看到节点关系 | 主选 |
| NetworkX | 几千节点以内 | 图算法方便,但查询要自己写 | 中,适合做算法分析 | 作为补充分析工具 |
| CSV 三元组 | 任意 | 每次查都全量遍历 | 低,看不出图结构 | 只做数据备份 |
Neo4j 的部署方式有两种:官方桌面版和 Docker 容器。毕设环境不建议在桌面版上浪费时间,用 Docker 跑官方镜像更干净,出了问题重开容器就行。注意 py2neo 的版本要和你装的 Neo4j 大版本匹配,py2neo 4.x 配 Neo4j 4.x,py2neo 5 配 Neo4j 5,装错版本最常见的表现是连接成功但执行CREATE CONSTRAINT时语法报错。
2.3 Python 环境与依赖:从零到能跑 Flask 的最小命令
环境搭建是 python 入门阶段最容易翻车的一步。先把 Python 版本固定在 3.8 到 3.10 之间,太新的版本偶尔会遇到某些库还没有预编译包的问题。然后建虚拟环境,不要直接往全局 Python 里装包,不然后面换机器复现时会缺依赖。
# 创建并激活虚拟环境 python -m venv venv # Windows 下激活 venv\Scripts\activate # Linux/macOS 下激活 source venv/bin/activate # 升级 pip 后安装核心依赖 pip install -U pip pip install flask py2neo jieba gensim pandas依赖清单里每个包都有明确用途:flask负责对外提供问答接口;py2neo是 Python 操作 Neo4j 的客户端;jieba做中文分词,虽然后面实体识别主要靠词典匹配,但分词在意图判断里能当辅助;gensim用于第 6 章的相似度兜底;pandas处理原始医疗表格数据。这里面最容易装出问题的是gensim,它是所有包里依赖链最长的,如果安装时编译报错,先确认 Python 版本,再检查是不是用了pip install gensim而不是安装到当前虚拟环境。
依赖装好后,可以先用一行代码验证 Py2neo 能不能连上 Neo4j,别等到写完整个系统再排查连接问题。
3. 把医疗数据变成知识图谱:清洗、三元组与批量导入
3.1 数据来源与脱敏:整理公开词条而不是抓患者数据
医疗知识图谱的数据从哪来,是毕设开题时最常被问的问题。常见做法是用 Python 爬虫整理公开的医学词条和问答站点词条,比如疾病介绍、症状描述、药品说明书里的公开条目。做这一步必须守住一条底线:不采集任何个人信息和患者数据。只保留疾病名、症状名、药物名、科室名这些公共知识条目,处理之前先把来源字段记录下来,方便论文里写数据来源和局限性。医疗数据合规问题尽量在开题阶段就规避掉,比答辩时被问住要好处理得多。
原始数据通常是一张表,常见格式是每行一个疾病,包含疾病名、症状描述、常用药物、科室等字段。有些公开词条抓下来是半结构化的 JSON,需要先压平成表格。这个阶段的数据质量非常差,会出现疾病名带空格、症状列里混入“发热、乏力、全身酸痛”这种逗号分隔串、同一种病出现两个写法等。这些脏数据要交给 pandas 处理,不要手动改 Excel。
3.2 清洗去重:pandas 处理脏数据的三个关键参数
pandas 清洗这一步,我建议把三个参数牢牢记住:dtype=str、dropna(subset=...)、drop_duplicates。dtype=str看起来不起眼,但能把“39”这种纯数字症状名按字符串读,而不是被读成数字造成后面匹配失败;dropna只删除关键字段为空的行,比如疾病名或症状名为空,不能把整行直接清掉,否则会误删药物和科室信息;drop_duplicates去重时必须按[疾病名, 症状名]这样的实体对去重,而不是按单一字段。
import pandas as pd # 读取原始医疗词条表,强制按字符串读入,避免数字被转换 df = pd.read_csv("raw_medical.csv", encoding="utf-8", dtype=str) # 关键字段为空的行直接删除 df = df.dropna(subset=["disease", "symptom"]) # 去掉疾病名和症状名两端的空格 df["disease"] = df["disease"].str.strip() df["symptom"] = df["symptom"].str.strip() # 对同一疾病和症状组合去重,保留统计次数 df = df.groupby(["disease", "symptom"], as_index=False).size() df.columns = ["disease", "symptom", "cnt"]分组去重比单纯drop_duplicates多一点价值:cnt字段可以告诉你某对关系在原始语料里出现过多少次。后面生成三元组时,可以把cnt当作权重字段;同一对关系出现次数太少,往往是抽取错误,可以考虑过滤掉。这段代码执行完,你应该能看到一张干净的两列表:疾病名和症状名。如果表格里还有繁体字和全角字符,趁这一步统一转成简体,比如“發燒”转成“发烧”。
3.3 三元组生成:用症状词典在疾病描述里做规则抽取
清洗之后还没有三元组,因为原始数据里通常只有一个疾病名加一段自由文本描述。最可靠的三元组生成方式是“词典 + 规则抽取”:先把所有已知症状名收集成词典,然后在疾病的文本描述里扫描,命中了哪个症状词,就生成一条哈斯症状关系。这种方式比训练命名实体识别模型稳定,因为毕设数据量小,模型容易过拟合,而且规则抽取的结果可解释,论文里能写清楚。
from ahocorasick import Automaton # 把症状词典构建成 Aho-Corasick 自动机 def build_symptom_ac(symptom_list): ac = Automaton() for idx, symptom in enumerate(symptom_list): ac.add_word(symptom, idx) ac.make_automaton() return ac # 在疾病描述文本中扫描症状词,生成三元组 def extract_triples(df_disease_desc, symptom_list): ac = build_symptom_ac(symptom_list) triples = [] for _, row in df_disease_desc.iterrows(): disease = row["disease"] desc = row["description"] if not isinstance(desc, str): continue for end_index, idx in ac.iter(desc): symptom = symptom_list[idx] triples.append((disease, "HAS_SYMPTOM", symptom)) return triples这段代码有几个可以调的参数:一是症状词典的长度,词典越长,抽取出的关系越多,但误报也越多;二是描述文本是否需要分词,实际上 AC 自动机是字符级匹配,不需要先分词,反而能避免分词错误。抽取结果需要做一次人工核对,看有没有“描述里提到某种药物,但被误判成症状”的情况。常见现象是“高血压患者的常用药是硝苯地平”,如果药物词典和症状词典混在一起,漏标类型会导致交叉实体。
3.4 写入 Neo4j:先建唯一约束再按批 merge
三元组表生成后,写入 Neo4j 这件事有大量血泪经验。最不推荐的做法是用循环逐条执行CREATE,几千条数据能跑好几分钟,而且中途报错后整个数据库里全是重复节点。正确做法分两步:先给实体建唯一约束,再按批次用MERGE写入。唯一约束就是后悔药,没有它,第二次运行导入脚本时图谱节点直接翻倍。
from py2neo import Graph, Node, Relationship # 默认 bolt 端口 7687,注意不要和 http 端口 7474 混淆 graph = Graph("bolt://localhost:7687", auth=("neo4j", "123456")) # 给实体名建唯一约束:同一个疾病/症状名只有一个节点 graph.run("CREATE CONSTRAINT FOR (n:Disease) REQUIRE n.name IS UNIQUE") graph.run("CREATE CONSTRAINT FOR (n:Symptom) REQUIRE n.name IS UNIQUE") def import_triples(triples): tx = graph.begin() batch_size = 300 for i, (disease, rel_type, symptom) in enumerate(triples): # merge 按 name 命中已有节点,不存在则新建 d_node = tx.merge(Node("Disease", name=disease), "Disease", "name") s_node = tx.merge(Node("Symptom", name=symptom), "Symptom", "name") tx.create(Relationship(d_node, rel_type, s_node)) if i % batch_size == 0: tx.commit() tx = graph.begin() tx.commit()这里的参数有两个值得说明。batch_size=300是经验值:一次事务塞太多操作会占用大量内存,Neo4j 默认事务内存超限会报错;塞太少则网络往返开销大。如果你的三元组只有几百条,可以放宽到一次性提交。另一个参数是merge的第三个参数name,意思是按节点的 name 属性作为主键去判断“节点是否已存在”,这也是前面建唯一约束的意义所在。代码执行完后,可以到 Neo4j Browser 里执行MATCH (n) RETURN count(n)验证节点总数,应该和去重后的实体数一致。
4. 医疗问答系统的翻译层:问句解析与 Cypher 生成
4.1 问句归一化:同义词替换、标点替换、助词处理
用户输入的问句不能直接拿去匹配,第一步是归一化。归一化解决三件事:全角半角标点统一、同义词替换、无意义助词清理。中文问句的标点比较乱,“感冒吃什么药?”结尾可能是中文问号,也可能没有标点,直接在实体识别阶段处理标点容易误伤,最好的办法是把所有标点替换成空格,让后续匹配只关注有意义的词。
# 同义词表:左边是图谱中的标准名,右边是用户可能的说法 SYNONYMS = { "发烧": ["发热", "低烧", "高烧", "体温高"], "感冒": ["着凉", "伤风"], "高血压": ["血压高", "高血压病"], } def normalize_question(question): q = question.strip() # 统一中文和英文标点为空格 for c in [",", "。", "?", "?", "、", "!", "!", ",", "?"]: q = q.replace(c, " ") # 同义词替换 for std_name, aliases in SYNONYMS.items(): for alias in aliases: q = q.replace(alias, std_name) # 去掉无实际语义的助词 for w in ["请问", "一下", "我想", "我想要"]: q = q.replace(w, "") return q同义词表必须和前面建的实体词典保持一致,最好由实体词典反向生成。常见做法是:在 Neo4j 里给节点加 alias 属性,比如感冒节点上存["着凉", "伤风"],导出 alias 后自动生成这份 SYNONYMS 表。这样图谱更新时同义词表不会忘改。这部分有一个坑:替换顺序可能相互覆盖,比如“血压高”被替换成“高血压”后,恰好“高血压”也是另一个替换的别名,就会出现连锁替换。稳妥的做法是在替换时加一个保护标记,或者约定标准名绝对不能出现在别名表里。
4.2 实体识别:用 Aho-Corasick 自动机命中疾病与症状
实体识别是问答系统的关键一步。很多人直接用 jieba 分词再比对词典,但 jieba 的分词结果和医学实体名经常对不上,比如“流行性感冒”可能被切成“流行性”和“感冒”。更可控的做法是用 Aho-Corasick 自动机做多模式串匹配,一次扫描问句,把所有出现在词典里的疾病名、症状名、药物名全部找出来。
from ahocorasick import Automaton # 实体表格式: list of (实体名, 实体类型) entities = [ ("感冒", "disease"), ("发烧", "symptom"), ("发热", "symptom"), ("布洛芬", "drug"), ] ac = Automaton() for idx, (name, etype) in enumerate(entities): ac.add_word(name, (idx, etype)) ac.make_automaton() def find_entities(text): hits = [] # iter 返回 (结束位置, 值),结束位置用于反向找回词本身 for end_index, (idx, etype) in ac.iter(text): name = entities[idx][0] start_index = end_index - len(name) + 1 hits.append({"name": name, "type": etype, "start": start_index}) return hits print(find_entities("我感冒了,有点发烧"))这里有个细节:ac.iter返回的是匹配到的结束位置,不是起始位置,所以必须用end_index - len(name) + 1反推。重叠匹配时自动机默认会把多个词都输出,比如“发热”和“发烧”都出现时,两条都会命中,这会在后面的查询里产生多条候选,需要用同义词归一化去消除。如果发现文本里“感冒”和“流感”被当作两个无关实体同时命中,而你的词典里“流感”是单独一个疾病节点,没关系,保留两者,交给关系映射层去排优先级。
4.3 意图与关系映射:把“什么病”翻译成 MATCH 语句
实体识别只能告诉系统“用户提到了感冒”,但用户到底是问症状还是问药,需要意图判断。毕设阶段的意图判断不建议上复杂模型,用关键词规则就够了,关键在于把规则表设计成数据和逻辑分离的结构。每一条规则包含意图名、触发关键词和对应的 Cypher 模板,后面要扩展问句类型时只加这一张表。
INTENT_RULES = [ { "intent": "disease_by_symptom", "keywords": ["什么病", "啥病", "得了什么"], "cypher": ( "MATCH (s:Symptom {name:$name})" "<-[:HAS_SYMPTOM]-(d:Disease) RETURN d.name AS result LIMIT $limit" ), }, { "intent": "drug_by_disease", "keywords": ["吃什么药", "用什么药", "用药", "服药"], "cypher": ( "MATCH (d:Disease {name:$name})" "-[:USE_DRUG]->(m:Drug) RETURN m.name AS result LIMIT $limit" ), }, ] def detect_intent(question): for rule in INTENT_RULES: if any(kw in question for kw in rule["keywords"]): return rule["intent"] return "unmatched"生成查询时务必要用参数化查询,把实体名作为$name传进去,而不是直接拼接字符串。理由在后面第 5.5 节展开。这里的limit也建议做成参数,默认 5,防止一个疾病关联几十条药物时返回结果把前端塞满。答不上来时,不要直接抛异常给前端,而是返回一条固定话术,比如“这个知识点我还没有收录”,同时把命中实体和意图代号一起返回,方便排查是哪一步断了。
4.4 Flask 接口:把问答封装成 POST 服务并暴露中间结果
问答逻辑写好后,用 Flask 包一层 HTTP 接口,前端和测试脚本才能调用。接口设计建议返回三个字段:answer是最终答案,entity是识别到的实体,intent是命中的意图。这样在浏览器里就能看到系统内部的黑匣子,答辩调试时省很多事。常见误用是只返回答案字符串,调试时根本不知道到底是实体识别错了还是意图判断错了。
from flask import Flask, request, jsonify app = Flask(__name__) # 让接口返回中文而不是 \uXXXX 转义 app.config["JSON_AS_ASCII"] = False @app.route("/qa", methods=["POST"]) def qa(): payload = request.get_json() question = payload.get("question", "") answer, entity, intent = answer_question(question) return jsonify({"answer": answer, "entity": entity, "intent": intent}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)启动服务后,用 curl 测试是最快的验证方式,不用先写前端页面。debug=False是必须的,生产联调时开着 debug 模式会暴露代码路径,而且 Flask 的热重载会和 Py2neo 的连接池抢资源,偶尔造成“第一次请求成功,第二次超时”的诡异现象。如果出现中文乱码,优先检查两处:操作系统终端编码和 Flask 的JSON_AS_ASCII配置,前者影响日志,后者影响接口响应。
5. 医疗问答系统的常见故障排查:5 个会翻车的地方
5.1 发热和发烧查不到同一份答案:实体别名没对齐
现象:用户问“发热吃什么药”,系统返回“该知识点未收录”,但问“发烧吃什么药”能正常返回。原因:图谱里只有“发烧”节点,问句里的“发热”没有被同义词表替换。这不是实体识别的问题,而是归一化阶段没做干净。解决:把“发热”加进 SYNONYMS 表的别名里,同时检查 Neo4j 里是否有两个相似节点。建议在导入阶段就把实体 alias 属性统一维护,别等查询出错了再补。别名表设计的原则是只做单向替换,标准名统一选病历和教材里最常用的写法。
5.2 病人说“没有发烧”,系统却当作已发烧:否定意图被忽略
现象:问“我没有发烧,但有点咳嗽,是什么病”,系统返回了发烧相关的疾病。原因:规则问答没有处理否定词,“没有”被助词清理阶段误删了。解决:保留否定词表,在进行实体识别之前先检测问句是否含否定词。
NEG_WORDS = ["没有", "不", "没", "别", "无", "未"] def has_negation(question): return any(w in question for w in NEG_WORDS)检测到否定后,常见做法是把对应关系查询方向反转,或直接把被否定的实体从候选实体中删除。更简单的方式是:在知识图谱里新建“否定症状”的临时节点,把发烧节点与被否定的疾病断开,但毕设里这么做太绕。我一般选择在实体列表中过滤掉被否定的实体,并在返回答案时追加提示“检测到否定表达,已忽略‘没有发烧’”,这个提示在答辩演示中反而是加分项。
5.3 换个问法就返回空:关系模板匹配太刚性
现象:“感冒吃什么药”能答,“感冒该用什么药”却答不上来。原因:意图规则表里只写了“吃什么药”,没覆盖“用什么药”“用药建议”等变体。这是规则系统的通病,解决分两步:第一步扩充关键词表,把所有能想到的说法都写进去;第二步给没有命中意图的问句加降级逻辑,比如把命中的实体返回给前端,同时展示这个实体在图谱中的相关关系,让用户至少看到系统“听懂了一半”。降级逻辑对毕设尤其重要,因为评委在现场会随机问一些你没预料到的话术,降级能保证系统不崩。
5.4 导入两次图谱节点翻倍:缺唯一约束和实体合并
现象:第二次运行导入脚本后,查询正常但统计节点数量时发现“感冒”节点有多个。原因:第一次导入时没有建唯一约束,或者建了约束但用了CREATE而不是MERGE,第二次导入直接又建了一遍。解决:前面导入代码里的CREATE CONSTRAINT和merge必须成对出现,缺一不可。已经产生重复节点的补救办法是用 Cypher 把所有同名节点合并,但要保留关系,操作比较复杂,不如把数据库删了重新导入一遍。这也说明唯一约束是所有步骤里最值得提前做的准备。
5.5 查询含中文引号报错、Neo4j 内存不足:环境级问题与参数化
现象:问句包含“”或单引号时接口报语法错误,或者导入大数据时控制台提示内存不足。原因:前者是 Cypher 采用字符串拼接导致引号没有转义,后者是 Neo4j 默认堆内存太小。解决:查询一律改用参数化,实体名和 limit 都通过$name、$limit传入,不拼字符串。Neo4j 内存参数在安装目录conf/neo4j.conf里,主要调dbms.memory.heap.initial_size和dbms.memory.heap.max_size,八 G 内存的笔记本可以给到 1G 到 2G,不要在只有 4G 内存的机器上硬撑。检查方式也很简单:导入数据时盯着 Neo4j 日志,出现OutOfMemoryError就调大,同时把批次减小到 100 条。
6. 让医疗问答更稳的下一步:用症状向量兜底实体识别
6.1 用 gensim 给问句里的词算相似度
实体识别漏报是所有规则问答的短板,比如用户说“浑身发冷”,词典里只有“发热”,AC 自动机匹配不到。这时候可以加一层向量兜底:先给问句和实体列表做相似度计算,用 gensim 训练一个极小的词向量模型,语料就用你收集到的疾病描述文本分词结果。毕设规模的数据训出来的向量不算精确,但它能在“发热”和“发冷”之间捕捉到一定的语义相似度,足够做降级实体识别。
from gensim.models import Word2Vec import jieba # 语料格式: list[list[str]],每段描述分词后的结果 sentences = [ jieba.lcut(desc) for desc in all_descriptions ] w2v_model = Word2Vec(sentences, vector_size=128, window=5, min_count=1, workers=4) def closest_entity(word, candidate_entities, threshold=0.6): best_name = None best_score = 0 for entity_name in candidate_entities: if entity_name not in w2v_model.wv: continue score = w2v_model.wv.similarity(word, entity_name) if score > best_score: best_name, best_score = entity_name, score if best_score < threshold: return None, 0 return best_name, best_score这里threshold是玄学参数,0.6 只是起点。阈值太高会漏,阈值太低会乱认实体,比如把“咳嗽”和“感冒”强行关联。我调试时会打印每个候选实体的相似度分数,观察分布后再定阈值。词向量模型训练很快,但注意每次启动问答服务都要重新 load 模型文件,所以训练完要用w2v_model.save("medical_w2v.model")保存,启动时用Word2Vec.load加载,避免每次都要重新训练。
6.2 验证回答质量的 20 问脚本与阈值调整
加完向量兜底后,需要一套能反复跑的验证脚本。我会准备一个test_cases.json,写二十个不同问法的题目,覆盖“疾病查症状”“症状查疾病”“疾病查药物”和几个故意刁难的否定问句,每改一次代码就跑一遍,统计回答成功率。验证脚本里可以直接调用answer_question函数,不需要走 HTTP 接口。回答非空率低于八成,就说明兜底策略有漏洞。
跨平台音乐管理系统 v2.0 这类源码在网上一抓一把,但医疗知识图谱问答因为数据质量限制,很难靠抄代码跑通。我这几年做这类系统的习惯是永远保留一个“实体命中但意图不匹配”的降级出口,以及一套随时能跑的测试题。这两样东西比模型选得多高级都管用。把数据整理干净、规则写清楚、兜底设计到位,这个毕设方向你再往下扩展,比如加入检验报告问答或用药注意事项查询,也不会推翻重来。希望帮到你。
本文还有配套的精品资源,点击获取