ARTICLE DETAIL

资讯详情

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

《西游记》知识图谱构建全流程:实体抽取与Neo4j导入

《西游记》知识图谱构建全流程:实体抽取与Neo4j导入 简介以《西游记》为蓝本的Python知识图谱大作业项目面向自然语言处理与知识工程方向的初学者以及需要完成课程设计的高校学生有助于理解实体识别、关系抽取、图数据构建与可视化展示的核心流程。压缩包共670个文件主体为447个Python源脚本与135个pyc字节码文件另包含配置、说明、许可证、命令行工具等多类文件并带有虚拟环境相关配置整体大小仅3.83MB轻量且结构清晰。项目完整演示了从《西游记》文本解析到人物、地点、事件实体抽取再到关系边构建与知识查询的过程并集成图存储与可视化展示模块可支撑人物关系分析、取经路线梳理等典型应用场景。同时附有txt说明与依赖环境配置内容便于本地复现运行目前已有547人学习下载适合作为知识图谱课程设计模板、毕业设计原型或Python自然语言处理进阶练习直接参考。1. 一个《西游记》zip 压缩包拿什么姿势打开才算没浪费拿着一份《西游记》知识图谱.zip多数人第一时间想的是“里面到底有没有现成的图谱文件能不能直接可视化”。但以我做知识图谱的脾气第一步永远是先确认里面的数据形态再决定投入多少力气去补抽、去建库、去对齐。这个压缩包不论装的是原著全文、实体标注、三元组 CSV 还是半成品本体最终目标其实同一个让“孙悟空和牛魔王在哪一回结拜”“金箍棒被谁借走过”这种跨回目联想问题能用一条查询直接答出来。这套落地流程覆盖实体抽取、关系建立、Neo4j 存储和结果校验四个环节适合理工科背景、想入门中文古籍知识图谱构建的从业者也适合刚上手 neo4j 构建知识图谱、想用一个封闭领域数据集练手的人。2. 从 zip 里的文本到图结构本体先行、别名归并和关系白名单很多人拿到语料第一件事就跑 NLP 模型这是做知识图谱最常见的翻车起点。古典文学文本和新闻语料差别太大模型在现代汉语上表现好不代表在明清白话上能直接平移。做古籍知识图谱真正的第一步是定本体第二步才是上模型顺序反了后面每一步都在给前面的草率还债。2.1 为什么《西游记》语料让分词和实体识别集体翻车先看两个最直观的问题。第一是分词器词典覆盖不住古籍词现代分词器会把“行者”切成“行/者”把“摩云洞”切成“摩/云/洞”因为通用词典里根本没有这些词条的权重第二是别名爆炸孙悟空在全书写法极多齐天大圣、行者、孙行者、心猿、金公、猴王都指同一个人沙僧也叫悟净、沙和尚唐僧又叫玄奘、三藏、金蝉子、江流儿。如果抽取阶段不做实体对齐图谱里会出现十几个孤立的实体节点每个节点挂着各自的几根边一查“孙悟空”只出来三分之一的关系图看起来热闹实际上一问就露馅。除了别名还有一层隐性难点原著的诗赞、偈语、回目对仗句里藏着大量实体比如“那魔王神通广大变化多端”这种句子模型可能把“魔王”也当实体抽出来而它其实是泛指称谓不是具体人物。再叠加“奈河”“幽冥”“丹元”这类道教词汇实体边界的判定比现代文本复杂得多。所以做《西游记》知识图谱分词和实体识别这一步的预期不应该是“用 SOTA 模型一把过”而是“用模型打底靠词典和规则兜底”。2.2 本体建模先把这个实体和关系表敲定我一般会建议先花半天通读百回回目列一张纸的实体类型和关系类型再落到本体模型里。常见的做法是实体类型控制在 6 类左右关系类型控制在 10 类以内。实体类型典型实例说明人物孙悟空、唐僧、观音、牛魔王包括神仙、妖怪、凡人帝王地点花果山、灵山、流沙河、陷空山无底洞山、洞、国、海、寺庙器物金箍棒、九齿钉耙、紧箍儿、芭蕉扇兵器与法宝事件大闹天宫、三打白骨精、真假美猴王回目级别的里程碑事件经书《法华经》《心经》注意和书名实体区分称谓/官职弼马温、齐天大圣、御弟单独成类便于关联人物关系类型我建议用白名单锁死大战、收服、使用、前往、属于、是徒弟、对话、逃离、借走、占据。这些动词在原著里出现频率高、语义边界清楚抽出来之后能直接支撑“孙悟空的结拜兄弟”“观音菩萨一共点化过谁”这类问题。其余动词一律不抽。关系一旦放开等于把自己推进噪音堆里。2.3 为什么推荐 schema 先行而不是模型自动归纳学术路线喜欢先跑模型聚类出实体和关系再归纳成 schema好处是召回高坏处是一套语料一套结构图完全不可复用。工程路线反过来先把 schema 定死再按 schema 去标注或抽取。工业场景下的知识图谱设计绝大多数走后者因为业务方关心的是查询能不能稳定答上而不是模型抽出了多少新关系。《西游记》是封闭领域文本全书实体范围基本固定一次性穷举回目里的角色、法宝、地点并不难。我在项目里见过把“道”“曰”“说”都抽成关系的图一查全是对话噪音也见过用聚类方法抽出一堆“灵山”“大雷音寺”“西天”三个节点其实指的是同一个地方。这些问题的根子都不在模型精度而在 schema 没定好。所以我的习惯永远是先把实体类型和关系白名单表打印出来贴在桌上再开始写抽取代码。3. 实体与关系抽取流水线HanLP 词典兜底加规则模板的三层方案这一章的方案分三层第一层用开源 NLP 工具做初筛第二层用自定义词典和别名表做修正与归并第三层用规则模板抽关系。三层各干各的活互不干扰后面哪一层出了问题排查范围都很小。3.1 用 HanLP 跑第一版分词与实体识别这里以 HanLP 2.x 的 API 为例。HanLP 的优势是同时输出分词、词性和命名实体一个管线跑完省得手动拼。代码很短但几个输出字段得搞清楚。from hanlp import HanLP # 默认加载 tok、pos、ner 三个任务 pipeline HanLP() text 那行者闻言急纵云头径到积雷山摩云洞。 doc pipeline(text) # doc 是 dicttok / pos/ud / ner/ontonotes 分别对应切词、词性、实体 for i, token in enumerate(doc[tok]): print(token, doc[pos/ud][i], doc[ner/ontonotes][i])逻辑说明HanLP 默认管线把句子拆成一个列表doc[“tok”] 是切词结果doc[“pos/ud”] 是每个词性标注doc[“ner/ontonotes”] 是命名实体标签形如“人物_孙悟空”“地名_积雷山”。先跑这一版是为了拿到“模型视角的候选实体”而不是直接采信结果。参数上注意两点一是模型加载只需要一次循环跑批量语料时不要把 HanLP() 放进循环内部二是 HanLP 对古籍没有专门的西游模型默认模型表现一般看到“行者”被切成“行/者”不用惊讶这是预期内的由第二层词典解决。如果环境装不了 HanLP退一步用 jieba 加自建词典也能打底import jieba # 词典格式词语 词频 词性每行一个词 jieba.load_userdict(xiyou_dict.txt) # xiyou_dict.txt 内容示例 # 行者 200 nz # 悟净 200 nr # 摩云洞 100 ns print(jieba.lcut(那行者闻言急纵云头径到积雷山摩云洞。))参数说明词典文件必须是 UTF-8 无 BOM否则第一行词可能加载失败词频给 200 以上能压过关联合成默认切分词性标 nz 代表“其他专名”、nr 代表人名、ns 代表地名。走 jieba 方案时没有 NER 输出就靠词典把实体词钩出来效果同样够用。3.2 别名归并把“齐天大圣”和“孙悟空”绑到同一节点实体识别跑完最闹心的就是“孙悟空齐天大圣行者”这类等价问题。与其训练一个指代消解模型不如写死一张别名表因为原著名家称呼是枚举有限的几十个词条手工维护五分钟搞定精确率 100%。ALIAS_MAP { 孙悟空: [孙悟空, 齐天大圣, 行者, 孙行者, 心猿, 金公, 美猴王], 猪八戒: [猪八戒, 八戒, 悟能, 呆子, 净坛使者], 沙僧: [沙僧, 沙和尚, 悟净, 卷帘大将], 唐僧: [唐僧, 玄奘, 三藏, 金蝉子, 江流儿, 长老], } # 先按长度降序排列避免“行者”先替换掉“孙行者” def normalize(name: str) - str: for canonical, aliases in ALIAS_MAP.items(): for alias in sorted(aliases, keylen, reverseTrue): if name alias: return canonical return name # 示例抽取结果里三个不同名字归一化后指向同一实体 for name in [孙悟空, 齐天大圣, 行者]: print(name, -, normalize(name))这里有个关键边界归一化只作用于抽取结果绝不提前替换原文。一旦把原文里所有“行者”替换成“孙悟空”回目文字被污染后续做事件抽取、索引原文都会错位。实际项目里我会在实体识别层后面挂一个标准化函数拿到实体文本先查别名表命中就换 canonical 名最后入库的名字永远只有“孙悟空”“猪八戒”“沙僧”“唐僧”这几个主键。参数上唯一要注意的就是排序别名之间不要互相嵌套导致链式替换。3.3 关系抽取规则模板拿到底线远程监督再补召回关系抽取我习惯先用规则模板理由是《西游记》的回目和正文句式高度模板化“XX大战XX”“XX来到XX”“XX对XX道”这类结构反复出现正则能稳定抽中。规则模板的准确率高召回率低但作为第一版足够。import re PATTERNS [ (r(.{2,6}?)大战(.{2,8}?)[。], 大战), (r(.{2,6}?)来到(.{2,10}?)[。], 前往), (r(.{2,6}?)对(.{2,8}?)道, 对话), ] def extract_by_rules(sentences, known_entities): triples [] for sent in sentences: for pat, rel in PATTERNS: for m in re.finditer(pat, sent): subj, obj m.group(1), m.group(2) # 主语和宾语都必须在实体表里否则丢弃 if subj in known_entities and obj in known_entities: triples.append((subj, rel, obj)) return triples逻辑说明正则模板是最值得信赖的底线每条规则只匹配一个固定句式主语宾语长度限制在 2 到 6/8 个汉字之间防止把整个句子截进来。后面的 known_entities 判定非常关键它保证“孙悟空大战牛魔王”会入库而“猴王大闹天宫”这种主语不在实体表里的噪声会被丢掉。参数上正则里的长度区间要根据文本试跑调整区间太宽会把“那妖精闻言战战兢兢”这种句子误截进来。规则模板抽完你会发现召回率不够不少关系语序不标准、插入了状语模板直接漏掉。这时候我一般把规则抽出的高置信度三元组当作远程监督的种子让语料里的句子按实体对自动对齐到关系再训练一个关系分类器。远程监督适合这里的原因是《西游记》实体密集、句式重复度高种子质量够好时噪声会被淹没掉。如果只是为了交一个项目 demo规则模板已经能顶住远程监督是加分项不做也能交付。4. Neo4j 建库与落地导入约束、LOAD CSV 和冒烟查询实体和关系抽完接下来是把三元组装进图数据库。这一章的最终目标不是把数据导进去就行了而是让图谱能经得住几个“带点脑子的问句”查询。4.1 解压 zip 后先确认包里是哪一类资产拿到《西游记》知识图谱.zip先别急着找“一键导入脚本”先解压确认包内数据的完整程度。不同形态的包落地步骤差很远。包内资产需要的后续工作工作量只有原著文本从第 3 章完整走一遍抽取高文本 实体词典只需要跑关系抽取与对齐中三元组 CSV直接进入对齐与导入低本体定义 JSON 标注数据直接导入即可最低这一步要不要认真做取决于你的诉求。如果你是想研究抽取算法只有语料的包反而最好省得被别人的标注口径绑住手脚如果你只想快速跑通 neo4j 构建知识图谱的流程那优先找带三元组 CSV 的包把时间留给 Cypher 和数据校验。包里没有三元组也没关系第 3 章的代码可以直接产出你要的 CSV。4.2 用 Cypher 建约束并导入三元组 CSVNeo4j 5.x 的建约束语法和 4.x 略有不同这里以 5.x 为例。导入前必须先建唯一约束否则同一实体会在第二次导入时被 MERGE 重复创建。// 唯一约束同名实体的节点只能存在一个 CREATE CONSTRAINT entity_unique IF NOT EXISTS FOR (n:实体) REQUIRE n.name IS UNIQUE; // 常规索引按实体类型过滤时用避免全表扫描 CREATE INDEX entity_type IF NOT EXISTS FOR (n:实体) ON (n.type); // 导入三元组 CSV LOAD CSV WITH HEADERS FROM file:///xiyou_triples.csv AS row MERGE (s:实体 {name: row.subject}) SET s.type row.subject_type MERGE (o:实体 {name: row.object}) SET o.type row.object_type MERGE (s)-[r:关系 {type: row.relation}]-(o);逻辑说明LOAD CSV 逐行读取文件每条三元组里先用 MERGE 确保主语实体存在再用 MERGE 确保宾语实体存在最后用 MERGE 建关系。MERGE 和 CREATE 的区别在于MERGE 先查重、命中就更新属性、没有才创建这正好能满足“重复导入不产生重复节点”的要求。SET 语句给实体补上类型属性方便后续按类型过滤。代码里 CSV 列名必须和文件表头严格一致subject、subject_type、relation、object、object_type 五个字段一个都不能少。参数说明CSV 文件必须放在 Neo4j 安装目录下的 import 文件夹内LOAD CSV 里的 file:/// 指向的是这个目录不是磁盘跟路径。Windows 用户最常踩的坑是写 file:///C:/Users/xxx/xiyou_triples.csv这种写法会直接报错正确姿势是只写相对 import 目录的文件名。CSV 尽量用纯英文列名中文列名在部分版本下会编码出错文件本身存成 UTF-8 无 BOM否则表头第一列会带上不可见字符LOAD CSV 会报找不到字段。下面是一个可以直接用的三元组 CSV 样例三列实体类型对抽取结果的规范化要求很高也再次说明了第 3 章别名归并的意义subject,subject_type,relation,object,object_type 孙悟空,人物,大战,牛魔王,妖怪 孙悟空,人物,使用,金箍棒,器物 唐僧,人物,是徒弟,孙悟空,人物 哪吒,人物,大战,孙悟空,人物4.3 三个冒烟查询确认图谱真的能答上问题数据导完先跑三个冒烟查询别急着上可视化。第一个查询看孙悟空的一跳邻居第二个查询看“大战”关系的分布第三个查询看实体度数排查图谱结构是否合理。// 1. 孙悟空的一跳邻居查他直接关联的所有实体和关系类型 MATCH (n:实体 {name: 孙悟空})-[r]-(m) RETURN type(r) AS relation, m.name AS entity LIMIT 50; // 2. 大战关系分布谁被“大战”最多基本就是出场率最高的妖怪 MATCH (n:实体)-[r:大战]-(m:妖怪) RETURN m.name AS victim, count(*) AS times ORDER BY times DESC LIMIT 10; // 3. 实体度数排行找出图谱里的枢纽节点 MATCH (n:实体)-[r]-() RETURN n.name AS name, count(r) AS degree ORDER BY degree DESC LIMIT 15;这三条查出来之后怎么读第一条如果返回的全是“对话”说明关系模板没控制好白名单失效第二条如果榜首不是牛魔王、白骨精这类原著高频妖怪说明实体抽取漏得严重第三条度数最高的节点应该是孙悟空、唐僧这级别角色如果冒出一个“大王”之类的泛称说明实体识别混入大量非实体词。这三条查询跑完图谱能不能用心里基本有数了。5. 避坑与排错古籍知识图谱的 5 个翻车现场这一章全是实操里反复遇到的坑。每一条都按现象、原因、解决的顺序写照着排查就行。5.1 zip 伪加密解压就要密码但数据没加密现象双击 zip 弹出“需要密码”网上又说这包根本没设密码换 7-Zip 解压时提示加密头错误但用某些工具又能强解出来一部分文件。原因这是典型的 zip 伪加密。文件头里加密标志位被置成 1但数据本身没有真正加密。多见于发布者为了防止搜索工具直接预览内容手动改了标志位也有的是打包工具抽风。这类文件不是真正密码加密找密码移除根本没用正确做法是清除伪加密标志。import zipfile path 西游记知识图谱.zip z zipfile.ZipFile(path) for info in z.infolist(): # flag_bits 第一位是 1 代表“声明加密”如果文件头是伪加密数据其实没加密 print(info.filename, 伪加密 if info.flag_bits 0x1 else 正常)验证逻辑flag_bits 与 0x1 按位与的结果为 1说明加密标志位被置位。此时如果直接用空密码读取内容能成功就能确认是伪加密。参数说明zipfile 不需要额外依赖Python 3 自带如果读取时报错说明确实加密了那不是伪加密别硬解。修复方式也简单用 7-Zip 解压时选忽略密码或者写脚本把 flag 位清零后重新保存。5.2 分词器把“行者”切得只剩“行”和“者”现象jieba 分词输出“行/者”“悟/净”HanLP 把“摩云洞”切成“摩/云/洞”实体识别彻底失效。原因通用词典没有古籍专名词条“行者”在现代汉语里不是常用词分词器按通用语料统计概率切分结果被切成两个字“悟净”“摩云洞”同样是词典外词。解决给分词器挂一份领域词典词条覆盖人物别名、地名、法宝名。词频给高一点至少 200词性按类型标注。加载词典之后重新分词“行者”会稳定地作为一个整体输出。这里有个细节词典文件不能带 BOM否则第一行词加载会失败而且错误不会报出来只表现为“第一行词始终切不对”。我最开始就在这上面栽过后来每次建词典都会用十六进制看一眼文件头有没有 EF BB BF。5.3 别名没对齐图谱里冒出一堆孙悟空现象度数最高的实体不是“孙悟空”而是“齐天大圣”“行者”各占一个节点各自只连着几条边查“孙悟空”相关的战斗结果只返回三分之一。原因抽取阶段没有做实体归并模型把不同称呼当成了不同实体。齐天大圣、行者、孙行者、心猿、金公在原著里指同一个人但入库时各建了一个节点。解决导入前强制跑一遍别名归并把实体名归一化到“孙悟空”等主键上同时在 Neo4j 里建唯一约束再配合 MERGE 导入第二次跑 LOAD CSV 就不会产生重复节点。注意导入脚本里必须用 MERGE 而不是 CREATE否则唯一约束会直接报 Duplicate 错误而且数据导一半卡住。名字归并这一步放在抽取阶段做最省事放到导入阶段就要写复杂的 Cypher 合并逻辑。5.4 关系爆炸把所有动词都抽成了边现象规则抽取跑完生成十几万条三元组图上每个节点都被几十条边包围放大一看全是“说”“道”“看”“听”这类动词关系图谱根本没法看。原因关系抽取没设白名单把文本里所有动词短语都当成了关系。抽取阶段没有语义筛选图谱当然变成动词大杂烩。解决关系类型必须锁死白名单。我在第 2.2 节给出的“大战、收服、使用、前往、属于、是徒弟、对话、逃离、借走、占据”这 10 个关系就是筛选出来的常用集合。规则模板只在这些关系上匹配远程监督训练时也只把这类关系当目标。导入阶段可以再加一道置信度门槛三元组文件里带一列置信度LOAD CSV 时用 WHERE row.confidence 0.5 过滤低于阈值的直接不导入。5.5 LOAD CSV 导入失败协议头、目录与编码现象LOAD CSV 报“Couldnt load external resource”或者“Invalid input C”文件明明放在磁盘上路径也复制对了就是导不进去。原因file:/// 是 Neo4j 的协议头指向数据库安装目录下的 import 文件夹不是磁盘绝对路径。Windows 用户写 file:///C:/Users/xxx/triples.csv 会被解析成从服务器根目录找文件自然报错另外 CSV 带中文列名或 UTF-8 BOM 时字段名会变成 \ufeffsubject查不到字段。解决把 CSV 复制到 $NEO4J_HOME/import 目录下LOAD CSV 里只写文件名路径分隔符统一用正斜杠CSV 列名改用纯英文文件另存为 UTF-8 无 BOM。记住这一套四连Windows 环境导入基本不会再卡。如果改了之后还报找不到文件检查 Neo4j 配置文件里 dbms.directories.import 是否被改过这是最后一个隐藏变量。6. 五个查询验证图谱可用性再决定要不要做前端可视化图谱建完别急着上可视化。先跑五个查询把家底盘清楚这五条查询是我每建完一个图谱都会做的固定动作。-- 1. 实体总量衡量抽取覆盖度 MATCH (n:实体) RETURN count(n) AS entity_count; -- 2. 孤立点数量只有节点没有关系的实体 MATCH (n:实体) WHERE NOT (n)--() RETURN count(n) AS isolated_count; -- 3. 关系类型分布确认噪音占比 MATCH (n:实体)-[r]-() RETURN type(r) AS rel, count(*) AS cnt ORDER BY cnt DESC; -- 4. 同名名词节点数检查有没有漏掉归并 MATCH (n:实体) RETURN n.name AS name, count(*) AS cnt ORDER BY cnt DESC LIMIT 10; -- 5. 度数 Top 15枢纽节点是否符合直觉 MATCH (n:实体) RETURN n.name, size((n)--()) AS degree ORDER BY degree DESC LIMIT 15;第一条实体数太少说明抽取阶段漏得多得回第 3 章补词典第二条孤立点占比超过三成说明关系模板覆盖不足优先扩展“XX地点在地界”“XX人物属于XX阵营”这类高频句式模板第三条里如果“对话”排第一说明关系白名单失效返回去砍掉过度泛化的关系第四条查重复实体名一查一个准任何名字出现两次以上都是没归并干净第五条度数最高的节点必须是孙悟空、唐僧、如来这类核心角色如果榜首是“长老”这种泛称说明实体识别把称谓误当成具体人物了。这套验证跑完图谱才谈得上可视化。常见做法是把节点和关系导出成 JSON交给现成的知识图谱前端插件去渲染这类插件基本基于 ECharts 封装喂一个节点数组加一个边数组就能出交互图不走前端插件的话导成 CSV 丢进 Gephi 也行。可视化本身没有难度真正难的是在可视化之前搞清楚图谱有没有建歪。我吃过一次亏把没做孤立点检查的图直接交出去结果界面上几百个悬空节点像撒了一把芝麻第一版根本没法看。后来养成了习惯先跑完上面五条查询再谈渲染这个顺序帮我挡掉至少三次“图做完了才发现实体抽取有硬伤”的尴尬。希望帮到你。本文还有配套的精品资源点击获取
返回列表