系统: ubuntu-16.04.5

目的: 跑通支持中文的deepdive中给出的股权交易关系抽取示例

参考: deepdive官网、Deepdive抽取演员-电影间关系、支持中文的deepdive、PostgreSQL 10.1 手册

说明:本文后半段与支持中文的deepdive示例教程对应部分一样。


准备

deepdive安装

在支持中文的deepdive中下载CNdeepdive并解压;进入CNdeepdive目录,运?install.sh,选择1安装deepdive:

./install.sh

如果出现:

则修改 install.sh文件,将第193行:tar xzvf "$tarball" -C "$PREFIX" 修改为 tar xvf "$tarball" -C "$PREFIX"

配置环境变数,deepdive的可执??件?般安装在~/local/bin?件夹下。在~/.bashrc下添加如下内容并保存:

export PATH="/local文件夹所在路径/local/bin:$PATH"

找到local所在目录,运行pwd得到local文件夹所在路径。

由此可知最终在~/.bashrc中添加的内容为:

export PATH="/home/linux/local/bin:$PATH"

保存更改,执行source ~/.bashrc设置环境变数。

如果出现 deepdive: command not found,一般是这一步环境变数配置出了问题; 测试时遇到的两种情况:

1.在~/.bashrc中添加 export PATH="/root/local/bin:$PATH"

2.在~/.bash_profile中添加 export PATH="/root/local/bin:$PATH"

以上两种情况在测试时均不能正确添加环境变数。

postgresql安装

先验知识: 1.创建资料库:

2.删除资料库:

运?install.sh,选择6安装postgresql。 运行:

psql postgres

如果效果如下,则说明postgresql安装成功。

运行q退出psql。

运行:

createdb transaction

建立资料库;运行:

psql transaction

进入对应的postgresql账户;运行l显示所有的资料库;运行q退出psql。

nlp环境安装

运?nlp_setup.sh,配置中?standford nlp环境。

框架搭建

建???的项??件夹transaction,在项??件夹下配置资料库配置?件:

echo "postgresql://localhost:5432/transaction" >db.url

再在transaction下分别建?输?数据?件夹input,脚本?件夹udf,?户配置?件app.ddlog,模型配置?件deepdive.conf,可参照给定的transaction?件夹样例格式。 (此处新建立的transaction存放待编译的项目,CNdeepdive目录中的transaction是已经建?完毕的项?,后?所需的脚本和数据?件都可以从CNdeepdive中的对应模块中直接复制);测试时按照中文deepdive官方教程设置资料库配置文件 echo "postgresql://$USER@$HOSTNAME:5432/db_name" >db.url,无法正确连接deepdive与postgresql。

复制CNdeepdive/transaction/udf/?录下的bazzar?件夹到当前示例项?的udf/中。这个模块需要重新编译。进?bazzar/parser?录下,执?编译命令:

sbt/sbt stage

编译完成后会在target中?成可执??件。

实验步骤

先验数据导入

我们需要从知识库中获取已知具有交易关系的实体对,来作为训练数据。本项?采?的数据来源于从国泰安资料库。

通过匹配有交易的股票代码对和代码-公司对,过滤出存在交易关系的公司对,存?transaction_dbdata.csv中;之后将csv?件放?input/?件夹下。此处只需要将 CNdeepdive/transaction/input中的transaction_dbdata.csv复制到当前示例项目的input/?件夹下即可。

在app.ddlog中定义相应的数据表:

@source
transaction_dbdata(
@key
company1_name text,
@key
company2_name text
).

命令??成postgresql数据表:

$ deepdive compile && deepdive do transaction_dbdata

在执?app.ddlog前,如果有改动,需要先执?deepdive compile编译才能?效;对于不依赖于其他表的表格,deepdive会?动去input?件夹下找到同名csv?件,在postgresql?建表导?;运?命令时,deepdive会在当前命令???成?个执?计划?件,和vi语法?样,先按esc再使用:wq保存执?并退出。

上述代码执行成功之后则会显示:

注意此处如果为 run/ABORTED,这说明建表导入资料库的过程中出现了错误。

待抽取?章导?

准备待抽取的?章(示例使?上市公司公告)已经在CNdeepdive/transaction/input中存在,直接复制到当前示例项目的input目录下。

在app.ddlog中建?对应的articles表:

articles(
id text,
content text
).

表中的间隔使用空格即可。

同理,执?命令?,导??章到postgresql中。

$ deepdive do articles

deepdive可以直接查询资料库数据,?query语句或者deepdive sql "sql语句"进?资料库操作。进?查询id指 令,检验导?是否成功:

$ deepdive query ?- articles(id, _).

结果如图:

考虑到下一步所花的时间和待抽取文章的行数相关,此处仅取了原articles.csv中的前五行作为一个小的数据集进行测试。

?nlp模块进??本处理

deepdive默认采?standford nlp进??本处理。输??本数据,nlp模块将以句?为单位,返回每句的分词、 lemma、pos、NER和句法分析的结果,为后续特征抽取做准备。我们将这些结果存?sentences表中。

在app.ddlog?件中定义sentences表,?于存放nlp结果:

sentences(
doc_id text,
sentence_index int,
sentence_text text,
tokens text[],
lemmas text[],
pos_tags text[],
ner_tags text[],
doc_offsets int[],
dep_types text[],
dep_tokens int[]
).

定义NLP处理的函数nlp_markup:

function nlp_markup over (
doc_id text,
content text
) returns rows like sentences
implementation "udf/nlp_markup.sh" handles tsv lines.

使?如下语法调?nlp_markup函数,从articles表中读取输?,输出存放在sentences表中:

sentences += nlp_markup(doc_id, content) :-
articles(doc_id, content).

声明?个ddlog函数,这个函数输??章的doc_id和content,输出按sentences表的栏位格式。 函数调?udf/nlp_markup.sh调?nlp模块,nlp_markup.sh的脚本内容?transaction示例代码中的udf/?件夹,它调?udf/bazzar/parser下的run.sh实现; 此处需要将CNdeepdive示例代码目录transaction/udf下的nlp_markup.sh复制到当前项目的对应目录下。

执?以下命令来查询?成结果:

deepdive query doc_id, index, tokens, ner_tags | 5
?- sentences(doc_id, index, text, tokens, lemmas, pos_tags, ner_tags, _, _, _).

结果如图:

这?步跑的会?常慢,可能需要四五个?时。因此此处减少了articles的?数,来缩短时间以快速完成demo。

实体抽取及候选实体对?成

这?步,我们要抽取?本中的候选实体(公司),并?成候选实体对。 ?先在app.ddlog中定义实体数据表:

company_mention(
mention_id text,
mention_text text,
doc_id text,
sentence_index int,
begin_index int,
end_index int
).

每个实体都是表中的?列数据,同时存储了实体的id,、实体内容、所在文本的id、句子索引、在句中的起始位置和结束位置。

再定义实体抽取的函数:

function map_company_mention over (
doc_id text,
sentence_index int,
tokens text[],
ner_tags text[]
) returns rows like company_mention
implementation "udf/map_company_mention.py" handles tsv lines.

map_company_mention.py也需要从CNdeepdive示例代码目录transaction/udf中复制到当前项目对应目录中; 这个脚本遍历每个资料库中的句?,找出连续的NER标记为ORG的序列,再做其它过滤处理,返回候选实体;这个脚本是?个?成函数,?yield语句返回输出?。 其它所有CNdeepdive示例代码目录transaction/udf下的脚本和文件都要复制过去(包括company_full_short.csv)。

然后在app.ddlog中写调?函数,从sentences表中输?,输出到company_mention中:

company_mention += map_company_mention(
doc_id, sentence_index, tokens, ner_tags) :-
sentences(doc_id, sentence_index, _, tokens, _, _, ner_tags, _, _, _).

最后编译并执?:

$ deepdive compile && deepdive do company_mention

测试刚刚抽取得到的实体表:

$ deepdive query mention_id, mention_text, doc_id,sentence_index, begin_index, end_index
| 50 ?- company_mention(
mention_id, mention_text, doc_id, sentence_index, begin_index, end_index).

下??成实体对,即要预测关系的两个公司。在这?步我们将实体表做笛卡尔积,同时按?定义脚本过滤 ?些不符合形成交易条件的公司。定义数据表如下:

transaction_candidate(
p1_id text,
p1_name text,
p2_id text,
p2_name text
).

统计每个句?的实体数:

num_company(doc_id, sentence_index, COUNT(p)) :-
company_mention(p, _, doc_id, sentence_index, _, _).

定义过滤函数:

function map_transaction_candidate over (
p1_id text,
p1_name text,
p2_id text,
p2_name text
) returns rows like transaction_candidate
implementation "udf/map_transaction_candidate.py" handles tsv lines.

您可以在这个函数内定义筛选候选实体的规则;调用这个函数并结合其他规则对实体对进行进一步的筛选,将筛选结果存储到 transaction_candidate 表中:

transaction_candidate += map_transaction_candidate(p1, p1_name, p2, p2_name) :-
num_company(same_doc, same_sentence, num_p),
company_mention(p1, p1_name, same_doc, same_sentence, p1_begin, _),
company_mention(p2, p2_name, same_doc, same_sentence, p2_begin, _),
num_p < 5,
p1_name != p2_name,
p1_begin != p2_begin.

?些简单的过滤操作可以直接通过app.ddlog中的资料库语法执?,?如p1_name != p2_name,过滤掉两个 相同实体组成的实体对。

编译并执?:

$ deepdive compile && deepdive do transaction_candidate

?成候选实体表。

如果此处报错,可以将udf/transform.py中company_full_short.csv(ENTITY_FILE)的相对路径改为绝对路径:

测试刚刚得到的候选实体对表:

特征抽取

这?步我们抽取候选实体对的?本特征。

定义特征表:

play_feature(
p1_id text,
p2_id text,
feature text
).

这?的feature列是实体对间?系列?本特征的集合。

?成feature表需要的输?为实体对表和?本表,输?和输出属性在app.ddlog中定义如下:

function extract_transaction_features over (
p1_id text,
p2_id text,
p1_begin_index int,
p1_end_index int,
p2_begin_index int,
p2_end_index int,
doc_id text,
sent_index int,
tokens text[],
lemmas text[],
pos_tags text[],
ner_tags text[],
dep_types text[],
dep_tokens int[]
) returns rows like transaction_feature
implementation "udf/extract_transaction_features.py" handles tsv lines.

函数调?extract_transaction_features.py来抽取特征。这?调?了deepdive?带的ddlib库,得到各种POS/NER/词序列的窗?特征。此处也可以?定义特征。

把sentences表和mention表做join,得到的结果输?函数,输出到transaction_feature表中。

transaction_feature += extract_transaction_features(
p1_id, p2_id, p1_begin_index, p1_end_index, p2_begin_index, p2_end_index,
doc_id, sent_index, tokens, lemmas, pos_tags, ner_tags, dep_types, dep_tokens
) :-
company_mention(p1_id, _, doc_id, sent_index, p1_begin_index, p1_end_index),
company_mention(p2_id, _, doc_id, sent_index, p2_begin_index, p2_end_index),
sentences(doc_id, sent_index, _, tokens, lemmas, pos_tags, ner_tags, _, dep_types,
dep_tokens).

然后编译并执?,?成特征资料库:

$ deepdive compile && deepdive do transaction_feature

执?如下语句,查看?成结果:

deepdive query | 20 ?- transaction_feature(p1_id, p2_id, feature).

现在,我们已经有了想要判定关系的实体对和它们的特征集合。

样本打标

这?步,我们希望在候选实体对中标出部分正负例。 利?已知的实体对和候选实体对关联; 利?规则打部分正负标签;

?先在app.ddlog?定义transaction_label表,存储监督数据:

@extraction
transaction_label(
@key
@references(relation="has_transaction", column="p1_id", alias="has_transaction")
p1_id text,
@key
@references(relation="has_transaction", column="p2_id", alias="has_transaction")
p2_id text,
@navigable
label int,
@navigable
rule_id text
).

rule_id代表在标记决定相关性的规则名称。label为正值表示正相关,负值表示负相关。绝对值越?,相关性越 ?。

初始化定义,复制transaction_candidate表,label均定义为零:

transaction_label(p1, p2, 0, NULL) :- transaction_candidate(p1, _, p2, _).

将最开始准备好的transaction_dbdata数据导?transaction_label表中,ruleid标记为"from_dbdata"。因为国泰安的数据?较官?,可以基于较?的权重,这?设为3。在app.ddlog中定义如下:

transaction_label(p1,p2, 3, "from_dbdata") :-
transaction_candidate(p1, p1_name, p2, p2_name), transaction_dbdata(n1, n2),
[ lower(n1) = lower(p1_name), lower(n2) = lower(p2_name) ;
lower(n2) = lower(p1_name), lower(n1) = lower(p2_name) ].

如果只利?下载的实体对,可能和未知?本中提取的实体对重合度较?,不利于特征参数推导。因此可以 通过?些逻辑规则,对未知?本进?预标记。

function supervise over (
p1_id text, p1_begin int, p1_end int,
p2_id text, p2_begin int, p2_end int,
doc_id text,
sentence_index int,
sentence_text text,
tokens text[],
lemmas text[],
pos_tags text[],
ner_tags text[],
dep_types text[],
dep_tokens int[]
) returns (
p1_id text, p2_id text, label int, rule_id text
)
implementation "udf/supervise_transaction.py" handles tsv lines.

函数调?udf/supervise_transaction.py,规则名称和所占的权重定义在脚本中;在app.ddlog中定义标记函数。

调?标记函数,将规则抽到的数据写?transaction_label表中:

transaction_label += supervise(
p1_id, p1_begin, p1_end,
p2_id, p2_begin, p2_end,
doc_id, sentence_index, sentence_text,
tokens, lemmas, pos_tags, ner_tags, dep_types, dep_token_indexes
) :-
transaction_candidate(p1_id, _, p2_id, _),
company_mention(p1_id, p1_text, doc_id, sentence_index, p1_begin, p1_end),
company_mention(p2_id, p2_text, _, _, p2_begin, p2_end),
sentences(
doc_id, sentence_index, sentence_text,
tokens, lemmas, pos_tags, ner_tags, _, dep_types, dep_token_indexes
).

不同的规则可能覆盖了相同的实体对,从未给出不同甚?相反的label。建?transactionlabelresolved表,统?实体对间的label。利?label求和,在多条规则和知识库标记的结果中,为每对实体做vote。

transaction_label_resolved(p1_id, p2_id, SUM(vote)) :-transaction_label(p1_id, p2_id,
vote, rule_id).

执?以下命令,得到最终标签:

$ deepdive do transaction_label_resolved

模型构建

通过上面的所有操作,得到了所有前期需要准备的数据。下?开始构建模型。

变数表定义

首先定义最终存储的表格,[?]表示此表是用户模式下的变数表,即需要推到关系的表。这?我们预测的是公司间是否存在交易关系:

@extraction
has_transaction?(
p1_id text,
p2_id text
).

根据打标的结果,写?已知的变数:

has_transaction(p1_id, p2_id) = if l > 0 then TRUE
else if l < 0 then FALSE
else NULL end :- transaction_label_resolved(p1_id, p2_id, l).

此时变数表中的部分变数label已知,成为了先验变数。

最后编译执?决策表:

deepdive compile && deepdive do has_transaction

因?图构建

指定特征:将每?对has_transaction中的实体对和特征表连接起来,通过特征factor的连接,全局学习这些特征的权重。在app.ddlog中定义:

@weight(f)
has_transaction(p1_id, p2_id) :-
transaction_candidate(p1_id, _, p2_id, _),
transaction_feature(p1_id, p2_id, f).

指定变数间的依赖性:我们可以指定两张变数表间遵守的规则,并给这个规则以权重。?如c1和c2有交易,可以推出c2和c1也有交易。这是?条可以确保的定理,因此给予较?权重:

@weight(3.0)
has_transaction(p1_id, p2_id) => has_transaction(p2_id, p1_id) :-
transaction_candidate(p1_id, _, p2_id, _).

变数表间的依赖性使得deepdive很好地?持了多关系下的抽取。

最后,编译,并?成最终的概率模型:

$ deepdive compile && deepdive do probabilities

查看我们预测的公司间交易关系概率:

$ deepdive sql "SELECT p1_id, p2_id, expectation FROM has_transaction_label_inference
ORDER BY random() LIMIT 20"

?此,我们的交易关系抽取demo就完成了。


推荐阅读:
相关文章