简介这是一份面向中文自然语言处理与人工智能研究者的开源知识图谱资源以约1.4亿实体和三元组构建起大规模结构化中文知识库可应用于智能问答、语义搜索、推荐系统、数据分析等方向。压缩包体积仅819KB共含4个文件其中2张PNG图片展示知识图谱的整体结构与可视化效果1个Python脚本提供数据读取或转换的参考实现1份Markdown文档说明数据格式与使用注意事项。该资源已有1739人学习浏览适合希望快速上手大规模中文知识图谱的开发者与研究人员。通过这份资源读者可以了解实体、关系、属性的组织方式掌握三元组数据的基本处理思路并获得可直接参考的脚本工具为后续扩展知识图谱应用或开展语义计算实验提供起点。无论是学术研究还是商业项目都能从中受益。1. 这份中文知识图谱资源解决的不只是“有没有数据”想搭一个中文问答系统或者行业知识库卡在第一步的人不在少数开源的中文知识图谱要么规模太小跑一两个demo还行稍微上点业务就断层要么格式混乱实体、关系、属性全揉在一起清洗成本比建模还高。这份标题里写着“最大规模1.4亿中文知识图谱”的资源说白了就是给你一份可以直接导入图数据库或者关系型数据库的三元组大合集——面向中文领域的知识图谱实体、关系、属性一应俱全省去从零爬取和抽取的时间。它的价值不在“图怎么画”而在“数据怎么落库、怎么查、怎么不翻车”。适合做问答系统、实体链接、行业知识图谱预研的开发者也适合想拿真实规模数据练手图数据库的初学者。2. 拆开数据包格式、规模与三元组的真实结构拿到资源后第一件事不是解压了就跑得先搞清楚里面到底是什么组织方式。这一章把数据格式、字段语义、规模校验和导入路径一次说清楚。2.1 三元组文件的目录结构与文件命名这份资源解压后是若干个按领域或来源划分的目录每个目录下再放若干份纯文本数据文件。命名规律一般是“域名或行业名_编号”比如通用的百科类数据、医疗类、金融类、教育类会分成不同的子目录。这种按域划分的做法在实际项目中很常见好处是你可以按需加载子集——只想做医疗问答的就没必要把1.4亿条全量灌进库里。每个文本文件内部是标准的“头实体-关系-尾实体”三元组有些文件还带第四列或第五列一般是属性的补充描述或来源置信度。字段分隔符大多是制表符少数文件用逗号但逗号分隔的坑在于实体名里可能本身就含中文逗号所以解析时要优先按制表符处理。先跑一个耗时几秒的字段探测脚本把每列的样例输出出来比你直接开搞快得多。2.2 数据规模的真实校验方法“1.4亿”这个数字是标题宣传口径实际拿到文件后你必须自己做一次统计不能直接信文件名。常见做法是写一个快速遍历脚本统计每个文件的行数、非空行数、去重后的三元组数量。注意行数不等于有效三元组数量因为可能存在表头行、空行、重复行。import os import csv data_dir path/to/knowledge_graph_data total_rows 0 valid_rows 0 empty_rows 0 for root, dirs, files in os.walk(data_dir): for fname in files: if not fname.endswith((.txt, .csv)): continue fpath os.path.join(root, fname) with open(fpath, r, encodingutf-8) as f: reader csv.reader(f, delimiter\t) for row in reader: total_rows 1 if len(row) 3: empty_rows 1 continue if row[0] and row[1] and row[2]: valid_rows 1 print(ftotal rows: {total_rows}) print(fvalid triples: {valid_rows}) print(finvalid/empty rows: {empty_rows})这段逻辑是先遍历所有子目录只处理文本类文件逐行按制表符切割如果一行不足三列就直接算无效行。统计出有效三元组数量后再决定要不要做去重。去重的代价很高1.4亿条全集去重对内存和耗时都是大考验但如果只加载某个子域去重成本就完全可接受。我一般会先在子目录级别验证数据质量比如医疗子集如果有效行率低于80%说明这个来源的脏数据偏多后续清洗优先级就得调高。2.3 字段语义:实体、关系、属性与扩展列三元组的语义不是一成不变的。关系列有时候是“出生于”“主演”“属于”有时候则是“属性”性质的描述比如“身高-175cm”“颜色-红色”。这两种混在一个文件里很常见。区分办法是看尾实体的类型——如果尾实体是数值、日期、颜色词这类不可再分的值那它本质上是属性如果尾实体本身又是另一个头实体那它是关系。这直接影响图建模的方式。属性值建议在导入图数据库时直接作为节点的属性字段保存关联关系则建模成边否则图上会堆满“身高节点”“颜色节点”这种低价值的孤立点查询效率也会被拖累。可以先用一个脚本把属性型三元组过滤出来单独建属性索引剩下纯关系三元组再进图库。3. 落库与建模从纯文本到可查询的图数据库数据格式摸清了接下来要解决的是“怎么查”。直接把4.4亿行文本丢进搜索引擎是不现实的必须做结构化落库。这一章讲图数据库建模的选型思路以及一个可直接照抄的Cypher导入模板。3.1 选型:图数据库优先还是关系型数据库三元组数据最自然的落库方式是图数据库比如开源生态里常见的Neo4j或Apache AGE。图数据库对多层关系查询有天然优势——例如“查询所有参演过某导演电影的演员”这种跨两跳的查询关系型数据库要做多次JOIN图数据库一条遍历语句就能出结果。但如果你的目标是做统计型分析或训练集构造关系型数据库比如PostgreSQL配Apache AGE扩展反而更合适因为SQL生态对聚合、去重、采样要成熟得多。对于1.4亿规模这个量级我倾向先用关系型数据库做预清洗再导入图数据库做线上查询。原因是图数据库的全量导入对事务配置和内存参数非常敏感翻车率比SQL导入高一个量级。先落成三列宽表既能做质量探查又方便后续子图抽取等到查询场景确定了再决定导哪个图库。3.2 Cypher批量导入模板与参数说明以Neo4j为例使用LOAD CSV批量导入是成本最低的方案。先把三元组数据切成CSV格式保留制表符也行但CSV更容易排查字段错位再用Cypher写导入语句。下面是一个可以直接套用的模板假设数据文件是triples.csv三列依次是head、rel、tail。LOAD CSV WITH HEADERS FROM file:///triples.csv AS row WITH row WHERE row.head IS NOT NULL AND row.tail IS NOT NULL MERGE (h:Entity {name: row.head}) MERGE (t:Entity {name: row.tail}) MERGE (h)-[r:REL {type: row.rel}]-(t)这段Cypher的执行逻辑分三步先做非空过滤再用MERGE创建头实体和尾实体节点。如果实体已存在MERGE不会重复创建所以多次导入同一份数据不会产生重复节点。最后一步是给两个实体创建关系关系类型统一叫REL具体的语义存在关系属性type里。几个关键参数要特别留意。LOAD CSV WITH HEADERS要求CSV首行是列名如果你的原始文件没有表头可以把WITH HEADERS去掉改用row[0]这种方式取列。file://路径默认指向Neo4j的import目录数据文件必须放在该目录下否则会报找不到文件的错。批量导入时建议在语句前加上PERIODIC COMMIT 50000表示每处理5万行提交一次事务防止单个超大事务撑爆内存。这个数字不是固定的机器内存紧张就调到1万内存充裕可以放到10万。3.3 关系属性与双向查询的建模取舍建模时最容易被忽略的问题是“方向”。一条三元组“张三-出生于-北京”在图数据库里建成的边是从张三指向北京但很多业务查询需要的是反向操作——查出所有出生于北京的人。如果你只存了单向边反向查询就得用MATCH (b:Entity {name:北京})-[:REL]-(p)遍历效率尚可但如果关系类型多、路网深反向遍历的性能会明显下降。常见的解法有两种。一是导入时同时插入双向边查询会变快但存储体积膨胀近一倍1.4亿条变2.8亿条压力比较大二是用Cypher的WITH子句做方向无关查询从两个方向同时匹配代码稍微绕一点但省存储。我一般选择方案二因为这份数据的实体名重复度很高双向边会导致大量冗余。MATCH (a:Entity {name: 某实体}), (b:Entity) WHERE (a)-[:REL {type:相关}]-(b) OR (a)-[:REL {type:相关}]-(b) RETURN b.name LIMIT 100这里的OR条件就把方向问题绕过去了代价是执行计划可能变慢但配合LIMIT和实体名的索引一般场景都能接受。如果后续发现某些热点实体的双向查询频繁再定向建双向边也不迟。4. 数据清洗与避坑从1.4亿里捞“干净”数据的五个关键问题数据量越大脏数据的花样越多。很多人拿到资源直接导入图库结果查询结果里冒出一堆“None”“空”“未知”或者关系类型五花八门难以统计。这一章把我在清洗过程中遇到的典型问题和处理方式完整列出来。4.1 空值与占位符被当成实体导入拿到数据后先抽查尾部样本发现大量三元组的实体列是“NULL”“None”“-”“unknown”“未知”这种占位符甚至直接是空字符串。不处理就直接导入图库这些值会被当成一张实体节点的大集合而且这些节点往往还关联着大量边拉低全库质量。根源在于上游抽取环节从半结构化页面里提取实体时遇到缺失字段就填充了占位标记而不是跳过该三元组。解决分两步走第一步是建一个黑名单字典把这些占位符统一过滤掉第二步是把过滤之后的空列也一并剔除避免导入阶段再次报错。placeholder_set {, NULL, None, null, unknown, 未知, -, 无, N/A, 待补充} valid_triples [] with open(raw_triples.txt, r, encodingutf-8) as f: for line in f: parts line.strip().split(\t) if len(parts) 3: continue h, r, t parts[0], parts[1], parts[2] if h in placeholder_set or r in placeholder_set or t in placeholder_set: continue valid_triples.append((h, r, t))这段清洗脚本很直接——逐行切成三列任何一列命中占位符字典就整行丢弃。脏数据占比很高的时候这个脚本跑完你会看到数据量明显缩水这是正常的。宁可少导入几百万条也不能让一堆“未知节点”污染全库的统计口径。4.2 关系类型分布极不均衡长尾关系淹没统计统计关系类型的频次时发现排名前10的关系类型占了超过60%的实例长尾关系类型有几十万种其中大部分只出现几次。做查询或训练时如果不对关系类型做剪枝很多算法都会被高频关系带偏低频关系完全失去意义。合理做法是定义“有效关系”的标准某个关系类型出现次数少于阈值比如少于100次就统一标记为“低频关系”可以单独存到一个备份文件里不参与常规查询。阈值怎么定取决于使用场景——做问答意图识别的话关系频次低于100次的几乎不可能被用户命中剪掉不损失做学术研究想探索长尾关系则保留另一份完整数据两边互不干扰。4.3 全量导入图数据库时内存爆掉直接用LOAD CSV加MERGE跑全量1.4亿三元组跑到一半就报堆内存溢出这是撞上了Neo4j的事务处理上限。原因在于一个超大事务包含的节点和关系数量太多Neo4j的写事务把所有变更都驻留在内存里内存配置稍微保守一点就翻车。解决方案分两层。第一层是给导入脚本加PERIODIC COMMIT强制按批次提交前面已经提到过第二层是把导入拆成按子目录分批执行医疗、金融、教育分开导每导完一批就用CREATE CONSTRAINT给实体名建索引下一批再导的时候MERGE查找会更快内存压力也小很多。4.4 同一实体多种写法没有统一别名表“北京大学”和“北大”、“中国”和“中华人民共和国”在数据里同时存在它们被当成了不同实体导致子图碎裂。这个问题在中文知识图谱里非常突出几乎每一份大规模中文数据都有。务实的做法是先不全量做实体对齐因为1.4亿规模的实体对齐计算成本太高。我一般只针对高频查询热点做定向别名映射比如先统计实体出现频次挑出前几万个高频实体人工或规则半自动构造别名映射表查询阶段做实时的名称归一化。4.5 单个文件过大导致编辑器和分析工具打不开解压后有的单文件超过几十GB文本编辑器直接卡死各种脚本读取时也容易出现编码问题。从源头处理不要试图整文件打开用流式读取或分块处理。脚本改造方式是把文件按行数拆成多个子文件再并行处理效率提升明显。5. 进阶用法子图抽取与实体链接的实战技巧数据落地之后更高阶的问题是“怎么把这份知识图谱用出业务价值”。常见做法是实体链接和子图快照——针对特定实体快速取出局部子图用这份子图做推理或者问答检索。这里分享一个我自己用过、能显著提效的小技巧。5.1 用双向BFS抽取实体周围的关联子图很多场景只需要某个实体的局部知识没必要加载全库。比如问答系统里用户问“某公司的创始人是谁”如果这个实体已经用别名表关联过下一步就是取出以该实体为中心、两跳以内的子图。直接用图数据库全库遍历效率不高尤其当数据量到了亿级。常见做法是先用关系型存储做一次轻量级BFS从起始实体出发第一跳找出所有关联实体第二跳限制在上一轮结果集内展开同时用布隆过滤器快速剪枝掉重复访问避免环状子图造成死循环。from collections import deque import pybloom_live visited pybloom_live.BloomFilter(capacity100000, error_rate0.001) queue deque([(start_entity, 0)]) max_depth 2 result [] while queue: entity, depth queue.popleft() if depth max_depth: continue if entity in visited: continue visited.add(entity) neighbors query_neighbors(entity) # 从数据库取一跳邻居 result.append((entity, neighbors)) for nxt in neighbors: queue.append((nxt, depth 1))这里的query_neighbors是一个从三元组表里查询实体关联关系的函数可以换成SQL也可以换成图数据库查询接口。BloomFilter起到剪枝作用——它允许少量误判但能保证已经访问过的节点不会被反复入队。这个方案的优势在于内存占用是固定的不会随遍历规模线性增长适合局部子图的高频抽取。5.2 子图快照与缓存策略问答接口如果每次查询都现场跑一轮BFS响应延迟根本压不下来。工程上通常建议把热点实体的子图快照做成缓存。比如用户高频问到的实体可以提前算出两跳子图并序列化存储查询时直接读取缓存耗时从秒级降到毫秒级。缓存失效策略也要提前想清楚。知识图谱数据不是天天更新的快照缓存可以设置24小时或一周的有效期到期后重新抽取。如果上线初期数据源还没有增量更新机制直接写一个每日定时任务刷新热点实体的子图缓存即可。5.3 实体链接的简化实现实体链接的本质是给定一段文本识别里面提到的实体并映射到知识图谱节点。全流程训练一个深度学习模型成本太高效率也不稳定我一般会做一个词典加规则的轻量方案拿知识图谱所有高频实体名构建AC自动机对输入文本做最大匹配命中之后经别名表映射到标准实体。这个方法在限定领域下能稳定跑到80%以上的准确率对于初版问答系统完全够用。从那以后我每次拿到新数据源都强制走一遍“字段探测→统计校验→占位符清洗→分批导入”的流程而不是解压完直接导入图库——这套流程帮我少走了不少弯路。希望帮到你。本文还有配套的精品资源点击获取