前言
前面实践篇基于DeepDive实现从股权交易公告获取企业与企业之间存在交易关系的概率–实践篇 已经大概地走了一波如何使用deepdive去做实体抽取,关系概率计算等操作,接下将基于deepdive的数据处理、实体抽取、特征抽取,结合示例相关文件进行解析。
文章目录
1.数据处理脚本解析
前面我们第一步就是把股权交易公告数据通过执行 deepdive do articles
实现公告数据导入。
articles
是deepdive的目标名,对应文件app.ddlog
具体导入数据表结构定义如下
@source
articles(
@key
@distributed_by
id text,
@searchable
content text
).
当前articles对象包含两个字段,分别是id
和content
实现数据导入的规则是在app.ddlog
中articles
的命名与当前目录的input
文件夹下需要导入的csv文件名对应
2. 数据标注脚本解析
通过执行 deepdive do sentences
实现公告数据标注,具体使用standfordnlp自然语言处理包。
2.1 数据标注结构定义
deepdive默认用standford nlp进行文本处理,可以返回句子的分词、lemma、pos、NER,具体看一下自然语言处理基础。
字段名 | 字段解释 |
---|---|
doc_id | doc_id表示的是articles表中公告对应的id |
sentence_index | sentence_index表示的是公司所在的句子在文章中对应的索引 |
tokens | tokens的结构如下:1: “证券”,其中1是分词的索引,“证券”是分词的内容 |
lemmas | lemmas与pos_tag、ner_tags、dep_types和tokens的结构是一样的,表示词元 |
pos_tags | pos_tags表示的是句子的词性 |
ner_tags | ner_tags表示的是实体类型的识别,如果是公司则表示为“ORG” |
doc_offsets | doc_offsets表示的是每个分词在文章中的开始位置的索引 |
dep_types | dep_types表示的是每个分词的句法结构 |
@source
sentences(
@key
@distributed_by
doc_id text,
@key
sentence_index int,
@searchable
sentence_text text,
tokens text[],
lemmas text[],
pos_tags text[],
ner_tags text[],
doc_offsets int[],
dep_types text[],
dep_tokens int[]
).
2.2 数据标注函数实现
这里定义函数名为
nlp_markup
的函数,定义入参、返回值以及具体执行程序。over后面定义接入参
doc_id
和context
,然后将返回数据rows数据结构对应定义的sentences
的行数据,最后一句说明了我们这个程序文件是udf/nlp_markup.sh
,输入是tsv的一行。
function nlp_markup over (
doc_id text,
content text
) returns rows like sentences
implementation "udf/nlp_markup.sh" handles tsv lines.
udf/nlp_markup.sh
文件就是具体实现标注的程序,一般来说我们是可以不动它
set -euo pipefail
cd "$(dirname "$0")"
: ${BAZAAR_HOME:=$PWD/bazaar}
# 启动standordnlp 自然语言处理执行程序 具体在 udf/bazaar/parser/target/start
# 启动程序由 sbt/sbt stage编译得
[[ -x "$BAZAAR_HOME"/parser/target/start ]] || {
echo "No Bazaar/Parser set up at: $BAZAAR_HOME/parser"
exit 2
} >&2
[[ $# -gt 0 ]] ||
# default column order of input TSV
set -- doc_id content
# 将数据tvs行数据转json
# start Bazaar/Parser to emit sentences TSV
tsv2json "$@" |
# 解析json数据,实现标注
"$BAZAAR_HOME"/parser/run.sh -i json -k doc_id -v content
定义完函数跟执行程序,就需要实现函数调用
sentences += nlp_markup(doc_id, content) :-
articles(doc_id, content).
上面的
+=
其实和其他语言差不多,就是对于来源是articles中的每一行的doc_id
和content
我们都调用nlp_markup
然后结果添加到sentences表中。
这一部分的函数基本都不需要进行改动,都是套路,沿着走就是了。
3.候选实体生成
首先我们再次明确的我们的目标,我们需要从这波非结构化数据里面,找到企业并且判断企业跟企业之间的关系。
我们上面以及完成数据初始化导入以及进行的数据标注,接下来我们根据标注后的数据,按照我们的规则,抽取出这段公告里面的实体也就是企业
3.1 抽取候选实体结构定义
老套路,定义实体表
@extraction
company_mention(
@key
# id
mention_id text,
@searchable
# 企业名称
mention_text text,
@distributed_by
@references(relation="sentences", column="doc_id", alias="appears_in")
# 对应sentences表的id
doc_id text,
# 对应sentences表的sentence_index
@references(relation="sentences", column="doc_id", alias="appears_in")
sentence_index int,
# 句中开始位置
begin_index int,
# 句中结束位置
end_index int
).
3.2 抽取实体函数实现
这里的定义就是函数入参、返回值以及程序。
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.
udf/map_company_mention.py
就是核心抽取实体代码,具体逻辑就是根据 sentences里面的ner_tags,实体类型的识别,抽取类型为ORG
的数据进行转换存储到company_mention里面,核心逻辑如下:
4. 候选实体关系抽取
前面说了,我们要判断企业跟企业之间的是否存在交易关系的概率,首先我们得建立企业与企业的集合,基于笛卡儿积。
假如有3个企业,笛卡儿积之后就会出现这样的结果:
企业1 | 企业2 |
---|---|
阿里 | 腾讯 |
阿里 | 百度 |
腾讯 | 阿里 |
腾讯 | 百度 |
百度 | 阿里 |
百度 | 腾讯 |
4.1 候选实体关系结构定义
@extraction
transaction_candidate(
p1_id text,
p1_name text,
p2_id text,
p2_name text
).
4.2 抽取候选关系函数
由于套路一致,讲讲核心就行
执行函数:
这里其实是基于数据库操作进行笛卡儿积,要求相关联的两个候选实体,要是在同一句话里的,不再同一句话中怎么知道他们两个有关系。而且两个名字不能一样,识别的时候也有可能一个部分识别了两个,所以开始位置也不能一样。
执行程序udf/map_transaction_candidate.py
主要做去重
一般来说,也是不用怎么改动的。
5.提取特征逻辑
这就是寻常的机器学习领域了,提取特征,利用机器学习去分类哪些是有交易关系的。
对于自然语言来说,他的特征就是上下文,就是上下文
5.1 定义特征表
@extraction
transaction_feature(
@key
@references(relation="has_transaction", column="p1_id", alias="has_transaction")
p1_id text,
@key
@references(relation="has_transaction", column="p1_id", alias="has_transaction")
p2_id text,
@key
feature text
).
5.2 提取特征实现
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.
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).
核心脚本 udf/extract_transaction_features.py
,调用了ddlib库
的get_generic_features_relation
产生了特征.
以其中一句为例子,1201734457_3
SELECT sentence_text FROM sentences WHERE doc_id = '1201734457' AND sentence_index =3
公司拟将全资子公司山丹县芋兴粉业 有限责任公司(以下简称:芋兴粉业)100%股权、全资子公司甘肃天润 薯业有限责任公司(以下简称:天润薯业)100%股权及甘肃天润薯业有 限责任公司的全资子公司甘肃大有农业科技有限公司(以下简称:大有 科技)100%股权同价转让给甘肃亚盛薯业有限责任公司(以下简称:亚 盛薯业)。
根据我们观察 实体是甘肃大有农业科技有限公司以及甘肃天润薯业有限责任公司,他们进行了股权转让
甘肃大有农业科技有限公司实体id:1201734457_3_46_51
甘肃天润薯业有限责任公司实体id: 1201734457_3_22_27
select feature from transaction_feature where p1_id = '1201734457_3_46_51' and p2_id='1201734457_3_22_27'
总共抽取了79个特征
具体解析参考:https://blog.csdn.net/weixin_42001089/article/details/90749577
6. 样本打标
做过机器学习的朋友都知道,训练模型需要训练大量的标签数据进行模型迭代优化,这里示例的逻辑是导入先验数据,按照我们预先设定的规则,进行标记,产生一堆正例样本和反例样本。
6.1 导入先验数据
导入已知有交易关系的先验数据,先验数据结构如下:
transaction_dbdata(
@key
company1_name text,
@key
company2_name text
).
6.2 初始化标签表并对结合先验数据进行打标
定义标签表 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
).
先验数据打标规则: 先验数据与抽选集合企业名称一致的权重为3,就是正相关比较大
6.3 对候选数据进行打标
核心脚本udf/supervise_transaction.py
:
这里基本就是按照定好的逻辑规则进行标注
通过sum计算打标结果总计获得最终标记transaction_resolve
SELECT r0.p1_id AS column_0,
r0.p2_id AS column_1,
sum(r0.label) AS column_2
FROM transaction_label r0
GROUP BY r0.p1_id, r0.p2_id
最终标注结果如下:
7 变量表定义
首先定义最终存储的表格,[?]表示此表是用户模式下的变量表,即需要推到关系的表。这里我们需要推到的是企业和企业间是否存在交易关系。
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).
8 构建因子图
8.1 因子图解释
Factor Graph 是概率图的一种,概率图有很多种,最常见的就是Bayesian Network (贝叶斯网络)和Markov Random Fields(马尔可夫随机场)。
在概率图中,求某个变量的边缘分布是常见的问题。这问题有很多求解方法,其中之一就是可以把Bayesian Network和Markov Random Fields 转换成Facor Graph,然后用sum-product算法求解。基于Factor Graph可以用sum-product算法可以高效的求各个变量的边缘分布。
将一个具有多变量的全局函数因子分解,得到几个局部函数的乘积,以此为基础得到的一个双向图叫做因子图。
所谓factor graph(因子图),就是对函数因子分解的表示图,一般内含两种节点,变量节点和函数节点。我们知道,一个全局函数能够分解为多个局部函数的积,因式分解就行了,这些局部函数和对应的变量就能体现在因子图上。
在概率论及其应用中, 因子图是一个在贝叶斯推理中得到广泛应用的模型。
8.2 应用因子图获取交易概率
将每一对 has_transaction 中的实体对和特征表连接起来,通过特征因子连接,全局学习这些特征的权重 f
@weight(f)
has_transaction(p1_id, p2_id) :-
transaction_candidate(p1_id, _, p2_id, _),
transaction_feature(p1_id, p2_id, f).
# Inference rule: Symmetry
@weight(3.0)
has_transaction(p1_id, p2_id) => has_transaction(p2_id, p1_id) :-
transaction_candidate(p1_id, _, p2_id, _).
获取交易概率如下: