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以后就可以优化。