多读书多实践,勤思考善领悟

从零开始学习知识图谱 之 三.电影知识图谱构建 3.基于REfO的简单知识问答

本文于1734天之前发表,文中内容可能已经过时。

一. 简介

基于浙江大学在openKG上提供的基于REfO的KBQA实现及示例。代码部分浙大方面已经完成绝大部分,这里主要将其应用到自己的知识图谱上。在运行KBQA代码前,应按照前面的教程将电影类知识图谱导入到Jena的TDB数据库中,并运行fuseki服务器,这样我们才能进行访问查询。

本教程的项目代码放在github上,下载地址为《从零开始学习知识图谱》项目源代码

二. 环境准备

1. 操作系统

支持操作系统:windows、macOS、Linux。为了方便大家搭建开发环境,笔者尽可能在windows下构建,系列篇未特意说明时操作系统都是windows。Linux安装可以参考VirtualBox虚拟机安装UbuntuVirtualBox虚拟机安装CentOS8进行安装。

2. jdk

安装参见windows系统安装JDK

3. Python3

安装参见从零开始学习知识图谱 之 一

三. 项目构建

1. 代码结构

代码结构为
.:
data/ get_dict.sh query.py utils/
./data:
actorName.txt get_dict.txt movieName.txt
./utils:
init.py init.pyc rules.py rules.pyc word_tagging.py word_tagging.pyc

其中data 目录存放由数据库倒出生成的字典文件,用于扩展jieba分词,由 get_dict.sh 生成。
utils/ 内存放查询预处理的模块。word_tagging.py 用于将词的文本和词性打包,视为词对象,对应:class:Word(token, pos)。rules.py 内定义各种规则并将自然语言转换为SPARQL查询语言,最终以JSON返回结果。
query.py 为程序入口,运行它来进行简单的KBQA。

2. 具体实现

基于REfO的简单知识问答的原理很简单,就是通过REfo提供的匹配能力,在输入的自然语言问题中进行匹配查找。如果找到我们预先设定的词或词性组合,那么就认为该问题与这个词或词性组合匹配。而一个词或词性的组合又对应着一个SPARQL查询模板,这样我们就借助REfO完成了自然语言到查询模板的转换。得到查询模板后,我们就利用Jena fuseki 服务器提供的端口进行查询得到返回的结果。

1). 模块一 word_tagging部分

该部分利用jieba分词对中文句子进行分词和词性标注。将词的文本和词性进行打包,视为词对象,对应 :class:Word(token, pos)。

1
2
3
4
5
6
7
8
9
10
11
class Tagger:
def __init__(self, dict_paths):
# TODO 加载外部词典
for p in dict_paths:
jieba.load_userdict(p)

def get_word_objects(self, sentence):
"""
Get :class:WOrd(token, pos)
"""
return [Word(bytes.decode(word.encode('utf-8')), tag) for word, tag in pseg.cut(sentence)]

2). 模块二 rules 部分

该部分为程序核心,负责将自然语言转换为SPARQL模板。

下面为rules的程序入口,customize_rules 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def customize_rules():
# some rules for matching
# TODO: customize your own rules here
person = (W(pos="nr") | W(pos="x") | W(pos="nrt") | W(pos="nz"))
movie = (W(pos="nz"))
place = (W("出生地") | W("出生"))
intro = (W("简介") | W(pos="介绍"))

rules = [

Rule(condition=W(pos="r") + W("是") + person | \
person + W("是") + W(pos="r"),
action=who_is_question),

Rule(condition=person + Star(Any(), greedy=False) + place + Star(Any(), greedy=False),
action=where_is_from_question),

Rule(condition=movie + Star(Any(), greedy=False) + intro + Star(Any(), greedy=False) ,
action=movie_intro_question)

]
return rules

该函数中我们设置了一些简单的匹配规则,例如我们设置 ‘’’movie = (W(pos=”nz”))’’‘,即movie 的词性应该是nz。其中的W()是我们在继承REfO的Predicate方法的基础上扩展更新了match方法。您可以简单的把它理解为re中compile后的match,只不过多个W()间出现的顺序可以变化。这样通过多个定制的W()和Star(Any(), greedy=False)(相当于.*?)这种通配符的组合,我们就定义了一组匹配规则,当遇到符合该规则的句子时,就选取该规则后action对应的查询模板。

例如当输入为“周星驰是谁”这样的问题时,会匹配到rules 中的 第一条规则。而后执行该规则后对应的action, who_is_question。而who_is_question对应的查询模板为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def who_is_question(x):
select = u"?x0"

sparql = None
for w in x:
if w.pos == "nr" or w.pos == "x":
e = u" ?a :actor_chName '{person}'. \n \
?a :actor_bio ?x0".format(person=w.token)

sparql = SPARQL_TEM.format(preamble=SPARQL_PREAMBLE,
select=select,
expression=INDENT + e)
break
return sparql

有了查询模板后,我们通过SPARQLWrapper 模块的SPARQLWrapper 执行该查询,并对返回的结果进行转换得到回答。对应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from SPARQLWrapper import SPARQLWrapper, JSON
from utils.word_tagging import Tagger
from utils.rules import customize_rules

if __name__ == "__main__":
print("init...........")
sparql_base = SPARQLWrapper("http://localhost:3030/kg_movie/query")
tagger = Tagger(['data/actorName.txt', 'data/movieName.txt'])
rules = customize_rules()
print("done \n")

while True:
print("Please input your question: ")
default_question = input()
seg_list = tagger.get_word_objects(default_question)

for rule in rules:
query = rule.apply(seg_list)
if query:
sparql_base.setQuery(query)
sparql_base.setReturnFormat(JSON)
results = sparql_base.query().convert()

if not results["results"]["bindings"]:
print("No answer found :(")
continue
for result in results["results"]["bindings"]:
print("Result: ", result["x0"]["value"])

3. 项目运行

运行,提问示例结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
C:\my\Python\python.exe C:/d/mmm/pycharm/patternREfO/query.py
Building prefix dict from the default dictionary ...
init...........
Loading model from cache C:\Users\mmm\AppData\Local\Temp\jieba.cache
Loading model cost 0.816 seconds.
Prefix dict has been built succesfully.
done

Please input your question:
刘德华是哪个
Result: 刘德华(Andy Lau),1961年9月27日出生于中国香港,中国香港男演员、歌手、作词人、制片人。1981年出演电影处女作《彩云曲》 [1] 。1983年主演的武侠剧《神雕侠侣》在香港获得62点的收视纪录 [2-3] 。1991年创办天幕电影公司 [4] 。1992年,凭借传记片《五亿探长雷洛传》获得第11届香港电影金像奖最佳男主角提名 [5] 。1994年担任剧情片《天与地》的制片人 [6] 。2000年凭借警匪片《暗战》获得第19届香港电影金像奖最佳男主角奖 [7] 。2004年凭借警匪片《无间道3:终极无间》获得第41届台湾金马奖最佳男主角奖 [8] 。2005年获得香港UA院线颁发的全港最高累积票房香港男演员”奖 [9] 。2006年获得釜山国际电影节亚洲最有贡献电影人奖 [10] 。2011年主演剧情片《桃姐》,并凭借该片先后获得台湾金马奖最佳男主角奖、香港电影金像奖最佳男主角奖 [11] ;同年担任第49届台湾电影金马奖评审团主席 [12] 。2017年主演警匪动作片《拆弹专家》 [13] 。1985年发行首张个人专辑《只知道此刻爱你》 [14] 。1990年凭借专辑《可不可以》在歌坛获得关注 [15] 。1994年获得十大劲歌金曲最受欢迎男歌星奖 [16] 。1995年在央视春晚上演唱歌曲《忘情水》 [17] 。2000年被《吉尼斯世界纪录大全》评为“获奖最多的香港男歌手” [18] 。2004年第六次获得十大劲歌金曲最受欢迎男歌星奖。2016年参与填词的歌曲《原谅我》正式发行 [19] 。1994年创立刘德华慈善基金会 [20] 。2000年被评为世界十大杰出青年 [21] 。2005年发起亚洲新星导计划 [22] 。2008年被委任为香港非官守太平绅士 [23] 。2016年连任中国残疾人福利基金会副理事长。 [21]
Please input your question:
刘德华出生地
Result: 香港新界大埔镇泰亨村
Please input your question:
七小福简介
Result: 《七小福》(Painted Faces)是由罗启锐导演,洪金宝、郑佩佩、林正英、岑建勋、午马等领衔主演的喜剧电影。影片讲述京剧大师于占元当年创办的“中国戏剧研究学院”,名字虽然取得唬人,但要论规模在香港众多戏校中也只算得中等,而且校舍破败,条件简陋,所幸师傅并未误人子弟,教得认真。“七小福“戏班最终名扬天下的故事。