基於DeepDive實現從股權交易公告獲取企業與企業之間存在交易關係的概率--實踐篇

實踐目標

基於DeepDive 經過 數據處理、數據標註、學習推理和交互迭代步驟實現從股權交易公告數據裏面獲取企業與企業之間存在交易關係的概率

控制檯輸入 docker exec -it sanbox_deepdive-notebooks_1 進入deepdive環境

1.示例目錄說明

我們將使用 /deepdive-examples/spouse 示例目錄,目錄文件說明:

文件/文件夾名 描述
app.ddlog deepdive的規劃文件,此文件定義了數據的來源,數據的結構,數據的處理,KBC的構建。
db.url 此文件定義了數據庫的連接信息
deepdive.conf deepdive環境配置,不用修改。
input 此目錄放置數據文件,該數據文件需要按照app.ddlog中的規則來命名,該數據文件爲應用提供源數據。
udf/ 存放用戶定義的函數的目錄,可以從deepdive.conf引用相對於應用程序根目錄的路徑名

修改db.url,修改爲本地環境數據庫

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dnKjrhIH-1585289391171)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200325194719314.png)]

2.數據處理

2.1 定義原始數據導入數據庫表結構

編輯app.ddlog

定義數據庫表結構,並且規範數據文件名稱

@source
articles(
    @key
    @distributed_by
    id      text,
    @searchable
    content text
).

articles表示一個執行目標,上面代碼定義articles的數據結構,包含主鍵id以及content,該目標首先去input目錄下查找以articles開頭的數據文件,然後將其按照上面定義的數據格式導入到數據庫中。

執行編譯命令,每次修改app.ddlog都需要執行編譯

deepdive compile

2.2 導入數據

deepdive do articles

表示導入成功,查看數據庫articles表如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DFPQxIJY-1585289391174)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200325175854500.png)]

3. 數據標註

我們將使用斯坦福大學的CoreNLP自然語言處理(NLP)系統向輸入數據添加有用的標記和結構。此步驟將把我們的文章分成句子和它們的組成標記(大概是單詞)。此外,我們還將獲得引理(標準化詞形式),詞性(POS)標籤,命名實體識別(NER)標籤以及句子的依存關係分析。

3.1 定義處理後的數據存放結構

編輯app.ddlog

@source
sentences(
    @key
    @distributed_by
    # XXX This breaks the search index.  @source should not be derived from another @source
    #@references(relation="articles", column="id")
    doc_id         text,
    @key
    sentence_index int,
    @search_type("text[]")
    tokens         json,
    @search_type("text[]")
    lemmas         json,
    @search_type("text[]")
    pos_tags       json,
    @search_type("text[]")
    ner_tags       json,
    @search_type("int[]")
    doc_offsets    json,
    @search_type("text[]")
    dep_types      json,
    @search_type("int[]")
    dep_tokens     json
).

字段名 字段解釋
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表示的是每個分詞的句法結構

3.1 定義NLP處理函數

編輯app.ddlog

該函數遵守ddlog的語法規則。需要在udf 目錄下創建nlp_markup.sh腳本文件,裏面包含對內容的處理邏輯。

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

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

function用來定義函數,後面nlp_markup 是函數名 over後面接的是參數表。
returns 說明了函數返回的形式,返回就像我們前面定義的sentences那樣的一行。
最後一句說明了我們這個程序文件是udf/nlp_markup.sh ,輸入是tsv的一行

說明:上面的+= 其實和其他語言差不多,就是對於來源是articles中的每一行的doc_idcontent 我們都調用nlp_markup 然後結果添加到sentences表中。

3.3 說明nlp_markup.sh

#!/usr/bin/env bash
# A shell script that runs Bazaar/Parser over documents passed as input TSV lines
#
# $ deepdive env udf/nlp_markup.sh doc_id _ _ content _
##
set -euo pipefail
cd "$(dirname "$0")"

: ${BAZAAR_HOME:=$PWD/bazaar}
[[ -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

# convert input tsv lines into JSON lines for Bazaar/Parser


# start Bazaar/Parser to emit sentences TSV
tsv2json "$@" |
"$BAZAAR_HOME"/parser/run.sh -i json -k doc_id -v content

所有#開頭的(除了#!)都是普通註釋
參數的用法(

  • $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 作爲參數輸入。

3.4 運行標註

控制檯輸入以下命令執行處理函數:

 deepdive do sentences

一開始會報錯

2020-03-25 20:18:43.677869 Exception in thread "main" Error: A JNI error has occurred, please check your installation and try again
2020-03-25 20:18:43.678557 Exception in thread "main" java.lang.NoClassDefFoundError: scala/Function0
2020-03-25 20:18:43.678739      at java.lang.Class.getDeclaredMethods0(Native Method)
2020-03-25 20:18:43.678798      at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
2020-03-25 20:18:43.678856      at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
2020-03-25 20:18:43.678957      at java.lang.Class.getMethod0(Class.java:3018)
2020-03-25 20:18:43.678986      at java.lang.Class.getMethod(Class.java:1784)
2020-03-25 20:18:43.679003      at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
2020-03-25 20:18:43.679019      at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
2020-03-25 20:18:43.679124 Caused by: java.lang.ClassNotFoundException: scala.Function0
2020-03-25 20:18:43.679160      at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
2020-03-25 20:18:43.679178      at java.lang.ClassLoader.loadClass(ClassLoader.java:419)
2020-03-25 20:18:43.679247      at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
2020-03-25 20:18:43.679283      at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
2020-03-25 20:18:43.679301      ... 7 more

需要進行編譯nlp處理器,執行以下命令

cd ~/CNdeepdive/transaction/udf/bazaar/parser
sbt/sbt stage

重新執行 deepdive compile && deepdive do sentences

這一段會很慢,所以想快點就刪數據吧。

tips: 可以看到sentences給出的plan中包含articles表的執行。plan中前面有冒號的行表示默認已經執行,不會重做,否則將要生成。如果articles有更新,需要重新deepdive redo articles或者用deepdive mark todo articles來將articles標記爲未執行,這樣在生成sentences的過程中就會默認更新articles了。

在數據庫sentences表可以看到解析結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EPO3CJiD-1585289391175)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200326155337901.png)]

4 實體抽取以及候選實體生成

4.1 實體抽取

  1. 實體數據表定義

每個實體都是表中的一列數據,同時存儲了實體在句中的起始位置和結束位置。

@extraction
company_mention(
        @key
    mention_id     text,
    @searchable
    mention_text   text,
    @distributed_by
    @references(relation="sentences", column="doc_id",         alias="appears_in")
    doc_id         text,
    @references(relation="sentences", column="doc_id",         alias="appears_in")
    sentence_index int,
    begin_index    int,
    end_index      int
).

  1. 實體抽取函數

定義實體抽取函數

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見樣例。這個腳本遍歷每個數據庫中的句子,找出連續的NER標記爲ORG的序列,再做其它過濾處理,其它腳本也要複製過去。這個腳本是一個生成函數,用yield語句返回輸出行。

  1. 調用函數定義:
company_mention += map_company_mention(
doc_id, sentence_index, tokens, ner_tags
) :-
sentences(doc_id, sentence_index, _, tokens, _, _, ner_tags, _, _, _).
  1. 執行實體抽取

修改~/udf/transform.py

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1vYOGoOv-1585289391176)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200326161451669.png)]

控制檯輸入:

deepdive compile && deepdive do company_mention

查看具體解析實體列表如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qd3IhMJi-1585289391176)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200326161534058.png)]

4.2 生成實體對,即要預測關係的兩個公司

  1. 實體對錶結構定義
transaction_candidate(
	p1_id   text,
	p1_name text,
	p2_id   text,
	p2_name text
).
  1. 統計每個句子的實體數:

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

  2. 定義過濾函數:

    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.
    
  3. 描述函數的調用:

    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,過濾掉兩個相同實體組成的實體對。

  1. 編譯並執行:

deepdive compile && deepdive do transaction_candidate

生成候選實體表。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eagjDTgF-1585289391176)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200326164410282.png)]

4.3 特徵抽取

這一步我們抽取候選實體對的文本特徵。

(1). 定義特徵表:

transaction_feature(
	p1_id   text,
	p2_id   text,
	feature text
)

這裏的feature列是實體對間一系列文本特徵的集合。

(2). 生成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/詞序列的窗口特徵。此處也可以自定義特徵。

(3).把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).

(4). 然後編譯並執行,生成特徵數據庫:

 deepdive compile && deepdive do transaction_feature

執行如下語句,查看生成結果:

 deepdive query '| 20 ?- transaction_feature(_, _, feature).'

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TZ0RoewK-1585289391177)(C:\Users\lps\Documents\myblog\deepdive\DeepDiveSAMPLE.assets\image-20200326164714337.png)]

現在,我們已經有了想要判定關係的實體對和它們的特徵集合。

4.4 樣本打標

這一步,我們希望在候選實體對中標出部分正負例。

  • 利用已知的實體對和候選實體對關聯
  • 利用規則打部分正負標籤

(1). 首先在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爲正值表示正相關,負值表示負相關。絕對值越大,相關性越大。

(2). 初始化定義,複製transaction_candidate表,label均定義爲零。

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

(3).將前面準備的db數據導入transaction_label表中,rule_id標記爲"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) ].

(4). 如果只利用下載的實體對,可能和未知文本中提取的實體對重合度較小,不利於特徵參數推導。因此可以通過一些邏輯規則,對未知文本進行預標記。

 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中定義標記函數。

(5). 調用標記函數,將規則抽到的數據寫入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
).

(6). 不同的規則可能覆蓋了相同的實體對,從未給出不同甚至相反的label。建立transaction_label_resolved表,統一實體對間的label。利用label求和,在多條規則和知識庫標記的結果中,爲每對實體做vote。

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

(7). 執行以下命令,得到最終標籤。

 deepdive do transaction_label_resolved

5. 模型構建,進行知識推理

通過前面步驟,我們已經得到了所有前期需要準備的數據。下面可以構建模型了。

5.1 變量表定義

(1). 定義最終存儲的表格,『?』表示此表是用戶模式下的變量表,即需要推導關係的表。這裏我們預測的是公司間是否存在交易關係。

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

(2). 根據打標的結果,灌入已知的變量

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已知,成爲了先驗變量。

(3). 最後編譯執行決策表:

$ deepdive compile && deepdive do has_transaction

5.2 因子圖構建

(1). 指定特徵

將每一對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).

(2). 指定變量間的依賴性

我們可以指定兩張變量表間遵守的規則,並給這個規則以權重。比如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很好地支持了多關係下的抽取。

(3). 最後,編譯,並生成最終的概率模型:

deepdive compile && deepdive do probabilities

假如出現報錯:

pbzip2: error while loading shared libraries: libbz2.so.1.0: cannot open shared object file: No such file or directory

解決方案:建立libbz2.so.10軟連接

cd /usr/lib64/
ls -alhtr | grep libbz2.so.1
ln -s libbz2.so.1.0.6  libbz2.so.1.0 
ls -alhtr | grep libbz2.so.1

查看我們預測的公司間交易關係概率:

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

在這裏插入圖片描述

至此,我們的交易關係抽取就基本完成了。更多詳細說明請見http://deepdive.stanford.edu

6.總結

經過實踐可以得出:

6.1 Deepdive知識抽取步驟

一種基於Deepdive的領域文本知識抽取方法,包括以下步驟:

(1) 獲取知識庫構建系統所需的原始文本,並且採用jieba工具對原始文本分詞, 並採用斯坦福的core NLP工具對分詞後的文本進行詞性標註、命名實體標註以及語法依 賴處理,得到預處理後的文本數據;

(2)對預處理後的文本數據進行實體連接,找到與預設特定關係對應的目標實體,並生成滿足實體-關係-實體的三元組,組成候選關係實體對集;

(3)採用弱監督的方法對候選關係實體對集中的多個候選關係實體對進行學習和標註,生成大量的候選關係實體對作爲Deepdive工具的訓練樣本,並將訓練樣本中候選關係實體對對應的關係組成的關係集作爲真值標籤;

⑷將訓練樣本和真值標籤輸入至Deepdive工具中,以目標函數y最大爲目標,對 Deepdive進行訓練,並輸出概率值大於閾值的候選關係實體對,組成提取的知識庫。

步驟(2)中,知識庫構建的原始數據是非結構化的文本數據對象,通過特定的本 體和先驗知識,從中提取出所需要的知識三元組。候選關係實體對的獲取通過構建一個映射表和簡單的判斷規則來得到,例如對於公司類的實體,需要去除一些後綴詞彙例如“股份”、“有限”等。

6.2 Deepdive學習與標註步驟

使用弱監督方法對候選關係實體對進行學習與標註的具體步驟包括:

(1) 候選關係實體對集中的候選關係實體對標註爲正例,採用負抽樣方法獲得反例;

(2)使用規則進行弱監督,對於大多數垂直領域,領域專家都有相應的規則來表達某些特定的關係,因此可以利用相似的語法結構來制定相應的規則從而檢測某些語句 是否表達某一特定關係,並且將這些數據標註爲正例;

(3)不斷迭代步驟(2),直到滿足迭代次數或獲得足夠多的候選關係實體爲止,輸出最後得到的所有候選關係實體。

(4)不同於傳統的基於規則提取的方法,Deepdive提供了一套更健壯性的特徵提取的方法來獲取目標知識三元組。

6.3 Deepdive訓練原理

Deepdive進行訓練的過程爲:

(1) 首先,Deepdive內建的特徵庫處理訓練樣本中候選關係實體對的上下文,從上下文的分詞結果、語法依賴、詞性標註結果中提取詞語的nGram特性和詞性標籤;

(2) 然後,根據提取的nGram特性和詞性標籤以及訓練樣本,採用Factor Graph進行圖概率的統計推理和知識學習,得到概率值大於閾值的候選關係實體對,組成提取的知識庫。

參考資料:

https://www.ljjyy.com/archives/2019/10/100595.html#2-%E7%BC%96%E8%AF%91%E5%8F%8A%E7%94%9F%E6%88%90%E6%95%B0%E6%8D%AE%E8%A1%A8

https://patents.google.com/patent/CN107169079A/zh

推薦閱讀:
基於DeepDive實現從股權交易公告獲取企業與企業之間存在交易關係的概率–解析篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章