上篇文章《电影知识图谱问答(三)|Apache Jena知识存储及SPARQL知识检索》中讲到如何将处理后的RDF数据存储至Apache Jena数据库之中、如何利用SPARQL语句从Apache Jena之中进行知识检索和答案推理。本篇文章将主要介绍如何理解问句所表达的深层语义含义、如何将自然语言问句转换成SPARQL查询语句、如何进行答案推理。
上篇文章讲到利用SPARQL语句能够从Apache Jena数据库之中检索得到问题答案,那么如果想要构建电影知识图谱问答系统,亟需解决的问题就是如何将自然语言问句转换成SPARQL查询语句。比如问句“流浪地球的主演有哪些?”,转换成如下SPARQL查询语句需要经过哪些步骤呢?
PREFIX : <http://www.douban_kgqa.com#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?x WHERE {
?s :movie_info_name '流浪地球'.
?s :has_actor ?a.
?a :movie_person_name ?x.
}
LIMIT 25
针对用户提问的自然语言问句,首先需要理解其中的深层次语义信息,即获取问句实体和目标属性信息。以问句“流浪地球的导演是谁?”为例,其问句实体是流浪地球、目标属性是导演,所采用的方法分别是实体识别和属性链接。
从问句中提取出实体可以采用以下两种方法:1)构建诸如BiLSTM-CRF(https://arxiv.org/pdf/1508.01991.pdf)等深度学习模型,然后利用训练好的深度学习模型预测出问句实体。2)构建实体词表,从问句中提取词表中所包含的实体。
第一种深度学习方法,能够预测得到训练数据中未出现过的电影名称,预测准确率保持在90%以上。缺点是需要构建训练数据,从头开始训练深度学习模型,耗费时间长;第二种词表方法,构建快捷方便,缺点是只能够发现词表中包含的电影实体名称,无法发现新的电影实体。
比较推荐的方法是词表+BiLSTM-CRF深度学习模型,但此处为了构建方便,只采用词表方法。词表构建方法是从爬取的数据之中,选出其中的电影和书籍名称、人物名称加入到词表之中。另外,有兴趣的朋友,可加上深度学习预测模型。BiLSTM-CRF模型在GitHub上有很多,可自主寻找。
属性链接可以采用以下两种方法:1)构建诸如CNN等多分类深度学习模型,然后利用训练好的深度学习模型预测问句的目标属性。2)构建关键词集合,把问句中所包含的关键词当作问句的目标属性。
同样,此处为了方便,直接采用关键词方法。有兴趣的朋友,可自主加上CNN等多分类预测模型。CNN等多分类模型GitHub上有很多,此处不再介绍。
需要注意的是,同一目标属性可以表达成多种含义。比如流浪地球的评分是多少?、也可以表达成流浪地球在豆瓣有多少分数?,那么此时我们就需要同时考虑评分和分数关键词。
rating = (W('评分') | W('分数')) # 评分
获取问句的实体和目标属性之后,便可根据规则模版将传统自然语言问句转换得到SPARQL查询语句,进而从Apache Jena数据库之中推理得到问题答案。构建规则模型可利用Python Refo库进行构建,比如构建某某电影的导演是谁?模糊匹配规则,方法如下所示。
movie_info_rules = [
Rule(condition_num=1,condition=(book_or_movie_entity + Star(Any(), greedy=False) + director + Star(Any(), greedy=False)) | (writer + Star(Any(), greedy=False) + book_or_movie_entity) + Star(Any(), greedy=False), action=QuestionSet.has_director)
]
模糊匹配规则中has_director为SPARQL检索语句转换函数,函数定义方法如下所示。
SPARQL_PREFIX = u"""PREFIX : <http://www.douban_kgqa.com#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#> #某电影有哪些导演
"""
SPARQL_SELECT_TEM = u"{prefix}\n" + \
u"SELECT DISTINCT {select} WHERE {{\n" + \
u"{expression}\n" + \
u"}}\n"
def has_director(word_objects):
"""
某电影有哪些导演
:param word_objects:
:return:
"""
select = u"?x"
sparql = None
for w in word_objects:
if w.pos == pos_book_or_movie:
e = u"?m :movie_info_name '{movie}'.\n" \
u"?m :has_director ?a.\n" \
u"?a :movie_person_name ?x".format(movie=w.token)
sparql = SPARQL_SELECT_TEM.format(prefix=SPARQL_PREFIX,
select=select,
expression=e)
break
return sparql
通过上述规则模版,能够处理以下类型的简单问句。
# 某电影的图片/上映地区/语言/上映时间/时长/其他名称/介绍/评分/ 评价人数
# 某电影的类型
# 某电影有哪些演员
# 某电影有哪些编剧
# 某电影有哪些导演
# 某电影的详细信息
# 某人的图片/性别/星座/生日/出生地/职业/其他名称/介绍/
# 某人演了哪些电影
# 某人写了哪些电影
# 某人指导了哪些电影
# 某人的详细信息
当然,也可以处理以下类型的复杂问句,但规则模版构建比较复杂。
# 某电影的评分是否大于8
# 哪些喜剧电影的评分小于4
# ...
# 某人出演了多少部电影
# 某演员参演的评分大于X的电影有哪些
# 某演员出演过哪些类型的电影
# 演员A和演员B合作出演了哪些电影
# ...
将问句转换成SPARQL查询语句之后,便可从Apache Jena之中检索得到问句答案,查询代码如下所示。另外,为提高推理的准确率,还可以对《电影知识图谱问答(三)|Apache Jena知识存储及SPARQL知识检索》中所介绍的自定义推理规则进行补充。
# -*- coding:utf-8 -*-
"""
jena fuseki查询
"""
from collections import OrderedDict
from SPARQLWrapper import SPARQLWrapper, JSON
class SparqlQuery:
"""
SPARQL 查询
"""
def __init__(self, endpoint_url='http://localhost:3030/douban_kgqa/query'):
"""
初始化链接
:param endpoint_url:
"""
self.sparql_conn = SPARQLWrapper(endpoint_url)
def get_sparql_result(self, query):
"""
根据查询条件,得到查询结果
:param query:
:return:
"""
self.sparql_conn.setQuery(query)
self.sparql_conn.setReturnFormat(JSON)
return self.sparql_conn.query().convert()
@staticmethod
def parse_result(query_result):
"""
解析返回的结果
:param query_result:
:return:
"""
try:
query_head = query_result['head']['vars']
query_results = []
for r in query_result['results']['bindings']:
temp_dict = OrderedDict()
for h in query_head:
temp_dict[h] = r[h]['value']
query_results.append(temp_dict)
return query_head, query_results
except Exception as err:
print('解析结果错误' + str(err))
def get_sparql_result_value(self, query_result):
"""
列表存储结果值
:param query_result:
:return:
"""
query_head, query_result = self.parse_result(query_result)
if query_head is None:
return query_result
else:
values = []
for qr in query_result:
for _, value in qr.items():
values.append(value)
return values
通过问句理解模块,能够得到问句的实体和目标属性信息。然后结合基于模版的答案推理方法,能够将问句转换成SPARQL查询语句,进而在Apache Jena数据库之中推理得到问题答案。但基于规则的答案推理仅能够处理已定义的规则,不能覆盖问句的所有情况。而我们又不能定义所有规则,这应该怎么处理呢?
这时,可以采用基于表示学习的答案推理方法,比如知识图谱嵌入中经典的Trans系列方法。这里我们以TransE(https://www.utc.fr/~bordesan/dokuwiki/_media/en/transe_nips13.pdf)为例进行解释,知识图谱中三元组向量化后可以表示为<h, r, t>,其中头实体为h、关系为r、尾实体为t。TransE假设实体和关系之间存在h+r ≈ t,即头实体h加上关系r的向量信息近似等于尾实体,那么我们便能够通过头实体和关系预测得到尾实体。也就是说,能够通过问句中实体和目标属性信息,预测得到问句答案。
此处对TransE原理内容不进行过多介绍,有兴趣的朋友可以去看论文。TransE训练代码可以从thunlp/OpenKE(https://github.com/thunlp/OpenKE)获取,训练数据可以从已爬取的豆瓣数据中抽取,训练完成后便可结合问句理解模块进行答案预测。
本篇文章介绍了如何理解问句深层次语义信息、如何将自然语言问句转换成SPARQL查询语句、如何利用TransE表示学习进行答案预测。至此,通过【一、二、三、四(本文)】几篇文章的介绍,我们已经了解如何从豆瓣官网中爬取数据;如何将爬取的数据转换得到可用的三元组数据,并存储至Apache Jena之中;如何利用SPARQL查询语言进行知识检索和答案推理;如何理解问句所表达的深层语义信息,即获取问句实体和目标属性信息;如何利用问句的深层语义信息,结合规则和表示学习方法,推理得到问题答案。结合上面几篇文章,已经能够从零开始构建一个电影知识图谱问答系统,有兴趣的朋友可以尝试构建。
下篇文章,将介绍如何将电影知识图谱问答系统部署至微信公众平台,并利用微信公众号进行知识问答,构建一个完整的知识图谱问答系统Demo。