在港科大rnet(https://github.com/HKUST-KnowComp/R-Net) 實現的基礎上作了復現 python
採用melt框架訓練,緣由是港科大實如今工程上不是很完美,包括固定了batch size,固定了context 長度爲400,過濾了超過長度的context,每一個batch長度都固定到400。 git
melt框架支持dynamic batch size, dynamic batch length,多gpu,修復了bucket的問題,目前採用長度<=400 batch size 64 長度> 400 batch size 32 github
Dynamic batch能夠比較準確的eval 由於樣本數目不必定是64的倍數, dynamic batch length 能夠必定程度提速 而且結合buket和不一樣的batch size來跑較長的文本,港科大去掉了部分長度>400的訓練數據 api
港科大版README展現的效果(word+char) 安全
下載港科大代碼修改hiden size 75->76 實際跑沒有達到上面這麼高的dev效果 實際效果以下 app
Word + char 框架
Exact Match: 70.39735099337749, F1: 79.38397223161256 dom
Word only ide
Exact Match: 69.59318826868495, F1: 78.64400408874889 函數
港科大word only
港科大 word + char hidden 76
Word + char hidden 75 (原始代碼不作任何改動, python3 tensorflow_version: 1.8.0-dev20180330) 彷佛仍是沒達到readme 給出的那麼高效果 em 差不到0.6個點
訓練速度以下圖 採用單gpu gtx1080ti訓練 硬盤ssd,參考港科大我提的issue 改動港科大默認hidden size 75 -> 76 (4的倍數)不然速度比較慢, 不使用bucket
buckets按照 40,361 加速一點點
兩個gpu加速效果甚小
四個gpu
單gpu word + char 訓練 char embedding和港科大同樣 設置爲8
對應大概0.35s/it
在這個數據集 其它一些實驗結果
adadelta 0.5 learning rate效果最好 > adam 0.001 > adagrad 0.01
按照dev loss動態減少learning rate有效 和港科大同樣採用decay patience 3, decay factor 0.5 後面也能夠考慮對比採用監控dev em或者f1來作lr decay
Glove word embedding 若是finetune 效果提高一點
context和question採用不一樣rnn encoder 效果變差。
4個gpu 64*4 batch size訓練 因爲batch size的增大 收斂加快,word only模型f1值 能達到0.783因爲 單gpu batch size 64,可是比較奇怪是dev loss在30 epoch以後就呈現增加過擬合趨勢 儘管dev em 和 f1指標沒有過擬合傾向
後來我發現 始終和HKUST版本效果有差距 港科大代碼實測 雖然em 70+ f1 78+ 若是隻使用word em69.6 f1 78+, 差在哪裏呢 只能說理解不深。。。 通過屢次實驗定位。。。
這裏發現開始階段表現更好的模型 通常來講最後效果也會好一些。
緣由在於dropout。。
我改爲了這個樣子
而 港科大代碼是dropout放在了類的初始化函數中 每一個layer一個dropout
A Theoretically Grounded Application of Dropout in
Recurrent Neural Networks
The variant Gal and Ghahramani [6] proposed is to use the same dropout mask at each time step for both inputs, outputs, and recurrent layers.
來自 <https://becominghuman.ai/learning-note-dropout-in-recurrent-networks-part-1-57a9c19a2307>
來自 <https://www.tensorflow.org/api_docs/python/tf/contrib/rnn/DropoutWrapper>
這裏港科大采用的是variational_recurrent 也就是個人修改也沒有改動這個事實 港科大爲啥要放在init裏面?
來自 <https://www.tensorflow.org/api_docs/python/tf/layers/dropout>
我理解主要目的是爲了context 和question在一次訓練batch中 同一個context和question共享 相同的droput
不過港科大 處理char的時候 context question 的embedding用了不一樣droput。。 彷佛用cudnn gru 也不太好share。。。由於是3緯度的 展開以後 batch size 不同 對應不上去了 固然也能夠實現share(title + reshape)
爲啥港科大對單層 char 沒有用cudnn gru 仍是用普通gru 我理解同樣的 我這裏仍是使用cudnn gru對char建模
Ok 最後是運行效果
對應squad 這個 HKUST只選了glove有預訓練的詞 而且不finetune,實測是否finetune word embedding針對這個應用效果差很少
特別須要注意是context 和 question須要共享相同的dropout不然效果變差
無一例外 不共享的.nsd效果差
實驗了 模仿HKUST的gloveonly,選取訓練集合中min_count>=10的做爲vocab增長少許不在glove中的詞(91590->91651不到100),選取glove+train全量
彷佛min count10 效果好一點點
Word only採用了context和question共享droput以後 效果追上HKUST效果
比較明顯的 貌似 gloveonly 仍是 finetune效果比較好 說明基本都是訓練中出現充分的詞彙finetune效果會好一些 ?
可是rnet.fixebm效果好於rnet說明 可能 部分詞彙finetune了 部分沒訓練到 用的初始隨機變量 效果會稍微差一些?
Word only版本 min10.fixemb效果好 說明若是詞彙出現足夠 訓練過程當中參與rnn調整 對效果有必定提高 儘管隨機的詞向量不變化
看dev指標提高一點 訓練數據指標提高明顯 包括loss 可是看dev loss已經有明顯過擬合傾向 雖然dev指標仍是稍好 安全起見squad能夠不finetune word embedding, char embedding能夠補充不常見詞的知識。
TODO 能夠嘗試 ngram embedding 而且嘗試不一樣組合方式 以及添加highway network 和 layer normalization.
下圖是我對char 也採用了 droput share機制 相似word對應.chsd 不過看起來相比word的droput share 對結果影響不大。。 因爲模型自己存在隨機性 幾個對比效果以下
不過感受效果仍是比HKUST的實現差一丟丟,儘快已經不多了,可是特別eval loss 仍是明顯比HKUST的高 word only 2.9+ word+char 2.88 大概 而 HKUST word only 2.88 word+char 2.85大概
又仔細看了下HKUST的預處理髮現一個diff 我對於 unk的處理和HKUST不同 HKUST使用的是全0向量做爲unk向量 而且訓練過程當中再也不變化。。
儘管直覺感受這樣很差。。 不過我仍是實驗了一下 設置 specail_as_zero == True(對應模型.hkust)
可是這組實驗仍是dev loss 不如HKUST。。這不是關鍵性的影響
最後考慮是不是bucket 400 影響了效果 儘管>400的文本很少 是個人的tf bucket代碼使用 或是 tf bucket自己影響了隨機性?
HKUST注意是去掉了全部>400長度的文章,這裏我仍是使用全部文章 可是不使用bucket(nobucket) bucket貌似有一點點影響 不大
另外以前我使用了手工設定的按照特定epoch修改learning rate的方式,這裏也改成使用HKUST的patience 3,decay 0.5的方式。
實驗結果wordonly基本打平了HKUST的原版效果,儘管彷佛dev loss後期不是如HKUST原版穩定可是數值基本一致,
這說明decay是很是重要的。。。 可能由於訓練數據集合比較小,針對dev監控的動態學習率調整比較關鍵。 而bucket若是隻是400這個影響不大。
另外語料不是太大 隨機性也起到很大效果 可能陷入局部最優 不能找到全局最優
HKUST word only 最佳
Em 69.69 F1 78.69 dev loss 2.87
可是word_char效果仍是明顯比HKUST的差 , 定位問題應該出在char的處理上,注意HKUST的char的encoding是context和qustion 先走的recurrent droput 也就是說
noise_shape=[shape[0], 1, shape[-1]]狀況下的droputout,因而改原來word和char都用cudnn gru一樣encoding方式爲HKUST原版的處理方式。
不過這裏實際用cudnn效果差的緣由是last state不對,由於cudnn不支持動態長度 因此padding 0也參與計算了 最後last state極可能是padding 0對應的 因此不許確。
實現效果 dev loss效果確實更貼近了2.85 可是f1值仍是有差距79.1
HKUST word+char 最佳
Em 70.37 F1 79.34 dev loss 2.85 效果彷佛還有一點點上升趨勢
不過按照HKUST的issue
2.82 in dev, 1.32 in train
來自 <https://github.com/HKUST-KnowComp/R-Net/issues/6>
另一個遺留的問題 cudnn gru HKUST的實現 我搬動到其它地方使用save模型 只有 cudnn_gru/opaque_kernel (DT_FLOAT) [87096]
可是原版代碼 save以後都是帶有
不知道是哪裏影響的 我猜想這裏不影響模型效果 opaque_kernel 應該存儲了全部信息,下面的只是不知道什麼開關影響的 存儲了轉換後的權重數據。
Finally 總結
測試集合長度偏短 顯然位置的softmax loss會低一點了 不過彷佛去掉長文本以後效果還穩定了一些 並且提高了f1值。。 那麼說明loss須要考慮下文本長度嗎? TODO
Train 87599
Dev 10570
Filter context > 400 or qeustion > 100
Train 87391 99.7%
Dev 10483 99.1%
HKUST 60k step
Exact Match: 70.39735099337749, F1: 79.3839722316126
gezi@gezi:~/mine/hasky/deepiu/squad$ c0 sh ./train/evaulate.sh ./mount/temp/squad/model/rnet.hkust/epoch/model.ckpt-37.00-50505
Mine:
[('metric/valid/loss', 2.892608779740621), ('metric/valid/em', 0.7044465468306528), ('metric/valid/f1', 0.7919028182076531)]
epoch:37.3626/60 valid_step:51000 valid_metrics:['metric/valid/loss:2.87955', 'metric/valid/em:0.70466', 'metric/valid/f1:0.79236']
[('metric/valid/loss', 2.9003277952412523), ('metric/valid/em', 0.7048249763481551), ('metric/valid/f1', 0.7928490855150692)]
最高
./mount/temp/squad/model/rnet.hkust/epoch/model.ckpt-39.00-53235
[('metric/valid/loss', 2.890633753983371), ('metric/valid/em', 0.7052980132450332), ('metric/valid/f1', 0.7933954605863096)]
60k
[('metric/valid/loss', 2.887699271541044), ('metric/valid/em', 0.7054872280037843), ('metric/valid/f1', 0.7925004189917955)]
仍是f1差一點點 那麼 發現HKUST的init state是訓練出來的 而不是使用它zero默認初始化的方式 修改按照HKUST的方式
Model增長變量。。
rnet/main/encoding/cudnn_gru/bw_0/init_state (DT_FLOAT) [1,1,76]
效果。。 就差在這個地方了 至少對應squad的應用 init state是很是影響效果的。。。 貌似以前各類應用場景都是用的zero做爲init state 能夠試一下對init state訓練。
Rnet.hkust 表示char使用random uniform
rnet.hkust2 表示char使用random normal stddev 0.01
Rnet.hkust3 表示char使用相似google transofer的random normal , stddev dim ** -0.5
Rnet.hkust.initstate同rnet.hkust可是 使用了 訓練 init state
貌似hkust3的初始化方式好一點 dev loss
因爲測試數據少了一小部分 最終測試結果
[('metric/valid/loss', 2.871750499110624), ('metric/valid/em', 0.7094607379375591), ('metric/valid/f1', 0.7962988473545626)]
gezi@gezi:~/mine/hasky/deepiu/squad$ c3 sh ./train/evaulate.sh ~/temp/squad/model/rnet.hkust.initstate/epoch/model.ckpt-40.00-54600
HKUST 60k step
Exact Match: 70.39735099337749, F1: 79.3839722316126
基本完整復現了 HKUST RNET固然是在幾乎徹底copy的基礎上 不過作了不少工程改進,有了這個baseline, 更加方便後續實驗對比,有了這個baseline,後面能夠嘗試快速的conv seq2seq, transformer, qanet中的encoder用在文本分類和圖文關係的效果。
最終定位了 影響最終效果的幾個核心地方 感謝HKUST無私的代碼開源! cudnn的使用 droput的使用 init state參與訓練,太讚了
TODO:
Train init state的效果和 <S> </S> 相比呢 是否添加 <S>能夠起到一樣效果?
Random normal google transform效果不錯的 特別是dev loss相對較低 可是隨機性起到了更大的做用 也就是說相同配置 不一樣運行 結果可能會diff很大 特別是因爲動態learning rate調整的不肯定性形成 最終結果不肯定性很大
最終目前跑出來的最佳結果 對應
使用random uniform embedding, char dim 8, glove only vocab, not finetune word embedding, char use share dropuout using cudnn gru with outpout max pooling, using all trainging data for train,dev data for evaluate, buckets [400] batchs size[64,32]
75k step EM 70.96 F1 79.86