知识图谱整个建立过程可以分为以下几点:
其中:
这里我们使用哈工大的开源工具包LTP进行展示
本文主要介绍整体流程,中间的技术细节,会在后面分章节展开
我们自己创建一段文本
当然也可以导入文本,或者批量导入csv等格式的数据,为了简便我们使用一段话来展示。
text="苏轼是宋朝的著名文学家,黄庭坚是他的好朋友。苏轼擅长写词,而黄庭坚擅长写诗。他在常州去世。"
LTP支持将全部处理流程进行pipeline执行,当然我们也可以选择分布执行,在后面技术章节会详细展开
# 分词 cws、词性 pos、命名实体标注 ner、语义角色标注 srl、依存句法分析 dep、语义依存分析树 sdp、语义依存分析图 sdpg
output = ltp.pipeline(text, tasks=["cws", "pos", "ner", "srl", "dep", "sdp", "sdpg"])
我们把output展开看一下
output
LTPOutput(cws=['苏轼', '是', '宋朝', '的', '著名', '文学家', ',', '黄庭坚', '是', '他', '的', '好', '朋友', '。', '苏轼', '擅长', '写', '词', ',', '而', '黄庭坚', '擅长', '写', '诗', '。', '他', '在', '常州', '去世', '。'], pos=['nh', 'v', 'nt', 'u', 'a', 'n', 'wp', 'nh', 'v', 'r', 'u', 'a', 'n', 'wp', 'nh', 'v', 'v', 'n', 'wp', 'c', 'nh', 'v', 'v', 'n', 'wp', 'r', 'p', 'ns', 'v', 'wp'], ner=[('Nh', '苏轼'), ('Nh', '黄庭坚'), ('Nh', '苏轼'), ('Nh', '黄庭坚'), ('Ns', '常州')], srl=[{'predicate': '是', 'arguments': [('A0', '苏轼'), ('A1', '宋朝的著名文学家')]}, {'predicate': '是', 'arguments': [('A0', '黄庭坚'), ('A1', '他的好朋友')]}, {'predicate': '擅长', 'arguments': [('A0', '苏轼'), ('A1', '写词')]}, {'predicate': '写', 'arguments': [('A1', '词')]}, {'predicate': '擅长', 'arguments': [('A0', '黄庭坚'), ('A1', '写诗')]}, {'predicate': '写', 'arguments': [('A1', '诗')]}, {'predicate': '去世', 'arguments': [('A0', '他'), ('ARGM-LOC', '在常州')]}], dep={'head': [2, 0, 6, 3, 6, 2, 2, 9, 2, 13, 10, 13, 9, 2, 16, 2, 16, 17, 16, 22, 22, 16, 22, 23, 2, 29, 29, 27, 2, 2], 'label': ['SBV', 'HED', 'ATT', 'RAD', 'ATT', 'VOB', 'WP', 'SBV', 'COO', 'ATT', 'RAD', 'ATT', 'VOB', 'WP', 'SBV', 'COO', 'VOB', 'VOB', 'WP', 'ADV', 'SBV', 'COO', 'VOB', 'VOB', 'WP', 'SBV', 'ADV', 'POB', 'COO', 'WP']}, sdp={'head': [2, 0, 6, 3, 6, 2, 2, 9, 2, 13, 10, 13, 9, 9, 16, 9, 16, 17, 16, 22, 22, 16, 22, 23, 22, 29, 28, 29, 22, 29], 'label': ['EXP', 'Root', 'TIME', 'mDEPD', 'FEAT', 'LINK', 'mPUNC', 'EXP', 'eSUCC', 'FEAT', 'mDEPD', 'FEAT', 'LINK', 'mPUNC', 'AGT', 'eSUCC', 'dCONT', 'CONT', 'mPUNC', 'mRELA', 'AGT', 'eCOO', 'dCONT', 'CONT', 'mPUNC', 'EXP', 'mRELA', 'LOC', 'eSUCC', 'mPUNC']}, sdpg=[(1, 2, 'EXP'), (2, 0, 'Root'), (3, 6, 'TIME'), (4, 3, 'mDEPD'), (5, 6, 'FEAT'), (6, 2, 'LINK'), (7, 2, 'mPUNC'), (8, 9, 'EXP'), (8, 10, 'eCOO'), (9, 2, 'eSUCC'), (10, 13, 'FEAT'), (11, 10, 'mDEPD'), (12, 13, 'FEAT'), (13, 9, 'LINK'), (14, 9, 'mPUNC'), (15, 16, 'AGT'), (16, 9, 'eSUCC'), (17, 16, 'dCONT'), (18, 17, 'CONT'), (19, 16, 'mPUNC'), (20, 22, 'mRELA'), (21, 22, 'AGT'), (22, 16, 'eCOO'), (23, 22, 'dCONT'), (24, 23, 'CONT'), (25, 22, 'mPUNC'), (26, 29, 'EXP'), (27, 28, 'mRELA'), (28, 29, 'LOC'), (29, 22, 'eSUCC'), (30, 22, 'mPUNC'), (30, 29, 'mPUNC')])
当然,我们也可以使用字典分别返回其结果
# 使用字典格式作为返回结果
print(output.cws) # print(output[0]) / print(output['cws']) # 也可以使用下标访问
print(output.pos)
print(output.sdp)
print(output.ner)
print(output.srl)
我们这里创建知识图谱主要使用命名实体识别和语义角色标注的结果
实体创建主要是命名实体识别的结果
print(output.cws)
print(output.ner)
['苏轼', '是', '宋朝', '的', '著名', '文学家', ',', '黄庭坚', '是', '他', '的', '好', '朋友', '。', '苏轼', '擅长', '写', '词', ',', '而', '黄庭坚', '擅长', '写', '诗', '。', '他', '在', '常州', '去世', '。']
[('Nh', '苏轼'), ('Nh', '黄庭坚'), ('Nh', '苏轼'), ('Nh', '黄庭坚'), ('Ns', '常州')]
LTP目前可以识别的实体类别主要有三种:
标记 | 含义 |
---|---|
Nh | 人名 |
Ni | 机构名 |
Ns | 地名 |
当然如果是复杂项目的数据,这个明显是不能支持的,后面我们会介绍如果训练自己的命名实体识别模型
从上面的结果我们可以看到,目前识别的结果只有人名和地名
正常来讲,北宋作为时间,应该也需要进行识别
接下来我们创建实体:
def entity_extraction(ner_result):
ner_list_Nh=[]
ner_list_Ni=[]
ner_list_Ns=[]
for i in range(len(ner_result)):
if output.ner[i][0] == "Nh":
ner_list_Nh.append(ner_result[i][1])
elif output.ner[i][0] == "Ni":
ner_list_Ni.append(ner_result[i][1])
elif output.ner[i][0] == "Ns":
ner_list_Ns.append(ner_result[i][1])
return list(set(ner_list_Nh)),list(set(ner_list_Ni)),list(set(ner_list_Ns))
根据不同的识别标签创建三个list,并且用set去重,最后得到实体
ner_list_Nh,ner_list_Ni,ner_list_Ns
(['苏轼', '黄庭坚'], [], ['常州'])
最终的实体有三个,两个人名,一个地名
我们使用角色标注的结果创建三元组
实际情况下,只使用角色标注创建是不行的,需要根据依存句法、语义依存等一起来进行构建,这样会更好的理解上下文和全局
print(output.cws)
print(output.srl)
['苏轼', '是', '宋朝', '的', '著名', '文学家', ',', '黄庭坚', '是', '他', '的', '好', '朋友', '。', '苏轼', '擅长', '写', '词', ',', '而', '黄庭坚', '擅长', '写', '诗', '。', '他', '在', '常州', '去世', '。']
[{'predicate': '是', 'arguments': [('A0', '苏轼'), ('A1', '宋朝的著名文学家')]}, {'predicate': '是', 'arguments': [('A0', '黄庭坚'), ('A1', '他的好朋友')]}, {'predicate': '擅长', 'arguments': [('A0', '苏轼'), ('A1', '写词')]}, {'predicate': '写', 'arguments': [('A1', '词')]}, {'predicate': '擅长', 'arguments': [('A0', '黄庭坚'), ('A1', '写诗')]}, {'predicate': '写', 'arguments': [('A1', '诗')]}, {'predicate': '去世', 'arguments': [('A0', '他'), ('ARGM-LOC', '在常州')]}]
为了更好的展示,我们分别打印一下
for i in range(len(output.srl)):
#if srl[i]
print(output.srl[i])
{'predicate': '是', 'arguments': [('A0', '苏轼'), ('A1', '宋朝的著名文学家')]}
{'predicate': '是', 'arguments': [('A0', '黄庭坚'), ('A1', '他的好朋友')]}
{'predicate': '擅长', 'arguments': [('A0', '苏轼'), ('A1', '写词')]}
{'predicate': '写', 'arguments': [('A1', '词')]}
{'predicate': '擅长', 'arguments': [('A0', '黄庭坚'), ('A1', '写诗')]}
{'predicate': '写', 'arguments': [('A1', '诗')]}
{'predicate': '去世', 'arguments': [('A0', '他'), ('ARGM-LOC', '在常州')]}
语义角色类型可以在LTP官网上查看
我们这里筛选有A0角色的进行创建
从一般的结果看,一般A0会对应到命名实体识别的实体上,当然我个人觉得这里是有优化空间的
kg_list=[]
for i in range(len(output.srl)):
judge_for_a0=[]
for j in range(len(output.srl[i]["arguments"])):
judge_for_a0.append(output.srl[i]["arguments"][j][0])
#print((judge_for_a0))
if "A0" in judge_for_a0:
index_a0 = judge_for_a0.index("A0")
if "A1" in judge_for_a0:
index_a1 = judge_for_a0.index("A1")
elif "ARGM-LOC" in judge_for_a0:
index_a1 = judge_for_a0.index("ARGM-LOC")
kg_list.append([output.srl[i]["arguments"][index_a0][1],output.srl[i]["predicate"],output.srl[i]["arguments"][index_a1][1]])
生成的结果就是:
[['苏轼', '是', '宋朝的著名文学家'],
['黄庭坚', '是', '他的好朋友'],
['苏轼', '擅长', '写词'],
['黄庭坚', '擅长', '写诗'],
['他', '去世', '在常州']]
#实体清洗,与ner结果匹配
def kg_list_rule(kg_list,ner_list_Nh):
for i in range(len(kg_list)):
for j in ner_list_Nh:#如果A0角色,包含ner识别的实体,将其替换为实体名称
if j in kg_list[i][0]:
kg_list[i][0]=j
for i in range(len(kg_list)):#如果语义角色识别的角色不再ner结果中则删除,此处需要优化
if kg_list[i][0] in ner_list_Nh:
continue
del kg_list[i]
predicate = []
for i in range(len(kg_list)):#提取谓语
predicate.append(kg_list[i][2])
return kg_list,predicate
最终结果如下:
([['苏轼', '是', '宋朝的著名文学家'],
['黄庭坚', '是', '他的好朋友'],
['苏轼', '擅长', '写词'],
['黄庭坚', '擅长', '写诗']],
['宋朝的著名文学家', '他的好朋友', '写词', '写诗'])
目前neo4j应该是最容易使用的图数据库,所以我们使用neo4j进行创建,如果大家不想用图数据库,我们下一节介绍一个工具来进行可视化
from py2neo import Node, Graph, Relationship, NodeMatcher
graph = Graph("bolt://localhost:7687",
user="neo4j",
password='1qa2ws3ed',
name="demo"
)
def node_create(ner_list_Nh,ner_list_Ni,ner_list_Ns,predicate):
if len(ner_list_Nh)!=0:
for i in range(len(ner_list_Nh)):
graph.create(Node('人名', name=ner_list_Nh[i]))
if len(ner_list_Ni)!=0:
for i in range(len(ner_list_Ni)):
graph.create(Node('机构名', name=ner_list_Ni[i]))
if len(ner_list_Ns)!=0:
for i in range(len(ner_list_Ns)):
graph.create(Node('地名', name=ner_list_Ns[i]))
if len(predicate)!=0:
for i in range(len(predicate)):
graph.create(Node('标签', name=predicate[i]))
将所有实体,包括标签进行创建
def relationship_create(kg_list):
for m in range(len(kg_list)):
try:
rel = Relationship(matcher.match("人名").where("_.name=" + "'" + kg_list[m][0] + "'").first(),
kg_list[m][1],
matcher.match("标签").where("_.name=" + "'" + kg_list[m][2] + "'").first())
graph.create(rel)
except AttributeError as e:
print(e, m)
这里可以看到,其实这个结果有点不太理想,就是苏轼和黄庭坚之间没有建立关系,其实这个就是我们前面提到的,只使用语义角色标注创建三元组是不行的,后面我们可以结合其他方法进行优化。