经典论文复现 | 基于标注策略的实体和关系联合抽取
過去幾年發表于各大 AI 頂會論文提出的 400 多種算法中,公開算法代碼的僅占 6%,其中三分之一的論文作者分享了測試數據,約 54% 的分享包含“偽代碼”。這是今年 AAAI 會議上一個嚴峻的報告。?人工智能這個蓬勃發展的領域正面臨著實驗重現的危機,就像實驗重現問題過去十年來一直困擾著心理學、醫學以及其他領域一樣。最根本的問題是研究人員通常不共享他們的源代碼。?
可驗證的知識是科學的基礎,它事關理解。隨著人工智能領域的發展,打破不可復現性將是必要的。為此,PaperWeekly 聯手百度 PaddlePaddle 共同發起了本次論文有獎復現,我們希望和來自學界、工業界的研究者一起接力,為 AI 行業帶來良性循環。
作者丨戴一鳴
學校丨清華海峽研究院
研究方向丨自然語言處理
引言
筆者本次復現的是中科院自動化所發表于 ACL 2017 的經典文章——Joint Extraction of Entities and Relations Based on a Novel Tagging Scheme。
對于實體和關系的聯合抽取一直是信息抽取中重要的任務。為了解決這一問題,論文提出了一個新型的標注方式,可以解決聯合信息抽取中的標注問題。隨后,基于這一標注方法,論文研究了不同的端到端模型,在不需要分開識別實體和關系的同時,直接抽取實體和實體之間的關系。
論文在使用了遠程監督制作的公開數據集上進行了實驗,結果說明這一標注策略較現有的管道和聯合學習方法。此外,論文所提出的端到端模型在公開數據集上取得了目前最好的效果。
論文復現代碼:?
http://aistudio.baidu.com/aistudio/#/projectdetail/26338
論文方法
論文提出了一個新型的標注方式,并設計了一個帶有偏置(Bias)目標函數的端到端模型,去聯合抽取實體和實體間的關系。
標注方式
圖 1 是一個如何將原始標注數據(實體+實體關系)轉換為論文中提到的新型標注方式的示例。在數據中,每一個詞匯會被賦予一個實體標簽,因此通過抽取和實體有關的詞語,構成實體。
▲?圖1.?一個構成實體和關系的數據實例
第一個標簽是“O”,表示這個詞屬于“Other”標簽,詞語不在被抽取結果中。除了標簽“O”以外,其他標簽都由三部分組成:1)詞語在實體中的位置,2)實體關系類型,3)關系角色。
論文使用“BIES”規則(B:實體起始,I:實體內部,E:實體結束,S:單一實體)去標注詞語在實體中的位置信息。對于實體關系類型,則通過預先定義的關系集合確定。對于關系角色,論文使用“1”和“2”確定。一個被抽取的實體關系結果由一個三元組表示(實體 1-關系類型-實體 2)。“1”表示這個詞語屬于第一個實體,“2”則表示這個詞語屬于第二個實體。因此,標簽總數是:Nt = 2*4 *|R|+1。R 是預先定義好的關系類型的數量。?
從圖 1 可以看出,輸入的句子包含兩個三元組:
{United States, Country-President, Trump}?
{Apple Inc, Company-Founder, Steven Paul Jobs}
預先定義的兩組關系是:
Country-President: CP?
Company-Founder:CF
由于“United”,“States”,“ Trump”,“Apple”,“Inc” ,“Steven”, “Paul”, “Jobs”構成了描述實體的詞匯,因此這些詞語都被賦予了特定的標記。
例如,“United”是實體“United States”的第一個詞語,同時也和“Country-President”關聯,因此“United”詞語的標注是“B-CP-1”。“B”表示Begin,“CP”表示Country President,“1”表示“United”詞語所在的實體“United States”是三元組中的第一個對象。
同理,因為“States”是“United States”實體的結尾詞語,但依然屬于“Country President”關系,同時也是三元組的第一個對象,因此“States”的標注是“E-CP-1”。?
對于另一個詞語“Trump”,它是構成“Trump”這一實體的唯一詞語,因此使用“S”。同時,Trump 實體屬于“Country President”關系,因此它具有CP標簽,又同時這一實體在三元組中是第二個對象,因此它被標注“2”。綜上,“Trump”這一詞語的標注是:“S-CP-2”。除了這些和實體有關的詞語外,無關詞語被標注“O”。
當然,對于擁有兩個和兩個以上實體的句子,論文將每兩個實體構成一個三元組,并使用最小距離原則(距離最近的兩個實體構成一對關系)。在圖 1 中,“United States”和“Trump”因為最小距離構成了一對實體。此外,論文只探討一對一關系三元組。
端到端模型
雙向長短時編碼層(Bi-LSTM Encoder Layer)
在序列標注問題中,雙向長短時編碼器體現了對單個詞語的語義信息的良好捕捉。這一編碼器有一個前向和后向的長短時層,并在末尾將兩層合并。詞嵌入層則將詞語的獨熱編碼(1-hot representation)轉換為詞嵌入的向量。
▲?公式1.?雙向長短時編碼器
公式 1 中的 i,f 和 o 分別為 LSTM 模塊在 t 時刻的輸入門,遺忘門和輸出門。c 為 LSTM 模塊的輸出,W 為權重。對于當前時刻,其隱層向量的結果取決于起義時刻的,上一時刻的,以及當前時刻的輸入詞語。
對于一句話,表示為。其中是第 d 維度下在第 t 個詞匯的詞向量,n 則是句序列的長度。在經過了詞嵌入后,前向和后向的長短時神經網絡分布接受數據輸入,前向則句子順序從前向后,后向則從后向前。
對于每一個詞語向量(經過詞嵌入后), 前向長短時神經網絡層通過考慮語義信息,將到的信息全部編碼,記為。同樣,后向長短時則為。編碼器最后將兩個層的輸入相接。
長短時解碼器
論文同時使用了長短時解碼器用于標注給定序列。解碼器在當前時刻的輸入為來自雙向編碼器的隱層向量,前一個預測的標簽的嵌入,前一個時刻的神經元輸入,以及前一時刻的隱層向量。解碼器根據雙向長短時編碼器的輸出進行計算。解碼器的內部公式類似于公式 1。
▲?公式2.?長短時解碼器
Softmax層
在解碼器后加入 softmax 層,預測該詞語的標簽。解碼器的內部結構類似于編碼器。
▲?公式3.?softmax層
為 softmax 矩陣,為總標簽數,為預測標簽的向量。
▲?圖2.?網絡整體結構圖
偏置目標函數(Bias Objective Function)
▲?公式4.?訓練中激活函數使用RMSprop
|D| 是訓練集大小,是句子的長度,是詞語 t 在的標簽,是歸一化的 tag 的概率。I(O) 是一個條件函數(switching function),用于區分 tag 為“O”和不為“O”的時候的損失。
▲?公式5.?條件函數
α 是偏置權重,該項越大,則帶關系的標簽對模型的影響越大。
import?paddle.v2?as?paddle
from?paddle.fluid.initializer?import?NormalInitializer
import?re
import?math
#coding='utf-8'
import?json
import?numpy?as?np
from?paddle.v2.plot?import?Ploter
train_title?=?"Train?cost"
test_title?=?"Test?cost"
plot_cost?=?Ploter(train_title,?test_title)
step?=?0
#=============================================global?parameters?and?hyperparameters==================================
EMBEDDING?=?300
DROPOUT?=?0.5
LSTM_ENCODE?=?300
LSTM_DECODE?=?600
BIAS_ALPHA?=?10
VALIDATION_SIZE?=?0.1
TRAIN_PATH?=?'/home/aistudio/data/data1272/train.json'
TEST_PATH?=?'/home/aistudio/data/data1272/test.json'
FILE_PATH?=?'/home/aistudio/data/'
X_TRAIN?=?'/home/aistudio/data/data1272/sentence_train.txt'
Y_TRAIN?=?'/home/aistudio/data/data1272/seq_train.txt'
X_TEST?=?'/home/aistudio/data/data1272/sentence_test.txt'
Y_TEST?=?'/home/aistudio/data/data1272/seq_test.txt'
WORD_DICT?=?'/home/aistudio/data/data1272/word_dict.txt'
TAG_DICT?=?'/home/aistudio/data/data1272/tag_dict.txt'
EPOCH_NUM?=?1000
BATCH_SIZE?=?128
#=============================================get?data?from?the?dataset==============================================
def?get_data(train_path,?test_path,?train_valid_size):
????'''
????extracting?data?for?json?file
????'''
????train_file?=?open(train_path).readlines()
????x_train?=?[]
????y_train?=?[]
????for?i?in?train_file:
????????data?=?json.loads(i)
????????x_data,?y_data?=?data_decoding(data)
????????'''
????????appending?each?single?data?into?the?x_train/y_train?sets
????????'''
????????x_train?+=?x_data
????????y_train?+=?y_data
????test_file?=?open(test_path).readlines()
????x_test?=?[]
????y_test?=?[]
????for?j?in?test_file:
????????data?=?json.loads(j)
????????x_data,?y_data?=?data_decoding(data)
????????x_test?+=?x_data
????????y_test?+=?y_data
????return?x_train,?y_train,?x_test,?y_test
def?data_decoding(data):
????'''
????decode?the?json?file
????sentText?is?the?sentence
????each?sentence?may?have?multiple?types?of?relations
????for?every?single?data,?it?contains:?(sentence-splited,?labels)
????'''
????sentence?=?data["sentText"]
????relations?=?data["relationMentions"]
????x_data?=?[]
????y_data?=?[]
????for?i?in?relations:
????????entity_1?=?i["em1Text"].split("?")
????????entity_2?=?i["em2Text"].split("?")
????????relation?=?i["label"]
????????relation_label_1?=?entity_label_construction(entity_1)
????????relation_label_2?=?entity_label_construction(entity_2)
????????output_list?=?sentence_label_construction(sentence,?relation_label_1,?relation_label_2,?relation)
????????x_data.append(sentence.split("?"))
????????y_data.append(output_list)
????return?x_data,?y_data
def?entity_label_construction(entity):
????'''
????give?each?word?in?an?entity?the?label
????for?entity?with?multiple?words,?it?should?follow?the?BIES?rule
????'''
????relation_label?=?{}
????for?i?in?range(len(entity)):
????????if?i?==?0?and?len(entity)?>=?1:
????????????relation_label[entity[i]]?=?"B"
????????if?i?!=?0?and?len(entity)?>=?1?and?i?!=?len(entity)?-1:
????????????relation_label[entity[i]]?=?"I"
????????if?i==?len(entity)?-1?and?len(entity)?>=?1:
????????????relation_label[entity[i]]?=?"E"
????????if?i?==0?and?len(entity)?==?1:
????????????relation_label[entity[i]]?=?"S"
????return?relation_label
def?sentence_label_construction(sentence,?relation_label_1,?relation_label_2,?relation):
????'''
????combine?the?label?for?each?word?in?each?entity?with?the?relation
????and?then?combine?the?relation-entity?label?with?the?position?of?the?entity?in?the?triplet
????'''
????element_list?=?sentence.split("?")
????dlist_1?=?list(relation_label_1)
????dlist_2?=?list(relation_label_2)
????output_list?=?[]
????for?i?in?element_list:
????????if?i?in?dlist_1:
????????????output_list.append(relation?+?'-'?+?relation_label_1[i]?+?'-1'?)
????????elif?i?in?dlist_2:
????????????output_list.append(relation?+?'-'?+?relation_label_2[i]?+?'-2')
????????else:
????????????output_list.append('O')
????return?output_list
def?format_control(string):
????str1?=?re.sub(r'\r','',string)
????str2?=?re.sub(r'\n','',str1)
????str3?=?re.sub(r'\s*','',str2)
????return?str3
def?joint_extraction():
????vocab_size?=?len(open(WORD_DICT,'r').readlines())
????tag_num?=?len(open(TAG_DICT,'r').readlines())
????def?bilstm_lstm(word,?target,?vocab_size,?tag_num):
????????????x?=?fluid.layers.embedding(
????????????????input?=?word,
????????????????size?=?[vocab_size,?EMBEDDING],
????????????????dtype?=?"float32",
????????????????is_sparse?=?True)
????????????y?=?fluid.layers.embedding(
????????????????input?=?target,
????????????????size?=?[tag_num,?tag_num],
????????????????dtype?=?"float32",
????????????????is_sparse?=?True)
????????????fw,?_?=?fluid.layers.dynamic_lstm(
????????????????input?=?fluid.layers.fc(size?=?LSTM_ENCODE*4,?input=x),
????????????????size?=?LSTM_ENCODE*4,
????????????????candidate_activation?=?"tanh",
????????????????gate_activation?=?"sigmoid",
????????????????cell_activation?=?"sigmoid",
????????????????bias_attr=fluid.ParamAttr(
????????????????????initializer=NormalInitializer(loc=0.0,?scale=1.0)),
????????????????is_reverse?=?False)
????????????bw,?_?=?fluid.layers.dynamic_lstm(
????????????????input?=?fluid.layers.fc(size?=?LSTM_ENCODE*4,?input=x),
????????????????size?=?LSTM_ENCODE*4,
????????????????candidate_activation?=?"tanh",
????????????????gate_activation?=?"sigmoid",
????????????????cell_activation?=?"sigmoid",
????????????????bias_attr=fluid.ParamAttr(
????????????????????initializer=NormalInitializer(loc=0.0,?scale=1.0)),
????????????????is_reverse?=?True)
????????????combine?=?fluid.layers.concat([fw,bw],?axis=1)
????????????decode,?_?=??fluid.layers.dynamic_lstm(
????????????????input?=?fluid.layers.fc(size?=?LSTM_DECODE*4,?input=combine),
????????????????size?=?LSTM_DECODE*4,
????????????????candidate_activation?=?"tanh",
????????????????gate_activation?=?"sigmoid",
????????????????cell_activation?=?"sigmoid",
????????????????bias_attr=fluid.ParamAttr(
????????????????????initializer=NormalInitializer(loc=0.0,?scale=1.0)),
????????????????is_reverse?=?False)
????????????softmax_connect?=?fluid.layers.fc(input=decode,?size=tag_num)
????????????_cost?=?fluid.layers.softmax_with_cross_entropy(
????????????????logits=softmax_connect,
????????????????label?=?y,
????????????????soft_label?=?True)
????????????_loss?=?fluid.layers.mean(x=_cost)
????????????return?_loss,?softmax_connect
????source?=?fluid.layers.data(name="source",?shape=[1],?dtype="int64",?lod_level=1)
????target?=?fluid.layers.data(name="target",?shape=[1],?dtype="int64",?lod_level=1)
????loss,?softmax_connect?=?bilstm_lstm(source,?target,?vocab_size,?tag_num)
????return?loss
def?get_index(word_dict,?tag_dict,?x_data,?y_data):
????x_out?=?[word_dict[str(k)]?for?k?in?x_data]
????y_out?=?[tag_dict[str(l)]?for?l?in?y_data]
????return?[x_out,?y_out]
def?data2index(WORD_DICT,?TAG_DICT,?x_train,?y_train):
????def?_out_dict(word_dict_path,?tag_dict_path):
????????word_dict?=?{}
????????f?=?open(word_dict_path,'r').readlines()
????????for?i,?j?in?enumerate(f):
????????????word?=?re.sub(r'\n','',str(j))
#?????????????word?=?re.sub(r'\r','',str(j))
#?????????????word?=?re.sub(r'\s*','',str(j))
????????????word_dict[word]?=?i?+?1
????????tag_dict?=?{}
????????f?=?open(tag_dict_path,'r').readlines()
????????for?m,n?in?enumerate(f):
????????????tag?=?re.sub(r'\n','',str(n))
????????????tag_dict[tag]?=?m+1
????????return?word_dict,?tag_dict
????def?_out_data():
????????word_dict,?tag_dict?=?_out_dict(WORD_DICT,?TAG_DICT)
????????for?data?in?list(zip(x_train,?y_train)):
????????????x_out,?y_out?=?get_index(word_dict,?tag_dict,?data[0],?data[1])???
????????????yield?x_out,?y_out
????return?_out_data
def?optimizer_program():
????return?fluid.optimizer.Adam()
if?__name__?==?"__main__":
????sentence_train,?seq_train,?sentence_test,?seq_test?=?get_data(TRAIN_PATH,TEST_PATH,VALIDATION_SIZE)
????train_reader?=?paddle.batch(
????????paddle.reader.shuffle(
????????????data2index(WORD_DICT,?TAG_DICT,?sentence_train,?seq_train),?
????????????buf_size=500),
????????batch_size=128)
????test_reader?=?paddle.batch(
????????paddle.reader.shuffle(
????????????data2index(WORD_DICT,?TAG_DICT,?sentence_test,?seq_test),?
????????????buf_size=500),
????????batch_size=128)
????place?=?fluid.CPUPlace()
????feed_order=['source',?'target']
????trainer?=?fluid.Trainer(
????????train_func=joint_extraction,
????????place=place,
????????optimizer_func?=?optimizer_program)
????trainer.train(
????reader=train_reader,
????num_epochs=100,
????event_handler=event_handler_plot,
????feed_order=feed_order)
▲?模型和運行函數train代碼展示
實驗
實驗設置
數據集
使用 NYT 公開數據集。大量數據通過遠程監督的方式提取。測試集則使用了人工標注的方式。訓練集總共有 353k 的三元組,測試集有 3880 個。此外,預定義的關系數量為 24 個。?
評價方式?
采用標準的精確率(Precision)和召回率(Recall)以及 F1 分數對結果進行評價。當三元組中的實體 1,實體 2,以及關系的抽取均正確才可記為 True。10% 的數據用于驗證集,且實驗進行了 10 次,結果取平均值和標準差。
超參數?
詞嵌入使用 word2vec,詞嵌入向量是 300 維。論文對嵌入層進行了正則化,其 dropout 概率為 0.5。長短時編碼器的長短時神經元數量為 300,解碼器為 600。偏置函數的權重 α 為 10。
論文和其他三元組抽取方法進行了對比,包括多項管道方法,聯合抽取方法等。
實驗結果
表 1 為實體和實體關系抽取的表現結果,本論文正式方法名稱為“LSTM-LSTM-Bias”。表格前三項為管道方法,中間三項為聯合抽取方法。
▲?表1.?實體和實體關系抽取結果
從實驗結果看出,論文提到的方法普遍優于管道方法和絕大多數聯合抽取方法。本論文另一個值得注意的地方是,論文提出的方法較好地平衡了精確率和召回率的關系,盡管在精確率指標上略低于 LSTM-CRF。?
表 1 也說明深度學習方法對三元組結果的抽取基本上好于傳統方法。作者認為,這是因為深度學習方法在信息抽取中普遍使用雙向長短時編碼器,可以較好地編碼語義信息。
在不同深度學習的表現對比中,作者發現,LSTM-LSTM 方法好于 LSTM-CRF。論文認為,這可能是因為 LSTM 較 CRF 更好地捕捉了文本中實體的較長依賴關系。
分析和討論
錯誤分析
表 2 為深度學習方法對三元組各個元素的抽取效果對比,E1 表示實體 1 的抽取結果,E2 表示實體 2 的抽取結果,(E1,E2)表示實體的關系的抽取結果。
▲?表2.?深度學習方法對三元組各元素抽取效果
表 2 說明,在對三元組實體的抽取中,對關系的抽取較三元組各個實體的抽取的精確率更好,但召回率更低。論文認為,這是由于有大量的實體抽取后未能組成合適的實體關系對。模型僅抽取了第一個實體 1,但未能找到合適的對應實體 2,或者僅有實體 2 被正確抽取出來。
此外,作者發現,表 2?的關系抽取結果比表 1?的結果提高了約 3%。作者認為,這是由于 3% 的結果預測錯誤是因為關系預測錯誤,而非實體預測錯誤導致的。
偏置損失分析
作者同時將論文方法和其他深度學習方法在識別單個實體(實體 1,實體 2)上的表現進行了對比。作者認為,雖然論文方法在識別單個實體上的表現低于其他方法,但能夠更好地識別關系。
▲?表3.?單個實體識別結果
作者對比發現,當偏置項等于 10 時,F1 數值最高。因此建議偏置項設為 10。
▲?表4.?偏置項(α)數值和各項表現指標的關系
結論
本文提出一種新型的標注方式,將傳統的命名實體識別和關系抽取任務聯合起來,使用端到端模型進行直接聯合信息抽取。在和傳統方法以及深度學習方法的對比中均取得了滿意的成果。
考慮到目前論文設計的實體關系抽取僅限于單個的關系,無法對一句話中重合的多個實體關系進行抽取,論文作者考慮使用多分類器替換 softmax 層,以便對詞語進行多分類標注。
關于PaddlePaddle
使用 PaddlePaddle 進行工作大體上感覺不錯,優點主要有:
1. 構建模型的過程較為順利?
PaddlePaddle?的官方文檔較為清楚,大量的函數和 TensorFlow 主流框架對應,因此在尋找組件的時候可以找到。
2. 運行速度快?
據了解,PaddlePaddle?底層優化較好,速度比 TensorFlow?快很多。
3. 對 GPU 的支持?
主流框架目前都支持了 GPU,PaddlePaddle?也同樣具有這一特性。
4. 動態圖架構?
在數據更加復雜的情況下,動態圖的構建優勢比靜態圖更為明顯。PaddlePaddle?框架下的 fluid 版本甚至比?TensorFlow?的動態圖支持更領先。
當然,考慮到 PaddlePaddle?依然年輕,仍有不少問題需要進一步優化:
1. 在筆者使用的時候,仍然不支持 Python 3.x(2018 年 9 月)。據說在 11 月份會開始支持 Python 3.x,正在期待中。
2. Debug 仍然困難。可能一方面是因為筆者使用了 AI studio 而非傳統的 IDE 進行項目,另一方面是 PaddlePaddle?內部的優化問題,代碼出錯的時候,很難找到問題原因。這一點和 TensorFlow?有點像——各種各樣的報錯。
接下來期待 PaddlePaddle?更加支持 TPU 和 NPU,并更好地增加對小型移動設備和物聯網系統的支持,使模型可以無障礙部署。
點擊標題查看更多論文解讀:?
經典論文復現 | 基于深度學習的圖像超分辨率重建
手把手帶你復現ICCV 2017經典論文—PyraNet
經典論文復現 | LSGAN:最小二乘生成對抗網絡
PyraNet:基于特征金字塔網絡的人體姿態估計
經典論文復現 | InfoGAN:一種無監督生成方法
經典論文復現 | ICML 2017大熱論文:Wasserstein GAN
#投 稿 通 道#
?讓你的論文被更多人看到?
如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢??答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。?
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得或技術干貨。我們的目的只有一個,讓知識真正流動起來。
??來稿標準:
? 稿件確系個人原創作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)?
? 如果文章并非首發,請在投稿時提醒并附上所有已發布鏈接?
? PaperWeekly 默認每篇文章都是首發,均會添加“原創”標志
? 投稿郵箱:
? 投稿郵箱:hr@paperweekly.site?
? 所有文章配圖,請單獨在附件中發送?
? 請留下即時聯系方式(微信或手機),以便我們在編輯發布時和作者溝通
?
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
▽ 點擊 |?閱讀原文?| 收藏復現代碼
總結
以上是生活随笔為你收集整理的经典论文复现 | 基于标注策略的实体和关系联合抽取的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EMNLP2018论文解读 | 利用篇章
- 下一篇: NIPS 2018论文解读 | 基于条件