pyTorch版OpenNMT的学习笔记

前言

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。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容