这一篇主要介绍Generation的一些技巧。
一、正则化
以视频的text generation举例,我们按下图形式表示Attention的权重值,上标表示component,也就是视频帧,下标表示时间步:
好的Attention应该使得每个component都能被Attent到。在实际训练的过程中可能会出现以下情况,也就是某一帧被过分地Attent了多次,这可能会产生“A woman is frying a woman.”这种奇怪的text:
为了解决这个问题,我们可以通过给Attention机制添加正则化来实现,举例来说,我们可以通过在损失函数中添加以下正则化项:
∑ i ( τ − ∑ t α t i ) 2 \sum _{i}(\tau -\sum _{t}\alpha _{t}^{i})^{2} i∑(τ−t∑αti)2
这个正则化项的目的就是让每一个component的所有时间步上Attention的值的总和接近于一个确定的值 τ \tau τ, τ \tau τ是一个超参数,是可以自行调整的。
这只是添加正则项的一个举例,在实际操作过程中可以自行设计合理的正则化项。
二、train和test之间的mismatch问题
- mismatch
以RNN的generation为例,在训练时我们通常会这样做,每个时间点除了会输入同样的condition外还会输入reference中的内容,整个过程如下图所示:
也就是说,无论RNN的输出如何,我们都会把正确答案(reference中的内容)输入到下一个时间点,然后我们通过minimize每个时间点的交叉熵的和来训练这个RNN:
但是在测试时我们没有reference可用,只能把上一个时间点的RNN的输出(是指输出向量的argmax出来的独热编码)输入到下一个时间点,这也就造成了train和test的mismatch,也叫做Exposure Bias:
我们可以更直观地解释这件事,下图的树状图中节点代表了时间点的隐层状态,箭头代表了当输入A或B时隐层状态的变化方向。在训练时我们将reference输入到下一个时间点,因此即使中间RNN的一个时间点出现错误,下一个时间点也会获得reference中的正确输入:
而在testing时会把前一个时间点的输出输入到下一个时间点,而如果一个时间点出现错误的话,可能后续所有的时间点都会出现错误,也就是“一步错,步步错”:
我们可以尝试在训练时使用上一个时间点的输出作为下一个时间点的输入,这样train和test就match了,但是事实上通过这种方式没能取得较好的效果。至于效果不好的原因可以这样理解,如果某个时间点输出了错误的结果B,然后把这个结果B输入到下一个时间点,在训练的过程中使用梯度下降就会将这个错误的结果B逐步调整为正确的结果A,然后下一个时间点的输入就会发生变化,那么下一个时间点之前根据输入为B训练的成果就没有意义了:
- Scheduled Sampling
Scheduled Sampling的方法是用来解决mismatch的问题的,其做法是在训练时下一个时间点的输入要根据一个概率来决定是使用reference还是前一个时间点的输出:
通常设计这个决定输入来源的概率有三种方式,分别是线性衰减(Linear decay)、指数衰减(Exponential decay)和反向sigmoid衰减(Inverse sigmoid decay)。下图展示了这三种方式中使用reference的概率随训练过程的变化情况,可以看到通常设计在训练开始时使用较多的reference作为输入,慢慢地随着训练进行开始使用更多的模型输出作为输出:
以下是使用Scheduled Sampling做caption generation时的效果,BLEU-4、METEOR、CIDER是指评价指标,可以看到使用Scheduled Sampling能够有较好的效果,完全使用模型输出的话效果会很差:
- Beam Search
在测试时我们通常选择每个时间点输出向量最大的维度(也就是概率最大)作为下一个时刻的输入,但是每个时间点选择最大概率并不能保证生成的句子的概率最大。我们还是使用树状图来说明上述这件事:
显然按照每个时间点选择最大概率应该选择“ABB”这一路径,但对于“BBB”这一路径显然有 0.4 × 0.9 × 0.9 > 0.6 × 0.6 × 0.6 0.4\times 0.9\times 0.9>0.6\times 0.6\times 0.6 0.4×0.9×0.9>0.6×0.6×0.6。
通常我们也不希望去查找每一条路径,为此我们引入Beam Search的方法,也就是在每个时间点选择概率最大的前几条路径,比如我们设置Beam size为2,也就是在第一个时间点的输出向量中选择最大的2维,然后将2个对应独热编码分别输入到模型中并在接下来的每个时间点取概率最大的路径:
下图也很好地展示了Beam Search的过程,下图是假设输出向量只有四个维度并且设置Beam size为3:
- Better Idea?
我们无论是使用reference还是前一个时间点的输出作为当前时间点的输入,都是用的独热编码。当使用前一个时间点的输出时是选择输出的向量的最大的一维(argmax)然后将对应的独热编码输入到当前时间点,我们可能会想直接将输出的向量分布输入到当前时间点,这样对输出不做处理的好处是梯度可以一路反向传播回来,可能更有利于训练模型。
事实上上述做法的效果并不好,可以看以下的一个例子。在问答系统中,当问模型“你觉得如何?”时,它既可以回答“高兴想笑”或者“难过想哭”,也就是说在模型的第一个时间点输出“高兴”和“难过”的几率几乎相等,然后将这个输出输入到下一个时间点的话得到“想笑”和“想哭”的几率也很可能会相等,然后就有可能得到“高兴想哭”或者“难过想笑”这样的答案。也就是说当前时刻输出的独热编码应该对后续时刻的输出有引导作用,才不会产生奇怪的答案:
三、Object level v.s. Component level
- 不同的损失函数
以caption generation任务为例,我们在生成图片的描述时通常minimize每个时间点的交叉熵的和:
C = ∑ t C t C=\sum _{t}C_{t} C=t∑Ct
在下图中,我们要生成这只狗的图片的描述,其reference为“The dog is running fast”,我们使用上面的指标来进行训练,在模型生成结果跟reference差异很大时loss很大,差异很小时loss就会很小,比如“The dog is is fast”和“The dog is running fast”的loss相差就会很小,而如果想要减小这相差的这部分loss是很困难的:
使用交叉熵的和是用的Component level的度量标准,我们也可以尝试使用Object level的标准也就是直接度量生成的句子。我们假设 y y y为生成的描述, y ^ \hat {y} y^为真实的reference,存在 R ( y , y ^ ) R(y,\hat {y}) R(y,y^)为句子级别的度量标准,如果两个句子 y y y和 y ^ \hat {y} y^完全一样,则 R ( y , y ^ ) = 0 R(y,\hat {y})=0 R(y,y^)=0。
但实际上上述标准 R ( y , y ^ ) R(y,\hat {y}) R(y,y^)是不合理的,因为没办法做梯度下降。可以想象如果模型的参数进行了微调而生成的句子没有变化时, R ( y , y ^ ) R(y,\hat {y}) R(y,y^)也是不变的,因为它只考虑句子的差异。
- 强化学习的思路
在下图的例子中,使用强化学习的方法来使机器人学会玩游戏,通过给机器人每个时间点一个observation,然后使其选择一个action,如果选择了正确的action将得到一个reward,然后通过最大化reward来训练:
类似的思路可以用generation任务中,如下图,我们将每个时间点的输入看做observation,每个时刻的输出看做action set,sample出的维度就是选择的action,但是整个过程只会在最后一个时刻有一个reward,然后通过通过最大化reward来训练:
下面展示了几种不同的方法在不同的任务上的效果,纵轴表示了相比于传统方法的提升效果,其中DaD相当于Scheduled Sampling的方法,E2E代表将前一时间点的输出直接作为当前时间点输入的方法,MIXER代表使用了强化学习的方法:
下图也是对比了不同方法在不同的任务上的效果,其中k代表beam search选择的路径数目: