基于规则的知识抽取主要还是通过人工定义一些抽取规则,从文章中抽取出三元组信息(实体-关系-实体)。重点即是定义规则。虽然定义规则这种抽取方式看起来有点low,但却简单实用,很多时候,效果比很多高深的算法还要好一些(非绝对,具体领域具体分析)。
本文的数据来源和https://blog.csdn.net/qq_21120275/article/details/102159314保持一致。
本文的抽取原理主要分为三个步骤
第一步:定义需要抽取的关系集合,比如【父亲,母亲,儿子,女儿,…】
第二步:遍历文章的每一句话,将每句话中非实体和非关系集合里面的词去掉
第三步:从每句话的第二个词开始遍历,遇到关系集合中的词,就选出该词左右最近的实体
本文没有贴出全部代码,只拿重点代码作为讲解。
本文从简单入手,依旧和https://blog.csdn.net/qq_21120275/article/details/102159314一样抽取人物关系,先定义需要抽取的关系集合
allowRelationships = ['母亲','父亲','儿子','女儿','母','父','下嫁','又嫁','祖父','祖母','孙','孙子','改嫁','哥哥','姐姐','弟弟']
使用requests进行文章内容的爬取
import requests
from lxml import etree
import jieba
from jieba import posseg
import re
hraders={'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763'}
url='http://www.uuqgs.com/lsrw/1358.html'
html = requests.get(url,headers=hraders)
text = html.content.decode("gb2312","ignore")
selector = etree.HTML(text)
content=selector.xpath('//div[@id="newscont"]/p/text()')
通过jieba词性识别抽取出nr的实体和带有关系的词组
# 抽取的实体词性
allowTags = ['nr']
relationships = set()
for line in content:
sentence = []
for word, tag in posseg.cut(line):
if tag == 'nr' or word in alowRelationships:
sentence.append(word)
sentence = ' '.join(sentence)
遍历一句话,从第二个词组开始,如果该词组属于要抽取的关系词组,则抽出前一个词组作为主语,后一个词语作为宾语:
sentenceSplit = sentence.split(' ')
print("原始文本:", line)
for i in range(1,len(sentenceSplit)-1):
if sentenceSplit[i] in alowRelationships:
source = sentenceSplit[i-1]
relationship = sentenceSplit[i]
target = sentenceSplit[i+1]
print('提取结果:',source+'->'+relationship+'->'+target)
relationships.add(source+'->'+relationship+'->'+target)
结果如下:
'''
原始文本: 武德九年(626年),玄武门之变后,李渊退位称太上皇,禅位于儿子李世民。
提取结果: 太上皇->儿子->李世民
原始文本: 李渊的祖父李虎,在西魏时官至太尉,是西魏八柱国之一。李渊的父亲李昞,北周时历官御史大夫、安州总管、柱国大将军,袭封唐国公。李渊的母亲是隋文帝独孤皇后的姐姐[7] 。
提取结果: 李渊->祖父->李虎
提取结果: 李渊->父亲->李昞
提取结果: 李渊->母亲->隋文帝 #错误结果
'''
本文的存储形式采用rdf格式,使用rdflib工具包进行操作
import rdflib
g = rdflib.Graph()
pesonUrl = 'http://www.huoyo.org/person/'
relationshipUrl = 'http://www.huoyo.org/relationship/'
for line in relationships:
line = line.split('->')
p1 = rdflib.URIRef(pesonUrl+line[0])
re = rdflib.URIRef(relationshipUrl+line[1])
#p2 = rdflib.URIRef(pesonUrl+line[2])
p2 = rdflib.Literal(line[2])
g.add((p1,re,p2))
g.serialize("graph.rdf")
文件结果如下
<rdf:RDF
xmlns:ns1="http://www.huoyo.org/relationship/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
>
<rdf:Description rdf:about="http://www.huoyo.org/person/李渊">
<ns1:女儿>南昌公主ns1:女儿>
...
<ns1:女儿>高密公主ns1:女儿>
<ns1:女儿>长沙公主ns1:女儿>
<ns1:儿子>彭思王李元则ns1:儿子>
rdf:Description>
<rdf:Description rdf:about="http://www.huoyo.org/person/周王李元方">
<ns1:母>张婕妤ns1:母>
rdf:Description>
...
<rdf:Description rdf:about="http://www.huoyo.org/person/韩王李元嘉">
<ns1:母>宇文昭仪ns1:母>
rdf:Description>
rdf:RDF>
使用sparql语言进行查询验证
g = rdflib.Graph()
g.parse("graph.rdf", format="xml")
q = "PREFIX p: <"+pesonUrl+">" \
"PREFIX r: <"+relationshipUrl+">" \
"select distinct ?b where { p:李渊 r:儿子 ?b }"
x = g.query(q)
t = list(x)
for i in t:
print(i)
本文主要基于规则进行知识的抽取,核心点就是关系规则的定义和规则两边的实体抽取,整体效果还行,会有部分错误需要人工干预。基于规则抽取的优缺点如下:
- 无需训练
- 实现简单
- 需要人工干预,特别是细节处理上
- 只能抽取实体-关系-实体模式的三元组,对于实体-实体-关系模式需要人工识别
- 局限于通用领域的实体识别,特殊实体和词性需要单独处理