ARTICLE DETAIL

资讯详情

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

医疗知识图谱问答系统构建全流程:从实体建模到答案排序

医疗知识图谱问答系统构建全流程:从实体建模到答案排序 简介医疗知识图谱问答系统涉及实体识别、图谱构建、意图分类与答案检索等多个环节这套项目代码以7类实体、3.7万实体、21万实体关系的医学数据为基础完整演示了从零构建KBQA可运行系统的实现路径适合有Python基础、希望入门知识图谱或问答技术的开发者学习。资源压缩包仅3.73MB共包含15个文件4个Python脚本覆盖图谱构建、实体抽取、意图模型训练与问答测试等核心流程5个txt词表与1个CSV数据文件提供症状、疾病、并发症、别名等医疗基础语料两个.m模型文件分别保存意图识别模型与TF-IDF模型2张PNG效果图展示知识图谱及问答运行效果另有1个UTF-8停用词文件。作者手工标注了210条意图分类训练数据通过对比SVM与朴素贝叶斯算法最终选择朴素贝叶斯模型最佳测试F1值达到96.68%资源已附带训练完毕的模型可直接运行复现。目前已有787人学习/下载项目目录结构清晰且代码注释简洁适合作为医疗领域KBQA的入门实战范例。1. 从3.7万实体入场医疗KBQA到底解决什么问题医疗领域的知识图谱问答系统KBQA和通用闲聊问答完全不是一回事。通用问答可以靠大模型生成医疗问答要求“答得准、答得有出处”不能一本正经地编。我接手医疗KBQA项目时需求通常很具体7类实体、约3.7万实体、21万实体关系要支撑用户用自然语言问“高血压有什么症状”“糖尿病吃什么药”“咳嗽挂什么科”系统能给出可靠答案。这套系统的难点不在“建图”而在实体别名归并、关系权重设置、问句到Cypher的映射以及答案排序。这篇文章把建模、导入、问答管线和排序的完整链路走一遍适合正在做知识图谱落地、需要把问答准确率做上去的工程团队。2. 医疗知识图谱建模7类实体的schema设计与21万关系导入2.1 七类实体与八大关系节点标签、属性表与分析医疗KBQA的实体类别不宜贪多。我常用的7类设计是疾病Disease、症状Symptom、药物Drug、检查Examination、科室Department、手术Surgery、食物Food。这套设计的依据是医疗咨询的高频问题都落在“得了什么病、有什么症状、做什么检查、吃什么药、挂什么科、要不要手术、忌什么口”这七个槽位上。3.7万实体看起来多分配到每一类后就清晰了——疾病1000多个标准概念加3000多别名症状4000多药物6000多标准名加一万多商品名/别名检查3000多科室一百多个手术1200多食物2600多剩下的就是各类别名实体。实体标签和必填属性如下表实体类型标签必填属性示例疾病Diseasestandard_id, name, icd10高血压I10症状Symptomstandard_id, name, description头晕无药物Drugstandard_id, name, atc阿莫西林J01CA04检查Examinationstandard_id, name, category血常规检验科室Departmentstandard_id, name, alias心内科心血管内科手术Surgerystandard_id, name, indications冠脉搭桥术冠心病多支病变食物Foodstandard_id, name, category高钾食物饮食禁忌关系类型我固定为八类分别覆盖“疾病到其他实体”和“药物到其他实体”两类主线关系名起点终点含义HAS_SYMPTOMDiseaseSymptom疾病表现出哪些症状NEEDS_EXAMDiseaseExamination疾病需要做哪些检查HAS_DRUGDiseaseDrug疾病的主要用药VISIT_DEPTDiseaseDepartment该挂哪个科室OPT_SURGERYDiseaseSurgery可考虑的手术方式HAS_COMPLICATIONDiseaseDisease并发哪种疾病FOOD_AVOIDDrugFood服用该药的禁忌食物DRUG_WATCHDrugExamination用药期间需监测的指标21万关系按这八类分摊量级大概是HAS_SYMPTOM约8万、HAS_DRUG约5万、NEEDS_EXAM约3万、VISIT_DEPT约2万、HAS_COMPLICATION约1.5万其余关系占1.5万左右。每条关系建议都带evidence和confidence字段evidence记录出处临床指南、药典、教材confidence是0到1的数值后面做答案排序会用到。这里有一个容易扯皮的问题3.7万实体到底怎么算。医疗数据源里同名同义的情况非常多“冠心病”“冠状动脉粥样硬化性心脏病”“CAD”都会以独立行出现。我一般保留两层结构标准概念实体作为主节点别名实体通过ALIAS_OF关系指向主节点。这样3.7万是去重后的可查询实体数而实际问答时全部归一化到标准概念ID避免同一道题答出五个相似节点。2.2 为什么选Neo4j而不是RDF三元组库查询路径与生态匹配知识图谱存储可以选RDF三元组库Virtuoso、GraphDB也可以选图数据库Neo4j、NebulaGraph。医疗KBQA的常见问题是“疾病到症状一跳”“疾病到并发症再到症状两跳”这类路径查询Neo4j的Cypher对这种多跳遍历天然友好写起来直观团队里后端工程师接手成本低。RDF/SPARQL在逻辑推理和OWL本体表达上更强但医疗问答场景用到推理的地方不多反而SPARQL语法和联邦查询会拖慢落地速度。还有一个现实原因医疗团队需要和医生一起核对关系是否正确。Neo4j Browser可以直接按疾病节点展开子图医生看到“高血压-头晕”这条边有问题指一下就能改。RDF库做可视化往往要额外接前端组件沟通成本高。如果团队后续要上规则推理再考虑在Neo4j前面加一层推理服务而不是把存储换掉。不选关系型数据库的理由更直接7类实体的属性不完全一致强行用MySQL存要么建一大堆空列要么搞EAV表查询时多表join写起来非常痛苦。多跳路径在SQL里要反复join自己21万关系下性能很难控制。Neo4j的属性图模型和Cypher正好是这块的“标准答案”。2.3 用LOAD CSV把3.7万实体落进Neo4j约束、索引、批量提交导入前先建约束和索引这一步决定后面导入速度。注意必须在导入关系之前执行否则每条关系匹配两端节点时都是全表扫描21万关系能导到怀疑人生。CREATE CONSTRAINT disease_standard_id IF NOT EXISTS ON (d:Disease) ASSERT d.standard_id IS UNIQUE; CREATE CONSTRAINT symptom_standard_id IF NOT EXISTS ON (s:Symptom) ASSERT s.standard_id IS UNIQUE; CREATE INDEX drug_name_idx IF NOT EXISTS FOR (d:Drug) ON (d.name); CREATE INDEX exam_name_idx IF NOT EXISTS FOR (e:Examination) ON (e.name); CALL db.awaitIndexes();约束和索引的区别约束保证standard_id唯一同时自带索引后面四个普通索引给名称查询加速。实体导入用MERGE而不是CREATE因为CSV里可能还有重复行MERGE能以standard_id为唯一键做幂等写入重复执行导入脚本不会产生重复节点。USING PERIODIC COMMIT 500 LOAD CSV WITH HEADERS FROM file:///disease.csv AS row MERGE (d:Disease {standard_id: row.standard_id}) ON CREATE SET d.name row.name, d.icd10 row.icd10, d.description row.description;USING PERIODIC COMMIT 500表示每500行提交一次事务。3.7万实体一次性塞进单个事务会让Neo4j的堆内存吃紧小事务分段提交更稳。ON CREATE SET只在节点不存在时写入属性避免二次导入覆盖已有数据。7类实体分别用各自的CSV导入后再导关系。关系表用两列存端点的standard_id这样匹配走的是约束索引而不是name索引速度更快USING PERIODIC COMMIT 1000 LOAD CSV WITH HEADERS FROM file:///rel_disease_symptom.csv AS row MATCH (d:Disease {standard_id: row.disease_id}) MATCH (s:Symptom {standard_id: row.symptom_id}) MERGE (d)-[r:HAS_SYMPTOM { evidence: row.evidence, confidence: toFloat(row.confidence) }]-(s);MERGE关系的逻辑和MERGE节点一致先查两端节点再检查关系是否已存在存在就跳过不存在才创建。toFloat把CSV里的字符串置信度转成浮点数后续排序直接可用。这里如果前面没有建好standard_id约束两个MATCH会变成全库扫描导入速度会从几分钟拖到几十分钟。如果处理的是药物和食物这类使用name匹配的关系就要确保对应的name索引已经创建。2.4 入库后三组统计查询验证实体数和关系数没有虚标导入后不要急着写问答先用三组查询核对数字。第一组按实体类型统计数量确认和CSV里的预期一致MATCH (n) RETURN labels(n)[0] AS entity_type, count(*) AS cnt ORDER BY cnt DESC;第二组按关系类型统计确认21万关系没有因为MERGE去重而缩水太多。如果发现某种关系数量比CSV少很多多半是端点的standard_id匹配失败去查CSV里的ID是否有前导空格或全角字符MATCH ()-[r]-() RETURN type(r) AS rel_type, count(*) AS cnt ORDER BY cnt DESC;第三组查孤立节点。医疗知识图谱里检查、科室这类节点的关系本来就少孤立比例会偏高但整体超过2%就要怀疑导入时把一部分关系漏掉了MATCH (n) WHERE NOT (n)--() RETURN labels(n)[0] AS entity_type, count(*) AS isolated_cnt ORDER BY isolated_cnt DESC;孤立节点排查有个现实经验CSV中端点ID是数字但被Excel转成了科学计数法或者ID列有看不见的空格都会导致MATCH失败。导入前用Python先对ID做strip和类型校验能省掉后面一大半排查时间。3. KBQA的意图识别与实体链接把口语问句转成Cypher模板3.1 四段式管线意图分类、实体识别、槽位填充、模板生成知识图谱已经就位接下来要回答用户的问题。KBQA的常见落地路径是模板法加实体链接不直接端到端生成Cypher——端到端在医疗场景的问题是无法保证查询语句可执行、答案可溯源。我的管线分成四段先做意图分类判断用户问的是症状、用药、科室、手术还是忌口再做实体链接从问句里抽出疾病、药物等实体并归一化到标准概念ID然后槽位填充把“吃什么药”映射到关系谓词HAS_DRUG最后按模板生成参数化的Cypher查询Neo4j。意图分类和实体链接是整个管线的承重墙。意图错了后面模板全部白搭实体链接错了查出来的就是另一个疾病的结果。实际项目里这两步的准确率目标分别要卡在0.95和0.90以上否则用户问十句话就会遇到一次答非所问。3.2 意图识别用规则轻量分类器不要一上来就套大模型意图集合要跟着模板和关系类型走我常用的意图如下表意图触发表达示例问句SYMPTOM什么症状、表现、哪里不舒服高血压有什么症状DRUG吃什么药、用药、服用什么糖尿病吃什么药EXAMINATION做什么检查、查什么胸闷做什么检查DEPARTMENT挂什么科、哪个科室看咳嗽挂什么科SURGERY要不要手术、怎么手术冠心病需要手术吗FOOD_AVOID不能吃什么、忌口吃阿莫西林不能吃什么规则用触发词表就能覆盖大部分问法但“我应该去哪个诊室看看”这种说法规则很容易漏掉。我在规则之后加一个轻量分类器兜底jieba分词加TF-IDF加逻辑回归训练数据1500到3000条就够CPU机器上推理延迟在毫秒级。以下是训练代码import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline # 训练语料格式标签\t问句每行一条 samples [] labels [] with open(intent_train.tsv, encodingutf-8) as f: for line in f: tag, text line.rstrip(\n).split(\t) samples.append( .join(jieba.cut(text))) labels.append(tag) model make_pipeline( TfidfVectorizer(ngram_range(1, 2), max_features5000), LogisticRegression(C4, max_iter200) ) model.fit(samples, labels)n-gram范围设为(1,2)能捕捉“挂什么科”这类两词搭配max_features限制特征数防止过拟合C值控制正则强度医疗问句比较短C4是我试下来比较稳的值。推理时用predict_proba概率低于0.6就返回“意图未知”走降级策略不要硬猜。这套方案比微调BERT省事得多效果在固定问法下能到0.92以上适合从零起步。3.3 实体链接用AC自动机打底吃下3.7万实体别名表意图识别做完接下来是从问句里抽出实体。3.7万实体去掉别名之后也有上万条词表用Python列表逐个find太慢用户问一句“高血压有什么症状”要遍历上万次。我一般用pyahocorasick把整个实体词表编译成AC自动机一次扫描文本就能命中全部词条耗时和词表大小基本无关。import ahocorasick # 词表结构标准概念ID - [标准名, 别名1, 别名2, ...] concept_names load_concept_alias_table() automaton ahocorasick.Automaton() for std_id, names in concept_names.items(): for name in names: automaton.add_word(name, (std_id, name)) automaton.make_automaton() def link_entities(text: str, automaton, max_span6): hits [] for end_idx, (std_id, matched) in automaton.iter(text): start_idx end_idx - len(matched) 1 hits.append({ standard_id: std_id, matched: matched, start: start_idx, end: end_idx }) return merge_overlap_hits(hits, max_span)merge_overlap_hits要处理“高血压病”和“高血压”同时命中的情况策略是优先保留最长匹配如果两个命中词有重叠但长度相同按实体类型优先级处理疾病优先于症状因为“糖尿病肾病”既包含“糖尿病”又是独立疾病。max_span控制一个实体最多占几个词防止把“冠心病患者日常注意事项”整句当成实体匹配。词典打底有一个盲区用户口语里的说法不在词表里比如“我心口疼”不会出现在标准症状表里。AC自动机miss时用NER模型兜底。我常用bert-base-chinese在BioBERT参数上继续微调序列标注训练数据2000到3000句就能见效数据不够就退回规则把“心口疼”这类高频口语手动补进别名表等积累多了再训模型。3.4 模板表驱动Cypher生成参数化查询拒绝字符串拼接实体链接做完手里有了意图和实体ID下一步是生成Cypher。这一步最忌讳直接拼字符串“高血压”带个括号或引号就能让查询崩溃。我用一张模板表把意图、关系类型、返回字段和排序逻辑固化下来TEMPLATES { SYMPTOM: { entity_type: Disease, cypher: ( MATCH (d:Disease {standard_id: $disease_id})-[:HAS_SYMPTOM]-(s:Symptom) RETURN s.name AS answer, r.confidence AS conf ORDER BY conf DESC LIMIT $top_k ) }, DRUG: { entity_type: Disease, cypher: ( MATCH (d:Disease {standard_id: $disease_id})-[:HAS_DRUG]-(drug:Drug) RETURN drug.name AS answer, r.confidence AS conf ORDER BY conf DESC LIMIT $top_k ) }, DEPARTMENT: { entity_type: Disease, cypher: ( MATCH (d:Disease {standard_id: $disease_id})-[:VISIT_DEPT]-(dept:Department) RETURN dept.name AS answer, r.confidence AS conf ORDER BY conf DESC LIMIT $top_k ) }, FOOD_AVOID: { entity_type: Drug, cypher: ( MATCH (drug:Drug {standard_id: $drug_id})-[:FOOD_AVOID]-(f:Food) RETURN f.name AS answer, r.level AS level ORDER BY CASE level WHEN strong THEN 0 WHEN medium THEN 1 ELSE 2 END LIMIT $top_k ) } } def generate_query(intent: str, entities: dict, top_k: int 5): t TEMPLATES[intent] return t[cypher], { f{t[entity_type].lower()}_id: entities[t[entity_type]][standard_id], top_k: top_k }这里所有变量值都用参数传入不拼进字符串。Neo4j Python Driver会对参数做转义医疗实体名里的括号、引号、特殊字符都不会影响查询。Cypher的label和relationship type不能参数化但模板表已经把rel_type固定死在字符串里不存在用户输入污染关系名的可能。模板表本身就是“意图到谓词”的映射字典加一个新问法本质上是加一条模板不需要改管线代码。4. 让答案从21万关系里出来候选召回、路径排序与降级策略4.1 为什么不能只按实体返回答案需要path不是节点很多初版KBQA实现只做一件事把和实体直接相连的所有节点返回。比如问“高血压有什么症状”就把HAS_SYMPTOM关系指向的全部症状倒出来。这在实体关系稀疏时没问题但21万关系下一个疾病节点往往关联几十个症状、几十种药、十几项检查。直接返回的结果是一个没有主次的长列表用户看到的第一眼就会觉得系统不聪明。正确的做法是把答案看成路径而不是节点。问“高血压有什么并发症”时“高血压-并发-糖尿病-并发-视网膜病变”这条两跳路径能回答“高血压可能导致视网膜病变吗”一条查询同时覆盖了实体和关系。答案排序也不能只看关系类型还要看关系上的confidence和evidence字段。4.2 PathRank排序衰减因子、最大跳数、top-k 三个参数我常用的排序做法叫PathRank思路是从起始实体出发做1到3跳的路径扩展对每条候选路径打分分数由路径上所有关系的weight加权求和再按跳数做衰减。这样一跳直接关系有优势但两跳的间接关系也有机会浮出水面。from neo4j import GraphDatabase def fetch_candidates(driver, disease_id, max_hop3, top_k5): cypher ( MATCH p (d:Disease {standard_id: $disease_id})-[*1..{hop}]-(candidate) WITH p, nodes(p) AS ns, relationships(p) AS rs WITH last(ns) AS candidate, reduce(s0.0, r IN rs | s coalesce(r.weight, 1.0)) AS weight_sum, length(p) AS hops RETURN candidate.name AS answer, candidate.standard_id AS answer_id, labels(candidate) AS answer_type, weight_sum AS raw_score, weight_sum * pow(0.85, hops - 1) AS score ORDER BY score DESC LIMIT $top_k ).format(hopmax_hop) with driver.session() as session: return session.run(cypher, disease_iddisease_id, top_ktop_k).data()reduce函数累加路径上所有关系的weightcoalesce把没有weight值的关系默认成1.0避免空值把整个和算成0。衰减因子0.85的控制逻辑是多走一跳权重打85折。这样“疾病-症状”的一跳关系不会被三条弱关系的长路径压下去同时两跳、三跳里真正重要的并发症路径也能进候选。三个参数在实际项目里的调整经验gamma在0.75到0.9之间取值太接近1会让间接关系无限拔高太接近0又会让多跳路径出不来max_hop取3医疗问题很少需要4跳以上而且跳数越多噪声越大top_k取5适合对话式问答卡片答案只有一两种时取10反而分散用户注意力。4.3 降级策略模板没有意义时的三级兜底KBQA最怕的不是答错而是直接返回空。医疗场景用户问一句“我心口疼应该怎么处理”意图可能的分类是DEPARTMENT但实体链接可能没有在词表里找到“心口疼”。如果直接返回空用户会觉得系统坏了。我按三级降级来做第一级意图命中但实体没命中。比如识别出“高血压”但没识别出症状实体这时去掉症状槽位只按疾病实体查询并返回该疾病的全部相关科室和常用药同时附带一句“以上信息来自知识图谱请以医生诊断为准”。第二级意图没命中但实体命中。比如用户问“高血压怎么引起的”规则和分类器都没覆盖“怎么引起”这个意图但实体“高血压”命中。此时返回疾病的定义、病因描述、就诊科室三个维度的知识卡片让用户自己挑选想要的方向。第三级意图和实体都没命中返回一个固定的引导答法列出当前知识图谱覆盖的科室和常见疾病清单建议用户换一种问法。医疗场景宁可少答不要瞎答降级策略的存在意义就是把答不出的情况转化成可接受的交互。5. 避坑医疗KBQA从0到1最常见的5个翻车现场5.1 别名没对齐实体链接命中率暴跌一半现象用户问“冠状动脉粥样硬化性心脏病”没有结果问“冠心病”有结果同一套问答系统换个问法准确率从0.8掉到0.3。原因实体词表只收了标准名别名表没有统一维护。更隐蔽的情况是CSV里同一个别名映射到了两个不同的standard_id导致“一人两ID”查询时根据命中的ID不同返回不同的子图。解决先建标准概念ID表ICD-10或ATC编码作为唯一键一个标准ID下挂一组别名。导入时统一用standard_id做唯一匹配别名单独存一张表。每次迭代跑回归集时把用户问过但没命中的说法补进别名表这是持续维护的活。5.2 LOAD CSV导入关系越来越慢先建约束再导数据现象关系CSV导入开始很快前两三万条嗖嗖的到五万条之后越来越慢21万关系导了一下午还没跑完。原因MATCH (d:Disease {standard_id: row.disease_id})没有走索引Neo4j对每个端点做全库扫描。关系越多扫描越慢复杂度是O(关系数×节点数)21万条关系时基本等死。解决导入关系之前先执行CREATE CONSTRAINT并调用CALL db.awaitIndexes()等待索引生效。LOAD CSV里加USING PERIODIC COMMIT 500把事务切小。如果数据源来自MySQL或Oracle按standard_id排序后分批导出断点续传也更方便。5.3 同义词节点当独立实体建3.7万变成虚标现象问“高血压”返回5个相似节点图谱实体数比数据源去重前还多回归集分数反而上升因为答案列表里多出来的全是重复内容。原因导入时没有做实体归一CSV里每一行字符串都当成了独立实体。商品名、通用名、简称全建成了节点没有用ALIAS_OF关系指向标准概念。解决导入前用Python做实体归一去首尾空格、全角半角统一、括号统一为半角、去掉“片/胶囊/注射液”这类剂型后缀。同义词先归并到标准概念ID再落图。3.7万实体指的是去重后的可查询实体数不是原始表的行数。5.4 医疗实体名带括号引号Cypher拼接查询直接翻车现象问“阿莫西林(克拉维酸钾)片”系统报语法错误。药品名里带括号、注册商品名里带®符号的实体在医疗数据里非常常见。原因Cypher语句是Python里用f-string拼出来的括号和引号破坏了Cypher语法。这种问题在开发环境测不出来因为测试集里全是干净的疾病名。解决所有值一律走参数化查询模板里只保留关系和label实体名、top_k全部用参数传递。模板里的relationship type和label用白名单dict固定不接收外部输入。实体名在模板匹配前再做一次标准化去掉剂型后缀进一步降低特殊字符带来的影响。5.5 答案一堆不分主次用户骂“废话机器人”现象问“咳嗽挂什么科”返回20个科室呼吸内科排第七问“高血压吃什么药”前几条全是二线药物。原因关系只有类型没有权重导入时没带confidence字段模板里也没有ORDER BYNeo4j按内部存储顺序返回基本等于随机排序。解决关系导入时带上evidence和weight字段evidence标记数据来源是临床指南还是普通百科weight由来源等级和confidence折算。模板里统一ORDER BY weight DESC, confidence DESC。如果一个疾病有“主要就诊科室”和“可挂科室”的区分在VISIT_DEPT关系上加level属性主科室排前面。6. 把回归集变成迭代的后悔药离线自测、LLM摘要与三个先做6.1 回归集是KBQA的后悔药一个JSON文件守住改动知识图谱项目的迭代频率不低每加一批关系、改一次别名表、调一个模板都可能让之前能答对的问题答错。我习惯维护一个回归集JSON文件每条case包含问句、期望意图、期望实体、期望答案列表。每次改动后跑一遍回归脚本top1和top5准确率一降就知道哪里改坏了。import json regression json.load(open(regression.json, encodingutf-8)) def evaluate(regression): hit1 0 hit5 0 for case in regression: result run_kbqa(case[question], top_k5) expected set(case[expected_answers]) if result and result[0][answer] in expected: hit1 1 if set(x[answer] for x in result) expected: hit5 1 n len(regression) return {top1: hit1 / n, top5: hit5 / n} print(evaluate(regression))回归集不用一开始就追求量大先拿100条线上真实问句把答案让医生确认过再固化下来后面每次改动都跑。top1掉两个点以上必须查清楚再上线别心存侥幸。6.2 大模型接入的正确姿势让它做摘要不让它生成Cypher现在很多医疗问答项目想用大模型一步到位把用户问句直接扔给LLM让它返回答案。这在知识图谱项目里是灾难LLM会生成图谱里根本没有的关系和数值用户拿着错误答案去用药后果不堪设想。我现在的做法是KBQA只负责从图谱里召回Top5候选LLM只负责把候选答案整理成通顺的一段话。图谱里的关系有出处、有权重LLM负责润色表达。比如召回结果是“阿莫西林、头孢呋辛、左氧氟沙星”加三条注意事项LLM把它整理成“根据知识图谱中的指南信息常用药物包括阿莫西林等请在医生指导下选用”。LLM的幻觉被限制在“怎么把话讲顺”的层面进不了医学事实层面。6.3 三个先做先对齐实体再标权重最后建回归集我现在接手一个新的医疗KBQA项目不会先问用哪个大模型而是先要一张从旧系统导出的、带标准ID和出处的实体关系表。没有这张表后面所有排序和模板都是纸上谈兵。具体顺序是先把实体对齐和别名表做干净这直接决定实体链接的天花板再把每条关系的置信度标出来这决定排序质量最后建回归集让后续每个改动都有据可依。这套流程走完3.7万实体21万关系才能真正变成用户问得出、答得对的系统。希望这篇能帮你少走几步弯路。本文还有配套的精品资源点击获取
返回列表