文章概要
最近喜欢用思维导图列一下提纲,感觉这样思路会更清晰,也更方便记忆,所以先放一张思维导图上来吧。
工作需求
首先说明一下我们的工作是在做什么:
我们希望从DBLP网站上获取计算机领域论文的一系列信息,以此构建一张知识图谱,然后进行进一步的工作。
以Author为核心的dblp知识图谱
首先我们要明确的是我们要构建的知识图谱是以Author为核心的,在接下来要描述的三个数据集中我们可以看到,我们所定义的rdf都是某个有作者的具体的发表物,然后连接各种各样的其他实体或属性,我们强调以Author为核心的原因是在分析dblp.xml文件的时候我们会发现里面有一些record记录的并不是某人的具体谋篇发表物,也可能是某一个期刊的某一卷或某一本book等等。接下来讲一下我们需要构建的三个数据集格式。
Author数据集
第一个数据集记录所有paper的Author信息,三元组形式为:
(paperKey, paperAuthorBy, authorName)
Paper数据集
第二个数据集记录了所有paper的一些具体信息,分别为modefydate,发表时间year,论文标题title这三个属性,每个paper实体都记录3条rdf数据,三元组形式分别为:
(Paperkey, paperTitile, title)
(Paperkey, paperMDate, mdate)
(PaperKey, paperYears, year)
PublishedIN数据集
PublishedIN数据集用来记录每一篇paper发表在具体的哪一个journal或conf或series或某某发表物上,每篇paper一般都对应一条rdf关系,三元组为:
(paperKey, paperPublishedIN, journal)
DBLP文件解读
这部分解读一下构建知识图谱时使用到的DBLP中的各个文件,对文件内部的结构进行一定的梳理与解释,同时给出一些例子。
下面提到的论文统一是《DBLP-Some Lessons Learned》,是DBLP的官方论文。
dblp.xml
首先是dblp.xml文件,解压后文件大小越2.3GB,这个xml文件中包含了dblp数据库中所有相关的信息。
这里做一个简单的梳理,首先这个xml是一个3级树结构的文件,根节点为dblp,下一层是dblp中记录的各个实体类别,我们统计到的共有article,book,proceedings,inproceedings,incollection,phdthesis,masterthesis,www这几类(接下来会对这几类做更详细的说明并举例),最后一级是每一个实体标签下的具体属性标签。
注:其实xml是一个4级树结构,因为再title标签下还会有类似于<sub></sub>这样的特殊标签,但是因为这些标签我们在处理xml时会预处理掉,所以我们直接把xml看作一个3级树结构文件。
article
article是最常见的记录一篇paper的记录,一般会标明作者,发表时间,发表在哪个journal上等等,article在schema-2017-04.rdf(一个用来的定义知识图谱rdf关系的文件,之后会仔细介绍)有如下的comment,可见article就是表示在某个会议上发表的文章:
A journal article.
这里给出一个常见的示例:
1 | <!-- an article --> |
book
book在schema-2017-04.rdf有如下的comment:
A book or a thesis.
首先我们要对book是什么做出一些解释,就像再comment里说的,book有两种可能,一种是论文可能会统一发表在某个book上,因此这时的book就是许多篇论文的一个集合,在xml中我们可以看到这样的记录是没有author的,取而代之的是有book的编辑排版者,用一个editor的tag记录,因此这样的book就不再是某人的具体的发表物,对应我们在工作需求中所说的,这样的book也就不是我们所需要的记录。
而book的另一种情况则是一条具体的thesis的记录,正常情况下我们可以看到这样的记录是有author而没有editor的,也就是我们希望的一条具体某人的发表物的记录。
存在问题:按我们上面所说的,一个book的tag可能有两种情况,并且我们现在所使用的简单的判别方法是查看是否有author标签,那么当某条book记录的author标签丢失的时候,我们也会错误的将整条book记录丢弃,因此后续需要找一个更可靠的判别方法。
这个对两种不同的book情况分别给出例子:
1 | <!-- a book --> |
1 | <!-- a thesis --> |
proceedings/inproceedings
proceedings和inproceedings到底是什么,有什么区别,一度让我困惑不已,直到我在论文原文里找到了这句话:
we use inproceedings record for the paper and a proceedings record for the volume
这句话我们可以理解为,proceedings是一条对某个期刊的某一卷的记录,而inproceedings则是具体的某篇paper的记录,这样也就可以解释为什么proceedings里只有editor的tag而inproceedings里面是author的tag,正因为proceedings记录的是某篇期刊的某一卷,所以只有编辑排版者editor,而inproceedings则是具体的发表物,也就对应了具体的作者author。
这里我们同样给出一个常见的proceedings的例子和一个inproceedings的例子:
1 | <!-- proceedings --> |
1 | <!-- inproceedings --> |
incollection
incollection在rdf中的comment是:
A part in a book or a collection.
因此incollection也是一种发表物,并且是没有什么歧义的,这里直接给出一个例子:
1 | <!-- incollection --> |
phdthesis/masterthesis
这两个就更没有什么歧义了,一个是博士论文一个是硕士论文,要注意的一点是硕士博士论文没有具体发表在哪个会议上,而是有一个对应的接收学校,也就是schema中的thesisAcceptedBySchool关系。这里分别给出一个例子:
1 | <!-- phdthesis --> |
1 | <!-- mastersthesis --> |
www
最后一个要介绍的是www,www在schema中甚至没有被提到,但是还好在论文中有一些相关的描述,论文原文中提到的www是:
a web publication
也就是一个网络发表物。但是在实际搜索dblp.xml时我们发现,www有两种情况,第一种是用来记录网络发表物的信息的,第二种是用来记录某个人的个人主页的,在论文第三章PERSON的Person Record小节可以看到相关的描述,因此我们也要对www进行筛选,如果这个www记录的是一个网络发表物,那么就需要提取转化,如果只是记录了某个人的个人信息,那么就可以舍弃了。
这里给出两种不同www的各一个例子
1 | <!-- a web publication --> |
1 | <!-- a person record --> |
dblp.dtd
第二个要梳理的文件是dtd文件,dtd文件在网上的简介是:
文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
因此说白了dtd文件就是xml的外部申明文件,因此我们可以在dtd文件中看到很多关于dblp.xml的具体描述,dblp.dtd中描述了在dblp.xml中会出现哪些ELEMENT,然后又会用一个ATTLIST(attribution list)来表述这些element可能有哪些属性,因此对了解整个dblp.xml十分有帮助。
此外还要注意的是dtd文件中会定义xml中可能用到的entity,xml中的引用格式为&xxx;其中xxx表示任意字符,xml除了几个常规定义好的实体以外,只有在dtd中定义了某个实体才能引用这个实体。但也正是这些实体在后续读取xml并做转化时带来了很多的麻烦,在之后转化的部分再详细讲一下这个问题。
schema-2017-04.rdf
最后一个是schema文件,如果说一个dtd文件是对应于一个xml文件,用来描述xml文件的,那么schema文件就可以理解为是对应于一个知识图谱的,在网上有相关的定义:
简单来说,一个知识图谱的schema就是相当于一个领域内的数据模型,包含了这个领域里面有意义的概念类型以及这些类型的属性。任何一个域的schema主要由类型(type)和属性(property)来表达。
在上面理解dblp.xml中各个标签的具体含义是什么的时候,我们很多时候都参考了schema中对应的comment,可见schema对知识图谱的构建是非常重要的,是起到定义作用的文件。
但是也有一个小问题是,schema中定义了包括属性,关系等各种各样内容,有一些是非常有共性的,我们在dblp上随意下载一个nt(在dblp上随便搜索一篇论文,能下载他对应的.nt文件)都能看到这个属性或关系,但是有的内容就很冷门或者说很不容易找,我们现在在做的工作之一就是把schema中定义的所有类都找出一个具体的例子,来知道里面的内容到底是在什么情况下会用到,是怎么用的。
xml转rdf
将xml转化为rdf,也就是我们要读取xml文档,按照上面工作需求里所写的3个数据集格式转化成对应的rdf,形成一张知识图谱。
因此在转化过程中我们要读取每条记录的key,mdate,author,发表位置(journal/booktitle/series等等),year,title这几个属性,然后通过schema中定义的关系,将实体与实体或实体与属性连接起来组成一条三元组数据。
tag转url的规则
rdf中不管是头实体,尾实体还是中间的关系,都是使用的url格式,因此我们要解析出xml中各个标签如何转化为对应的rdf,这里一部分是在论文中找到的,一部分是直接分析具体例子得到的。需要转化为url的有key,author,还有几个schema中的关系,下面我们一个一个说明。
name转化规则
name的转化规则是我研究了最久的一个(虽然其实也没有多久啦),dblp中name转化为url的规则在论文中有这么一段描述:
All pages for persons who name start with ‘m’ are stored in the directory …/indices/a-tree/m/, and all ‘h’-anmes are stored in …/indices/a-tree/h/, etc. The filenames inside these directories are formed last-name:first-name.html, Blanks are mapped to underscores, all other characters which are not alphanumeric are mapped to ‘=’. This avoids illegal URLs.
这段话说明了name转化为url的规则,文章中的地址 …/indices/a-tree/ 可能因为更新原因已经失效了,但是也只要替换为新的dblp的url前缀即可,而后面的name转化为url后缀规则仍然是不变的,也就是对于一个名字,我们把他的lastname和firstname分开,组织为lastname:firstname的形式,然后在前面添上lastname第一个字母的小写即可,最后组织为 c/lastname:firstname.html 的格式(这里的c通指lastname第一个字母的小写,而不是字母c),注意,因为xml中的实体读取问题(后面会进一步解释),有时lastname是以 = 开头的,那么这时候前面的前缀也是用 =。
此外因为名字中可能会有一些特殊字符,如空格等等,这里也规定了,空格统一转化为下划线,其余除了字母数字的字符全部转化为等号。
但是这里还会遇到两个小问题,首先我们要如何判断一个名字哪个是lastname哪个是firstname呢,而且英文命中还有middlename,印度人的名字可能有四五个字段,完全无法判断如何划分。于是dblp的组织者选择了最简单暴力的方式:不划分。我在论文截取了这样两段话:
In retropspect it was a design error to split the person names into first names and last names.
For some users of DBLP the incorrect splitting of their names is annoying, but we do not claim any longer that the name parts are first/last name.
也就是到了最后,dblp的作者自己也发现这种规则有问题了,如果真的要严格实现每个人的last-name:first-name,需要code一大段规则,并且还不能保证覆盖全。因此最后dblp采用了最暴力的方法,不管你名字有多少个字段,统一把你最后一个字段作为我们说的lastname,前面的全叫firstname,然后拼接起来。并且人家也一本正经的告诉你,我们知道这样可能有人不高兴,但是我们现在也不再说我们这是fisrt/last的名字格式了,我们就是把你的名字给截了(滑稽)。
而第二个问题,是另一个非常普遍也是重要的问题,重名问题,dblp的处理方式是整理出重名的所有人,对他们进行编号,编号的格式为四位数字,比如Lei Zhao 0001,Lei Zhao 0002,Lei Zhao 0003依此类推。而这样重名的情况下转化的格式则是把最后个字段作为lastname,前面的全看做firstname,比如Lei Zhao 0001转化之后就是z/Zhao_0001:Lei。
特殊情况:正常情况下我们直接按照上述规则进行name到url的转化即可,但是在后续检测知识图谱url转化准确率的时候我们发现,name的url出现了一些小问题,一部分原因就是来自于这里的几个特殊情况,因为官方没有给出说明文档,我们只能在测试过程中发现特殊情况并补充到我们的转化规则中来。
- 名字带有Jr.后缀,这个似乎是英文里表示儿子的后缀?查了一下似乎是的,这种情况下要把Jr.和倒数第二个字段一起算作lastname,比如Eric Cesar Esguerra Vidal Jr.应该要转化为v/Vidal_Jr=:Eric_Cesar_Esguerra。
- 名字后面带有罗马字母后缀,这是表示第几几几世,处理方法和上面带有Jr.相同,例如J. Edward Swan II应该转化为s/Swan_II:J=_Edward。
key转化规则
key转化为url的规则就十分简单了,key是每条记录的唯一表示,而转化为url的规则就是直接DBLPURL + key。现在的DBLPURL是https://dblp.org/rec/,以后有变化改掉就可以。
最后我们给出一个authorby三元组的例子,格式为(paperkey/authorby/author),可以同时看到key和name两个是如何转化为url的:
1 | <https://dblp.org/rec/tr/meltdown/s18> <https://dblp.uni-trier.de/rdf/schema-2017-04-18#authoredBy> <https://dblp.uni-trier.de/pers/hd/k/Kocher:Paul> |
关系转化规则
关系的转化其实就是直接用schema的url加上一个关系的具体后缀,但是要注意有一些关系并不是在dblp给出的schema-2017-04.rdf中定义的,应该是用的一些公用的schema定义?
我们这里给出两个例子,一个是dblp给出的schema-2017-04.rdf中定义的关系publishedInJournal,一个是公用的schema定义的关系modified:
1 | <!-- schema-2017-04.rdf --> |
属性转化规则
属性目前我们需要的有title,mdate,发表位置(journal, booktitle, series),year这四个,我们采用直接使用字符串作为尾实体的方式,因此不需要做转化,有一点点需要处理的地方是需要在字符串前后手动加上 “”。如果以后需要做属性的url转化再继续补充。
xml预处理
在实际转化过程中,我们发现因为各种各样的问题,xml需要先做出一些预处理,分别的xml中Entity的预处理和title标签下的一些子标签的预处理。
Entity预处理
这一步真的是,天坑,神坑,巨坑无比,我们以一个印度大哥的名字José de Jesús Luis González Ibánez为例子,他的名字在xml中的是José de Jesús Luis González Ibánez,然后我们用python读取xml的时候发现,我们接下来介绍的两个包,sax和dom,首先第一个坑是sax是直接不解析外部dtd定义的实体,你想读?可以,先在xml文件内部重新定义一遍这些实体,然后就迎来了第二个坑,就算你定义了之后,不管是sax还是dom读取xml的时候都会自动把例如实体 é; 转化为 é。这就尴尬了啊,我不要转化啊,因为dblp中要把name解析成为url需要把 é; 转化为 =eacute=,你这直接帮我转化成了印度字母我可怎么顶啊???
于是最后我采用了最暴力的方式,在xml转rdf之前,把所有xml里的&xxx;实体全部正则表达式匹配出来,替换成=xxx=,之后如果需要再换回来(微笑)。
Title子标签的预处理
Title子标签的问题也是我们在后来测试过程中发现的,我们发现<title></title>标签下可能会有四种子标签,分别是<sub></sub>、<sup></sup>、<i></i>、<tt></tt>,其实这并不能算是子标签,而应该算是xml里的特殊格式,因为这四个标签分别对应表示将标签内的字转化为下标,上标,斜体,粗体。但是因为我们使用的sax解析机制问题,会直接把这些标签连同标签里的内容一起删除,使得title不完整,因此我们需要对这些子标签进行处理,我们对比了dblp官网给出的例子,在官网上的nt文件中,title属性最终的格式是直接把这些子标签去掉,保留原有文本格式,因此我们直接使用正则表达式匹配,直接将标签去掉,替换为标签内文本。例如:
1 | <!-- 替换前 --> |
python解析xml
python解析xml在网上看到了SAX,DOM,ElementTree,还有师兄用的bf4,这里简单介绍一下。
SAX
sax解析调用的是xml.sax的包,菜鸟教程上的定义是:
Python 标准库包含 SAX 解析器,SAX 用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。
优点是流式读取处理,内存占用小,速度快。具体代码和操作方法直接百度就可以了,这里只是记录一下python有这么一个解析xml的方法。
DOM/ElementTree/bf4
其实这剩下的3种方法原理上都是针对xml文件建立一棵树,所以内存消耗,巨!大!无!比!反正dblp.xml文件我16G内存是没够跑的。具体的操作也是去百度吧,这里就不说了。
知识图谱准确性测试
最后我们要对我们构建的知识图谱的准确性做一下测试,以保证知识图谱内存储的信息的有效性和整张知识图谱结构的有效性。
准确性的定义
想要知道知识图谱是否准确,首先我们要搞清楚我们要验证知识图谱的哪些部分是否准确。这里我们讨论两个需要验证的方面:
- 知识图谱的结构是否是完整的,正确的,有没有遗漏的信息。
- 转化的url是否是一个有效链接。
因为知识图谱的结构是用图表示的,是一个符号化的数据,因此很难量化最终的准确性,而url则可以具体统计一共有多少条,有几条有效几条无效,最终可以计算出一个具体的准确率。具体的统计和计算方法我们在下面详细讨论。
知识图谱结构的准确性
我们这里所讨论的知识图谱结构的准确性,其实主要是讨论我们记录的每一条信息是否有属性或者关系的遗漏,或者属性和关系不对应的问题。例如incollection这个类,在dblp官网上分析实例可以看出一般他的publishedIN关系都是(incollection, publishedIN, booktitle),也就是说incollection类一般都是发表在某个book上的,但问题是dblp中有上千万条数据,我们不可能人去看完所有的数据,因此虽然我们看到的所有incollection的publishedIN关系对应的尾实体都是booktitle,但只要有一个我们没看到的例子不是这样的,那我们在构建知识图谱的时候就会出现关系和属性不对应的问题,并且会遗漏一条正确的rdf。
为了解决这个问题,我们决定实现一个脚本把dblp.xml内所有出现的记录扫描一遍,统计每个类可能出现的情况,我们以article举例,这里先给两个article的例子:
1 | <article mdate="2017-05-18" key="journals/rc/Rall07"> |
对于这两条article记录,我们将生成两个集合[‘article’, ‘title’, ‘pages’, ‘year’, ‘volume’, ‘journal’, ‘number’, ‘url’]和[‘article’, ‘author’, ‘title’, ‘pages’, ‘year’, ‘volume’, ‘journal’, ‘number’, ‘ee’, ‘url’]。我们对每个article记录的所有标签都生成一个集合,并加入一个新的大的集合,这样我们就可以记录article的记录可能会有哪些情况。并且我们最终会统计,哪些标签是所有记录都会出现的,哪些标签是可能出现的。
通过这样的方式,我们希望最终能提高整个知识图谱结构的准确性。
url的准确率
url准确率的统计只要一条一条尝试url是否准确就可以,同时因为在我们生成url的过程中,只有name和key是需要我们自己生成的,其余数据都是可以直接复制使用的,因此我们又简化了测试的范围,其实只要测试Author.txt中的头实体对应的key的url和尾实体对应的author的url就可以了。
因此实际操作时,本来我想直接把所有的数据都测试一遍,以保证最大程度地检测并提高url的准确率,然而我发现。。。平均一条url的get请求需要大约3.34秒,Author.txt中一共有六千多万条数据,每条数据都有两条url需要测试,那么最后大约需要。。。2年多。。。才能测试完。
被现实击垮的我最终选择了随机采样测试,我们每次采样1000条,测试这1000条中2000个url的准确率,注意,key的url准确率和author的url准确率我们是分开统计的。一共测试10轮,最后求一个平均准确率,即使这样也大概需要10个多小时才能测试完(如果网络不出问题),测试结果应该在明天(2019-11-16)早上能跑完吧,到时候来把测试结果补充到文章里来。
bad_case记录
url bad_case记录
在url的测试中发现了不少的bad_case,这里做一个简单的记录,具体的解决方法会整理到上面的url转化规则中,这里仅仅记录bad_case。
- author Jr.后缀问题,后缀也要算在lastname里。
1
2<!-- 原始名字为Frederick C. Harris Jr. -->
<https://dblp.org/pers/h/Harris_Jr=:Frederick_C=> - author 罗马数字后缀,表示几世几世的问题,后缀也要算在lastname里
1
2<!-- 原始名字为J. Edward Swan II -->
<https://dblp.org/pers/s/Swan_II:J=_Edward> - dblp的xml文件中author标签内容与网站上最新的版本不一致,这个问题目前没想到什么解决方案,因为拿不到正确的数据,从我们本地是无法解析出正确的url的。可以看到例子中,网站最新的xml与dblp.xml中的author是不一样的,dblp.xml多了前缀Dr,而正确的url是按照网站上的xml中的信息解析的。因此这列数据我们只能暂时算作噪声。
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<!-- dblp.xml的记录 -->
<inproceedings mdate="2019-10-01" key="conf/cogsci/SkirzynskiW19">
<author>Julian Skirzynski</author>
<author>Dr Piotr Wasilewski</author>
<title>Flexible Strategy Use in ACT-R's Tic-Tac-Toe.</title>
<pages>3574</pages>
<year>2019</year>
<booktitle>CogSci</booktitle>
<ee>https://mindmodeling.org/cogsci2019/papers/0882/index.html</ee>
<crossref>conf/cogsci/2019</crossref>
<url>db/conf/cogsci/cogsci2019.html#SkirzynskiW19</url>
</inproceedings>
<!-- 网站上的xml的记录 -->
<inproceedings key="conf/cogsci/SkirzynskiW19" mdate="2019-11-11">
<author>Julian Skirzynski</author>
<author>Piotr Wasilewski</author>
<title>Flexible Strategy Use in ACT-R's Tic-Tac-Toe.</title>
<pages>3574</pages>
<year>2019</year>
<booktitle>CogSci</booktitle>
<ee>
https://mindmodeling.org/cogsci2019/papers/0882/index.html
</ee>
<crossref>conf/cogsci/2019</crossref>
<url>db/conf/cogsci/cogsci2019.html#SkirzynskiW19</url>
</inproceedings>
知识图谱结构bad_case记录
暂无