基于知识图谱的文本自动注释(python+html)

在探索知识图谱的过程中,发现它可以做一个有趣的应用——文本自动注释。在此整理并分享给大家。为了具体说明它的效果,让我们先来看一个例子:

世界杯期间,伪球迷小B为了融入大家的话题讨论,上网看了不少足球新闻。然而,眼前一堆陌生的人名和术语看的他眼花缭乱……

央视网消息:北京时间6月30日晚22点,2018年俄罗斯世界杯1/8决赛迎来一场强强对话,欧洲豪门法国队迎战南美劲旅阿根廷。上半场比赛姆巴佩制造点球,格列兹曼主罚命中,而且任意球还击中横梁,迪马利亚世界波扳平比分,半场战罢,法国暂时1-1平阿根廷。下半场梅尔卡多打进1球,帕瓦尔破门,姆巴佩梅开二度,阿圭罗补时破门,法国队最终4-3击败阿根廷,成为第一支打进8强的球队,未来将对阵乌拉圭和葡萄牙的胜者。

“世界波是什么?迪马利亚又是谁?要是有办法一边看新闻,一边也能够看到关于这些名词的解释就好了。”小B心想。

这个时候,就该让文本自动注释发挥作用了,下面是注释后的文本(鼠标放在超链接上可以看到简介,点击则会跳转到相应的百度百科页面):

央视网消息:北京时间6月30日晚22点,2018年俄罗斯世界杯1/8决赛迎来一场强强对话,欧洲豪门法国队迎战南美劲旅阿根廷。上半场比赛姆巴佩制造点球,格列兹曼主罚命中,而且任意球还击中横梁,迪马利亚世界波扳平比分,半场战罢,法国暂时1-1平阿根廷。下半场梅尔卡多打进1球,帕瓦尔破门,姆巴佩梅开二度,阿圭罗补时破门,法国队最终4-3击败阿根廷,成为第一支打进8强的球队,未来将对阵乌拉圭和葡萄牙的胜者。

下面就来讲讲其实现的基本原理,这里使用的是python3.6,与python2的区别在于这里使用urllib.request而不是urllib2。

# coding=utf-8
import urllib.request
import codecs
from time import sleep
import json
import os

我们需要使用CN-DBpedia的API,这是一个大型开放的中文知识图谱,具体见:

Bo Xu, Yong Xu, Jiaqing Liang, Chenhao Xie, Bin Liang, Wanyun Cui, and Yanghua Xiao. CN-DBpedia: A Never-Ending Chinese Knowledge Extraction System. In International Conference on Industrial, Engineering and Other Applications of Applied Intelligent Systems, pp. 428-438. Springer, Cham, 2017.

简介:http://kw.fudan.edu.cn/cndbpedia/intro/

API:http://kw.fudan.edu.cn/apis/intro/

entitylinking_api = u"http://shuyantech.com/api/entitylinking/cutsegment?q="
avpair_api = "http://shuyantech.com/api/cndbpedia/avpair?q="

为了能够在程序中正确调用API,我们还需要把python中的中文表示方法转换为域名中可以正确识别的字符,下面是个简单的处理函数。实际使用中可能还有更复杂的情形,可根据URL编码再改写。

zh2url = lambda x: str(x.encode("utf-8"))[2:-1].replace(u"\\x",u"%").replace(" ", "%20")
text0 = "世界杯"
print(text0.encode("utf-8"),zh2url(text0))
b'\xe4\xb8\x96\xe7\x95\x8c\xe6\x9d\xaf' %e4%b8%96%e7%95%8c%e6%9d%af

读取网页内容所需函数

def getPage(url):
    try:
        page = urllib.request.urlopen(url)
        html0 = page.read()
        sleep(2)
        return html0
    except:
        print("error at %s" % url)
        return ""

准备工作已经大功告成,现在来看看这些API的作用吧。

1.三元组API

三元组(或者说属性-值对,attribute value pair,avpair)的API帮助我们获得关于一个词条(实体)的各项属性及信息。

url0 = avpair_api+zh2url("任意球")
html0 = getPage(url0)
print(url0)
http://shuyantech.com/api/cndbpedia/avpair?q=%e4%bb%bb%e6%84%8f%e7%90%83
data = json.loads(html0)
print("status:{}".format(data["status"]))
for (attribute, value) in data["ret"]:
    print("{}:{}".format(attribute, value))
status:ok
用途:犯规后重新开始比赛
分类:直接任意球
分类:间接任意球
任意球大师:C罗
任意球大师:贝克汉姆
任意球大师:卡洛斯
任意球大师:梅西
CATEGORY_ZH:足球
CATEGORY_ZH:体育
DESC:任意球是一种在足球(或手球)比赛中发生犯规后重新开始比赛的方法。任意球分两种:直接任意球(踢球队员可将球直接射入犯规队球门得分)及间接任意球(踢球队员不得直接射门得分,球在进入球门前必须被其他队员踢或触及)。国际足联最新规则为,判罚任意球后会使用一种泡沫喷剂划定球的摆放位置,以及人墙的站位,发任意球时需要用手触球,然后再裁判哨响后踢球。
CATEGORY_ZH:体育项目
中文名:任意球
外文名:free kick
运动:足球
运动:手球
规定:在罚球区内

返回的数据是json格式,其中有两个基本的属性。

  • status:本次API访问状态,如果成功返回“ok”,如果失败返回“fail”【不过即使是ok,也可能因为找不到该实体而不返回任何其他属性】
  • ret: 返回attribute-value pair list, 每个pair也是一个list ([attribute, value])

ret下有一些基本所有实体都具有的属性,因为结构化而有着比较高的价值。

  • DESC:该词条的简介
  • CATEGORY_ZH:所属类别
  • 中文名、外文名:在实体链接(后面提到)中有时可用作别名

对于CN-DBpedia中没有收录的词语,ret对应的是一个空列表,比如细心的读者在一开始的例子中也许注意到了这次世界杯上闪耀的新星姆巴佩并没有被加上注释,这可能是因为截至本文写作时,CN-DBpedia还没有来得及包括这个年轻小将的资料。
参见:http://shuyantech.com/api/cndbpedia/avpair?q=姆巴佩

2.实体链接API

输入中文文本,输出分词后的文本,以及识别的实体,json格式。

返回字段

  • cuts: 文本分词的结果,格式为字符串的列表

  • entities:从文本中识别的实体,格式为一个列表,列表的每个元素是一个链接的实体,表示为一个长度为2的列表,列表第一个元素是实体在输入文本中出现的位置,第二个元素为实体在CN-DBpedia中的名字。>值为一个列表,列表的每个元素表示一个链接的实体,表示为一个长度为2的列表,列表第一个元素是实体在输入文本中出现的位置,第二个元素为实体在CN-DBpedia中的名字。

url0 = entitylinking_api+zh2url("北京时间6月30日晚22点,2018年俄罗斯世界杯1/8决赛迎来一场强强对话,欧洲豪门法国队迎战南美劲旅阿根廷。")
html0 = getPage(url0)
print(url0)
http://shuyantech.com/api/entitylinking/cutsegment?q=%e5%8c%97%e4%ba%ac%e6%97%b6%e9%97%b46%e6%9c%8830%e6%97%a5%e6%99%9a22%e7%82%b9%ef%bc%8c2018%e5%b9%b4%e4%bf%84%e7%bd%97%e6%96%af%e4%b8%96%e7%95%8c%e6%9d%af1/8%e5%86%b3%e8%b5%9b%e8%bf%8e%e6%9d%a5%e4%b8%80%e5%9c%ba%e5%bc%ba%e5%bc%ba%e5%af%b9%e8%af%9d%ef%bc%8c%e6%ac%a7%e6%b4%b2%e8%b1%aa%e9%97%a8%e6%b3%95%e5%9b%bd%e9%98%9f%e8%bf%8e%e6%88%98%e5%8d%97%e7%be%8e%e5%8a%b2%e6%97%85%e9%98%bf%e6%a0%b9%e5%bb%b7%e3%80%82
data = json.loads(html0)

print("分词结果:" + " ".join(data["cuts"]))
print("识别实体:")
for pos, entity in data["entities"]:
    print(pos,entity)
分词结果:北京时间 6月30日 晚 22点 , 2018年俄罗斯世界杯 1 / 8 决赛 迎来 一场 强强 对话 , 欧洲 豪门 法国队 迎战 南美 劲旅 阿根廷 。
识别实体:
[0, 4] 北京时间(中国国家标准时间)
[14, 25] 2018年俄罗斯世界杯
[39, 41] 欧洲(地球七大洲之一)
[43, 46] 法国国家男子足球队
[52, 55] 阿根廷(阿根廷共和国)

可以直接用识别到的实体名(注意,与原文内容不一定相同)代入三元组API中以获得其详细信息,比如可以参见:http://shuyantech.com/api/cndbpedia/avpair?q=法国国家男子足球队

百度百科词条的对应位置也可以用类似格式得到,比如:https://baike.baidu.com/item/法国国家男子足球队

完成注释

现在,我们既有了实体在句中的位置,又可以得到其具体信息和百科链接。接下来,文本注释要做的,就是把相应的信息和链接放到对应的文本位置上。这里我们就要使用HTML的特性:在href属性上放上超链接,在title属性上则可以放上鼠标悬停时会看到的注释。

为清晰起见,这里仅提供一个简单示例:

origin_text = "而且任意球还击中横梁"
l,r,origin_entity0 = 2,5,"任意球"                                                       # 来自实体链接
href0 = "https://baike.baidu.com/item/" + zh2url(origin_entity0)
annotation = "任意球是一种在足球(或手球)比赛中发生犯规后重新开始比赛的方法。"           # 一般使用实体的"DESC"属性
annotated = r'%s' % (href0,annotation,origin_entity0)
annotated_text = origin_text[:l] + annotated + origin_text[r:]                            # 插入原文链接
print(annotated_text)
而且任意球还击中横梁

网页中的实际效果:

而且任意球还击中横梁

本文涉及到的完整代码可以在我的Github上找到:https://github.com/blmoistawinde/auto_annotation
也可以在binder上在线体验。

上面实现了一个完整的从txt文件到注释好的html的流程,也处理了实际应用中可能会出现的一些小问题。

事实上这个工程还有很多改进的空间,比如实体链接不准确、遗漏等问题,如果使用更加专业的领域知识图谱说不定就可以获得更好的准确率。

总之,有什么问题和建议都欢迎与我交流。

当然,要是觉得这个项目还不错的话,也希望大家star一下我的项目啦。

补注:
API的免费试用次数有限,比如我自己有段时间用的太多就被禁了233,所以大家要注意哈。
另外,对于英语等其他外语的实体链接,可以使用DBpedia的API: https://www.dbpedia-spotlight.org/

你可能感兴趣的:(知识图谱,知识图谱,python,html,entity,linking,jupyter,notebook)