超詳細解讀:神經語義解析的結構化表示學習 | 附代碼分析
在碎片化閱讀充斥眼球的時代,越來越少的人會去關注每篇論文背後的探索和思考。 在這個欄目里,你會快速 get 每篇精選論文的亮點和痛點,時刻緊跟 AI 前沿成果。 點擊即刻加入社區,查看更多最新論文推薦。
本期推薦的論文筆記來自 PaperWeekly 社區用戶 @marcw。為了解決目標輸出的不規範性問題以及提供可解釋的語義聚合過程,本文提出了一個神經語義解析器(neural semantic parser)。
關於作者:王克欣,中科院自動化研究所碩士生,研究方向為自然語言處理。
論文 | Learning Structured Natural Language Representations for Semantic Parsing
鏈接 | https://www.paperweekly.site/papers/963
源碼 | https://github.com/cheng6076/scanner
任務介紹
本文介紹的任務,語義解析(Sematic Parsing),是將自然語言的句子轉換為機器可解析的語義表達。具體而言,在這篇文章中,研究的問題為:
給定一個知識庫(knowledge base)K,以及標註有對應的匹配知識庫的語義表達(grounded meaning representation)G 或者問題答案(denotation)y的自然語言句子(問題)x,要學習得到一個語義解析器,使之可以完成經由一個中間未匹配知識庫的語義表達 U,來將 x 映射到對應的 G 的功能。
相關工作
大多數現有工作將語義解析的處理方法分成兩類,第一類是通過一個任務相關的文法將自然語言句子直接解析(parse)、匹配知識圖譜(grounded to a knowledge base)轉為語義表達。第二類方法是首先使用句法分析器將自然語言句子解析為任務無關的中間表達(intermediate representation),然後再將中間表達轉換為匹配知識圖譜的最終表達。
第一類方法
根據前人工作來看,目前使用編碼器-解碼器(encoder-decoder)模型處理語義解析的方法依然應當歸為第一類方法。這種方法減少了領域相關假設、文法學習以及大量的特徵工程工作的需要。
但是這種模型無法去解釋語義聚合(meaning composition)是如何進行的,這也是該類模型極大靈活性的代價。而語義聚合的可解釋性在處理邊界(modeling limitation)問題上有著很大的作用。
另外,由於缺乏任務相關的先驗知識的引入,學習任務關於可能推倒(derivation)的考慮上以及目標輸出的不規範性(ill-formed,如解析結果少了或多了若干括弧)問題上不能得到很好的限制。
第二類方法
第二類方法的一個優勢在於產生了可重複利用的中間表達,這種中間表達由於其任務無關性可以一定程度上處理未出現的詞(unseen words)以及不同領域的知識遷移(knowledge transfer)。
文章動機與主要貢獻
為了解決目標輸出的不規範性問題以及提供可解釋的語義聚合過程,這篇文章提出了一個神經語義解析器(neural semantic parser)。
與其他方法相比,該模型沒有使用外部的解析器(如 dependency parser)以及手工設計的 CCG 文法,而是採用了基於狀態轉移(transition-based)的方法生成謂詞-參數(predicate-argument)形式的中間表達,由此避免了生成不規範形式的語義表達。
另外與目前語義解析問題中大多數採用的 CKY 類似的自底向上(bottom-up)解析策略相比,本文提出的方法不需要對自然語言的句子結構的進行人為的特徵分解(decomposition),可以利用大量的非局部特徵。
基於假設匹配知識圖譜後的語義表達與未匹配的語義表達同構(isomorphic)的假設,本文提出的狀態轉移模型的輸出最終可以匹配到某一個知識圖譜。
模型細節與代碼
整個網路是在標註了對應的邏輯形式或者問題答案的自然語言句子集上進行端到端訓練的。這篇文章附上的開源代碼只給出了適用於標註邏輯形式的數據集的版本,下面將結合具體訓練樣本的訓練過程以及代碼來進行這部分的介紹。
適用標註邏輯形式的數據集的版本
這裡的邏輯形式為 Funql,一個訓練樣本及其標註為:
how many states have a city called rochester
answer(count(state(loc(city(cityid(rochester,_)))))) (*)
下面結合對該樣本的訓練更新過程進行闡述。
訓練過程
輸入:
訓練樣本集:這裡是用的語料庫為 GEOQUERY,包含 880 個關於美國地理信息的問題和對應的邏輯查詢語句);
通用謂詞表:這些謂詞與領域無關,例如 (?) 中的 answer,count,state,loc,city,cityid,_ 等,詳細的關於通用謂詞表見附錄-通用謂詞)。
輸出:訓練好的語義分析器,能夠將自然語言問題映射到相應的邏輯表達(Funql)。
過程:
1. 將問題的單詞加入單詞詞典(這裡和下文提到的詞典都是為了之後神經網路的 softmax 輸出對應輸出字元串而建立的);
word_vocab.feed_all(sen)
2. 對邏輯表達進行解析得到語法樹,可以從語法樹中獲得其中包含的非終結符和終結符以及能夠更直接指導狀態轉移系統訓練的標註信息 U(ungrouned meaning representation)。
其中 U=(a,u):
- a 為狀態轉移系統的動作(action)序列,包括 NT,TER 和 ACT(對應原文的 RED),分別代表在棧中加入一個非終結符、在棧中加入一個終結符以及規約;
- u 為項(包括終結符和非終結符)序列,為 NT,TER 動作的參數。
在對邏輯表達解析以及構造語法樹時, 首先將邏輯表達轉換為另一種方便處理形式:
對應這裡:
交互調用:
def parse_list(tokens) #對表達式深入分解(進到括弧內),直到不能分解(沒有括弧)
與:
def parse_operands(tokens) #每個括弧後緊跟的為運算符,也即動作,對這些動作進行記錄
最後得到了語法樹的嵌套 list 表示,這裡為:
<type list>: [answer, [count, [state, [loc, [city,[cityid, rochester,, _]]]]]]
這兩個函數的完整代碼見附錄-代碼-parse_list 和附錄-代碼-parse_operands。
得到所有的終結符和非終結符,加入對應的詞典:
nt_vocab.feed_all(nt)ter_vocab.feed_all(ter)
最後參照語法樹的嵌套 list 表示,構建語法樹。
def construct_from_list(self, content) #遞歸深度優先的自底向上構建樹,將嵌套list表示里的list對應子樹根節點;list中從第二個元素開始算起,若為字元串則將其對應為葉結點。
完整代碼見附錄-代碼-construct_from_list,這裡得到的語法樹為:
再根據語法樹得到 U。這部分可以自定向下(top-down)採用先序(pre-order)遍歷(本文介紹的),也可以採用自底向上(bottom_up)遍歷。
def top_down(self, identifier, general_predicates) #對語法樹進行先序遍歷,遍歷到葉節點時在項序列u里加入對應字元串,在動作序列a里加入NT;遍歷遇到子樹根節點時在項序列u里加入對應字元串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]
註:這裡的_或是可能出現的 all 也為通用謂詞,但在狀態轉移系統里對應的動作都為 TER。完整代碼見附錄-代碼-top_down。
3. 上一步得到的訓練用的信息為:
word_tokens[fname].append(sen) #自然語言問題句子對應的單詞列表 tree_tokens[fname].append(tree_token) #上一步得到的utran_actions[fname].append(action) #上一步得到的a
對應的:
word_tokens, tree_tokens, tran_actions #鍵為{train,valid,test}值為訓練用信息的dict
從這三個 dict 中按照序號各拿出一個同序號位置的元素即構成了一個訓練用實例 (sen,u,a)。
4. 下面介紹計算圖(computing graph)的構建以及狀態轉移系統的轉移過程。
這裡訓練的網路結構為:
對每個訓練實例,首先將 sen 中的單詞序列編碼映射到一個雙向 LSTM 的輸出的隱層向量序列 buffer。
tok_embeddings = [self.word_input_layer(self.word_lookup[self.word_vocab[word]]) forword 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)
接下來自頂向下的生成語義解析樹。
生的方式為根據狀態轉移系統的 buffer,stack 以及 action 信息來預測下一個動作,並和標註的動作比較得到相應的對數概率加入到 loss 里。
然後根據 stack 的狀態得到可行動作(valid action,來限制狀態轉移系統的動作),並根據當前標註動作分情況(NT,NT_general,TER,ACT)進行子操作,在每個子操作中根據 buffer 信息來預測動作的參數,並和標註的動作的參數來比較得到相應的對數概率加入到 loss 里。
下面分幾部分闡述計算圖構建以及狀態轉移過程:
- 根據狀態轉移系統的 buffer,stack 以及 action 信息來預測下一個動作
使用 stack 的當前隱層輸出 stack_embedding 以及 buffer 隱層向量序列作為輸入根據注意力機制來計算的權重,將 buffer 賦權累加(這裡有不同的賦權累加方式)得到 buffer 的嵌入表示 buffer_embedding。
再在 stack 中從後往前找到最後入棧的非終結符(對應子樹根節點),記錄其對應的詞向量經線性層(linear layer)輸出的嵌入表示 parent_embedding(還未經過隱層單元處理,下文稱其為原始表達(raw representation))。
最後將三個嵌入表示拼接得到解析器狀態的嵌入表示 parserstate:
再將 parser_state 依次經由一層隱層的前饋神經網路、dropout 處理、線性層處理、softmax 處理得到可行動作的對數概率,並和標註的當前動作對比,將對數概率加入到 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. breakparser_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.
- 根據 buffer 信息來預測動作的參數
對於動作 NT,TER,根據上一步預測動作中得到的注意力機制權重,對 buffer 進行加權累加,得到輸出的特徵,再經由線性層以及 softmax 處理得到對數概率,與相應標註的非終結符或終結符對比,將得到的對數概率加入 loss 中。
對於狀態轉移處理,將標註的非終結符或終結符對應的詞向量經由線性層得到嵌入表示 nt_embedding(ter_embedding),作為 LSTM 的下一個隱層單元的輸入,輸入進去得到當前棧的狀態 stack_state,將三元組(對於動作 NT):
或(對於動作 TER):
入棧,其中 p 和 c 分別對應父節點和子節點。
對於動作 NT_general, 因為可以由 action 的內容得到具體的領域無關謂詞的字元串,不作參數預測處理。# If the aciton is NT, then caculate the log probability of# the corresponding annotated term choices.#generate non-terminalnt = 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.
- 規約過程
規約時,先彈棧至剛將語義解析樹的父節點對應的三元組彈出,記錄每個三元組中的原始表達(也即詞向量經一層線性層處理得到的向量)。
將彈出的得到的葉節點對應的原始表達求平均,再和父節點的原始表達拼接得到組合表達 composed_rep,再將組合表達作為 stack 的 LSTM 的下一個輸入,構造新的隱層單元 stack_state,最後將以下三元組入棧。
#subtree completionfound_p = 0path_input = []#keep popping until the parent is foundwhile 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 representationrespectively. path_input.append(top_raw_rep) if top_label == p: found_p = 1parent_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 反向傳播可更新所有的參數。
這裡先給出用於訓練預測動作序列的目標函數,其中 T 表示訓練樣本集。訓練邏輯表達預測的目標函數在下文給出。
測試過程
測試過程與訓練過程相比,每次狀態轉移用到的動作以及動作的參數都是經過類似過程取對數概率最大對應的來預測得到的,而非採用標註信息。
根據問題答案標註獲得標註邏輯表示
給出了問題答案的標註,本文通過實體鏈接(entity linking),將自然語言問題中的實體與知識庫的實體進行匹配,可以得到一些候選子圖,這些子圖可以生成候選的邏輯表示,最終選擇可以搜索的到正確答案的邏輯表示作為標註邏輯表示指導訓練。
對於沒有經過實體鏈接的訓練集,本文使用 7 個手工模版,配合知識庫,將訓練集的實體標註出來,然後用這些實體去訓練結構化感知機(structrued perceptron)來進行實體消歧。
中間邏輯表示與最終邏輯表示
對於有邏輯表示標註的訓練樣本,最終邏輯表示(也即匹配了知識庫的邏輯表示)和中間邏輯表示是一樣的;而適用於僅僅標註了答案的訓練樣本的版本的代碼沒有完全給出(一些重要的部分沒有再代碼里實現)。這裡結合作者引用的另外一文章來討論可能的具體實現方式。
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)之間的映射關係:作者給出了一個假設,假設兩種同樣語義的邏輯表示是同構的,也即兩種表示的語法樹相同,只是謂詞或者終結符的字元串可能不同。所以在這種假設下,兩者之間的轉換僅僅變成了一個辭彙映射的問題。
映射關係建模:使用雙線性神經網路來刻畫中間邏輯表示項 ut(ungrounded term)到最終邏輯表示項 gt(grounded term)的後驗概率:
其中, 為上文提到的以自然語言問題輸入為輸入的雙向 LSTM 的關於某一個中間邏輯表示項的輸出; 為最終邏輯表示項的嵌入表示,Wug 為權值矩陣。
特定領域(closed domain)vs. 開放領域(open domain):對於特定領域,作者認為中間層的邏輯表示已經可為語義解析提供足夠多的上下文信息,可以直接選擇對應上述後驗概率最大的最終表達作為輸出(在特定領域內,一個中間表示項一般只匹配一個最終表示項);而對於開放領域,如Freebase,這種表示得建模顯得有些乏力。
對於開放領域語義解析,本文設計訓練了一個額外的排序模型,對可能的最終表示進行排序取序最靠前的解。這個排序模型使用最大熵分類器,以手工設計的關於兩種邏輯表示之間的關係的信息表示為特徵,優化以使得預測的最終邏輯表示在知識庫上搜索的到的答案準確率最高。
目標函數:由於邏輯中間表示項是隱變數(latent variable),作者採用最大化邏輯最終表示項的期望對數似然 p(u|x)logp(g|u,x) 的方式來構建優化目標:
結合上文提到的對動作預測的優化目標 La,可以得到最終的優化目標:
目標函數優化:作者採用了另一篇文獻優化有隱變數的模型的演算法。在這篇引用文章中,處理的問題是文本分類,和本文的模型結構類似,有兩層結構(涉及到一個生成器和一個編碼器,生成器生成中間變數),中間連接的變數是隱變數。
這篇引用文章的求解任務是想要對文本提取一些原文的片段(對應 gen(?),generate)作為梗概信息(rationales),使得這些梗概信息盡量短的前提下儘可能與原始文本作為輸入進行文本分類(對應 enc(?),encode)的效果一致。這就涉及到了生成梗概信息的問題,而引用文章的作者又試圖想把生成器和編碼器一起訓練(訓練enc(gen(x)))。
引用文章的最終的目標函數為:
其中,z,x,y 分別表示梗概信息,原始輸入以及標註結果;L 懲罰編碼器和標註結果的距離,Ω 懲罰中間生成的梗概的長度。由於中間項在訓練過程中不能被提供,所以作者試圖最小化期望損失(相當於把 z 消掉):
其中 θe,θg 分別表示編碼器和生成器的參數。作者假設用生成器採樣中間項對於問題是可行的。藉助恆等式:
可以求得上述期望損失對兩個參數的導數:
以及:
這樣對 z 使用生成器進行採樣,固定 z 就可以求得上述兩式裡邊的微分,進而可以求得對期望損失的微分,最後使用梯度優化的演算法來更新參數。
從兩個微分等式可以看出,生成 z,可以利用編碼器的預測結果指導更新生成器的參數;生成器被優化後,中間項的分布發生了改變,又會指導編碼器的優化過程,類似於 EM 演算法交互更新來學習隱變數的過程。
類比引用文章的求解方式,可以假定使用之前提到的雙向 LSTM 來對邏輯中間表示項進行採樣可以滿足問題需求,則有:
其中,BiLSTM(x) 為邏輯中間表示的生成器,θu 為生成器的參數。
實驗
數據集
GEOQUERY:關於美國地理的 880 個問題以及知識庫,並標註有邏輯表示。這些問題句子雖然組合性(compositional)強,但語言過於簡單,辭彙量過於小,大多數問題涉及實體不超過一個。
SPADES:包含 93,319 個從數據集 CLUEWEB09 通過隨機去除實體獲得的問題集合,僅僅標註有問題答案。雖然語料的組合性不強,但規模很大,並且每個句子包含 2 個或多個實體。
WEBQUESTIONS:包含 5810 個問題-答案對。同樣組合性不強,但問題均來源於真實的網路數據。
GRAPHQUESTIONS:包含 5166 個問題-答案對,來源於將 500 個 Freebase 的圖查詢由亞馬遜員工轉換成的自然語言問題。
評價指標
本文使用準確率以及 F1 值作為評價指標,這裡通過計算預測得到的語義表達與真實標註的一致比率來得到。
實驗結果
Table 5:下邊隔開的一塊內的模型均為神經模型。Dong and Lapata (2016) 將語義解析問題視為序列預測問題,使用 LSTM 直接將自然語言句子映射到邏輯形式。
Table 6:這裡之前的工作與本系統處理的總體思路類似,均是將自然語言句子映射到中間邏輯表示然後再匹配知識庫。
Table 3:隔開的第一塊內的模型均為符號系統(symbolic systems)模型。其中Bast and Haussmann (2015) 提出了一個相反的問答模型,不能產生語義表達。Berant and Liang (2015) 借鑒模仿學習(imitation learning)的思想,提出了一個複雜的基於智能體(agenda-based)的解析器。Reddy et al. (2016) 與本文概念上相似,也是通過語義中間表示來訓練一個語義解析器,其中這個中間表示基於依存關係解析器的輸出構建。
隔開的第二塊內的模型為一些神經系統。Xu et al. (2016) 在這個數據集上到了目前最好的性能,他們的系統利用維基百科來過濾掉從 Freebase 抽取的錯誤候選答案。
Table 4:比較的是三個符號系統和一個神經系統基準。
中間邏輯表示分析
這裡進行了一些額外的實驗來檢驗邏輯中間表示的生成質量。
Table 7:作者手工標註了一些邏輯中間表示,以此來觀察人的介入對邏輯中間表示的生成效果的影響。三行結果分別對應了不同的匹配標準。
Table 8:為了在其他 3 個數據集上也能夠進行類似的實驗,作者將評價方式換為對比生成的邏輯中間表示與句法分析器分析結果。作者用 EasyCCG 將自然語言問題句子轉換為事件-參數(event-argument)的結構。分析看出對於組合性強的數據集 GRAPHQUESTIONS,本文的系統性能差一些。
Table 9:展示了一些謂詞預測結果的事例,並與 EasyCCG 的結果相對比。對於例子:
ceo johnthain agreed to leave_.
本文的系統會難以區分控制(control)動詞和真正的謂詞。
附錄
通用謂詞
適用於 GEOQUERY 的通用謂詞表如下:(還需要再進行擴展,才能夠使用於通用領域)
coutryidcityidstateidriveridplaceidanswerlargestsmallesthighestlowestlongestshortestcountfewestintersectunionexcludeall_
代碼
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)
本文由 AI 學術社區 PaperWeekly 精選推薦,社區目前已覆蓋自然語言處理、計算機視覺、人工智慧、機器學習、數據挖掘和信息檢索等研究方向,點擊即刻加入社區!
關於PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智慧前沿論文成果的學術平台。如果你研究或從事 AI 領域,歡迎在公眾號後台點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
微信公眾號:PaperWeekly
新浪微博:@PaperWeekly
推薦閱讀:
※知識圖譜數據構建的「硬骨頭」,阿里工程師如何拿下?
※知識圖譜如何助力輿情監控升級為商業智能
※報告 | 肖仰華:知識圖譜研究的回顧與展望
※16.關於靠譜那回事
※為什麼知識圖譜終於火了?|甲子光年