TransCoder代码详解(三):DAE/BT的训练过程

前言

ATP的上一篇blog里讲了这个模型是怎么用Masked Language Model进行预训练的。其实就跟BERT的方法一样。

在预训练完了以后,模型的路径下肯定就有了一个预训练好的checkpoint。

接下来的步骤就应该是把这个预训练好的模型load进来,在它的基础上再来进行DAE和BT的训练。

DAE和BT的训练主要目的是训练模型的decoder。

建立模型build_model

因为这次是要建立一个完整的transformer,所以这里调用TransformerModel类的构造函数两次,分别实例化一个encoder和一个decoder。通过is_encoder这个参数来控制当前建立的到底是哪一部分。

注意到build_model函数的第二句涉及到一个separate_decoders参数。这个参数在train.py的getparser里面有定义,意思是“是否给不同的语言用不同的decoder”。换句话说,如果这个参数设置为true,现在训练涉及到三种语言,那么就会有三个decoder。然而readme里面给的命令行中,这个参数的值是false。所以原模型只有一个decoder。

接下来载入已经用MLM预训练好的参数即可。

关于decoder参数的初始化,ATP没有特别搞懂这部分代码。由于原文中说这部分基本上是照搬XLM的训练过程,ATP看了一下其他人对于XLM模型的解析。

这篇博客认为,在XLM模型中,是先用CLM/MLM预训练encoder,然后用这个encoder的参数来初始化后续MT训练过程要用到的encoder和decoder。实际上decoder的大部分也是使用之前预训练的参数来初始化的,只不过decoder比encoder多了一个enc-dec attention部分,这部分参数没法与encoder对应,所以也就没有办法初始化,需要在后续训练中进行学习。

意思就是如下图所示,红色框框的部分是随机初始化的,而蓝色和绿色框框的部分初始是用同一批参数填进去的。

ATP认为这说法非常可信,因为原论文中也是这个意思。

Denoising auto-encoding 训练过程 mt_step

从字面意思上也可以看出,这个DAE的训练实际上就是一个翻译的过程,只不过源语言和目标语言是同一种。

在一番初始化过后,首先需要注意到的是开头generate batch的部分。这一部分的作用是取出一批数据用于训练。

# generate batch
if lang1 == lang2:
    (x1, len1) = self.get_batch('ae', lang1)
    (x2, len2) = (x1, len1)
    (x1, len1) = self.add_noise(x1, len1)
else:
    (x1, len1, _, _), (x2, len2, _, _) = self.get_batch('mt', lang1, lang2)

在DAE的过程中,lang1和lang2是相等的。于是它执行if后面的三个语句。可以发现,它的源数据和目标数据实际上是同一批数据,只不过源数据加了一些噪声。

加噪声的过程add_noise也是定义在trainer的类里面的,包括三个步骤:打乱顺序、丢弃字符与打mask。

数据处理好以后就是非常经典的步骤,先送encoder,再送decoder,计算loss用来优化,就结束了。

Back-translation 训练过程 bt_step

bt_step是整个训练过程里面比较特殊的一个步骤。

这个bt_step过程有三个关于语言的参数lang1,lang2和lang3。其中lang1和lang3是相同的,lang2和lang1是不同的,恰好对应了back-translation的过程:先将A翻译成B,再将B翻译回A。于是整个bt_step也顺理成章地分成了两个大的部分。

bt_step函数的大致流程如下所示。

def bt_step(self, lang1, lang2, lang3, lambda_coeff, sample_temperature):
    """
    Back-translation step for machine translation.
    """
    # 初始化各种参数以及A->B过程要用的“临时”模型
    ......

    if self.generation_index >= len(self.generated_data):
        # 取数据
        ......
            
        # 开始生成A->B的数据
        with torch.no_grad():

            # 切换到eval模式
            self.eval_mode()

            # 将初始数据放入临时的encoder和decoder,生成数据
            ......

    # 整理数据准备B->A的过程使用
    ......

    # 切换到train模式,开始训练B->A过程
    self.train_mode()
    
    # 将数据送入encoder和decoder
    ......

    # 计算loss并优化
    ......

    # 收尾工作
    ......

这个过程中,实际上可以看作整个过程只有一个encoder和一个decoder。但在生成A->B的数据时,把模型调整到evaluation的模式,并且在torch.no_grad()的作用域下进行,这样就只会“用”一下模型来生成一批数据,不涉及梯度的传播,也不会对模型做什么更改。

只有在后续的B->A的过程中,将模型切换到了pytorch的train模式,才会有梯度的传播。所以优化的目标只限于B->A过程涉及到的部分。

可以理解成从原模型中copy了一份参数相同的出来,用于A->B的过程;而原模型只用于B->A的过程。但copy的那一份不会被训练,只有原来的模型会被训练。

A->B的过程输出的结果,会作为B->A过程的输入。B->A的过程与上文提到的普通MT过程差别不大。

B->A过程用到的ground_truth就是A->B过程的输入,也就是未经处理的原始数据。这两者共同计算出loss以后就可以优化。

原文地址:https://www.cnblogs.com/FromATP/p/13432797.html