基于Python与Neo4j的医疗知识图谱问答系统毕设实战

发布时间:2026/10/11 21:10:40
基于Python与Neo4j的医疗知识图谱问答系统毕设实战 简介面向Python毕业设计及知识图谱问答方向的开发者这款医疗知识图谱问答系统以Python实现完整覆盖从医疗数据清洗、实体关系建模到基于Neo4j的图谱存储与问答匹配的闭环流程。压缩包共28个文件、约15.85MB内含Python源码、医疗JSON图谱数据、TXT词典与说明文档、XML配置及界面演示图等。系统模块划分清晰涉及数据预处理、问句分类、实体识别、答案搜索与图谱查询等典型功能并附带疾病、症状、药品等结构化字典和基础数据文件便于直接运行调试或二次改造。已有625人学习下载适合作为本科毕业设计或课程项目的完整参考既能快速验证NLP与知识图谱组合技术栈也可基于其架构扩展新科室或新功能。1. 医疗知识图谱问答系统这个毕设题目到底在做什么如果你在毕业设计选题列表里看到「基于Python实现的医疗知识图谱的知识问答系统」第一反应多半是「这又是一个包装过的数据库课设」。实际把它拆开看它做的是三件事用爬虫或公开数据集整理医患问答数据用Neo4j这类图数据库存成「疾病-症状-科室-药品」的实体关系网再写一个能接收自然语言问题、解析意图、查图谱并返回答案的问答接口。整套东西跑通之后你输入「头疼两天该挂什么科」系统能告诉你「神经内科或普通内科伴随发热建议先发热门诊」而不是像传统关键词搜索那样甩给你一堆网页。这个方向值得做原因在于它同时覆盖了Python后端、数据处理、图数据库、NLP规则抽取这四个毕业设计里高频考察的点而且每个点都有肉眼可见的交付物neo4j里能看得见的图、能跑通的问答demo、能写进论文的准确率数据。相比之下纯爬虫或纯Web CRUD的题目在答辩时很难撑满20分钟而这个题目每个模块都能展开讲。技术栈上不挑机器Windows笔记本就能跑数据量不大瓶颈基本都在内存和Neo4j的配置上。适合的人群有两类一是Python基础尚可、但没接触过图数据库和NLP的学生这个题目能让你在一个月内把Neo4j的Cypher查询、HanLP或jieba分词、Flask接口串成一条线二是已经在做Web开发、想往知识图谱方向靠的从业者可以用同样的骨架接工业知识图谱的场景。本文按我自己的实现路径来写从数据准备、图谱构建、问答链路到部署排错每一步都给可复现的命令和参数最后落在几个最容易翻车的细节上。2. 数据从哪来构建医疗知识图谱的数据采集与清洗2.1 数据源选型为什么别一上来就爬网站医疗知识图谱的数据来源通常有三条路公开数据集、爬虫抓取、手工整理。公开数据集里最常用的是CMeKG中文医学知识图谱的发布包但完整版需要申请而且schema和你的问答需求不一定对得上。爬虫抓取一般指向寻医问药网、39健康网这类站点能拿到疾病描述、症状、科室、药品、饮食建议等字段但反爬、页面改版、编码乱码会让你在数据上耗掉两周。手工整理只适合做几十条demo撑不起问答系统的召回量。我一般建议的时间分配是60%时间花在公开数据集的schema对齐和清洗上30%时间写一个轻量爬虫补齐缺失实体10%留给人肉校对种子数据。推荐先找一份「症状-疾病-科室-药品」四元组的CSV格式类似疾病名称、症状描述、所属科室、常用药物、检查项目、推荐食物字段宁可多不可少因为知识图谱在构建阶段删字段容易后面想补字段要重新跑全量导入。选择数据源时还要考虑许可证问题。用于毕业设计或学习CMeKG和开放爬取数据的合规风险相对低但如果要写进论文并公开代码仓库建议在README里注明数据来源和用途限制。爬虫抓取时robots协议和对方网站的条款要过一眼别给答辩埋雷。2.2 一个可复用的清洗脚本去重、对齐、补空值拿到原始CSV后不要直接导入Neo4j一定要先清洗。常见脏数据包括同一疾病多种写法「2型糖尿病」和「II型糖尿病」、症状字段里混入标点和英文、药品字段为空、科室名称带括号备注。下面的脚本做三件事统一疾病名和科室名的映射、拆分症状字段、丢弃药品和科室双空的记录。import pandas as pd import re df pd.read_csv(medical_raw.csv, encodingutf-8-sig) print(f原始数据量: {len(df)}) # 1. 疾病名映射表手工维护常见别名 disease_map { 2型糖尿病: 2型糖尿病, II型糖尿病: 2型糖尿病, 糖尿病2型: 2型糖尿病, } df[disease] df[disease].map(lambda x: disease_map.get(x.strip(), x.strip())) # 2. 科室名去括号备注 dept_pattern re.compile(r[(].*?[)]) df[department] df[department].astype(str).map(lambda x: dept_pattern.sub(, x).strip()) # 3. 症状拆分按中文顿号、逗号切分 def split_symptoms(text): if pd.isna(text) or not str(text).strip(): return [] return [s.strip() for s in re.split(r[、,;], str(text)) if s.strip()] df[symptom_list] df[symptom].map(split_symptoms) # 4. 过滤无效记录 valid df[df[drug].notna() df[department].notna()].copy() print(f清洗后有效数据: {len(valid)}) # 5. 导出为图谱导入用长表 records [] for _, row in valid.iterrows(): for sym in row[symptom_list]: records.append({ disease: row[disease], symptom: sym, department: row[department], drug: row[drug], check: row.get(check_item, ) }) long_df pd.DataFrame(records) long_df.to_csv(medical_clean.csv, indexFalse, encodingutf-8-sig)逻辑说明第一步做实体对齐别名映射表虽然要手工维护但它是后续图谱实体唯一性的基础漏了这一步Neo4j里会出现两个「2型糖尿病」节点。第三步把症状拆成列表再展开成长表是为了后续导入时每条「疾病-症状」关系单独成行避免在Cypher里做字符串拆分。第四步过滤掉药品和科室为空的记录是因为问答系统里「该挂什么科」和「吃什么药」是两个核心意图缺了这两个字段的实体对问答毫无贡献。参数说明编码用utf-8-sig而不是utf-8是因为Windows下Excel另存的CSV默认带BOM头省编码问题症状拆分用正则[、,;]覆盖中英文分隔符实测数据里混用顿号和逗号的情况很常见dropna条件用notna而不是非空字符串避免把空字符串当有效值导入。2.3 数据量级怎么定多少实体关系才够答辩问答系统的效果和数据量不是线性关系。实体从500条涨到2000条问答准确率提升明显从5000条往上边际收益就很低了因为医疗问答的常见意图就那么几十类。我的建议是清洗后保留至少2000个疾病实体、8000条以上症状关系、1000条以上药品关联这个量级足以支撑论文里「覆盖30类常见疾病问答」的描述而且Neo4j导入只需十几秒查询响应在毫秒级。不要盲目追求数据量有一个知乎上常讲但实际血泪的教训实体越多实体对齐的坑越多。比如「头疼」「头痛」「头部疼痛」在数据里是三套写法如果不做归一化用户问「头痛」时图谱里只有「头疼」节点规则匹配失败直接返回空。解决方式是在清洗阶段维护一个症状同义词表或者导入后用Cypher做一次别名节点合并。后面第5章我会专门讲这个坑。3. Neo4j图建模与导入把CSV变成能查询的知识图谱3.1 图模型设计节点、关系、属性的取舍医疗知识图谱的schema设计直接决定问答系统的查询复杂度。我用的模型是四类节点和五类关系这是医疗问答里最稳定的一套设计。节点疾病Disease、症状Symptom、科室Department、药品Drug。每个节点带一个name属性作为唯一标识Disease额外带description、check_item、treat_principle等描述属性Symptom带body_part等可选属性。关系Disease-[:HAS_SYMPTOM]-Symptom、Disease-[:BELONGS_TO]-Department、Disease-[:RECOMMEND_DRUG]-Drug、Symptom-[:DEPARTMENT_HINT]-Department症状指向可能科室用于反向推导、Disease-[:NEED_CHECK]-CheckItem检查项目。这套设计的好处是问答意图和关系一一对应问「头疼挂什么科」实际是查Symptom节点出发的DEPARTMENT_HINT或Disease的BELONGS_TO问「糖尿病吃什么药」查RECOMMEND_DRUG问「这个病有什么症状」查HAS_SYMPTOM。另一种常见设计是把科室作为Disease的属性而不是节点但这样就没法回答「神经内科看什么病」这类反向问题所以我还是建议科室独立成节点。属性命名统一用英文小写下划线避免中文属性名在Cypher里需要反引号包裹的麻烦。name属性上建唯一约束这是后续所有实体对齐和导入幂等性的基础。3.2 用Cypher批量导入LOAD CSV的正确姿势Neo4j导入CSV有两条路neo4j-admin import用于全量初始导入要求数据库停止服务LOAD CSV用于在运行状态下增量导入。毕业设计场景数据量小我建议直接用LOAD CSV方便反复清洗重导。前提是CSV文件要放到Neo4j的import目录下默认是安装目录的import/文件夹。// 1. 建立唯一约束保证实体幂等 CREATE CONSTRAINT disease_unique IF NOT EXISTS FOR (d:Disease) REQUIRE d.name IS UNIQUE; CREATE CONSTRAINT symptom_unique IF NOT EXISTS FOR (s:Symptom) REQUIRE s.name IS UNIQUE; CREATE CONSTRAINT dept_unique IF NOT EXISTS FOR (d:Department) REQUIRE d.name IS UNIQUE; CREATE CONSTRAINT drug_unique IF NOT EXISTS FOR (d:Drug) REQUIRE d.name IS UNIQUE; // 2. 导入疾病节点 LOAD CSV WITH HEADERS FROM file:///medical_clean.csv AS row MERGE (d:Disease {name: row.disease}) ON CREATE SET d.check_item row.check, d.description row.description; // 3. 导入症状节点并建立关系 LOAD CSV WITH HEADERS FROM file:///medical_clean.csv AS row MERGE (s:Symptom {name: row.symptom}) MERGE (d:Disease {name: row.disease}) MERGE (d)-[:HAS_SYMPTOM]-(s);逻辑说明MERGE而不是CREATE是导入脚本里最重要的决定。MERGE先按唯一约束查重节点存在就不重复创建只更新缺失属性。CONSTRAINT用IF NOT EXISTS是为了脚本可重复执行否则第二次跑会报约束已存在。ON CREATE SET只在节点首次创建时写入属性避免后续重复导入覆盖已有描述。参数说明LOAD CSV WITH HEADERS要求CSV首行是列名列名的拼写要和row变量里的key完全一致注意大小写。文件路径用file:///medical_clean.csv斜杠是三个相对于Neo4j的import目录。如果CSV里有中文确保文件编码是UTF-8且无BOMNeo4j对BOM的处理在不同版本里不一致。导入科室和药品的关系同理把MERGE的节点类型换成Department和Drug即可。全量导入完用MATCH (n) RETURN count(n)统计节点总数和清洗后的CSV行数做交叉验证如果数量差太大说明MERGE把别名实体合并了大概率是清洗没到位。3.3 索引与查询验证先确认图是「活」的再写问答导入后不要急着写问答先用几个Cypher查询验证图谱的连通性和查询性能。// 验证1某疾病的完整信息 MATCH (d:Disease {name: 2型糖尿病})-[r]-(n) RETURN type(r) AS relation, n.name AS target; // 验证2某症状可能关联的疾病和科室 MATCH (s:Symptom {name: 头痛})-[:HAS_SYMPTOM]-(d:Disease) OPTIONAL MATCH (d)-[:BELONGS_TO]-(dept:Department) RETURN d.name AS disease, dept.name AS department LIMIT 20; // 验证3从科室反向找疾病 MATCH (dept:Department {name: 神经内科})-[:BELONGS_TO]-(d:Disease) RETURN d.name AS disease LIMIT 50; // 验证4执行计划检查是否走索引 EXPLAIN MATCH (d:Disease {name: 2型糖尿病}) RETURN d;逻辑说明验证1用可变长路径的写法检查节点有没有挂上关系如果返回空说明导入脚本里MERGE的name值不匹配通常是清洗阶段别名没对齐。验证2是问答系统里最核心的查询模式「症状反查疾病与科室」跑通这个就解决了50%的问答需求。验证3用于处理「科室实体在问题里出现」的场景是很多规则问答系统忽略的反向查询。参数说明EXPLAIN不会真正执行查询只是显示执行计划看有没有用上NodeIndexSeek。如果返回结果里出现NodeByLabelScan说明name约束没有生效检查约束是否创建成功。LIMIT 20在验证阶段加上能防止意外的大结果集把浏览器卡死。4. 问答链路实现从自然语言到Cypher的转换器4.1 意图识别与实体抽取基于规则的实现够不够用问答系统的核心是把用户输入的自然语言映射成Cypher查询。两条技术路线一是基于规则模板用jieba分词或HanLP做词法分析匹配关键词和句式模板二是基于意图分类模型BERT等训练一个分类器识别「求科室」「求药品」「求症状」等意图。毕业设计场景里基于规则完全够用原因有两点医疗问答的句式高度模板化用户问法集中在「XX怎么办」「XX挂什么科」「XX吃什么药」这几十种模式规则系统的行为可解释答辩时你可以一条条演示匹配逻辑而模型方案一旦出错很难向评委解释。规则系统的核心是维护一个「意图字典」和「实体词典」。意图字典把触发词映射到意图类型出现「挂什么科」「哪个科室」「看什么病」映射到DEPARTMENT意图出现「吃什么药」「用什么药」「药品」映射到DRUG意图出现「什么症状」「有哪些表现」映射到SYMPTOM意图。实体词典就是从Neo4j里导出的所有实体名列表用最大匹配法从用户问题里切出实体。import jieba from py2neo import Graph class MedicalQA: def __init__(self, uribolt://localhost:7687, userneo4j, pwdpassword): self.graph Graph(uri, auth(user, pwd)) self.entities self._load_entities() self.intent_patterns { department: [挂什么科, 哪个科室, 什么科, 看什么病, 就诊], drug: [吃什么药, 用药, 药品, 药物, 药], symptom: [什么症状, 有哪些表现, 症状, 表现], } def _load_entities(self): # 从图谱中一次性拉取所有实体名 query MATCH (n) RETURN labels(n)[0] AS type, n.name AS name data self.graph.run(query).data() entities {Disease: [], Symptom: [], Department: [], Drug: []} for row in data: entities[row[type]].append(row[name]) return entities def extract_entities(self, question): # 最大匹配按实体长度降序匹配避免短词截断长词 found {Disease: None, Symptom: None} for ent_type in [Disease, Symptom, Department]: candidates sorted(self.entities[ent_type], keylen, reverseTrue) for ent in candidates: if ent in question: found[ent_type] ent break return found def parse_intent(self, question): for intent, patterns in self.intent_patterns.items(): for p in patterns: if p in question: return intent return default逻辑说明实体抽取用最大匹配而不是jieba分词是因为jieba的分词结果对专有名词如「2型糖尿病」很不可靠常切成「2型」「糖尿病」。直接从图谱实体列表里按长度降序匹配能保证「2型糖尿病」优先被整体匹配而不是被「糖尿病」抢走。意图识别是简单的包含匹配胜在可控你可以给每个意图增加负例排除词避免「我没有药了」这种句子被误判为DRUG意图。参数说明py2neo的Graph初始化用bolt协议端口是7687和Neo4j浏览器的7474端口不同。_load_entities一次性把所有实体加载到内存2000个实体内存占用不到5MB但如果数据量到几万建议改为查询时用Cypher的WHERE n.name CONTAINS实时匹配。4.2 组装Cypher查询把意图和实体拼成可执行语句拿到意图和实体后下一步是根据组合关系生成查询。这是整个系统里最容易出错的环节因为用户问题里可能同时出现疾病和症状也可能只出现症状。查询模板要覆盖下面这些情况。def build_query(self, intent, entities): disease entities.get(Disease) symptom entities.get(Symptom) dept entities.get(Department) if intent department: if disease: return (fMATCH (d:Disease {{name:{disease}}})-[:BELONGS_TO]-(dept:Department) fRETURN dept.name AS answer) if symptom: return (fMATCH (s:Symptom {{name:{symptom}}})-[:HAS_SYMPTOM]-(d:Disease)-[:BELONGS_TO]-(dept:Department) fRETURN dept.name AS answer, collect(d.name) AS diseases LIMIT 5) if dept: return (fMATCH (dept:Department {{name:{dept}}})-[:BELONGS_TO]-(d:Disease) fRETURN collect(d.name) AS answer LIMIT 10) elif intent drug: if disease: return (fMATCH (d:Disease {{name:{disease}}})-[:RECOMMEND_DRUG]-(drug:Drug) fRETURN collect(drug.name) AS answer) if symptom: return (fMATCH (s:Symptom {{name:{symptom}}})-[:HAS_SYMPTOM]-(d:Disease)-[:RECOMMEND_DRUG]-(drug:Drug) fRETURN drug.name AS answer, d.name AS disease LIMIT 5) elif intent symptom: if disease: return (fMATCH (d:Disease {{name:{disease}}})-[:HAS_SYMPTOM]-(s:Symptom) fRETURN collect(s.name) AS answer) return None逻辑说明每个return的模板都按「实体类型→意图」的组合拆开症状查科室时用了一次两跳查询先由症状找疾病、再由疾病找科室同时返回疾病列表作为追溯依据。症状查药品同理。调用方拿到build_query返回的Cypher字符串后用py2neo执行并把结果格式化。参数说明这里直接把实体名拼进Cypher有注入风险但因为是本地demo且实体名来自图谱自身风险可控。如果你要部署成对外服务务必改用参数化查询——py2neo的graph.run(query, nameentity)会把实体名作为参数传参而不是拼接字符串。4.3 搭一个Flask接口让问答系统可被HTTP调用知识问答系统最后要有一个可演示的入口Flask是最轻的方案。一个/qa的POST接口接收JSON格式的问题返回答案文本。我习惯把MedicalQA类实例化一次放在模块级别而不是每次请求都重建因为_entities加载和Neo4j连接池复用能省掉大量重复开销。from flask import Flask, request, jsonify app Flask(__name__) qa MedicalQA() # 模块级单例避免每次请求重建实体词典 app.route(/qa, methods[POST]) def qa_endpoint(): data request.get_json() question data.get(question, ) if not question.strip(): return jsonify({answer: 问题不能为空}), 400 intent qa.parse_intent(question) entities qa.extract_entities(question) cypher qa.build_query(intent, entities) if not cypher: return jsonify({answer: 暂未找到相关答案试试换个问法}) results qa.graph.run(cypher).data() if not results: return jsonify({answer: 图谱中暂无此疾病的完整信息}) answer qa.format_answer(intent, results) return jsonify({answer: answer, intent: intent, entities: entities}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)逻辑说明返回的JSON里除了answer还带intent和entities字段这在实际调试时非常有用——前端或Postman调用后能直接看到系统理解成了什么意图、抽到了什么实体而不是只能看到一个答案字符串。format_answer方法把查询结果拼成自然语言比如把collect返回的药品列表用顿号连接后拼上「建议在医生指导下使用」。参数说明host0.0.0.0表示监听所有网卡地址这样答辩时可以在另一台电脑用http://你的IP:5000/qa调接口debugTrue开发时用但答辩演示时建议关掉否则出错会弹出Werkzeug的调试页面。5. 避坑与排查医疗知识图谱问答系统最常见的5个翻车点5.1 Neo4j导入中文乱码现象、原因、解决现象LOAD CSV导入后节点name属性显示为ç³å°¿ç之类乱码或者浏览器里中文正常但查询条件匹配不到。原因CSV文件编码不是UTF-8。最常见是Excel另存时用了ANSIGBK或者带BOM头。Neo4j在Linux下默认按UTF-8读取遇到GBK编码直接解码错误。解决清洗脚本导出时统一用encodingutf-8-sig这是最省心的方案。如果文件已经生成用VS Code或Notepad把编码转为UTF-8。注意转换后重新确认CSV列名没有乱码列名乱码会让row.疾病这种引用直接失效。5.2 实体对齐失败导致MERGE出重复节点现象、原因、解决现象导入后查询某疾病发现返回多条同名记录count(Disease)远超预期的疾病数。原因CSV里同一疾病有不同写法比如「头痛」和「头疼」、「2型糖尿病」和「II型糖尿病」。MERGE只在name属性完全一致时才合并任何字符差异都会创建新节点。解决在清洗脚本里维护同义词映射表或者用Neo4j里apoc.merge.node配合归一化函数做模糊合并。更实用的做法是在导入前跑一遍所有实体名的distinct列表人肉扫一遍把明显同义的字词手工写到disease_map和symptom_map里。这个活枯燥但值因为这直接决定问答的召回率。5.3 问答返回空结果但图谱里明明有数据现象、原因、解决现象用户输入「头疼挂什么科」系统返回「未找到相关答案」但在Neo4j浏览器里手动查「头疼」这个症状节点是有关系的。原因实体抽取阶段把「头疼」匹配成了「头痛」或者没匹配上。最大匹配算法是按实体列表长度降序遍历的如果图谱里只有「头部疼痛」而没有「头疼」天然匹配不上。解决维护一个问法别名表把高频口语词映射到图谱实体名。在extract_entities返回前加一层映射entity alias_map.get(matched, matched)。另一个办法是模糊匹配用difflib的get_close_matches找相似度大于0.8的实体名但要注意「痛」和「疼」的拼音相近但字不同difflib的字符相似度对中文字符不太友好更适合的做法是把别名表做厚。5.4 Neo4j连接失败bolt协议端口不通现象、原因、解决现象py2neo初始化时报ConnectionError: Cannot connect to host localhost:7687或bolt://握手失败。原因三件事最常出问题——Neo4j服务没启动、neo4j.conf里没开启Bolt监听、防火墙拦了7687端口。Windows下还常见Neo4j安装为桌面版Bolt默认监听localhost如果你改了server.bolt.listen_address为0.0.0.0但没重启服务。解决先访问http://localhost:7474确认Neo4j浏览器能打开再看配置文件neo4j.conf里的server.bolt.listen_address0.0.0.0:7687是否被注释。改完配置要重启Neo4j服务bin/neo4j restart。用Python端graph Graph(bolt://localhost:7687, auth(neo4j,password))逐项排查注意密码改过不要用默认neo4j。5.5 实体抽取把「糖尿病」和「2型糖尿病」同时命中现象、原因、解决现象问题「2型糖尿病吃什么药」返回的答案包含所有糖尿病亚型的药品而预期只返回2型糖尿病。原因extract_entities只取第一个命中的实体但在意图匹配阶段Cypher里可能同时用了两个实体导致查询路径发散。另一个情况是jieba分词把「2型糖尿病」切成「2型」「糖尿病」图谱里两个实体的关系交叉查询产生噪音。解决最大匹配按长度降序处理确保长实体优先被选中然后在extract_entities里加一个「长实体命中后跳过所有被包含的短实体」的过滤。具体做法是维护一个已命中实体集合后续匹配前先检查该实体是否是已命中实体的子串。这个逻辑放在build_query前做过滤能避免很多歧义。6. 进阶技巧把问答准确率从「能用」做到「答辩能吹」6.1 加一层问题改写把口语问法映射到图谱标准实体规则问答系统的天花板不在Neo4j查询而在实体识别的召回率。一个实测有效的技巧是维护「口语问法→标准实体名」的映射表并在extract_entities之前做一次问题改写。比如「脑袋疼」改写为「头痛」、「血压高」改写为「高血压」、「拉肚子」改写为「腹泻」。这个映射表不用一开始就建全运行一段后把未命中的问题收集起来手动补充映射两周后准确率能明显涨一截。question_rewrite { 脑袋疼: 头痛, 头很疼: 头痛, 头疼: 头痛, 血压高: 高血压, 血糖高: 高血糖, 拉肚子: 腹泻, } def rewrite_question(question): for k, v in question_rewrite.items(): if k in question: question question.replace(k, v) return question逻辑说明问题改写发生在实体抽取之前把口语词先归一化到图谱里的标准实体名这样最大匹配阶段就能直接命中。注意替换顺序短的别名词要放在后面替换否则「头很疼」可能先被「头痛」脚本替换成不存在的「头很痛」。实际运行时把没命中的用户问题存到日志里每周看一次补充映射表这是投入产出比最高的优化手段。6.2 用答案溯源增强答辩演示的说服力答辩演示时评委最常问的问题是「你怎么证明这个答案不是写死的」。除了展示Neo4j里的图之外还可以在API返回结构里增加path字段返回Cypher查询命中的完整路径。py2neo的graph.run(cypher).data()能拿到所有节点和关系把它格式化成一条可读的路径字符串前端展示为「2型糖尿病 → 推荐药物 → 二甲双胍」直观且可信。def format_answer(self, intent, results): if intent drug and results: drugs list(set([r[answer] for r in results])) diseases list(set([r.get(disease, ) for r in results if r.get(disease)])) base f「{diseases}」常用的药物包括{ 、.join(filter(None, drugs)) } return base 。具体用药请遵医嘱本结果仅供学习参考。 # 其他意图类似逻辑说明format_answer里加了一个免责声明这在医疗问答场景下不仅符合主流价值观也是答辩时自我保护的话术。答案聚合用set去重是因为同一个疾病关联的药品可能通过多条路径返回不去重会显得系统很「傻」。6.3 离线评估脚本用30条测试题量化系统效果问答系统的效果要有数字支撑不能只靠口头演示。我习惯维护一份30条左右的测试集覆盖每个意图的正例和反例跑一遍脚本统计准确率和召回率。这个数据可以直接写进毕业论文的测试章节。test_cases [ {question: 头痛挂什么科, expected_intent: department, expected_entity: 头痛}, {question: 2型糖尿病吃什么药, expected_intent: drug, expected_entity: 2型糖尿病}, {question: 高血压有哪些症状, expected_intent: symptom, expected_entity: 高血压}, ] def evaluate(qa, cases): hit 0 for case in cases: intent qa.parse_intent(case[question]) entities qa.extract_entities(qa.rewrite_question(case[question])) if intent case[expected_intent] and entities.get(Disease) or entities.get(Symptom): hit 1 print(f准确率: {hit}/{len(cases)}) evaluate(qa, test_cases)逻辑说明评估脚本不是只看最终答案对错而是拆成意图命中和实体命中两个维度分别统计这样能定位系统瓶颈是出在意图识别还是实体抽取。30条测试集里建议包含5条反例比如「我今天头不疼了」这种不含明确意图的句子意图识别应该返回default而不是误判为symptom。我在做这个题目时最深的感受是知识图谱问答系统的难度不在算法而在数据工程。实体对齐、同义词表、问题改写这些看起来不「高级」的工作占了我六成时间但恰恰是它们决定了答辩时演示效果是惊艳还是翻车。如果你按这条路径走把数据清洗做扎实、把规则匹配做厚、留下一个可解释的评估数字这个毕设就能稳稳落地。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询