通过【电影知识图谱问答(一)|爬取豆瓣电影与书籍详细信息电影知识图谱问答(二)|生成298万条RDF三元组数据电影知识图谱问答(三)|Apache Jena知识存储及SPARQL知识检索电影知识图谱问答(四)| 问句理解及答案推理】四篇文章的介绍,我们已经了解如何从豆瓣官网中爬取数据;如何将爬取的数据转换得到可用的三元组数据,并存储至Apache Jena之中;如何利用SPARQL查询语言进行知识检索和答案推理;如何理解问句所表达的深层语义信息,即获取问句实体和目标属性信息;如何利用问句的深层语义信息,结合规则和表示学习方法,推理得到问题答案。结合上面几篇文章,已经能够从零开始构建一个电影知识图谱问答系统,有兴趣的朋友可以尝试搭建。本篇文章,将介绍如何将电影知识图谱问答系统部署至微信公众平台,部署完成后能够通过微信公众号进行知识问答。

本项目相关代码已经发布至 GitHub,项目地址为weizhixiaoyi/DouBan-KGQA,欢迎Star。


1. 服务器

将代码部署至微信公众平台需要一个服务器,如果只是用于Demo展示的话,推荐购买腾讯云学生服务器或者阿里云学生服务器,价格十分优惠。个人购买的是腾讯云服务器,配置为1核CPU、2G内存、1Mbps带宽、50GB高性能云盘,价格6个月60元。

购买好服务器之后,需要安装系统环境,个人安装的是Ubuntu16.04系统。系统安装成功之后,安装Anaconda3,配置清华源。然后配置微信公众号,将服务器和微信公众号进行关联。

2. 微信公众号开发

根据微信公众平台开发文档接入指南,将服务器接入至微信公众号。微信官方文档已经比较详细,此处不再进行介绍,接入过程中有相关问题直接在文章下方评论即可。下图是个人微信公众号配置截图,有兴趣的话,也可以关注下个人微信公众号谓之小一

公众号开发配置.png

目前只是将服务器成功接入到微信公众号,但是还不能处理用户发送过来的请求,因此需编写用户请求处理方面的代码。处理方法可参考微信官方Demo,包括微信公众号处理用户请求的流程,如何接收消息,如何发送消息等问题。

熟悉官方Demo之后,开始编写豆瓣电影知识图谱(BM-KGQA)对话处理的代码。首先创建query_server.py文件,用于响应用户经微信公众号端发送过来的请求,并返回相应答案。代码如下所示,其中from query_main import Query是单条问句处理的接口,import receive, reply是定义的微信消息接收和返回函数。

# query_server.py
# -*- coding:utf-8 -*-

import web
import hashlib
from query_main import Query
import receive, reply

query = Query()

class Handle(object):
    def __init__(self):
        # 初始化query
        #  self.query = Query()
        pass

    def GET(self):
        try:
            data = web.input()
            if len(data) == 0:
                return "hello, this is handle view"
            signature = data.signature
            timestamp = data.timestamp
            nonce = data.nonce
            echostr = data.echostr
            # 和公众平台官网-->基本配置中信息填写相同
            token = "douban_kgqa"

            list = [token, timestamp, nonce]
            list.sort()
            sha1 = hashlib.sha1()
            map(sha1.update, list)
            hashcode = sha1.hexdigest()
            print("handle/GET func: hashcode, signature: ", hashcode, signature)
            if hashcode == signature:
                return echostr
            else:
                return "I don't Know"
        except Exception as err:
            print('ERROR: ' + str(err))
            return err

    def POST(self):
        try:
            webData = web.data()
            # 后台打印日志
            print('Handle Post webdata is ', webData)
            recMsg = receive.parse_xml(webData)
            if isinstance(recMsg, receive.Msg):
                toUser = recMsg.FromUserName
                fromUser = recMsg.ToUserName
                if recMsg.MsgType == 'text':
                    # result = "彩虹屁屁"
                    question = recMsg.Content
                    result = query.parse(question)
                    replyMsg = reply.TextMsg(toUser, fromUser, result)
                    return replyMsg.send()
                if recMsg.MsgType == 'image':
                    mediaId = recMsg.MsgId
                    replyMsg = reply.ImageMsg(toUser, fromUser, mediaId)
                    return replyMsg.send()
                else:
                    return reply.Msg().send()
            else:
                print('暂且不处理')
            return reply.Msg().send()
        except Exception as err:
            print('ERROR: ' + str(err))
            return err


urls = (
    '/douban_kgqa', 'Handle'
)

if __name__ == '__main__':
    douban_kgqa_web = web.application(urls, globals())
    douban_kgqa_web.run()

receive.py是接收用户经微信公众号发送过来的请求,解析后获取问句详细信息。

# receive.py
# -*- coding:utf-8 -*-
import xml.etree.ElementTree as ET


def parse_xml(web_data):
    if len(web_data) == 0:
        return None
    xmlData = ET.fromstring(web_data)
    msg_type = xmlData.find('MsgType').text
    if msg_type == 'text':
        return TextMsg(xmlData)
    elif msg_type == 'image':
        return ImageMsg(xmlData)


class Msg(object):
    def __init__(self, xmlData):
        self.ToUserName = xmlData.find('ToUserName').text
        self.FromUserName = xmlData.find('FromUserName').text
        self.CreateTime = xmlData.find('CreateTime').text
        self.MsgType = xmlData.find('MsgType').text
        self.MsgId = xmlData.find('MsgId').text
        self.Content = xmlData.find('Content').text


class TextMsg(Msg):
    def __init__(self, xmlData):
        Msg.__init__(self, xmlData)
        self.Content = xmlData.find('Content').text.encode("utf-8")


class ImageMsg(Msg):
    def __init__(self, xmlData):
        Msg.__init__(self, xmlData)
        self.PicUrl = xmlData.find('PicUrl').text
        self.MediaId = xmlData.find('MediaId').text

reply.py是将需要返回的答案封装成微信要求的数据类型。

# reply
# -*- coding:utf-8 -*-

import time


class Msg(object):
    def __init__(self):
        pass

    def send(self):
        return "success"


class TextMsg(Msg):
    def __init__(self, toUserName, fromUserName, content):
        self.__dict = dict()
        self.__dict['ToUserName'] = toUserName
        self.__dict['FromUserName'] = fromUserName
        self.__dict['CreateTime'] = int(time.time())
        self.__dict['Content'] = content

    def send(self):
        XmlForm = """
        <xml>
        <ToUserName><![CDATA[{ToUserName}]]></ToUserName>
        <FromUserName><![CDATA[{FromUserName}]]></FromUserName>
        <CreateTime>{CreateTime}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[{Content}]]></Content>
        </xml>
        """
        return XmlForm.format(**self.__dict)


class ImageMsg(Msg):
    def __init__(self, toUserName, fromUserName, mediaId):
        self.__dict = dict()
        self.__dict['ToUserName'] = toUserName
        self.__dict['FromUserName'] = fromUserName
        self.__dict['CreateTime'] = int(time.time())
        self.__dict['MediaId'] = mediaId

    def send(self):
        XmlForm = """
        <xml>
        <ToUserName><![CDATA[{ToUserName}]]></ToUserName>
        <FromUserName><![CDATA[{FromUserName}]]></FromUserName>
        <CreateTime>{CreateTime}</CreateTime>
        <MsgType><![CDATA[image]]></MsgType>
        <Image>
        <MediaId><![CDATA[{MediaId}]]></MediaId>
        </Image>
        </xml>
        """
        return XmlForm.format(**self.__dict)

代码编写完成之后,将所有代码移到服务器端,安装项目所需要的python依赖包,最后利用python query_server.py 80开启服务。开启成功之后,便能够利用微信公众号进行问答。

3.微信问答Demo

通过上面的配置,已经能够通过微信公众号处理电影,书籍方面的问题,下面来看看书籍-电影知识图谱问答系统(BM-KGQA)的最终效果,下图为BM-KGQA能够处理的问句类型。

KGQA_Demo1.png

针对电影类信息,可提问其主演、导演、编剧、海报、上映地区、上映时间、时长、其他名字、简介、详细信息、评分、评分人数等内容。比如提问“流浪地球的主演是谁?”、“流浪地球的上映时间是什么时候?”、“流浪地球的评分是多少?”、“流浪地球的详细信息是什么”等。需要注意的是,针对问句“流浪地球的评分是多少?”,因“流浪地球”既有书籍也有电影,所以返回两种答案,并对其进行标注区分。

KGQA_Demo2.png

针对电影人物类信息,可提问其照片、性别、星座、生日、出生地、职业、其他名字、详细信息、介绍等内容。比如提问“吴京的生日是什么时候?”、“吴京的出生地是在哪儿?”、“吴京的职业是什么?”、“吴京的星座是什么?”、“吴京个人的详细信息和我说一下”、“吴京主演了哪些电影”、“吴京指导了哪些电影”等。

KGQA_Demo3.png

针对书籍类信息,可提问其图片、出版社、出版日期、页数、目录、简介、评分、评价人数、详细信息等内容。比如提问“《追风筝的人》的出版社是哪儿?”、“《追风筝的人》的出版日期是什么时候?”、“《追风筝的人》总共多少页?”、“《追风筝的人》的作者是谁呢”、“《追风筝的人》的详细信息?”。

KGQA_Demo4.png

针对书籍类人物信息,可提问其图片、性别、生日、出生地、其他名称、介绍、详细信息等内容。比如提问“杨绛的图片内容?”、“杨绛写作了哪些书?”、“杨绛的出生地是哪儿?”、“杨绛的其他名字叫做什么?”、“杨绛的详细信息和我说一下?”。

KGQA_Demo5.png

以上,便是书籍-电影知识图谱智能问答系统(BM-KGQA)的最终效果,能够通过微信公众号来精确回答用户关于书籍-电影方面的问题。当然,目前该知识图谱问答系统仅能够处理书籍,电影领域的问题,处理过程也需要依赖大量规则模版,功能还不是很完善。但通过此项目,能够给初学者提供一个解决问题的完整流程,即如何利用知识图谱来进行特定领域知识问答。后续,将继续进行完善,包括但不限于将特定领域内知识问答推广至百科类知识问答;构建端到端的问句理解模块,直接从中抽取出问句三元组;能够提供复杂问句理解和复杂答案推理功能。


欢迎关注公众号谓之小一阅读更多内容。

推广.png

文章目录