从零开始学习知识图谱 之 五.电影知识图谱构建 5.基于Deepdive非结构化文本关系抽取
本文于1978天之前发表,文中内容可能已经过时。
一. 简介
前面完成了针对结构化数据和半结构化数据的知识抽取工作,本节我们进行基于Deepdive框架的非结构化文本关系抽取。所采用的文本来自于百度百科的人物介绍。本次实战基于OpenKG上的支持中文的deepdive:斯坦福大学的开源知识抽取工具(三元组抽取),我们基于此,抽取电影领域的演员-电影关系。
deepdive是由斯坦福大学InfoLab实验室开发的一个开源知识抽取系统。它通过弱监督学习,从非结构化的文本中抽取结构化的关系数据 。本项目CNDeepdive修改了自然语言处理的model包,使它支持中文,并提供中文tutorial。
DeepDive项目处于维护模式,并且不再处于主动开发状态。用户社区保持活跃,但是原始项目成员不能再承诺令人兴奋的新功能/改进或响应请求。有关最新研究,请参阅Snorkel项目或Ce Zhang的项目。
本教程的项目代码放在github上,下载地址为《从零开始学习知识图谱》项目源代码 。
二. 环境准备
1. 操作系统
支持操作系统:macOS、Linux、Docker容器。Linux安装可以参考VirtualBox虚拟机安装Ubuntu或VirtualBox虚拟机安装CentOS8进行安装。
**deepdive在高版本Linux下的支持并不好,虽然笔者修改安装代码最终也装上了,但是不建议新手来处理。所以推荐Ubuntu 16.04的版本。 **Ubuntu 16.04下载地址,安装Ubuntu主机可参考文章VirtualBox虚拟机安装Ubuntu,注意不要安装python3,使用Ubuntu自带的。
2. 安装JDK
- 设置下载目录
1 | [root@ubuntu-mmm CNdeepdive] |
- 下载JDK
1 | [root@ubuntu-mmm jdk]# wget https://download.oracle.com/otn/java/jdk/8u221-b11/230deb18db3e4014bb8e3e8324f81b43/jdk-8u221-linux-x64.tar.gz?AuthParam=1570778301_521350e291b71079469aa571d925a2ec |
- 解压
使用tar -zxvf 文件名进行解压。
1 | [root@ubuntu-mmm jdk] |
- 配置环境变量
1 | [root@ubuntu-mmm jdk] |
将如下配置添加至文件中,然后保存退出。
1 | #java |
执行命令,使文件修改后立即生效
1 | [root@ubuntu-mmm jdk] |
- 验证
1 | [root@ubuntu-mmm jdk]# java -version |
3. 安装Deepdive
1)下载Deepdive安装包 CNDeepdive。
2)修改install.sh,否则出现报错,无法安装
1 | # 修改前 tar xzvf "$tarball" -C "$PREFIX" |
3)运行install.sh,先选择【1 】安装deepdive,然后选择【6】 安装postgresql。
1 | [root@ubuntu-mmm CNdeepdive]# cd /usr/local/CNdeepdive |
4)配置环境变量,deepdive的可执⾏⽂件⼀般安装在/local/bin⽂件夹下。 在/.bashrc 文件的末尾添加下面的内容:
1 | [root# vi ~/.bashrc -mmm CNdeepdive] |
5)然后执行下面的命令,安装自然语言处理的部分 。
1 | root@ubuntu-mmm:/usr/local/CNdeepdive |
好的,到这里Deepdive就已经安装完成了 。
6)打开终端执行deepdive
我们可以看到Deepdive的版本信息和命令参数的Help。
1 | rootmmm:/usr/local/CNdeepdive# deepdive - |
安装也可以参考文档Deepdive 实战-从下载到跑路。
三. 项目框架搭建
1. 建立postgresql的数据库movie
数据库管理工具推荐
Navicat Premium,是一套数据库开发工具,让你从单一应用程序中同时连接 MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite 数据库。Navicat系列破解工具链接:https://pan.baidu.com/s/1KmH2aaESpjozQaxr2C7D2A
可以使用它来管理PostgreSQL数据库,Navicat连接PostgreSQL,PostgreSQL需要进行如下配置:
1 | 1. 通过find / -name postgresql.conf 和 find / -name pg_hba.conf 找到这两个文件 |
在这里我们不使用navicat,可以不安装。
1) 创建数据库movie
运行 sudo -u postgres createdb
建立数据库movie
1 | root@ubuntu-mmm:CNdeepdive |
如果要删除数据,可以使用下面命令
1 | root@ubuntu-mmm:CNdeepdive |
2) 验证数据库是否创建成功
首先运行sudo -i -u postgres 进入到postgresql账户
1 | root@ubuntu-mmm:/usr/local/CNdeepdive |
输入psql进入到postgresql命令界面
1 | postgres@ubuntu-mmm:~$ psql |
查看数据库列表
1 | postgres=# \l |
修改PostgreSQL登录密码:
1 | postgres=# ALTER USER postgres WITH PASSWORD 'postgres'; |
输入\q退出psql。
1 | postgres- |
3) 返回自己的程序目录
运行’’’su yourusername’’’ 回到自己的程序目录。
1 | postgres@ubuntu-mmm:~$ su root |
2. 项目框架搭建
1)创建项⽬⽂件夹
这部分的数据和代码以及程序都在前面我们解压后的CNdeepdive/transaction中,你可以把这个目录复制到你喜欢的地方(比如~/Projects/transaction 这个位置),然后再新建一个目录,可以叫做transaction2 我们后面的所有工作都是在这个transaction2目录中进行的,后面简称它为项目目录。
运行命令:
1 | root-mmm:/var/lib/postgresql# cd /usr/local/CNdeepdive |
2)建⽴数据库配置⽂件db.url
建⽴⾃⼰的项⽬⽂件夹transaction2,之前已在postgresql中为项⽬建⽴数据库movie,现要在项⽬⽂件夹下建⽴数据库配置⽂件:
运行命令:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2 |
命令说明:
echo “postgresql://$USER@$HOSTNAME:5432/db_name” >db.url
#$符号是shell中用来表示变量的,所以这个蓝色部分大概就是 用户名@主机名:。。。。
#db_name 我们这个项目的数据库名,也可以自定义。
3)创建项⽬⽂件
在deepdive内建立输入数据文件夹 input,用户定义函数文件夹udf,用户配置文件app.ddlog,模型配置文件deepdive.conf。
为了调用 stanford_nlp 进行文本处理,将下载的CNdeepdive 文件夹中的transaction/udf/bazzar复制到自己的 udf 文件夹下,并在udf/bazzar/parser/ 目录下运行 sbt/sbt stage 进行编译。编译完成后会在 target 中生成可执行文件。
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2/udf/bazaar/parser# chmod 777 sbt/sbt |
三. 数据准备
1. 先验数据导入
为了对样本进行打标,我们需要一定的先验数据,它包含确定的演员-电影关系对。我们直接从结构化数据的数据库中抽取出演员和对应的代表作作为先验数据。
1) actor_movie.txt文件
用MySQL Workbench导出工具把actor_to_movie表的数据导出到actor_movie.txt文件,也可以通过百度云(提取码:xgof)直接下载该文件。这一步在先前的windows环境下完成。数据格式如下:
1 | #actor_id movie_id |
2) 获取 actor_movie_dbdata.csv 文件
您可以通过脚本 udf/get_actor_movie.py 获取 input/actor_movie_dbdata.csv 文件,也可以通过百度云(提取码:jyxo)直接下载该文件。这一步在先前的windows环境下完成。
1 | C:\my\Python\python.exe C:/d/mmm/pycharm/deepdive/udf/get_actor_movie.py |
3) app.ddlog 中定义相应的数据表
获取该文件后,我们需要将其导入到数据库中以进行处理。首先我们在app.ddlog 中定义相应的数据表,文件内容如下:
1 | @source |
4) 编译及生成数据表
而后通过命令行编译及生成数据表
a. 行编译操作,也就是在项目目录执行下面的命令:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive compile |
b. 编译完成后,我们要执行deepdive do db
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive do actor_movie_dbdata |
这里do
后面应该和我们app.ddlog
中的名字相同,和input
文件夹中的文件名也相同,他们三个应该都是一致的。他会把input文件中的对应的文件导入postgresql数据库中。注意这个语句执行后,他会进入一个类似vi的界面让你审查他自动生成的处理代码是不是正确,这时候输入:wq
就可以保存并退出,继续执行后面的步骤。运行结果如下:
1 | 2019-10-16 15:36:50.793180 |
c. 执行完成后,可以执行下面代码在数据库中查询,看看是不是成功导入了
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive query '?- actor_movie_dbdata(actor_name, _).' |
2. 待抽取文章的导入
1) 爬取数据
为了获取到大量的电影和演员相关文章,我们直接套用之前的半结构化数据爬虫来获取非结构文本articles.txt。 这一步在先前的windows环境下完成。
cmd中执行命令:scrapy crawl 项目名
1 | PS C:\d\mmm\pycharm\deepdive\udf\baidu_baike\baidu_baike> scrapy crawl baidu |
2) 数据转换
接下来为了将其导入到数据库中,需要将其转换为csv格式,这里可以使用程序 udf/trans.py 进行转换。再更改名成input/articles.csv即可。 这一步在先前的windows环境下完成。
1 | C:\my\Python\python.exe C:/d/mmm/pycharm/deepdive/udf/trans.py |
3) app.ddlog 中定义相应的数据表
在 app.ddlog 中建立相应的 articles 表:
1 | @source |
4) 编译及生成数据表
因为下面的 stanford_nlp 进行句法分析时速度特别的慢,因此我抽取出 articles.csv 的头10 行进行实验。
a. 执行 ‘’’ deepdive do articles ‘’’ 将文章导入到 postgresql中
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive compile && deepdive do articles |
b. 验证是否成功
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive query '?- articles(id, _).' |
四. 文本处理
1. 用 stanford_nlp 模块进行文本处理
deepdive 默认采⽤standfor nlp进⾏⽂本处理。输⼊⽂本数据,nlp模块将以句子为单位,返回每句的分词、lemma、pos、NER和句法分析的结果,为后续特征抽取做准备。我们将这些结果存入sentences表中。
1) app.ddlog 中定义相应的数据表
首先在 app.ddlog 中建立 sentences 表:
1 | @source |
2) app.ddlog 中定义相应的函数
而后定义NLP 处理的函数 nlp_markup 来调用自定义的脚本 udf/nlp_markup.sh 进行文本处理。 我们需要对数据处理的方法,deepdive只是个框架,具体要怎么处理需要我们告诉他。所以我们要定义函数来处理articles让他变成sentences。
1 | function nlp_markup over ( |
说明:
function用来定义函数,后面
nlp_markup
是函数名 over后面接的是参数表。
returns 说明了函数返回的形式,返回就像我们前面定义的sentences那样的一行。
最后一句说明了我们这个程序文件是udf/nlp_markup.sh
,输入是tsv的一行。
3) app.ddlog 中定义相应的函数调用
使用如下语法调用 nlp_markup 函数,将函数的输出存储到sentences 表中:
1 | sentences += nlp_markup(_id, content) :- |
说明:上面的
+=
其实和其他语言差不多,就是对于来源是articles中的每一行的doc_id
和content
我们都调用nlp_markup
然后结果添加到sentences表中。
4) 编写 nlp_markup.sh
udf/nlp_markup.sh 代码如下:
1 |
|
说明:
所有#开头的(除了#!)都是普通注释
参数的用法(
- $0:调用文件使用的文件名,带有前面的路径,
- $1-∞:传给脚本的各个参数,
- $@,$*:这两个都表示传入的所有参数,
- $#:表示传入参数的个数)
第一行指定了脚本的执行程序
第六行指定了一些程序的错误处理方式等(详见Shell相关文档)
第七行改变当前目录到nlp_markup.py
所在目录,也就是udf
目录
第九行设置了一个变量BAZZER_HOME
他的值是bazaar
的路径
第10-13行执行/parser/target/start
文件,如果有错会不正常退出,并提示
第15-17行检查输入参数的正确性,看参数个数是不是大于0个,如果没有参数,自己设定参数名
第23-24行把全部输入的参数用tsv2json
工具转换成json
格式,然后在执行parser/run.sh
并以刚才的json
作为参数输入。
4) 编译及生成数据表
a. 编译并执行deepdive compile && deepdive do sentences
生成 sentences 表。
预告:运行很慢,耐心等待
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# chmod 777 udf/bazaar/parser/run.sh |
b. 您可以通过以下命令来查询生成的结果:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive query ' |
五. 实体抽取及候选实体对的生成
本部分包含两步:
- 获取候选实体
- 获取演员候选实体
- 获取电影候选实体
- 生成候选实体对,即演员-电影实体对
1.候选实体的生成
1) app.ddlog 中定义相应的数据表
首先介绍演员候选实体的生成。首先我们在app.ddlog 中定义实体数据表:
1 | @extraction |
该表包含了实体的id、内容、所在文本的id、所在句子的索引、起始和终止位置索引。
2) app.ddlog 中定义相应的函数
而后继续定义实体抽取函数:
1 | function map_actor_mention over( |
其中 udf/map_actor_mention.py 脚本遍历数据库中的句子,找到连续的NER标记为PERSON 且 POS 标记为NR的序列,再对其做过滤整合处理,返回候选实体。
3) app.ddlog 中定义相应的函数调用
最后我们在 app.ddlog 中调用函数,将 函数的输出存储到 actor_mention 表中。
1 | actor_mention += map_actor_mention( |
4) 编译及生成数据表
现在我们可以编译并执行得到 actor_mention 表了。
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# chmod 777 udf/map_actor_mention.py |
5) 生成电影类的实体表
同理可以得到电影类的实体表 movie_mention :
a. 在app.ddlog 中定义相应的内容
1 | @extraction |
b. 编译并执行
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# chmod 777 udf/map_movie_mention.py |
c. 查询看一下得到的实体:
1 | root deepdive query ' |
2. 生成候选实体对
1) app.ddlog 中定义相应的数据表
这一步我们要生成候选实体对,首先定义数据表如下:
1 | @extraction |
2) app.ddlog 中定义相应的统计每个句⼦的实体数
包含实体1/2 的id 和内容。接下来统计每个句子的实体数量以限制同一句内出现过多实体:
1 | num_entity(doc_id, sentence_index, COUNT(p) + COUNT(q)) :- |
3) app.ddlog 中定义相应的函数
而后定义过滤函数:
1 | function map_play_candidate over ( |
该函数内您可以定义一些规则对候选实体对进行筛选。
4) app.ddlog 中定义相应的函数调用
接下来调用上述函数并结合一些规则对实体对进行进一步的筛选,将筛选结果存储到 play_candidate 数据表中:
1 | play_candidate += map_play_candidate(p1, p1_name, p2, p2_name) :- |
5) 编译及生成数据表
a. 编译并执行得到候选实体对表:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# chmod 777 udf/map_play_candidate.py |
b. 查询一下看看结果:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive query ' |
六. 数据处理
1. 特征抽取
现在我们要用deeodive自带的 ddlib 库抽选实体对的文本特征。
1) app.ddlog 中定义相应的数据表
首先定义特征表:
1 | @extraction |
该特征是实体对间一系列文本特征的集合。
2) app.ddlog 中定义相应的函数
而后我们定义特征提取函数得到各种 POS/NER/次序列的窗口特征等。
1 | function extract_play_features over ( |
3) app.ddlog 中定义相应的函数调用
而后调用该函数:
1 | play_feature += extract_play_features( |
4) 编译及生成数据表
a. 编译并执行:
预告:运行很慢,耐心等待……
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# chmod 777 udf/extract_play_features.py |
b. 查询看一下特征:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive query '| 5 ?- play_feature(_, _, feature).' |
2. 有监督数据处理
1) app.ddlog 中定义相应的数据表
首先在 app.ddlog 中定义 play_label 表存放有监督数据:
1 | @extraction |
rule_id 表示相关性的规则名称,label 为正值表示正相关,负值表示负相关。绝对值越大,相关性越大。
2) app.ddlog 中定义,初始化定义
初始化定义,复制 play_candidate 表,label 初始化为0,rule_id 为 NULL:
1 | paly_label(p1, p2, 0, NULL) :- play_candidate(p1, _, p2, _). |
2) app.ddlog 中定义,导入
接下来将最开始导入的有监督数据表 actor_movie_dbdata 导入到 play_label 中,并赋予权重3:
1 | play_label(p1, p2, 3, "from_dbdata") :- |
3. 预标记
接下来定义一些逻辑规则对文本进行预标记
1) app.ddlog 中定义相应的数据表
1 | function supervise over ( |
程序 udf/supervise_play.py 里通过一些指定的规则如 出演这种特征来进行预标记。给出label和rule_id的值。
2) app.ddlog 中定义相应的函数调用
接下来调用标记函数,将规则抽到的数据写到play_label 表中:
1 | play_label += supervise( |
3) app.ddlog 中定义,最终标记
由于不同的规则可能覆盖了相同的实体对,因此利用 vote方法进行综合给出最终标记:
1 | play_label_resolved(p1_id, p2_id, SUM(vote)) :- |
4) 编译及生成数据表
最后编译并执行:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# chmod 777 udf/supervise_play.py |
4. 变量表定义
首先定义最终存储的表格,[?]表示此表是用户模式下的变量表,即需要推到关系的表。这里我们需要推到的是演员和电影间是否存在演出关系。
1) app.ddlog 中定义相应的数据表
1 | @extraction |
2) app.ddlog 中定义,写入已知的变量
根据打标结果,写入已知的变量:
1 | has_play(p1_id, p2_id) = if l > 0 then TRUE |
3) 编译及生成数据表
此时表中部分变量的label 已知,成为了先验变量。编译并执行:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive compile && deepdive do has_play |
5. 构建因子图
1) app.ddlog 中定义相应的,指定特征
将每一对 has_play 中的实体对和特征表连接起来,通过特征因子连接,全局学习这些特征的权重 f
1 | @weight(f) |
2) 编译及生成数据表
a. 编译并生成最终的概率模型:
1 | root@ubuntu-mmm:/usr/local/CNdeepdive/transaction2# deepdive compile && deepdive do probabilities |
b. 就完成了我们的推断,现在查询我们预测的演员-电影间的交易关系概率:
1 | root"SELECT p1_id, p2_id, expectation FROM has_play_label_inference ORDER BY random() LIMIT 5" deepdive sql |