超详细解读:神经语义解析的结构化表示学习 | 附代码分析
在碎片化閱讀充斥眼球的時代,越來越少的人會去關(guān)注每篇論文背后的探索和思考。
在這個欄目里,你會快速 get 每篇精選論文的亮點和痛點,時刻緊跟 AI 前沿成果。
點擊本文底部的「閱讀原文」即刻加入社區(qū),查看更多最新論文推薦。
這是 PaperDaily 的第?68?篇文章本期推薦的論文筆記來自 PaperWeekly 社區(qū)用戶 @marcw。為了解決目標(biāo)輸出的不規(guī)范性問題以及提供可解釋的語義聚合過程,本文提出了一個神經(jīng)語義解析器(neural semantic parser)。?
如果你對本文工作感興趣,點擊底部閱讀原文即可查看原論文。
關(guān)于作者:王克欣,中科院自動化研究所碩士生,研究方向為自然語言處理。
■?論文 | Learning Structured Natural Language Representations for Semantic Parsing
■ 鏈接 | https://www.paperweekly.site/papers/963
■ 源碼 | https://github.com/cheng6076/scanner
任務(wù)介紹
本文介紹的任務(wù),語義解析(Sematic Parsing),是將自然語言的句子轉(zhuǎn)換為機器可解析的語義表達。具體而言,在這篇文章中,研究的問題為: ?
給定一個知識庫(knowledge base)K,以及標(biāo)注有對應(yīng)的匹配知識庫的語義表達(grounded meaning representation)G 或者問題答案(denotation)y的自然語言句子(問題)x,要學(xué)習(xí)得到一個語義解析器,使之可以完成經(jīng)由一個中間未匹配知識庫的語義表達 U,來將 x 映射到對應(yīng)的 G 的功能。
相關(guān)工作
大多數(shù)現(xiàn)有工作將語義解析的處理方法分成兩類,第一類是通過一個任務(wù)相關(guān)的文法將自然語言句子直接解析(parse)、匹配知識圖譜(grounded to a knowledge base)轉(zhuǎn)為語義表達。第二類方法是首先使用句法分析器將自然語言句子解析為任務(wù)無關(guān)的中間表達(intermediate representation),然后再將中間表達轉(zhuǎn)換為匹配知識圖譜的最終表達。?
第一類方法
根據(jù)前人工作來看,目前使用編碼器-解碼器(encoder-decoder)模型處理語義解析的方法依然應(yīng)當(dāng)歸為第一類方法。這種方法減少了領(lǐng)域相關(guān)假設(shè)、文法學(xué)習(xí)以及大量的特征工程工作的需要。
但是這種模型無法去解釋語義聚合(meaning composition)是如何進行的,這也是該類模型極大靈活性的代價。而語義聚合的可解釋性在處理邊界(modeling limitation)問題上有著很大的作用。
另外,由于缺乏任務(wù)相關(guān)的先驗知識的引入,學(xué)習(xí)任務(wù)關(guān)于可能推倒(derivation)的考慮上以及目標(biāo)輸出的不規(guī)范性(ill-formed,如解析結(jié)果少了或多了若干括號)問題上不能得到很好的限制。?
第二類方法
第二類方法的一個優(yōu)勢在于產(chǎn)生了可重復(fù)利用的中間表達,這種中間表達由于其任務(wù)無關(guān)性可以一定程度上處理未出現(xiàn)的詞(unseen words)以及不同領(lǐng)域的知識遷移(knowledge transfer)。
文章動機與主要貢獻
為了解決目標(biāo)輸出的不規(guī)范性問題以及提供可解釋的語義聚合過程,這篇文章提出了一個神經(jīng)語義解析器(neural semantic parser)。
與其他方法相比,該模型沒有使用外部的解析器(如 dependency parser)以及手工設(shè)計的 CCG 文法,而是采用了基于狀態(tài)轉(zhuǎn)移(transition-based)的方法生成謂詞-參數(shù)(predicate-argument)形式的中間表達,由此避免了生成不規(guī)范形式的語義表達。?
另外與目前語義解析問題中大多數(shù)采用的 CKY 類似的自底向上(bottom-up)解析策略相比,本文提出的方法不需要對自然語言的句子結(jié)構(gòu)的進行人為的特征分解(decomposition),可以利用大量的非局部特征。
基于假設(shè)匹配知識圖譜后的語義表達與未匹配的語義表達同構(gòu)(isomorphic)的假設(shè),本文提出的狀態(tài)轉(zhuǎn)移模型的輸出最終可以匹配到某一個知識圖譜。
模型細節(jié)與代碼
整個網(wǎng)絡(luò)是在標(biāo)注了對應(yīng)的邏輯形式或者問題答案的自然語言句子集上進行端到端訓(xùn)練的。這篇文章附上的開源代碼只給出了適用于標(biāo)注邏輯形式的數(shù)據(jù)集的版本,下面將結(jié)合具體訓(xùn)練樣本的訓(xùn)練過程以及代碼來進行這部分的介紹。
適用標(biāo)注邏輯形式的數(shù)據(jù)集的版本
這里的邏輯形式為 Funql,一個訓(xùn)練樣本及其標(biāo)注為:
how many states have a city called rochester
answer(count(state(loc(city(cityid(rochester,_)))))) (*)
下面結(jié)合對該樣本的訓(xùn)練更新過程進行闡述。
訓(xùn)練過程
輸入:
訓(xùn)練樣本集:這里是用的語料庫為 GEOQUERY,包含 880 個關(guān)于美國地理信息的問題和對應(yīng)的邏輯查詢語句);
通用謂詞表:這些謂詞與領(lǐng)域無關(guān),例如 (?) 中的 answer,count,state,loc,city,cityid,_ 等,詳細的關(guān)于通用謂詞表見附錄-通用謂詞)。
輸出:訓(xùn)練好的語義分析器,能夠?qū)⒆匀徽Z言問題映射到相應(yīng)的邏輯表達(Funql)。
過程:
1. 將問題的單詞加入單詞詞典(這里和下文提到的詞典都是為了之后神經(jīng)網(wǎng)絡(luò)的 softmax 輸出對應(yīng)輸出字符串而建立的);
word_vocab.feed_all(sen)
2. 對邏輯表達進行解析得到語法樹,可以從語法樹中獲得其中包含的非終結(jié)符和終結(jié)符以及能夠更直接指導(dǎo)狀態(tài)轉(zhuǎn)移系統(tǒng)訓(xùn)練的標(biāo)注信息 U(ungrouned meaning representation)。?
其中 U=(a,u):
a 為狀態(tài)轉(zhuǎn)移系統(tǒng)的動作(action)序列,包括 NT,TER 和 ACT(對應(yīng)原文的 RED),分別代表在棧中加入一個非終結(jié)符、在棧中加入一個終結(jié)符以及規(guī)約;
u 為項(包括終結(jié)符和非終結(jié)符)序列,為 NT,TER 動作的參數(shù)。
在對邏輯表達解析以及構(gòu)造語法樹時, 首先將邏輯表達轉(zhuǎn)換為另一種方便處理形式:
對應(yīng)這里:
交互調(diào)用:
def parse_list(tokens) #對表達式深入分解(進到括號內(nèi)),直到不能分解(沒有括號)
與:
def parse_operands(tokens) #每個括號后緊跟的為運算符,也即動作,對這些動作進行記錄
最后得到了語法樹的嵌套 list 表示,這里為:
<type 'list'>: ['answer', ['count', ['state', ['loc', ['city',
['cityid', 'rochester,', '_']]]]]]
這兩個函數(shù)的完整代碼見附錄-代碼-parse_list 和附錄-代碼-parse_operands。
得到所有的終結(jié)符和非終結(jié)符,加入對應(yīng)的詞典:
nt_vocab.feed_all(nt)
ter_vocab.feed_all(ter)
最后參照語法樹的嵌套 list 表示,構(gòu)建語法樹。
def construct_from_list(self, content) #遞歸深度優(yōu)先的自底向上構(gòu)建樹,將嵌套
list表示里的list對應(yīng)子樹根節(jié)點;list中從第二個元素開始算起,若為字符串則將其對應(yīng)為
葉結(jié)點。
完整代碼見附錄-代碼-construct_from_list,這里得到的語法樹為:
再根據(jù)語法樹得到 U。這部分可以自定向下(top-down)采用先序(pre-order)遍歷(本文介紹的),也可以采用自底向上(bottom_up)遍歷。
def top_down(self, identifier, general_predicates) #對語法樹進行先序遍歷,
遍歷到葉節(jié)點時在項序列u里加入對應(yīng)字符串,在動作序列a里加入'NT';遍歷遇到子樹根節(jié)點時
在項序列u里加入對應(yīng)字符串content,如果該字符串為通用謂詞則在動作序列a里加入'NT(content)',
否則加入'NT';遍歷完一個子樹進行回溯時在動作序列a里加入'ACT'。 ?
這里得到:
動作序列a
<type 'list'>: ['NT(answer)', 'NT(count)', 'NT', 'NT', 'NT',
'NT(cityid)', 'TER', 'TER', 'ACT', 'ACT', 'ACT', 'ACT', 'ACT',
'ACT'] ? ? ?
項序列u
<type 'list'>: ['rochester,', '_', 'cityid', 'city', 'loc',
'state', 'count', 'answer'] ? ?
注:這里的_或是可能出現(xiàn)的 all 也為通用謂詞,但在狀態(tài)轉(zhuǎn)移系統(tǒng)里對應(yīng)的動作都為 TER。完整代碼見附錄-代碼-top_down。
3.?上一步得到的訓(xùn)練用的信息為:
word_tokens[fname].append(sen) #自然語言問題句子對應(yīng)的單詞列表 ? ? ? ? ? ? ? ?
tree_tokens[fname].append(tree_token) #上一步得到的u
tran_actions[fname].append(action) #上一步得到的a ? ?
對應(yīng)的:
word_tokens, tree_tokens, tran_actions #鍵為{'train','valid','test'}值為
訓(xùn)練用信息的dict ? ? ?
從這三個 dict 中按照序號各拿出一個同序號位置的元素即構(gòu)成了一個訓(xùn)練用實例 (sen,u,a)。
4. 下面介紹計算圖(computing graph)的構(gòu)建以及狀態(tài)轉(zhuǎn)移系統(tǒng)的轉(zhuǎn)移過程。?
這里訓(xùn)練的網(wǎng)絡(luò)結(jié)構(gòu)為:
對每個訓(xùn)練實例,首先將 sen 中的單詞序列編碼映射到一個雙向 LSTM 的輸出的隱層向量序列 buffer。
tok_embeddings =
[self.word_input_layer(self.word_lookup[self.word_vocab[word]]) for
word in words]
#get buffer representation with blstm
forward_sequence, backward_sequence =
self.blstm.predict(tok_embeddings)
buffer = [dy.concatenate([x, y]) for x, y in
? ? ? ? ?zip(forward_sequence,reversed(backward_sequence))]
#輸出為兩個方向相反的LSTM的輸出的拼接(concatenate) ? ?
接下來自頂向下的生成語義解析樹。?
生的方式為根據(jù)狀態(tài)轉(zhuǎn)移系統(tǒng)的 buffer,stack 以及 action 信息來預(yù)測下一個動作,并和標(biāo)注的動作比較得到相應(yīng)的對數(shù)概率加入到 loss 里。
然后根據(jù) stack 的狀態(tài)得到可行動作(valid action,來限制狀態(tài)轉(zhuǎn)移系統(tǒng)的動作),并根據(jù)當(dāng)前標(biāo)注動作分情況(NT,NT_general,TER,ACT)進行子操作,在每個子操作中根據(jù) buffer 信息來預(yù)測動作的參數(shù),并和標(biāo)注的動作的參數(shù)來比較得到相應(yīng)的對數(shù)概率加入到 loss 里。?
下面分幾部分闡述計算圖構(gòu)建以及狀態(tài)轉(zhuǎn)移過程:
根據(jù)狀態(tài)轉(zhuǎn)移系統(tǒng)的 buffer,stack 以及 action 信息來預(yù)測下一個動作
使用 stack 的當(dāng)前隱層輸出 stack_embedding 以及 buffer 隱層向量序列作為輸入根據(jù)注意力機制來計算的權(quán)重,將 buffer 賦權(quán)累加(這里有不同的賦權(quán)累加方式)得到 buffer 的嵌入表示 buffer_embedding。
再在 stack 中從后往前找到最后入棧的非終結(jié)符(對應(yīng)子樹根節(jié)點),記錄其對應(yīng)的詞向量經(jīng)線性層(linear layer)輸出的嵌入表示 parent_embedding(還未經(jīng)過隱層單元處理,下文稱其為原始表達(raw representation))。
最后將三個嵌入表示拼接得到解析器狀態(tài)的嵌入表示 parserstate:
再將 parser_state 依次經(jīng)由一層隱層的前饋神經(jīng)網(wǎng)絡(luò)、dropout 處理、線性層處理、softmax 處理得到可行動作的對數(shù)概率,并和標(biāo)注的當(dāng)前動作對比,將對數(shù)概率加入到 loss 中。
stack_embedding = stack[-1][0].output() # stack[-1] means the last one in the list 'stack'.
action_summary = action_top.output()
word_weights = self.attention(stack_embedding, buffer) # Get the attention weight list of the encoding words.
buffer_embedding, _ = attention_output(buffer, word_weights, 'soft_average')
# Get the attention-weighted word embedding vectors (a vector).
for i in range(len(stack)):
? ?if stack[len(stack)-1-i][1] == 'p':
? ? ? ?parent_embedding = stack[len(stack)-1-i][2]
? ? ? ?# parent_embedding is just the embeding vector computed from
? ? ? ?# the word vector into a linear layer Wx+b.
? ? ? ?# Just the word vector without processing
? ? ? ?# ?into the hidden state.
? ? ? ?break
parser_state = dy.concatenate([buffer_embedding, stack_embedding, parent_embedding, action_summary])
h = self.mlp_layer(parser_state)
if options.dropout > 0:
? ?h = dy.dropout(h, options.dropout)
if len(valid_actions) > 0:
? ?log_probs = dy.log_softmax(self.act_proj_layer(h), valid_actions)
? ?assert action in valid_actions, "action not in scope"
? ?losses.append(-dy.pick(log_probs, action))
? ?# Append the corresponding log probability of the action
? ?# annotated by the training examples. ? ?
根據(jù) buffer 信息來預(yù)測動作的參數(shù)?
對于動作 NT,TER,根據(jù)上一步預(yù)測動作中得到的注意力機制權(quán)重,對 buffer 進行加權(quán)累加,得到輸出的特征,再經(jīng)由線性層以及 softmax 處理得到對數(shù)概率,與相應(yīng)標(biāo)注的非終結(jié)符或終結(jié)符對比,將得到的對數(shù)概率加入 loss 中。
對于狀態(tài)轉(zhuǎn)移處理,將標(biāo)注的非終結(jié)符或終結(jié)符對應(yīng)的詞向量經(jīng)由線性層得到嵌入表示 nt_embedding(ter_embedding),作為 LSTM 的下一個隱層單元的輸入,輸入進去得到當(dāng)前棧的狀態(tài) stack_state,將三元組(對于動作 NT):
或(對于動作 TER):
入棧,其中 'p' 和 'c' 分別對應(yīng)父節(jié)點和子節(jié)點。
對于動作 NT_general, 因為可以由 action 的內(nèi)容得到具體的領(lǐng)域無關(guān)謂詞的字符串,不作參數(shù)預(yù)測處理。?
# If the aciton is NT, then caculate the log probability of
# the corresponding annotated term choices.
#generate non-terminal
nt = self.nt_vocab[oracle_tokens.pop(0)]
#no need to predict the ROOT (assumed ROOT is fixed)
if word_weights is not None:
? ?train_selection = self.train_selection if np.random.randint(10)
> 5 else "soft_average"
? ?output_feature, output_logprob = attention_output(buffer,
word_weights, train_selection)
? ?log_probs_nt =
dy.log_softmax(self.nt_proj_layer(output_feature))
? ?losses.append(-dy.pick(log_probs_nt, nt))
? ?# Append the corresponding log probability of the NT
? ?# annotated by the training examples.
? ?if train_selection == "hard_sample":
? ? ? ?baseline_feature, _ = attention_output(buffer,
word_weights, "soft_average")
? ? ? ?log_probs_baseline =
dy.log_softmax(self.nt_proj_layer(baseline_feature))
? ? ? ?r = dy.nobackprop(dy.pick(log_probs_nt, nt) -
dy.pick(log_probs_baseline, nt))
? ? ? ?losses.append(-output_logprob * dy.rectify(r))
stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT',
stack_top)
nt_embedding = self.nt_input_layer(self.nt_lookup[nt])
stack_state = stack_state.add_input(nt_embedding)
stack.append((stack_state, 'p', nt_embedding)) # 'p' for parent. ?
規(guī)約過程?
規(guī)約時,先彈棧至剛將語義解析樹的父節(jié)點對應(yīng)的三元組彈出,記錄每個三元組中的原始表達(也即詞向量經(jīng)一層線性層處理得到的向量)。
將彈出的得到的葉節(jié)點對應(yīng)的原始表達求平均,再和父節(jié)點的原始表達拼接得到組合表達 composed_rep,再將組合表達作為 stack 的 LSTM 的下一個輸入,構(gòu)造新的隱層單元 stack_state,最后將以下三元組入棧。
#subtree completion
found_p = 0
path_input = []
#keep popping until the parent is found
while found_p != 1:
? ?top = stack.pop()
? ?top_raw_rep, top_label, top_rep = top[2], top[1], top[0]
? ?# Raw representation, label('c' or 'p') and representation
respectively.
? ?path_input.append(top_raw_rep)
? ?if top_label == 'p':
? ? ? ?found_p = 1
parent_rep = path_input.pop()
composed_rep =
self.subtree_input_layer(dy.concatenate([dy.average(path_input),
parent_rep]))
stack_state, _, _ = stack[-1] if stack else (stack_top, 'ROOT',
stack_top)
stack_state = stack_state.add_input(composed_rep)
stack.append((stack_state, 'c', composed_rep))
reduced = 1 ?
這部分的完整代碼見附錄-代碼-top_down_train。
5.?最后將 loss 反向傳播可更新所有的參數(shù)。
這里先給出用于訓(xùn)練預(yù)測動作序列的目標(biāo)函數(shù),其中 T 表示訓(xùn)練樣本集。訓(xùn)練邏輯表達預(yù)測的目標(biāo)函數(shù)在下文給出。
測試過程
測試過程與訓(xùn)練過程相比,每次狀態(tài)轉(zhuǎn)移用到的動作以及動作的參數(shù)都是經(jīng)過類似過程取對數(shù)概率最大對應(yīng)的來預(yù)測得到的,而非采用標(biāo)注信息。
根據(jù)問題答案標(biāo)注獲得標(biāo)注邏輯表示
給出了問題答案的標(biāo)注,本文通過實體鏈接(entity linking),將自然語言問題中的實體與知識庫的實體進行匹配,可以得到一些候選子圖,這些子圖可以生成候選的邏輯表示,最終選擇可以搜索的到正確答案的邏輯表示作為標(biāo)注邏輯表示指導(dǎo)訓(xùn)練。?
對于沒有經(jīng)過實體鏈接的訓(xùn)練集,本文使用 7 個手工模版,配合知識庫,將訓(xùn)練集的實體標(biāo)注出來,然后用這些實體去訓(xùn)練結(jié)構(gòu)化感知機(structrued perceptron)來進行實體消歧。?
中間邏輯表示與最終邏輯表示
對于有邏輯表示標(biāo)注的訓(xùn)練樣本,最終邏輯表示(也即匹配了知識庫的邏輯表示)和中間邏輯表示是一樣的;而適用于僅僅標(biāo)注了答案的訓(xùn)練樣本的版本的代碼沒有完全給出(一些重要的部分沒有再代碼里實現(xiàn))。這里結(jié)合作者引用的另外一文章來討論可能的具體實現(xiàn)方式。
Tao Lei, Regina Barzilay, and Tommi Jaakkola. 2016. Rationalizing neural predictions. In Proceedings of the 2016 Conference on Empirical Methods in Natural Language Processing. Austin, Texas, pages 107–117.
中間邏輯表示(ungrounded representation)和最終邏輯表示(grounded representation)之間的映射關(guān)系:作者給出了一個假設(shè),假設(shè)兩種同樣語義的邏輯表示是同構(gòu)的,也即兩種表示的語法樹相同,只是謂詞或者終結(jié)符的字符串可能不同。所以在這種假設(shè)下,兩者之間的轉(zhuǎn)換僅僅變成了一個詞匯映射的問題。?
映射關(guān)系建模:使用雙線性神經(jīng)網(wǎng)絡(luò)來刻畫中間邏輯表示項 ut(ungrounded term)到最終邏輯表示項 gt(grounded term)的后驗概率:
其中,為上文提到的以自然語言問題輸入為輸入的雙向 LSTM 的關(guān)于某一個中間邏輯表示項的輸出;為最終邏輯表示項的嵌入表示,Wug 為權(quán)值矩陣。
特定領(lǐng)域(closed domain)vs. 開放領(lǐng)域(open domain):對于特定領(lǐng)域,作者認為中間層的邏輯表示已經(jīng)可為語義解析提供足夠多的上下文信息,可以直接選擇對應(yīng)上述后驗概率最大的最終表達作為輸出(在特定領(lǐng)域內(nèi),一個中間表示項一般只匹配一個最終表示項);而對于開放領(lǐng)域,如Freebase,這種表示得建模顯得有些乏力。?
對于開放領(lǐng)域語義解析,本文設(shè)計訓(xùn)練了一個額外的排序模型,對可能的最終表示進行排序取序最靠前的解。這個排序模型使用最大熵分類器,以手工設(shè)計的關(guān)于兩種邏輯表示之間的關(guān)系的信息表示為特征,優(yōu)化以使得預(yù)測的最終邏輯表示在知識庫上搜索的到的答案準確率最高。?
目標(biāo)函數(shù):由于邏輯中間表示項是隱變量(latent variable),作者采用最大化邏輯最終表示項的期望對數(shù)似然 p(u|x)logp(g|u,x) 的方式來構(gòu)建優(yōu)化目標(biāo):
結(jié)合上文提到的對動作預(yù)測的優(yōu)化目標(biāo) La,可以得到最終的優(yōu)化目標(biāo):
目標(biāo)函數(shù)優(yōu)化:作者采用了另一篇文獻優(yōu)化有隱變量的模型的算法。在這篇引用文章中,處理的問題是文本分類,和本文的模型結(jié)構(gòu)類似,有兩層結(jié)構(gòu)(涉及到一個生成器和一個編碼器,生成器生成中間變量),中間連接的變量是隱變量。
這篇引用文章的求解任務(wù)是想要對文本提取一些原文的片段(對應(yīng) gen(?),generate)作為梗概信息(rationales),使得這些梗概信息盡量短的前提下盡可能與原始文本作為輸入進行文本分類(對應(yīng) enc(?),encode)的效果一致。這就涉及到了生成梗概信息的問題,而引用文章的作者又試圖想把生成器和編碼器一起訓(xùn)練(訓(xùn)練enc(gen(x)))。
引用文章的最終的目標(biāo)函數(shù)為:
其中,z,x,y 分別表示梗概信息,原始輸入以及標(biāo)注結(jié)果;L 懲罰編碼器和標(biāo)注結(jié)果的距離,Ω 懲罰中間生成的梗概的長度。由于中間項在訓(xùn)練過程中不能被提供,所以作者試圖最小化期望損失(相當(dāng)于把 z 消掉):
其中 θe,θg 分別表示編碼器和生成器的參數(shù)。作者假設(shè)用生成器采樣中間項對于問題是可行的。借助恒等式:
可以求得上述期望損失對兩個參數(shù)的導(dǎo)數(shù):
以及:
這樣對 z 使用生成器進行采樣,固定 z 就可以求得上述兩式里邊的微分,進而可以求得對期望損失的微分,最后使用梯度優(yōu)化的算法來更新參數(shù)。
從兩個微分等式可以看出,生成 z,可以利用編碼器的預(yù)測結(jié)果指導(dǎo)更新生成器的參數(shù);生成器被優(yōu)化后,中間項的分布發(fā)生了改變,又會指導(dǎo)編碼器的優(yōu)化過程,類似于 EM 算法交互更新來學(xué)習(xí)隱變量的過程。
類比引用文章的求解方式,可以假定使用之前提到的雙向 LSTM 來對邏輯中間表示項進行采樣可以滿足問題需求,則有:
其中,BiLSTM(x) 為邏輯中間表示的生成器,θu 為生成器的參數(shù)。
實驗
數(shù)據(jù)集
GEOQUERY:關(guān)于美國地理的 880 個問題以及知識庫,并標(biāo)注有邏輯表示。這些問題句子雖然組合性(compositional)強,但語言過于簡單,詞匯量過于小,大多數(shù)問題涉及實體不超過一個。
SPADES:包含 93,319 個從數(shù)據(jù)集 CLUEWEB09 通過隨機去除實體獲得的問題集合,僅僅標(biāo)注有問題答案。雖然語料的組合性不強,但規(guī)模很大,并且每個句子包含 2 個或多個實體。
WEBQUESTIONS:包含 5810 個問題-答案對。同樣組合性不強,但問題均來源于真實的網(wǎng)絡(luò)數(shù)據(jù)。
GRAPHQUESTIONS:包含 5166 個問題-答案對,來源于將 500 個 Freebase 的圖查詢由亞馬遜員工轉(zhuǎn)換成的自然語言問題。
評價指標(biāo)
本文使用準確率以及 F1 值作為評價指標(biāo),這里通過計算預(yù)測得到的語義表達與真實標(biāo)注的一致比率來得到。
實驗結(jié)果
Table 5:下邊隔開的一塊內(nèi)的模型均為神經(jīng)模型。Dong and Lapata (2016) 將語義解析問題視為序列預(yù)測問題,使用 LSTM 直接將自然語言句子映射到邏輯形式。?
Table 6:這里之前的工作與本系統(tǒng)處理的總體思路類似,均是將自然語言句子映射到中間邏輯表示然后再匹配知識庫。?
Table 3:隔開的第一塊內(nèi)的模型均為符號系統(tǒng)(symbolic systems)模型。其中Bast and Haussmann (2015) 提出了一個相反的問答模型,不能產(chǎn)生語義表達。Berant and Liang (2015) 借鑒模仿學(xué)習(xí)(imitation learning)的思想,提出了一個復(fù)雜的基于智能體(agenda-based)的解析器。Reddy et al. (2016) 與本文概念上相似,也是通過語義中間表示來訓(xùn)練一個語義解析器,其中這個中間表示基于依存關(guān)系解析器的輸出構(gòu)建。?
隔開的第二塊內(nèi)的模型為一些神經(jīng)系統(tǒng)。Xu et al. (2016) 在這個數(shù)據(jù)集上到了目前最好的性能,他們的系統(tǒng)利用維基百科來過濾掉從 Freebase 抽取的錯誤候選答案。?
Table 4:比較的是三個符號系統(tǒng)和一個神經(jīng)系統(tǒng)基準。
中間邏輯表示分析
這里進行了一些額外的實驗來檢驗邏輯中間表示的生成質(zhì)量。
Table 7:作者手工標(biāo)注了一些邏輯中間表示,以此來觀察人的介入對邏輯中間表示的生成效果的影響。三行結(jié)果分別對應(yīng)了不同的匹配標(biāo)準。?
Table 8:為了在其他 3 個數(shù)據(jù)集上也能夠進行類似的實驗,作者將評價方式換為對比生成的邏輯中間表示與句法分析器分析結(jié)果。作者用 EasyCCG 將自然語言問題句子轉(zhuǎn)換為事件-參數(shù)(event-argument)的結(jié)構(gòu)。分析看出對于組合性強的數(shù)據(jù)集 GRAPHQUESTIONS,本文的系統(tǒng)性能差一些。?
Table 9:展示了一些謂詞預(yù)測結(jié)果的事例,并與 EasyCCG 的結(jié)果相對比。對于例子:
ceo johnthain agreed to leave_.?
本文的系統(tǒng)會難以區(qū)分控制(control)動詞和真正的謂詞。
附錄
通用謂詞
適用于 GEOQUERY 的通用謂詞表如下:(還需要再進行擴展,才能夠使用于通用領(lǐng)域)
coutryid
cityid
stateid
riverid
placeid
answer
largest
smallest
highest
lowest
longest
shortest
count
fewest
intersect
union
exclude
all
_ ?
代碼
parse_list
def parse_list(tokens):
? ?# expect '(' always as first token for this function
? ?if len(tokens) == 0 or tokens[0] != '(':
? ? ? ?raise SyntaxError("(parse_list) Error: expected '(' token, found <%s>" % str(tokens))
? ?first = tokens.pop(0) # consume the opening '('
? ?# consume the operator and all operands
? ?operator = tokens.pop(0) # operator always after opening ( syntatically
? ?operands, tokens = parse_operands(tokens)
? ?ast = [operator]
? ?ast.extend(operands)
? ?# consume the matching ')'
? ?if len(tokens) == 0 or tokens[0] != ')':
? ? ? ?raise SyntaxError("(parse_list) Error: expected ')' token, found <%s>: " % str(tokens))
? ?first = tokens.pop(0)
? ?return ast, tokens
parseo_perands
def parse_operands(tokens):
? ?operands = []
? ?while len(tokens) > 0:
? ? ? ?# peek at next token, and if not an operand then stop
? ? ? ?if tokens[0] == ')':
? ? ? ? ? ?break
? ? ? ?# if next token is a '(', need to get sublist/subexpression
? ? ? ?if tokens[0] == '(':
? ? ? ? ? ?subast, tokens = parse_list(tokens)
? ? ? ? ? ?operands.append(subast)
? ? ? ? ? ?continue # need to continue trying to see if more operands after the sublist
? ? ? ?# otherwise token must be some sort of an operand
? ? ? ?operand = tokens.pop(0) # consume the token and parse it
? ? ? ?operands.append(decode_operand(operand))
? ?return operands, tokens
construct_from_list
def construct_from_list(self, content): ? ?
? ?'''build the tree from hierarchical list'''
? ?if sexp.is_string(content):
? ? ? ?identifier = self.node_count
? ? ? ?new_node = ?Node(identifier, content)
? ? ? ?self[identifier] = new_node
? ? ? ?self.node_count += 1
? ? ? ?return identifier
? ?else:
? ? ? ?identifier = self.node_count
? ? ? ?new_node = ?Node(identifier, content[0])
? ? ? ?self[identifier] = new_node
? ? ? ?self.node_count += 1
? ? ? ?for child in content[1:]:
? ? ? ? ? ?child_identifier = self.construct_from_list(child)
? ? ? ? ? ?self[identifier].add_child(child_identifier)
? ? ? ? ? ?return identifier
top_down
def top_down(self, identifier, general_predicates):
? ? ? ?'''
? ? ? ?pre-order traversal, return a list of node content and a list of transition actions
? ? ? ?ACT: reduce top elements on the stack into a subtree
? ? ? ?NT: creates a subtree root node which is to be expanded, and push it to the stack
? ? ? ?TER: generates terminal node under the current subtree
? ? ? ?'''
? ? ? ?data = []
? ? ? ?action = []
? ? ? ?def recurse(identifier):
? ? ? ? ? ?node = self[identifier]
? ? ? ? ? ?if len(node.children) == 0:
? ? ? ? ? ? ? ?data.append(node.content)
? ? ? ? ? ? ? ?action.append('TER')
? ? ? ? ? ?else:
? ? ? ? ? ? ? ?data.append(node.content)
? ? ? ? ? ? ? ?if node.content in general_predicates:
? ? ? ? ? ? ? ? ? ?action.append("{}({})".format('NT', node.content))
? ? ? ? ? ? ? ?else:
? ? ? ? ? ? ? ? ? ?action.append('NT')
? ? ? ? ? ? ? ?for child in node.children:
? ? ? ? ? ? ? ? ? ?recurse(child)
? ? ? ? ? ? ? ?action.append('ACT')
? ? ? ?recurse(identifier)
? ? ? ?return data, action
top_down_train
def top_down_train(self, words, oracle_actions, oracle_tokens, options, buffer, stack_top, action_top):
? ?stack = []
? ?losses = []
? ?reducable = 0
? ?reduced = 0
? ?#recursively generate the tree until training data is exhausted
? ?while not (len(stack) == 1 and reduced != 0):
? ? ? ?valid_actions = []
? ? ? ?if len(stack) == 0:
? ? ? ? ? ?valid_actions += [self._ROOT]
? ? ? ?if len(stack) >= 1:
? ? ? ? ? ?valid_actions += [self._TER, self._NT] + self._NT_general
? ? ? ?if len(stack) >= 2 and reducable != 0:
? ? ? ? ? ?valid_actions += [self._ACT]
? ? ? ?action = self.act_vocab[oracle_actions.pop(0)] # Get the action identifier.
? ? ? ?word_weights = None
? ? ? ?#we make predictions when stack is not empty and _ACT is not the only valid action
? ? ? ?if len(stack) > 0 and valid_actions[0] != self._ACT:
? ? ? ? ? ?stack_embedding = stack[-1][0].output() # stack[-1] means the last one in the list 'stack'.
? ? ? ? ? ?action_summary = action_top.output()
? ? ? ? ? ?word_weights = self.attention(stack_embedding, buffer) # Get the attention weight list of the encoding words.
? ? ? ? ? ?buffer_embedding, _ = attention_output(buffer, word_weights, 'soft_average')
? ? ? ? ? ?# Get the attention-weighted word embedding vectors (a vector).
? ? ? ? ? ?for i in range(len(stack)):
? ? ? ? ? ? ? ?if stack[len(stack)-1-i][1] == 'p':
? ? ? ? ? ? ? ? ? ?parent_embedding = stack[len(stack)-1-i][2]
? ? ? ? ? ? ? ? ? ?# parent_embedding is just the embeding vector computed from
? ? ? ? ? ? ? ? ? ?# the word vector into a linear layer Wx+b.
? ? ? ? ? ? ? ? ? ?# Just the word vector without processing
? ? ? ? ? ? ? ? ? ?# ?into the hidden state.
? ? ? ? ? ? ? ? ? ?break
? ? ? ? ? ?parser_state = dy.concatenate([buffer_embedding, stack_embedding, parent_embedding, action_summary])
? ? ? ? ? ?h = self.mlp_layer(parser_state)
? ? ? ? ? ?if options.dropout > 0:
? ? ? ? ? ? ? ?h = dy.dropout(h, options.dropout)
? ? ? ? ? ?if len(valid_actions) > 0:
? ? ? ? ? ? ? ?log_probs = dy.log_softmax(self.act_proj_layer(h), valid_actions)
? ? ? ? ? ? ? ?assert action in valid_actions, "action not in scope"
? ? ? ? ? ? ? ?losses.append(-dy.pick(log_probs, action))
? ? ? ? ? ? ? ?# Append the corresponding log probability of the action
? ? ? ? ? ? ? ?# annotated by the training examples.
? ? ? ?# Carrying on dealing with the action from the training example.
? ? ? ?if action == self._NT:
? ? ? ? ? ?# If the aciton is NT, then caculate the log probability of
? ? ? ? ? ?# the corresponding annotated term choices.
? ? ? ? ? ?#generate non-terminal
? ? ? ? ? ?nt = self.nt_vocab[oracle_tokens.pop(0)]
? ? ? ? ? ?#no need to predict the ROOT (assumed ROOT is fixed)
? ? ? ? ? ?if word_weights is not None: ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?train_selection = self.train_selection if np.random.randint(10) > 5 else "soft_average"
? ? ? ? ? ? ? ?output_feature, output_logprob = attention_output(buffer, word_weights, train_selection)
? ? ? ? ? ? ? ?log_probs_nt = dy.log_softmax(self.nt_proj_layer(output_feature))
? ? ? ? ? ? ? ?losses.append(-dy.pick(log_probs_nt, nt))
? ? ? ? ? ? ? ?# Append the corresponding log probability of the NT
? ? ? ? ? ? ? ?# annotated by the training examples.
? ? ? ? ? ? ? ?if train_selection == "hard_sample":
? ? ? ? ? ? ? ? ? ?baseline_feature, _ = attention_output(buffer, word_weights, "soft_average")
? ? ? ? ? ? ? ? ? ?log_probs_baseline = dy.log_softmax(self.nt_proj_layer(baseline_feature))
? ? ? ? ? ? ? ? ? ?r = dy.nobackprop(dy.pick(log_probs_nt, nt) - dy.pick(log_probs_baseline, nt))
? ? ? ? ? ? ? ? ? ?losses.append(-output_logprob * dy.rectify(r))
? ? ? ? ? ?stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
? ? ? ? ? ?nt_embedding = self.nt_input_layer(self.nt_lookup[nt])
? ? ? ? ? ?stack_state = stack_state.add_input(nt_embedding)
? ? ? ? ? ?stack.append((stack_state, 'p', nt_embedding)) # 'p' for parent.
? ? ? ?elif action in self._NT_general:
? ? ? ? ? ?nt = self.nt_vocab[oracle_tokens.pop(0)]
? ? ? ? ? ?stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
? ? ? ? ? ?nt_embedding = self.nt_input_layer(self.nt_lookup[nt])
? ? ? ? ? ?stack_state = stack_state.add_input(nt_embedding)
? ? ? ? ? ?stack.append((stack_state, 'p', nt_embedding))
? ? ? ?elif action == self._TER:
? ? ? ? ? ?#generate terminal
? ? ? ? ? ?ter = self.ter_vocab[oracle_tokens.pop(0)]
? ? ? ? ? ?output_feature, output_logprob = attention_output(buffer, word_weights, self.train_selection)
? ? ? ? ? ?log_probs_ter = dy.log_softmax(self.ter_proj_layer(output_feature))
? ? ? ? ? ?losses.append(-dy.pick(log_probs_ter, ter))
? ? ? ? ? ?if self.train_selection == "hard_sample":
? ? ? ? ? ? ? ?baseline_feature, _ = attention_output(buffer, word_weights, "soft_average")
? ? ? ? ? ? ? ?#baseline_feature, _ = attention_output(buffer, word_weights, self.train_selection, argmax=True)
? ? ? ? ? ? ? ?log_probs_baseline = dy.log_softmax(self.ter_proj_layer(baseline_feature))
? ? ? ? ? ? ? ?r = dy.nobackprop(dy.pick(log_probs_ter, ter) - dy.pick(log_probs_baseline, ter))
? ? ? ? ? ? ? ?losses.append(-output_logprob * dy.rectify(r))
? ? ? ? ? ?stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
? ? ? ? ? ?ter_embedding = self.ter_input_layer(self.ter_lookup[ter])
? ? ? ? ? ?stack_state = stack_state.add_input(ter_embedding)
? ? ? ? ? ?stack.append((stack_state, 'c', ter_embedding)) # 'c' for child.
? ? ? ?else:
? ? ? ? ? ?# Into this branch means having met a reduce(ACT) action.
? ? ? ? ? ?#subtree completion
? ? ? ? ? ?found_p = 0
? ? ? ? ? ?path_input = []
? ? ? ? ? ?#keep popping until the parent is found
? ? ? ? ? ?while found_p != 1:
? ? ? ? ? ? ? ?top = stack.pop()
? ? ? ? ? ? ? ?top_raw_rep, top_label, top_rep = top[2], top[1], top[0]
? ? ? ? ? ? ? ?# Raw representation, label('c' or 'p') and representation respectively.
? ? ? ? ? ? ? ?path_input.append(top_raw_rep)
? ? ? ? ? ? ? ?if top_label == 'p':
? ? ? ? ? ? ? ? ? ?found_p = 1
? ? ? ? ? ?parent_rep = path_input.pop()
? ? ? ? ? ?composed_rep = self.subtree_input_layer(dy.concatenate([dy.average(path_input), parent_rep]))
? ? ? ? ? ?stack_state, _, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
? ? ? ? ? ?stack_state = stack_state.add_input(composed_rep)
? ? ? ? ? ?stack.append((stack_state, 'c', composed_rep)) ? ?
? ? ? ? ? ?reduced = 1
? ? ? ?action_embedding = self.act_input_layer(self.act_lookup[action])
? ? ? ?action_top = action_top.add_input(action_embedding)
? ? ? ?reducable = 1
? ? ? ?#cannot reduce after an NT
? ? ? ?if stack[-1][1] == 'p':
? ? ? ? ? ?reducable = 0
? ?return dy.esum(losses)
點擊標(biāo)題查看更多論文解讀:?
??Tree-CNN:一招解決深度學(xué)習(xí)中的「災(zāi)難性遺忘」
??當(dāng)前深度神經(jīng)網(wǎng)絡(luò)模型壓縮和加速都有哪些方法?
??新型RNN:將層內(nèi)神經(jīng)元相互獨立以提高長程記憶
??視覺跟蹤之端到端的光流相關(guān)濾波
CycleGAN:圖片風(fēng)格,想換就換
基于GAN的字體風(fēng)格遷移
基于置信度的知識圖譜表示學(xué)習(xí)框架
▲?戳我查看招聘詳情
#崗 位 推 薦#
企保科技招聘對話機器人方向自然語言處理工程師
? ? ? ? ? ?
關(guān)于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學(xué)術(shù)平臺。如果你研究或從事 AI 領(lǐng)域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
▽ 點擊 |?閱讀原文?| 查看原論文
總結(jié)
以上是生活随笔為你收集整理的超详细解读:神经语义解析的结构化表示学习 | 附代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何实现少样本学习?先让神经网络get√
- 下一篇: 自适应注意力机制在Image Capti