文章目錄
實踐目標
基於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,修改爲本地環境數據庫
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表如下:
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_id
和content
我們都調用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表可以看到解析結果
4 實體抽取以及候選實體生成
4.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
).
- 實體抽取函數
定義實體抽取函數
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語句返回輸出行。
- 調用函數定義:
company_mention += map_company_mention(
doc_id, sentence_index, tokens, ner_tags
) :-
sentences(doc_id, sentence_index, _, tokens, _, _, ner_tags, _, _, _).
- 執行實體抽取
修改~/udf/transform.py
控制檯輸入:
deepdive compile && deepdive do company_mention
查看具體解析實體列表如下:
4.2 生成實體對,即要預測關係的兩個公司
- 實體對錶結構定義
transaction_candidate(
p1_id text,
p1_name text,
p2_id text,
p2_name text
).
-
統計每個句子的實體數:
num_company(doc_id, sentence_index, COUNT§) :-
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 += 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
生成候選實體表。
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).'
現在,我們已經有了想要判定關係的實體對和它們的特徵集合。
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進行圖概率的統計推理和知識學習,得到概率值大於閾值的候選關係實體對,組成提取的知識庫。
參考資料: