语言PyTorch@IT·互联网

pyTorch版OpenNMT的学习笔记

2017-05-13  本文已影响1275人  AlexTuring

前言

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字典进行了存储。


train.py

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


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。

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

上一篇下一篇

猜你喜欢

热点阅读