Transformer系列:图文详解Decoder解码器原理

关键词:Transformer注意力机制Decoder解码器

前言

在之前系列的篇章的已经介绍了Transformer的Encoder编码器网络结构,包括

从本节开始本系列将对Transformer的Decoder解码器进行深入分析。


内容摘要

  • Encoder-Decoder框架简介
  • shifted right移位训练
  • 解码器的并行训练和串行预测
  • 解码器自注意力层和掩码
  • 解码器交互注意力层和掩码
  • 解码器输出和损失函数

Encoder-Decoder框架简介

在原论文中Transformer用于解决机器翻译任务,机器翻译这种Seq2Seq问题通常以Encoder-Decoder框架来进行建模,Transformer的网络结构也是基于encoder-decoder框架设计的。这种框架的模型分为两部分编码器Encoder和解码器Decoder,编码器负责将原文本数据编码为中间状态向量,该状态向量传递给解码器生成输出。示意图如下

Encoder-Decoder框架

以机器翻译场景为例,期望将某种语言的句子X翻译成另一种语言的句子Y,句子被表征为每个位置的字符id输入,则给定X=(x1,x2,x3,x4...)输入给模型,期望模型预测出Y=(y1,y2,y3,y4...),模型架构如下

机器翻译场景的Encoder-Decoder

编码器会对完整的输入句子通过各种复杂非线性变换生成State,代表原始输入被编码器编码之后形成的中间语义状态,形如公式

编码器输出中间语义向量

而解码器层需要融合解码器产出的中间状态State,和解码器已经生成出的信息Y1,Y2...Yi-1,来生成i时刻需要生成的单词Yi。

解码器融合解码器语义和历史解码信息

解码器是从第一个单词开始,逐位预测下一个单词,最终实现了从X翻译到Y的任务。
在实际网络中会在解码器中增加注意力机制,否则对于任何位置Yi的预测中间状态state都是一样的,显然源文本中每个位置的字符应该和目标翻译文本各位置字符存在一定的对照关系,因此源文本的编码器state向量应该在每个位置对于当下要预测的Yi有不一样的权重分配,公式如下

带有注意力机制的Decoder

注意力机制工作的方式是将当下需要预测的单词位置的隐向量,和编码器输出的每个输入位置的状态向量,一一通过一个对齐函数(Attention)来计算目标单词和输入中某单词对齐的可能性大小,可能性越大赋予更大的权重,代表当下预测单词应该更加关注源文本中其对照单词的信息,最终中间状态向量state会给该对照单词位置处的分量给予更多的权重,从而更好地预测出该位置的目标单词。带有注意力机制的Encoder-Decoder示意图如下

带有注意力机制的Encoder-Decoder

shifted right移位训练

Transformer的解码器和一般的Encoder-Decoder结构类似,融合编码器的输出以及解码器当前位置之前已经预测出的输出,一齐预测出当前位置的预测结果,通过逐位依次预测完成Seq2Seq的任务。Transformer解码器结构如下

Transformer的解码器

右侧部分为Decoder解码器,将期望预测的目标文本添加start和end标识位置,底部将目标文本作为输入,顶部将目标文本右移一格(shifted right)作为预测输出,编码器的输出和输出是错位设计的,以编码器输入为“I love you”,解码器输出为“我爱你”为例,在训练过程中编码器的输入和预测目标分别为

错位训练

每次总是以前面已经出现的单词加上编码器的中间状态,来预测下一个单词,比如红色阴影部分使用"<start>+我"来预测下一个单词“爱”,以此类推该条样本可以分为预测“我”,“爱”,“你”,“end”四个任务,Decoder的目标是输出“我”,“爱”,“你”,“end”四个位置的embedding,这四个任务的预测准确度作为整条样本的预测目标。
从输入输出的角度来看,"<start>"位置经过Decoder输出的向量embedding服务于“我”,"我"位置经过Decoder输出的向量embedding服务于“爱”,以此类推,当前词的Decoder结果用于预测它右边那个词的概率,这就是shifted right的体现,理解这点很重要。

理解shifted right错位训练

解码器的并行训练和串行预测

shifted right移位训练仅仅解决了预测目标的问题,移位训练实施起来比一般的分类任务要复杂,分为训练和预测两种场景。
在训练场景下答案数据集会提前给到,令一个批次数量为B,文本长度为L,输出embedding长度为D,我们只需要将前L-1的文本作为Decoder的输入,将后L-1的文本作为Decoder的预测目标即可,永远用前一个词的embedding来预测后一个词的概率分布,此时输入是[B,L-1,D],输出也是[B,L-1,D],再加上Transformer这种Self Attention天然地支持所有词并行输入训练,因此在训练场景可以将答案文本全局移位,然后全部一齐输入训练,考验模型在前词和更早之前的词确定的前提下,后面一个词的预测能力,将一个完成的句子拆成一个个单词的预测任务从而重复地训练模型能力。
在预测场景下不存在答案文本,只能从<start>位置开始逐位预测,因此预测场景的解码器必定是串行的。将预测的单词和历史预测单词一齐作为解码器输入来预测下一个新单词,重复这个过程直到预测结果为<end>截止,预测阶段输入文本是一个一个单独输入的,同时会配合在此之前的历史预测单词完成自注意力机制。

预测阶段解码器串行工作方式

解码器自注意力层和掩码

解码器主要包含两个注意力模块,分别是自注意力层和交互注意力层,自注意力层是对历史已经预测的单词序列做特征表征,交互注意力层是融合历史预测单词序列和编码器输出的特征表征。在自注意力层有两个要点,首先Q,K,V在训练和预测阶段怎么分配,另外是它独有的下三角掩码。

Q,K,V在训练和预测阶段的分配

解码器的自注意力机制和编码器中的网络结构一致,都是基于Self Attention,通过原始embedding加上位置编码来作为Decoder的输入,自注意力层包含Q,K,V注意力计算,残差链接,层归一化,前馈传播模块,mask机制等。
解码器的自注意力机制在训练阶段Q,K,V相同,都是带有mask掩码的答案文本embedding,而在预测阶段由于只需要用Decoder的最后一维(也就是最后一个token)embedding做概率分布,因此只需要将当前词的信息作为Q,将当前词和之前所有的词的信息作为K和V,对最后一个token位置单独做Self Attention即可,如果这点难以理解请回看上一段的shifted right训练方式。

下三角掩码

在编码器中仅需要对padding位置进行掩码,因为padding位置的信息不需要带有权重去干扰有实词位置的embedding表征,而在解码器模块不仅要考虑padding导致的mask,还要考虑后词偷看问题。由于答案是一齐输入的,而实际的部署场景是步进预测的,理论上当前步长是看不到当前步长之后的词的信息的,解决方案是使用下三角掩码,将答案中当前位置之后的单词全部mask为0,这样答案文本依旧可以一齐输入,在Keras的Transformer源码中实现如下

def GetSubMask(s):
    # TODO 生成一批下三角矩阵,就是斜对角线以下部分全是1
    len_s = tf.shape(s)[1]
    bs = tf.shape(s)[:1]
    mask = K.cumsum(tf.eye(len_s, batch_shape=bs), 1)
    return mask

令s为一个[batch_size,5,6]每个文本最大长度为5,每个单词映射维度为6,调用GetSubMask生成mask如下

>>> a = tf.reshape(tf.convert_to_tensor(list(range(30))), [1, 5, 6])
>>> GetSubMask(a)
>>> <tf.Tensor: shape=(1, 5, 5), dtype=float32, numpy=
array([[[1., 0., 0., 0., 0.],
        [1., 1., 0., 0., 0.],
        [1., 1., 1., 0., 0.],
        [1., 1., 1., 1., 0.],
        [1., 1., 1., 1., 1.]]], dtype=float32)>

该下三角掩码美一行代表当前位置,每一行的纵向只有当前位置和之前位置为1代表自注意力使用该词,否则为0代表该词还看不到不能使用,以句子序列ABCD为例图示如下

下三角掩码

例如在计算C单词的自注意力表征的时候,只能使用候选的ABC三个词的V信息,C和D的注意力权重必须干预改成0。

掩码中1代表计算出的Q,K相似度保留原值,而0位置代表Q,K相似度改为一个极负的值,使得注意力权重为0,如图所示


下三角掩码对自注意力的影响

考虑到在训练过程中答案本身会进行该批次下的统一padding,因此还需要再叠加padding的mask掩码,杜绝padding单词对实词的表征影响,这个和编码器中的掩码一致,在源码中实现如下

# TODO 输出该批次下每个文本样本,在每个词步长下的mask向量,由于pad和词步长无关,所以每个步长下的mask向量相同,就是pad位置的是0
self_pad_mask = Lambda(lambda x: GetPadMask(x, x))(tgt_seq)
# TODO 只允许该词和该词前面的词纳入计算,下三角 [batch_size, seq_len-1, seq_len-1]
# TODO 输出该批次下每个文本样本,在每个词步长下的mask向量,由于是生成模型,只能基于当下词和前词进行计算,所以是个下三角
self_sub_mask = Lambda(GetSubMask)(tgt_seq)
# TODO 只要两个有一个为0则为0 mask掉
self_mask = Lambda(lambda x: K.minimum(x[0], x[1]))([self_pad_mask, self_sub_mask])

其中self_pad_mask为答案句子的padding掩码,对于答案中每个单词,该掩码是相同的,例如ABCD四个单词组成的答案,其中D词为padding,有词位置仅有ABC,则self_pad_mask如下

编码器层的padding掩码

源码使用K.minimum将两个掩码合并,每个位置取最小值,相当于两个掩码只要有任意一种情况需要被遮蔽则就应该被遮蔽,如图所示

解码器的自注意力最终掩码

通过掩码机制,一齐并行输入文本得到的每个单词的自注意力表征和一个一个逐位循环预测进行的表征效果等同。


解码器交互注意力层和掩码

自注意力层是解码器输入自身的特征表征,而交互注意力层用到了编码器的输出,将Decoder和Encoder信息进行融合。交互注意力层和编码器中的注意力层网络结构基本没有差异,但是由于有两方进行交互因此Q,K,V的分配上需要单独设计,解码器交互注意力层的特写如下

交互注意力层

前文提到在Encoder-Decoder框架中会使用对齐函数来计算目标单词和编码器输出的每个单词对齐的可能性大小,而在Transformer中使用点乘注意力来作为对齐函数,解码器和编码器作为该对齐函数的输入,来比对当前解码器位置应该更多地关注哪个源文本位置,进一步将源文本信息携带到当前编码位置,因此解码器交互注意力层Q,K,V安排如下

  • Q:解码器自注意力层的输出
  • K:编码器的输出
  • V:编码器的输出

在训练之前需要对所有源文本和目标文本进行单独padding处理,源文本的seq_length通常不等于目标文本的seq_length,因此交互注意层计算的注意矩阵不是一个方阵,计算示意图如下

交互注意力计算

以解码层的单词A为例,A需要融合编码器中的a,b,c,d四个单词的信息表征,其中得分权重分别为(3.2,1.3,0.9,-1),同样的交互注意力也需要mask掩码,掩码的维度和注意力权重矩阵维度相同,在源码中实现如下

def GetPadMask(q, k):
    '''
    shape: [B, Q, K]
    '''
    # TODO [batch_size, seq_len - 1] => [batch_size, seq_len - 1, 1]
    ones = K.expand_dims(K.ones_like(q, 'float32'), -1)
    # TODO [batch_size, 1, seq_len - 1]
    mask = K.cast(K.expand_dims(K.not_equal(k, 0), 1), 'float32')
    # TODO [batch_size, seq_len-1, seq_len-1],相当于对mask直接复制
    # TODO 输出该批次下每个文本样本,在每个词步长下的mask向量,由于pad和词步长无关,所以每个步长下的mask向量相同,就是pad位置的是0
    mask = K.batch_dot(ones, mask, axes=[2, 1])
    return mask

# TODO 参数1决定步长,参数二决定pad
enc_mask = Lambda(lambda x: GetPadMask(x[0], x[1]))([tgt_seq, src_seq])

此处的掩码根据padding机制生成,其中GetPadMask的第一个参数tgt_seq决定文本步长,第二个参数决定padding的依据,显然使用了源文本的padding信息,例如在源文本abcd中d为padding位置,则mask矩阵如下

交互注意力掩码

代表解码器层中A,B,C都需要携带编码器中的a,b,c,d信息,但是A,B,C每个位置计算的时候都需要舍弃源文本中的d信息,因为d信息是padding的干扰项。
交互注意力层计算解码器输入每个单词位置相对于编码器源文本的表征,解码器每个输入本身通过下三角mask机制代表当前和之前位置信息,而编码器源文本是完整可见的,因此解码器每个位置都可以和全部编码器输出计算注意力,只需要主要编码器的padding部分在交互注意力的时候同样需要删除,源文本中的padding信息不能带入到解码器中,示意图如下

解码器中mask工作流程

解码器输出和损失函数

解码器经过自注意力层提取当前预测位置的表征,经过交互注意力层以当前预测位置的表征和编码器层的中间状态进行对齐,融合编码器中的信息表征到解码器中来,令该批次样本数为B,解码器最大文本长度为L,embedding维度为D,则解码器最终输出一个三维向量,维度为[B,L-1,D],其中L-1是在训练过程中使用错位训练策略导致。
Transformer在解码器的输出层加入线性层Linear使每个位置的embedding表征映射到预测词库中每个词的概率,以英文到德文翻译的数据集为例,输出为3665个样本中德文单词的得分,源码实现如下

target_layer = TimeDistributed(Dense(o_tokens.num(), use_bias=False))

# TODO decode out [batch_size, seq_len-1, 256]
dec_output = self.decoder(tgt_emb, tgt_seq, src_seq, enc_output, active_layers=active_layers)
# TODO final_output [batch_size, seq_len-1, 3665]
final_output = target_layer(dec_output)

因为错位训练的存在,L-1代表从源文本中出去<start>之外,第2个单词到最后<end>位置的信息表征,因此只需要将Linear的结果和实际的错位单词id进行比对即可计算该条样本的损失,源码中采用softmax交叉熵来计算每个L-1位置的loss,如果该位置实际为padding则忽略loss,最终采用所有实词位置的loss均值作为该样本的总损失,采用该批次的平均损失作为该批次的总损失。

        def get_loss(y_pred, y_true):
            y_true = tf.cast(y_true, 'int32')
            # loss=[None, len_seq],输出每个样本,在每个词位置的softmax loss
            loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y_true, logits=y_pred)
            # TODO 对于padding位置的预测,该预测预测,但是不记入loss
            mask = tf.cast(tf.not_equal(y_true, 0), 'float32')
            # 对非mask位置求均值
            loss = tf.reduce_sum(loss * mask, -1) / tf.reduce_sum(mask, -1)
            loss = K.mean(loss)
            return loss

全文完毕,本节基于训练过程探究Decoder的原理,下一节会基于预测部分来完整理解Decoder的工作方式。

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

推荐阅读更多精彩内容