美文网首页语言PyTorch@IT·互联网
pyTorch版OpenNMT的学习笔记

pyTorch版OpenNMT的学习笔记

作者: AlexTuring | 来源:发表于2017-05-13 18:27 被阅读1275次

    前言

    2017年1月18日Touch7的开发团队发布了pyTorch,pyTorch是一个python优先的深度学习框架,能够在GPU加速的基础上实现Tensor计算和动态神经网络。
    是的,相较于G家以静态图为基础的tensorFlow,pyTorch的动态神经网络结构更加灵活,其通过一种称之为「Reverse-mode auto-differentiation(反向模式自动微分)」的技术,使你可以零延迟或零成本地任意改变你的网络的行为。(然而我暂时并没有领略到这项技术的精髓... -.-!)
    关于pyTorch细节的问题另做讨论,这里说一说正题--基于pyTorch实现的OpenNMT。

    prepocess.py

    preprocess.py相对来说比较好理解,但对于OpenNMT-py环环相扣的编程方法感到很新奇,函数封装的很细致,便于后续的debug或修改,对自己以后的编程是一个很好的启发。此外其代码很优雅(beam search部分除外,稍后会有介绍)。
    关于这部分代码中makedata函数中:

    if opt.shuffle == 1:
        print('... shuffling sentences')
        perm = torch.randperm(len(src))
        src = [src[idx] for idx in perm]
        tgt = [tgt[idx] for idx in perm]
        sizes = [sizes[idx] for idx in perm]
    
    print('... sorting sentences by size')
    _, perm = torch.sort(torch.Tensor(sizes))
    src = [src[idx] for idx in perm]
    tgt = [tgt[idx] for idx in perm]
    

    预先shuffle一下,再根据句子长度排序,这样在每一种长度的句子的内部,句子是顺序是随机的,按照句长排序,使每一个batch中的句长基本相等,以加快训练速度。
    而以下这部分代码:

            src += [srcDicts.convertToIdx(srcWords,
                                          onmt.Constants.UNK_WORD)]
            tgt += [tgtDicts.convertToIdx(tgtWords,
                                          onmt.Constants.UNK_WORD,
                                          onmt.Constants.BOS_WORD,
                                          onmt.Constants.EOS_WORD)]
    

    tgt语句中,在句前加了BOS符号,在句末加了EOS符号。

    prepocess.py最后保存了一个.pt文件,其中:

    • dict:字典格式,保存有'src'和'tgt'的两个字典
    • train:字典格式,保存有'src'和'tgt'两个Dict类
    • valid:字典格式,保存有'src'和'tgt'两个Dict类

    此外,还对dict字典进行了存储。


    train.py

    直接从main()函数的'Building model'开始说起吧,中间串联对各个函数的理解。
    这里的encoder直接调用了pyTorch封装好的nn.LSTM()类,其初始化参数包括:

    • input_size : input的Embedding_size
    • hidden_size : 隐状态的数量
    • num_layers : 层数
    • bias : 默认为True,如果设置为False,网络将不使用 b_ih,b_hh。(详见链接中LSTM中的计算公式)
    • batch_fisrt : 如果设置为True,输入和输出的形状将变为(batch x seq_length x embedding_size)
    • dropout : 如果非0,除了最后一层,纵向层之间,丢弃(1-dropout)比例的隐藏神经元
    • bidirectional : 默认为False,如果为True,成为双向的RNN
      LTSM的输入为:input,(h_0,c_0)
    • input : seq_len x batch x enbedding_size
    • h_0 : num_layers * num_directions x batch x hidden_size
    • c_0 : num_layers * num_directions x batch x hidden_size
      输出为:
    • output :seq_len x batch x hidden_size * num_directions
    • h_n : num_layers * num_directions x batch x hidden_size
    • c_n : num_layers * num_directions x batch x hidden_size
      decoder中self.rnn却是用LSTMCell()堆叠出来的,然而为什么要这么做呢?-.-!
      LSTMCell()的输入输出维度为:
      输入:
    • input : batch x embedding_size
    • h_0 : batch x hidden_size
    • c_0 : batch x hidden_size
      输出:
    • h_1 : batch x hidden_size
    • c_1 : batch x hidden_size
      在decoder中引入了attention机制,类似于于pytorch tutorials中seq2seq模型中的attention机制
      图片截自pytorch tutorials
      但又略有不同,如图在bmm的到attn_applied之后,OpenNMT-py代码没有选择将attn_applied与embedd相结合,而是经过一次softmax后变形为batch x 1 x src_sent_length(attn3) ,再和context 矩阵相乘(weightedContext)后与input连接(contextCombined),最后经过线性变化再取tanh后返回。
      (其实对attention机制这样的处理方式并没有一个直观理解,求大神讲解)
      模型部分说明完毕接下来看看trainModel函数,这里首先需要注意的一点是,在Dataset.py中重写了getitem方法,每次给trainData一个一个batchIdx去的是一个batch的数据,也重写了len方法,用len(trainData)返回的是numBatchs。
      然后将batch输入进model,batch输入进model之后将tgt切掉最后一维EOS符号的,然后默认是以Teacher forcing的方式进行训练。Teacher forcing 就是将tgt的值作为decoder每次的输入,而不是使用其产生的预测值,这样做的好处就是可以使模型更快的收敛,但是对没有见到过的句子效果可能欠佳。

    translata.py

    这里面的重点是:Translator.py文件中的translateBatch()函数。

        #  (2) if a target is specified, compute the 'goldScore'
        #  (i.e. log likelihood) of the target under the model
        goldScores = context.data.new(batchSize).zero_()
        if tgtBatch is not None:
            decStates = encStates
            decOut = self.model.make_init_decoder_output(context)
            self.model.decoder.apply(applyContextMask)
            initOutput = self.model.make_init_decoder_output(context)
    
            decOut, decStates, attn = self.model.decoder(
                tgtBatch[:-1], decStates, context, initOutput)
            for dec_t, tgt_t in zip(decOut, tgtBatch[1:].data):
                gen_t = self.model.generator.forward(dec_t)
                tgt_t = tgt_t.unsqueeze(1)
                scores = gen_t.data.gather(1, tgt_t)
                scores.masked_fill_(tgt_t.eq(onmt.Constants.PAD), 0)
                goldScores += scores
    

    其中这部分代码,是计算model翻译的结果与标准答案对比后获得分数,分数由翻译正确的词的概率取和得到。
    接下来重点说明一下,OpenNMT-py优雅的代码中的一个槽点,beam-search部分,实在写的略难理解。
    首先:

        context = Variable(context.data.repeat(1, beamSize, 1))
        decStates = (Variable(encStates[0].data.repeat(1, beamSize, 1)),
                     Variable(encStates[1].data.repeat(1, beamSize, 1)))
    
        beam = [onmt.Beam(beamSize, self.opt.cuda) for k in range(batchSize)]
    

    将encoder 输出的context,decStates各沿第二维方向重复beamsize遍,其中context维度由seq_len x batch x hidden_size * num_directions变为seq_len x batch*beamsize x hidden_size * num_directions,并将beam初始化为一个含有batch个Beam类的列表。

            input = torch.stack([b.getCurrentState() for b in beam
                               if not b.done]).t().contiguous().view(1, -1)
    

    这行代码将每个beam中上一时间步的预测值取出来,再将得到的batch x beam_size 转置成beam_size x batch 后在view成一行,没隔batch个数据属于同一个beam,形成beam_size个batch恰好与context和decStates的seq_len x batch*beam_size x rnn_size相对应。而model计算之后的out与input相对应,故

    wordLk = out.view(beamSize, remainingSents, -1).transpose(0, 1).contiguous()
    

    此处对view的计算方法论存疑,理解上out应该是batch * beam x num_words ,

    wordLk = out.view(remainingSents, beamsize, -1).contiguous()
    

    就可以直接得到batch x beam x num_words 。
    然后关注Beam.advance()方法,
    其中的prevKs是后指针,即记录的是这一步结果对应来自上一步nextYs的第几个值,nextYs记录的是每一时间步产生的beam_size个最佳结果的idx。
    因为每次传进beam_size x num_words个值,展成一个列表之后选取的最佳beam_size个值在整除num_words后得到的是这个最佳值来自那个beam,而bestScoresId - prevK * numWords得到的是最佳结果的idx。

    (另有细节问题,会不定时更新。)

    相关文章

      网友评论

      • 1230c243ae82:请问楼主有beam search好的pytorch实现代码么?
        AlexTuring: @wanyao1992 OpenNMT就自带了beam_search的实现,其他关于pyTorch的b_s实现还真没看到过。。。
      • a6d16b2d995d:你好 我最近在看opennmt的源代码,但是感觉无从下手,之前就看过一篇神经网络的论文,现在看这些代码,感觉云里雾里,想问问我应该怎么做
        AlexTuring:@travel_going onmt感觉基本可以作为过去式了,建议转向更advance的fairseq~

      本文标题:pyTorch版OpenNMT的学习笔记

      本文链接:https://www.haomeiwen.com/subject/bwsatxtx.html