赞
踩
在原论文中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
Transformer的解码器和一般的Encoder-Decoder结构类似,融合编码器的输出以及解码器当前位置之前已经预测出的输出,一齐预测出当前位置的预测结果,通过逐位依次预测完成Seq2Seq的任务。Transformer解码器结构如下
Transformer的解码器
右侧部分为Decoder解码器,将期望预测的目标文本添加start和end标识位置,底部将目标文本作为输入,顶部将目标文本**右移一格(shifted right)**作为预测输出,编码器的输出和输出是错位设计的,以编码器输入为“I love you”,解码器输出为“我爱你”为例,在训练过程中编码器的输入和预测目标分别为
错位训练
每次总是以前面已经出现的单词加上编码器的中间状态,来预测下一个单词,比如红色阴影部分使用"+我"来预测下一个单词“爱”,以此类推该条样本可以分为预测“我”,“爱”,“你”,“end”四个任务,Decoder的目标是输出“我”,“爱”,“你”,“end”四个位置的embedding,这四个任务的预测准确度作为整条样本的预测目标。
从输入输出的角度来看,""位置经过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天然地支持所有词并行输入训练,因此在训练场景可以将答案文本全局移位,然后全部一齐输入训练,考验模型在前词和更早之前的词确定的前提下,后面一个词的预测能力,将一个完成的句子拆成一个个单词的预测任务从而重复地训练模型能力。
在预测场景下不存在答案文本,只能从位置开始逐位预测,因此预测场景的解码器必定是串行的。将预测的单词和历史预测单词一齐作为解码器输入来预测下一个新单词,重复这个过程直到预测结果为截止,预测阶段输入文本是一个一个单独输入的,同时会配合在此之前的历史预测单词完成自注意力机制。
预测阶段解码器串行工作方式
解码器主要包含两个注意力模块,分别是自注意力层和交互注意力层,自注意力层是对历史已经预测的单词序列做特征表征,交互注意力层是融合历史预测单词序列和编码器输出的特征表征。在自注意力层有两个要点,首先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安排如下
在训练之前需要对所有源文本和目标文本进行单独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代表从源文本中出去之外,第2个单词到最后位置的信息表征,因此只需要将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
感谢你们的阅读和喜欢,我收藏了很多技术干货,可以共享给喜欢我文章的朋友们,如果你肯花时间沉下心去学习,它们一定能帮到你。
因为这个行业不同于其他行业,知识体系实在是过于庞大,知识更新也非常快。作为一个普通人,无法全部学完,所以我们在提升技术的时候,首先需要明确一个目标,然后制定好完整的计划,同时找到好的学习方法,这样才能更快的提升自己。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!
这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。
随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。