(三)构建知识图谱
知识图谱构建使用neo4j作为图谱数据库,因数据量庞大(4572万个实体,1.41亿条关系),需使用neo4j-admin import工具导入,导入前需准备节点和关系的文件。
-
节点数据格式
neo4j节点数据使用以下数据格式,本文统一给实体增加Entity标签,便于检索。
id:ID name :LABEL 1 姚明 Entity 2 姚沁蕾 Entity 3 中国 Entity 说明:此处id应唯一,一一对应相关实体.
-
关系数据格式
neo4j关系数据使用以下数据格式,:START_ID表示起始实体的id,:END_ID表示终止的实体id,本文统一增加Relation关系类型,便于检索。
:START_ID :END_ID :TYPE name 1 2 Relation 女儿 1 3 Relation 国籍 1 4 Relation 身高 说明:id对应关系为 (:START_ID)-[r]->(:END_ID)
-
数据抽取
数据抽取部分包括三元组抽取,属性映射,城市映射和带有单位的属性值标准化等部分。话不多说,先上代码,再谈本文所遇到的问题。
spo_extract代码如下:
import os import csv import re import build_prop_dict dir_path = os.getcwd() attr_map = build_prop_dict.load_attr_map(dir_path + '/data/prop_mapping.txt') city_map = build_prop_dict.load_city_map(dir_path + '/data/city_mapping.txt') zz_map = build_prop_dict.load_attr_map(dir_path + '/data/zhengze_mapping.txt') spo_file = open('/mnt/zjb/data/allTypeCsv.csv', 'w', encoding='utf-8') alltypeCsv = csv.writer(spo_file) entity_file = open('/mnt/zjb/data/entity.csv','w', encoding='utf-8') spo_file = open('/mnt/zjb/data/relation.csv', 'w', encoding='utf-8') ent_csv = csv.writer(entity_file) ent_csv.writerow(("id:ID", "name", ":LABEL")) spo_csv = csv.writer(spo_file) spo_csv.writerow((":START_ID", ":END_ID", ":TYPE", "name")) def remove_stopwords(s): ans = (re.sub(r'[;::;、,,.。%【】、`?*①②③④◇●◆/(& --·)\\()“”〗""》〖《 \s\d]', '', s)).replace('\u200b', '').replace("[", '').replace("]", '') return ans entity_dict = dict() idx = 0 index = 0 rela_dict = dict() with open('/mnt/zjb/data/ownthink_v2.csv', 'r', encoding='utf-8') as ownfile: reader = csv.reader((line.replace('\0', '') for line in ownfile)) next(reader) entity_list = [] for row in reader: if len(row) == 3: row[1] = remove_stopwords(row[1]) row[2] = re.sub('<[^<]+?>', '', row[2]).replace('\n', '').strip() if row[1] != '' and row[2] != '': for i in range(3): row[i] = row[i].replace('\u200b', '') row[i] = row[i].strip() if row[1] in attr_map: # 属性映射 if row[1] in zz_map: row[2] = row[2] + zz_map[row[1]][0] row[1] = attr_map[row[1]][0] elif row[1] in zz_map: # 处理身高cm为身高,属性值+cm temp_str = row[1] row[1] = row[1].replace( zz_map[row[1]][0], '') # 替换掉cm kg等 row[2] = row[2] + zz_map[temp_str][0] if row[0] in city_map: row[0] = city_map[row[0]] if row[0] not in entity_dict and row[0]: idx += 1 entity_dict[row[0]] = idx ent_csv.writerow((str(idx), row[0], 'Entity')) if row[2] not in entity_dict and row[2]: idx += 1 entity_dict[row[2]] = idx ent_csv.writerow((str(idx), row[2], 'Entity')) if row[0] and row[2] and row[1]: rel = str(entity_dict[row[0]]) + \ str(entity_dict[row[2]])+'Relation'+row[1] if rel not in rela_dict: alltypeCsv.writerow(row) spo_csv.writerow((str(entity_dict[row[0]]), str( entity_dict[row[2]]), 'Relation', row[1])) index += 1 rela_dict[rel] = index # sys.stdout.write("%.2f"%(float(idx)*100/float(len_lines))) # sys.stdout.write("%\r") # sys.stdout.flush index_01 = index for key in list(entity_dict.keys()): entity = key ent_val = re.sub(u"\\[.*?\\]", '', key) if ent_val != entity: if ent_val in entity_dict: temp_rela = str(entity_dict[ent_val]) + \ str(entity_dict[entity])+'Relation'+'歧义关系' if temp_rela not in rela_dict: alltypeCsv.writerow((ent_val, '歧义关系', entity)) spo_csv.writerow((str(entity_dict[ent_val]), str( entity_dict[entity]), 'Relation', '歧义关系')) index_01 += 1 rela_dict[temp_rela] = index_01 else: idx += 1 entity_dict[ent_val] = idx spo_csv.writerow((str(idx), str(entity_dict[entity]), 'Relation', '歧义关系')) ent_csv.writerow((str(idx), ent_val, 'Entity')) index_01 += 1 temp_rela = str(entity_dict[ent_val]) + \ str(entity_dict[entity])+'Relation'+'歧义关系' rela_dict[temp_rela] = index_01 alltypeCsv.writerow((ent_val, '歧义关系', entity))
build_prop_dict 字典的程序如下:
import ahocorasick import pickle from collections import defaultdict import os cwd = os.getcwd() def load_attr_map(attr_mapping_file): f = open(attr_mapping_file) mapping = defaultdict(list) for line in f: parts = line.strip().split(" ") for p in parts: if p != '': mapping[p].append(parts[0]) return mapping def load_city_map(attr_mapping_file): f = open(attr_mapping_file) mapping = dict() for line in f: parts = line.strip().split(" ") mapping[parts[1]] = parts[0] return mapping
-
代码介绍
三元组提取时,首先要提取具有完整信息的三元组,即包含实体-属性-属性值的信息,其次还要剔除包含空字符、首尾空格、html标签以及不可见字符"\u200b",最后对每个实体建立唯一的id,用id建立对应的节点和关系数据,因源数据中只有部分数据包含歧义关系,对于一些没有歧义关系的实体,本文建立了对应的歧义关系,主要针对实体[xxxx]类型,建立(实体)-[歧义关系]->(实体[xxxx])。
build_prop_dict 为映射程序,将源数据中出现一些属性、城市名、带有单位的属性值映射为对应的标准数据,主要使用字典进行映射。
-
数据导入
neo4j数据批量导入使用neo4j-admin import工具,针对neo4j 4.0 和neo4j 3.5.17,在此给出两种不同的导入方法(进入neo4j目录)。
neo4j-4.0:
bin/neo4j-admin import --nodes import/entity.csv --relationships import/relation.csv --multiline-fields=true
neo4j-3.5.17:
bin/neo4j-admin import --database=graph.db --nodes import/entity.csv --relationships:relation import/relation.csv --multiline-fields=true
说明:在数据导入前,需要清空对应的数据库文件(4.0为neo4j,3.5.17为graph.db)
-
建立标签
为了大大提高检索效率,数据导入后应建立对应的标签,此处仅建立实体的名称和id。
Cypher:
CREATE INDEX ON:Entity(name) CREATE INDEX ON:Entity(id)
-
修改数据
原始数据中存在一部分错误数据,对与此类数据,需要在知识图谱构建完毕予以更新,经过测试,本文共发现了如下三种错误:
-
错误1
错误关系:《北京市见义勇为人员奖励和保护条例》实施办法->[市长]->王安顺
解决办法:删除此条关系
Cypher:
MATCH (m:Entity{name:'《北京市见义勇为人员奖励和保护条例》实施办法'})-[r:Relation{name:'市长'}]->(n:Entity{name:'王安顺'}) delete r
-
错误2
错误关系:杭州[浙江省省会、副省级市]-[歧义权重]->(15254060)。
解决办法:删除(杭州[浙江省省会、副省级市]-[歧义权重]->(15254060)),增加(杭州[浙江省省会]-[歧义权重]->(15254060))。
Cypher:
MATCH (m:Entity{name:'杭州[浙江省省会、副省级市]'})-[r:Relation{name:'歧义权重' }]->(n:Entity{name:'15254060'}) delete r MATCH (m:Entity{name:'杭州[浙江省省会]'}),(n:Entity{name:'15254060'}) create (m)-[r:Relation{name:'歧义权重'}]->(n)
-
错误3
错误关系:北京-[描述]->《北京-中关村》为王持久作词,孙川谱曲,蔡国庆演唱的晚会歌曲,赞美了北京中关村作为电子科技产品的聚集地飞速发展。
解决办法:删除(北京-[描述]->《北京-中关村》为王持久作词,孙川谱曲,蔡国庆演唱的晚会歌曲,赞美了北京中关村作为电子科技产品的聚集地飞速发展。),增加(北京-[描述]->北京,简称“京”,是中华人民共和国的首都、直辖市、国家中心城市、超大城市、国际大都市,全国政治中心、文化中心、国际交往中心、科技创新中心,是中国共产党中央委员会、中华人民共和国中央人民政府、全国人民代表大会、中国人民政治协商会议全国委员会、中华人民共和国中央军事委员会所在地,也是中部战区司令部驻地。)。
Cypher:
MATCH (m:Entity{name:'北京'})-[r:Relation{name:'描述' }]->(n:Entity) delete r MATCH (m:Entity{name:'北京'}),(n:Entity{name:'北京,简称“京”,是中华人民共和国的首都、直辖市、国家中心城市、超大城市、国际大都市,全国政治中心、文化中心、国际交往中心、科技创新中心,是中国共产党中央委员会、中华人民共和国中央人民政府、全国人民代表大会、中国人民政治协商会议全国委员会、中华人民共和国中央军事委员会所在地,也是中部战区司令部驻地。'}) create (m)-[r:Relation{name:'描述'}]->(n)
-