ARTICLE DETAIL

资讯详情

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

基于知识图谱的《三国演义》人物关系可视化与问答系统实战

基于知识图谱的《三国演义》人物关系可视化与问答系统实战 简介本资源为基于知识图谱的《三国演义》人物关系可视化及问答系统毕业设计项目面向计算机、人工智能、通信工程、自动化等专业的高校学生与科研人员可用于毕业设计、课程设计、项目立项演示或自学进阶。压缩包共605个文件约15.67MB包含20个Python脚本、3个Jupyter Notebook与5个JSON数据文件支撑知识图谱构建与问答逻辑490张JPG图片、8个HTML页面及配套CSS、JS、字体文件构成前端可视化界面另有设计文档、说明文本与CSV数据辅助理解项目结构。项目代码完整、资料齐全经严格测试可稳定运行便于复现与二次开发。已有73人学习关注适合借鉴图谱建模、关系抽取与问答交互的实现思路也能在此基础上修改扩展功能作为毕设或课设的完整参考方案。1. 从一份 zip 到能跑的系统《三国演义》人物关系可视化与问答到底在做什么如果你正在翻毕业设计选题看到「知识图谱 可视化 问答系统」这套组合大概率会心动因为它看起来既有技术含量又能出效果图。但真正动手时很多人会卡在第一步三国人物关系到底怎么变成一张图又怎么让机器听懂「曹操和刘备是什么关系」这种问题。这个标题背后其实是一条完整的工程链路——从原文文本抽取人物与关系构建知识图谱做前端可视化再叠一层自然语言问答。它适合计算机、大数据、人工智能方向的本科或硕士毕业设计也适合想快速入门知识图谱的开发者。核心难点不在算法多深而在数据清洗、关系定义和查询映射这三件事上。下面我按实际落地顺序把每个环节拆开讲清楚。2. 知识图谱构建从《三国演义》原文到 Neo4j 图数据库2.1 为什么选 Neo4j 而不是直接存 MySQL人物关系本质是图结构用关系型数据库做多跳查询会写大量 JOIN性能差且难维护。Neo4j 是原生图数据库用节点表示人物、势力、事件用边表示关系查「刘备的兄弟的敌人」这种多跳问题只需要一条 Cypher 语句。常见做法是用 Neo4j 社区版本地跑在 7474 端口Python 通过 py2neo 或官方 neo4j 驱动连接。如果你不想装数据库也可以用 NetworkX 在内存里建图但可视化和问答的持久化会麻烦很多毕业设计答辩时也不容易展示「数据库」这一层所以我还是推荐 Neo4j。2.2 人物与关系的抽取规则 词典比纯模型更稳《三国演义》是半文言文直接用现成的 NLP 模型抽关系准确率往往不到 60%而且关系类型不可控。我一般会先定义好关系体系比如「君臣」「兄弟」「父子」「夫妻」「敌对」「盟友」「同僚」这几类然后写规则加词典来抽。具体做法是先用 jieba 分词加载一份三国人物词典把刘备、关羽、张飞、曹操、孙权等主要人物加进去再对包含两个人名的句子用正则匹配关系词。比如「刘备与关羽、张飞结为兄弟」匹配「结为兄弟」→ 兄弟关系。下面是一段可复现的抽取代码import jieba import re # 加载人物词典确保人名不被切碎 jieba.load_userdict(sanguo_names.txt) # 关系词到关系类型的映射 relation_patterns { 兄弟: [结为兄弟, 义结金兰, 兄弟], 君臣: [主公, 臣, 拜为, 封为], 父子: [父子, 之子, 其父], 夫妻: [娶, 妻, 夫人], 敌对: [斩, 杀, 攻打, 伐, 敌], 盟友: [联合, 结盟, 共谋] } def extract_relations(sentence, name_set): words jieba.lcut(sentence) # 找出句子中出现的人名 persons [w for w in words if w in name_set] if len(persons) 2: return [] results [] for rel_type, keywords in relation_patterns.items(): for kw in keywords: if kw in sentence: # 简单策略取前两个出现的人名作为关系对 results.append((persons[0], persons[1], rel_type)) break return results # 示例 name_set set(open(sanguo_names.txt, encodingutf-8).read().split()) s 刘备与关羽、张飞结为兄弟。 print(extract_relations(s, name_set))这段代码的逻辑是先保证人名不被分词切错再用关系词触发关系抽取。参数上sanguo_names.txt每行一个人名关系词表可以根据你读原文时发现的表达不断补充。注意这里只取了前两个人物实际项目中需要处理一句多人的情况可以用滑动窗口或依存句法分析来改进。抽取完成后把结果写成 CSV字段为head, relation, tail方便批量导入 Neo4j。2.3 用 Cypher 批量导入并建索引数据准备好后导入 Neo4j 有两种方式一是用LOAD CSV二是用 Python 驱动逐条创建。数据量不大时三国主要人物几百个、关系几千条用 Python 驱动更灵活。下面是用官方驱动导入的代码from neo4j import GraphDatabase import csv driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, your_password)) def create_relation(tx, head, rel, tail): # 使用 MERGE 避免重复创建节点 query ( MERGE (a:Person {name: $head}) MERGE (b:Person {name: $tail}) MERGE (a)-[r:RELATION {type: $rel}]-(b) ) tx.run(query, headhead, relrel, tailtail) with driver.session() as session: with open(relations.csv, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: session.execute_write( create_relation, row[head], row[relation], row[tail] ) # 建索引加速查询 with driver.session() as session: session.run(CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.name))这里用MERGE而不是CREATE是为了防止同一个人物被重复创建成多个节点。关系类型统一用RELATION具体类型放在属性type里这样查询时可以用WHERE r.type 兄弟过滤。建索引后按人名查节点会快很多。导入完成后在 Neo4j Browser 里执行MATCH (n)-[r]-(m) RETURN n, r, m LIMIT 100就能看到初步的图。3. 可视化用 ECharts 把三国人物关系图嵌进网页3.1 前端选型ECharts 关系图 vs D3.js可视化大屏和毕业设计展示我优先推荐 ECharts 的 graph 系列。原因很直接配置项多、文档全、和 Vue/React 集成简单而且关系图的力引导布局开箱即用。D3.js 更灵活但学习曲线陡毕业设计时间紧的话不划算。ECharts 的关系图支持节点分类着色、边标签、拖拽缩放正好用来区分魏蜀吴三个势力。数据从后端接口拿格式是nodes和links两个数组。3.2 后端接口Flask 返回图数据用 Flask 写一个/graph接口从 Neo4j 查数据并转成 ECharts 需要的格式。代码如下from flask import Flask, jsonify from neo4j import GraphDatabase app Flask(__name__) driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, your_password)) app.route(/graph) def graph(): query MATCH (a:Person)-[r:RELATION]-(b:Person) RETURN a.name AS source, b.name AS target, r.type AS relation LIMIT 500 nodes_set set() links [] with driver.session() as session: result session.run(query) for record in result: source record[source] target record[target] nodes_set.add(source) nodes_set.add(target) links.append({ source: source, target: target, label: {show: True, formatter: record[relation]} }) nodes [{name: n, symbolSize: 30} for n in nodes_set] return jsonify({nodes: nodes, links: links}) if __name__ __main__: app.run(debugTrue, port5000)这个接口返回的nodes里每个人物是一个节点links里每条关系带标签。symbolSize可以先固定后面可以根据人物出现次数动态调整。注意LIMIT 500是防止一次性返回太多节点导致前端卡顿实际展示时可以按势力分页或按关系类型过滤。3.3 前端渲染ECharts 配置与势力着色前端用 Vue 或纯 HTML 引入 echarts.min.js然后请求/graph接口。关键配置是series.type graphlayout forceroam true允许缩放拖拽。势力着色可以通过给节点加category字段实现比如刘备、关羽、张飞归为「蜀」曹操归为「魏」孙权归为「吴」。下面是一段最小可运行的配置fetch(http://localhost:5000/graph) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(main)); const option { tooltip: {}, legend: [{ data: [蜀, 魏, 吴, 其他] }], series: [{ type: graph, layout: force, roam: true, label: { show: true, position: right }, edgeLabel: { show: true, formatter: params params.data.label.formatter }, force: { repulsion: 300, edgeLength: 150 }, categories: [{ name: 蜀 }, { name: 魏 }, { name: 吴 }, { name: 其他 }], data: data.nodes.map(n ({ ...n, category: getCategory(n.name) // 根据人名返回势力索引 })), links: data.links }] }; chart.setOption(option); });repulsion控制节点之间的斥力值越大节点越分散edgeLength控制边长。如果图太挤把repulsion调到 500 以上。getCategory是一个简单映射函数你可以把主要人物和势力的对应关系写死在一个对象里。这样渲染出来的图鼠标悬停能看到人物名边上显示关系类型拖拽缩放都支持答辩演示足够用了。4. 问答系统把自然语言问题翻译成 Cypher 查询4.1 问答的两种路线模板匹配 vs 语义解析智能问答系统听起来高大上但在毕业设计里最稳的路线是「模板匹配 槽位填充」。因为《三国演义》人物关系的问题类型有限无非是「A 和 B 是什么关系」「A 的兄弟是谁」「A 属于哪个势力」这几类。用模板匹配能覆盖 80% 以上的常见问题而且准确率可控。语义解析比如用 BERT 做意图识别当然更高级但需要标注数据训练成本高答辩时如果被问到「为什么不用大模型」你可以说模板匹配在限定领域内更可靠、响应更快这是工程上的取舍。4.2 意图识别与槽位抽取的代码实现先定义意图和对应的 Cypher 模板然后用正则从问题里抽人名。代码如下import re # 意图模板正则 - Cypher 模板 intent_patterns [ (r(.*)和(.*)是什么关系, relation), (r(.*)的兄弟是谁, brother), (r(.*)属于哪个势力, force), (r(.*)的敌人是谁, enemy) ] cypher_templates { relation: MATCH (a:Person {name: $a})-[r:RELATION]-(b:Person {name: $b}) RETURN r.type AS answer UNION MATCH (b:Person {name: $b})-[r:RELATION]-(a:Person {name: $a}) RETURN r.type AS answer , brother: MATCH (a:Person {name: $a})-[r:RELATION {type: 兄弟}]-(b:Person) RETURN b.name AS answer , force: MATCH (a:Person {name: $a})-[:BELONGS_TO]-(f:Force) RETURN f.name AS answer , enemy: MATCH (a:Person {name: $a})-[r:RELATION {type: 敌对}]-(b:Person) RETURN b.name AS answer } def parse_question(question): for pattern, intent in intent_patterns: match re.search(pattern, question) if match: slots match.groups() return intent, slots return None, None # 示例 q 刘备和曹操是什么关系 intent, slots parse_question(q) print(intent, slots) # relation (刘备, 曹操)这段代码先匹配问题模板抽出人名槽位再根据意图选择 Cypher 模板。注意relation模板用了UNION因为关系可能是有向的A 到 B 和 B 到 A 都要查。force意图需要你在图里额外建Force节点并把人物通过BELONGS_TO关系连到势力节点上这部分可以在导入数据时一起做。4.3 把查询结果拼成自然语言回答拿到 Cypher 查询结果后不能直接返回一串数据要拼成一句人话。比如查到关系类型是「敌对」就返回「刘备和曹操是敌对关系」。如果查不到返回「暂未收录这两人的关系」。代码很简单def answer_question(question): intent, slots parse_question(question) if not intent: return 抱歉我暂时无法理解这个问题。 with driver.session() as session: if intent relation: result session.run(cypher_templates[relation], aslots[0], bslots[1]) records [r[answer] for r in result] if records: return f{slots[0]}和{slots[1]}的关系是{records[0]}。 return 暂未收录这两人的关系。 elif intent brother: result session.run(cypher_templates[brother], aslots[0]) names [r[answer] for r in result] return f{slots[0]}的兄弟是{、.join(names)}。 if names else 未找到兄弟关系。 # 其他意图类似处理 return 查询失败。这里每个意图单独处理返回的句子尽量口语化。实际项目中可以把回答模板抽出来做成配置方便调整。问答接口同样用 Flask 暴露一个/ask路由前端加一个输入框和回答展示区就是一个完整的问答界面了。5. 避坑与排查毕业设计里最容易翻车的 5 个地方5.1 人物名歧义导致关系错乱现象图里出现「刘备」和「刘玄德」两个节点或者「曹操」和「曹孟德」被当成两个人。原因原文里人物有字、号、小名抽取时没有归一化。解决建一个别名字典把「玄德」「孟德」「仲谋」等映射到标准名在导入 Neo4j 之前统一替换。这个字典可以手动整理主要人物也就几十个。5.2 关系方向搞反导致问答答非所问现象问「刘备的兄弟是谁」查不到但问「关羽的兄弟是谁」能查到。原因建图时关系方向不一致有的写成 A→B有的写成 B→A。解决在 Cypher 查询里用无向匹配-[r]-而不是-[r]-或者在导入时统一方向比如「兄弟」关系只建一条无向边。我一般建议查询时用无向匹配省去方向维护的麻烦。5.3 Neo4j 内存不足导致导入中断现象导入几千条关系后Neo4j 报内存错误或者服务卡死。原因默认堆内存太小或者一次性CREATE太多节点。解决修改neo4j.conf里的dbms.memory.heap.max_size调到 2G 以上导入时用MERGE并分批提交每 500 条session.run一次。如果数据量真的很大考虑用LOAD CSV配合apoc.periodic.iterate。5.4 ECharts 图节点过多导致浏览器卡顿现象关系图渲染出来后拖拽缩放非常卡甚至浏览器崩溃。原因一次性渲染了上千个节点和边。解决后端接口加LIMIT前端按势力或关系类型做筛选默认只展示主要人物比如出现次数前 50 的。另外把layout的animation关掉force的迭代次数调低能明显提升流畅度。5.5 问答模板覆盖不全导致频繁「无法理解」现象用户问「刘备和曹操谁厉害」这种比较类问题系统直接返回无法理解。原因模板只覆盖了关系查询没有覆盖比较、列表、计数等意图。解决先统计一批常见问题把高频意图补进模板。如果时间允许可以加一个兜底策略用 jieba 提取问题里的人名如果只提到一个人名就返回这个人的所有关系列表至少不会答不上来。6. 进阶技巧用 PageRank 找核心人物让可视化更有说服力基础版做完后答辩时老师很可能问「你怎么体现深度挖掘」。这时候可以加一个图算法用 Neo4j 的 Graph Data Science 库或者自己写 PageRank计算每个人物在关系网络里的重要性。PageRank 值高的人物节点画得大一点颜色深一点一眼就能看出曹操、刘备、孙权是核心节点。下面是用 Python 的 networkx 算 PageRank 的代码import networkx as nx from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, your_password)) G nx.DiGraph() with driver.session() as session: result session.run(MATCH (a:Person)-[r:RELATION]-(b:Person) RETURN a.name AS a, b.name AS b) for record in result: G.add_edge(record[a], record[b]) pagerank nx.pagerank(G, alpha0.85) # 按 PageRank 排序取前 10 top10 sorted(pagerank.items(), keylambda x: x[1], reverseTrue)[:10] for name, score in top10: print(f{name}: {score:.4f})alpha0.85是阻尼系数一般不用改。算出来后把 PageRank 值写回 Neo4j 节点属性前端根据这个值动态设置symbolSize比如symbolSize 20 100 * pagerank。这样图上的核心人物会明显更大视觉上更有层次。另外你还可以用社区发现算法Louvain自动把人物分成几个阵营和魏蜀吴做对比如果算法分出来的阵营和实际势力高度吻合这就是一个很好的答辩亮点。最后说一个我自己的习惯每次改完抽取规则或 Cypher一定先在小样本上跑一遍确认结果对了再全量导入。毕业设计最怕的就是数据错了还硬着头皮往下做最后可视化出来一团乱麻回头改成本极高。希望帮到你。本文还有配套的精品资源点击获取
返回列表