SQUAD的rnet复现踩坑记

在港科大rnethttps://github.com/HKUST-KnowComp/R-Net) 实现的基础上做了复现

采用melt框架训练,原因是港科大实现在工程上不是很完美,包括固定了batch size,固定了context 长度为400,过滤了超过长度的context,每个batch长度都固定到400

melt框架支持dynamic batch size dynamic batch length,多gpu,修复了bucket的问题,目前采用长度<=400 batch size 64 长度> 400 batch size 32

   

Dynamic batch可以比较准确的eval 因为样本数目不一定是64的倍数, dynamic batch length 可以一定程度提速 并且结合buket和不同的batch size来跑较长的文本,港科大去掉了部分长度>400的训练数据

   

   

港科大版README展示的效果(word+char)

   

   

下载港科大代码修改hiden size 75->76 实际跑没有达到上面这么高的dev效果 实际效果如下

   

Word + char

Exact Match: 70.39735099337749, F1: 79.38397223161256

   

Word only

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 效果提升一点

contextquestion采用不同rnn encoder 效果变差。

4gpu 64*4 batch size训练 由于batch size的增大 收敛加快,word only模型f1 能达到0.783由于 gpu batch size 64,但是比较奇怪是dev loss30 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>

   

  • variational_recurrent: Python bool. If True, then the same dropout pattern is applied across all time steps per run call. If this parameter is set, input_size must be provided.

       

    来自 <https://www.tensorflow.org/api_docs/python/tf/contrib/rnn/DropoutWrapper>

       

    这里港科大采用的是variational_recurrent 也就是我的修改也没有改动这个事实 港科大为啥要放在init里面?

       

  • noise_shape: 1D tensor of type int32 representing the shape of the binary dropout mask that will be multiplied with the input.
  • For instance, if your inputs have shape (batch_size, timesteps, features), and you want the dropout mask to be the same for all timesteps, you can use noise_shape=[batch_size, 1, features].

       

    来自 <https://www.tensorflow.org/api_docs/python/tf/layers/dropout>

       

       

       

    我理解主要目的是为了context question在一次训练batch 同一个contextquestion共享 相同的droput

       

    不过港科大 处理char的时候 context question embedding用了不同droput。。 似乎用cudnn gru 也不太好share。。。因为是3纬度的 展开之后 batch size 不一样 对应不上去了 当然也可以实现share(title + reshape)

    为啥港科大对单层 char 没有用cudnn gru 还是用普通gru 我理解一样的 我这里还是使用cudnn gruchar建模

       

       

    Ok 最后是运行效果

       

    对应squad 这个 HKUST只选了glove有预训练的词 并且不finetune,实测是否finetune word embedding针对这个应用效果差不多

    特别需要注意是context question需要共享相同的dropout否则效果变差

       

    无一例外 不共享的.nsd效果差

       

    实验了 模仿HKUSTgloveonly,选取训练集合中min_count>=10的作为vocab增加少量不在glove中的词(91590->91651不到100),选取glove+train全量

       

       

    似乎min count10 效果好一点点

    Word only采用了contextquestion共享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 不过看起来相比worddroput 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的方式,这里也改为使用HKUSTpatience 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的处理上,注意HKUSTcharencodingcontextqustion 先走的recurrent droput 也就是说

    noise_shape=[shape[0], 1, shape[-1]]情况下的droputout,于是改原来wordchar都用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 效果似乎还有一点点上升趋势

       

       

       

       

    不过按照HKUSTissue

    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 总结

       

    • Word Share dropout 是最关键的 对应 一个context question pair 需要相同的droput
    • Char dropout也是很重要的
    • Adadelta optimizer的默认参数 按照keras 设置 很关键 已修改
    • 按照dev losslearning rate decay 很重要
    • Finetune emb train拟合的更好 但是dev似乎容易过拟合 不要finetune emb 或者开始bufinetune 后期finetune可能有一点点提升效果 目前(70.53,79.12是这样 不过finetune带来的提升很少)
    • Embedding 初始化影响一点效果 我之前都是 random_uniform HKUST使用random_normal 0.01 google transformer使用 random_normal dim ** -0.5 实验都还ok 影响不大
    • 最后还是eval loss差一点 f1效果还是差 em 达到了, 但是HKUST训练过程中的dev loss计算也是过滤了长度>=400 我这边实验了raindev都按照HKUST的做法 去掉长度>=400的(不过每个batch 长度还是按dynamic)

      测试集合长度偏短 显然位置的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差一点点 那么 发现HKUSTinit 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 transoferrandom normal , stddev dim ** -0.5

    Rnet.hkust.initstaternet.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

posted @ 2018-05-29 10:44  阁子  阅读(...)  评论(... 编辑 收藏