我覺得這篇文章的文筆真的有點不敢恭維,首先向量矩陣的維度不說清楚還能腦補,但是這邊前後不同層之間用一樣的變量名是什麼意思啊(這麼說出來會不會被MSRA鄙視,以後的簡歷都過不了了,ORZ),本文中儘量避免這種情況。嗯嗯,文章還是不錯的^@^
標記版文章下載地址:zsweet github
原始文章下載地址:
Gated Self-Matching Networks for Reading Comprehension and Question Answering
R-NET: MACHINE READING COMPREHENSION WITH SELF-MATCHING NETWORKS
這裏先總結下幾個特點:
- 同時使用了char-embedding和word-embedding,不同的是char-embedding是通過將char放入雙向gru之後,最終的是通過gru的最終狀態來得到的。
- 在attention之後添加gate,主要用在Question-Passage Matching和Passage Self-Matching之後。
- 對文章本身進行self-attention。
1.模型概述
其實我覺得整體結構就是math-lstm和transformer模型的融合,然後再加了一點的小trick.
模型整體可以分爲如下幾個模塊:
- embedding層
- RNN網絡分別對question和passage單獨編碼 (本文中將前兩部分放在一個部分介紹)
- 基於門限的注意力循環神經網絡(gated-attention based recurrent network)匹配question和passage,獲取問題的相關段落表示(question-aware passage representation)
- 基於自匹配注意力機制的循環神經網絡(self-matching attention network),將passage和它自己匹配,從而實現整個段落的高效編碼
- 基於指針網絡(pointer-network)定位答案所在位置
模型結構如下:
2.passage和question編碼層
輸入問題Q=wtQt=1m和段落P=wtPt=1n,分別進行word-level編碼和character-level編碼,得到向量e和c。這裏character-level編碼主要是爲了應對OOV的影響,以往OOV詞向量直接就是0,這裏可以緩和OOV的影響。之後,利用兩個雙向RNN網絡分別對question和passage再編碼。而之前多數都是用的CNN卷積和highway。另外,作者在這裏選用了GRU單元,而不是LSTM,原因在於GRU計算量更小。
utQ=BiGRU(ut−1Q,[etQ,ctQ])
utP=BiGRU(ut−1P,[etP,ctP])
其中:et,ct分別表示詞向量和字向量,編碼後的passage爲[u1Q,u2Q...umQ],query爲[u1P,u2P...unP]
3.Gated Attention-based RNN
首先對query做attention:
sjt=vTtanh(WuQujQ+WuPutP+WvPvt−1P),j=1,⋯,m
αjt=softmax(sjt)
ct=i=1∑mαituiQ
上述的attention可記爲ct=attn(uQ,[utP,vt−1P])。
上面的attention與match-lstm一樣,但是這裏又增加了一個gate:
gt=sigmoid(Wg[utP,ct])
[utP,ct]∗=gt⊙[utP,ct]
然後再像match-lstm一樣放入RNN:
vtP=BiGRU(vt−1P,[utP,ct]∗)
每個vtP動態地合併了來自整個Q的匹配信息。
可以看到,這一步驟和match-lstm的唯一區別就是增加了這個gt來控制passage和attention之後的query的輸出量。
Gate RNN的門機制:
- 與GRU和LSTM不同
- 門機制是基於當前Pt和它的對應的Q的注意力向量ct(包含當前pt和Q的關係)
- 模擬了閱讀理解中,只有P的一部分才與問題相關的特點
其實這個gate我覺得是很像那個highway的,在transformer裏面用的是residual,但是這裏和下面的self-attention之後換成了僞highway,甚至我覺得這個地方換成residual也未必不好,但是我沒嘗試過。
4.Self-Matching Attention
爲了充分利用Passage的上下文信息。增加對passage的self-attention :
sjt=vTtanh(WvPvjP+WvPˉvtP),j=1,⋯,n
βjt=softmax(sjt)
dt=i=1∑nβitviP
上述的attention可記爲dt=att(vP,vtP))
同樣在這裏添加gate:
gt′=sigmoid(Wg[vtP,dt])
[vtP,dt]∗=gt′⊙[vtP,dt]
同樣放入RNN計算:
htP=BiGRU(ht−1P,[vtP,dt]∗)
Self-Matching根據當前p單詞,從整個Passage中提取信息。最終得到Passage的表達HP。
我奇怪的是這裏在計算sjt的時候爲什麼沒有使用RNN中t-1時刻的輸出ht−1P?難道效果不好?
5.output layer
類似於match-lstm中的最後輸出層Ptr-net:
計算t時刻的attention-pooling passage (注意力ct)
sjt=vTtanh(WhPhjP+Whaht−1a)
γit=softmax(sjt)
ft=i=1∑nγithiP
pt=argimax(γit)
上述的attention可記爲ft=att(hP,ht−1)
RNN前向計算
hta=GRU(ht−1a,ft)
基於注意力權值去選擇位置。
上面的這是match-lstm中的Ptr,在R-net中是增加了RNN中的初始狀態初始化,
初始hidden state是Question的attention-pooling vector:
h0Q=rQ
基於Q的編碼和一組參數VrQ,利用注意力機制計算rQ
sj=vTtanh(WuQujQ+WvQVrQ),j=1,⋯,m
δi=softmax(si)
rQ=i=1∑mδiuiQ
上述的attention可記爲rQ=attn(uQ,VrQ)
同樣這裏有點奇怪的是,按說這裏的VrQ是一個向量的,並且WvQVrQ也是個向量,這不就相當於一個bias向量嗎?爲什麼寫的這麼麻煩?
之後從網上看到這個:
這裏肯定會有很多人困惑這個V_r^Q是怎麼來的,文中並沒有介紹,其實它是RNN的首狀態,大部分RNN的第一個輸入都是這麼處理的。看代碼還會發現它就是一個通過tf.contrib.layers.xavier_initializer()初始化的參數,那爲什麼不把這個乘法寫成一個參數呢,因爲兩個都是可學習的參數完全可以合併,我覺得是因爲要和attention的輸入保持一致(attention函數有兩個輸入)。
所以按照match-lstm中boundary方案,每次ft=att(hP,ht−1)產生的權重就可以作爲start-probability和end-probability了
目標函數:
−n=1∑Nlogp(an∣Pn,Qn)
本文用的是邊界模型,所以不用預測完整的序列,只預測開始和結束位置就可以了。
p(a∣Hr)=p(as∣Hr)⋅p(ae∣as,Hr)
6.源碼和參數
模型細節:
- 訓練集80%,驗證集10%,測試10%
- 分詞用的斯坦福的CoreNLP中的tokenizer
- 預訓練好的Glove Vectors。訓練中保持不變。
- 單層的雙向GRU,末尾隱狀態作爲該單詞的字符向量
- BiRNN編碼Question和Passage中使用3層的雙向GRU
- Hidden Size大小,所有都是75
- 每層之間的DropOut比例是0.2
- 優化器使用AdaDelta。初始學習率爲1,衰減率β=0.95,ϵ=1e−6
源碼:
原文中還提了很多的嘗試,其中有個sentence rank的,之前我也在Dureader裏面嘗試過,嘗試的是passage rank,效果也不好,其他的嘗試可以參考文章,別人踩過的坑還是不要再踩一次了,當然這只是針對SQuAD
參考鏈接