企业网站色彩,成品网站源码的优化技巧,wordpress数据源,网站建设的合同模板原文地址#xff1a;https://zhanghan.xyz/posts/17281/
进入Transformer
RNN很难处理冗长的文本序列#xff0c;且很容易受到所谓梯度消失/爆炸的问题。RNN是按顺序处理单词的#xff0c;所以很难并行化。 用一句话总结Transformer#xff1a;当一个扩展性极佳的模型和一…原文地址https://zhanghan.xyz/posts/17281/
进入Transformer
RNN很难处理冗长的文本序列且很容易受到所谓梯度消失/爆炸的问题。RNN是按顺序处理单词的所以很难并行化。 用一句话总结Transformer当一个扩展性极佳的模型和一个巨大的数据集邂逅结果可能会让你大吃一惊。 Transformer是如何工作的
1.位置编码Positional Encodings
RNN中按照顺序处理单词很难并行化。
位置编码的思路将输入序列中的所有单词后面加上一个数字表明它的顺序。
[(Dala,1),(say,1),(hello,1),(world,1)]原文中使用正弦函数进行位置编码
要点将语序存储作为数据而不是靠网络结构这样你的神经网络就更容易训练了。
2.注意力机制Attention
注意力是一种机制它允许文本模型在决定如何翻译输出句子中的单词时“查看”原始句子中的每个单词。 图源[1409.0473] Neural Machine Translation by Jointly Learning to Align and Translate (arxiv.org)
模型如何知道在每个时间步长中应该注意哪些单词就是从训练数据中学到的东西通过观察成千上万的法语和英语句子学会了什么类型的单词是相互依赖的。
3.自注意力机制Self-Attention
如何不是试图翻译单词而是建立一个理解语言中的基本含义和模式的模型——一种可以用来做任何数量的语言任务的模型怎么办
自注意力帮助神经网络消除单词歧义做词性标注命名实体识别学习语义角色等等。 以上仅为读【解析 Transformer 模型理解 GPT-3、BERT 和 T5 背后的模型】
的笔记以下是我对Transformer进行的详细理解和技术解析。
Transformer技术解析 1.位置编码Positional Encoding
从上面的整体框架图可以看出Transformer中的Encoder和Decoder都是通过堆叠多头自注意力和全连接层得到的不管是Encoder还是Decoder的输入都进行了Positional Encoding对于位置编码的作用在原文是这样解释的 Since our model contains no recurrence and no convolution, in order for the model to make use of the order of the sequence, we must inject some information about the relative or absolute position of the tokens in the sequence. To this end, we add “positional encodings” to the input embeddings at the bottoms of the encoder and decoder stacks. The positional encodings have the same dimension dmodel as the embeddings, so that the two can be summed. There are many choices of positional encodings, learned and fixed 由于我们的模型不包含递归和卷积为了使模型能够利用序列的顺序我们必须在序列中注入一些关于token的相对或绝对位置的信息。为此我们在编码器和解码器堆栈底部的输入嵌入中加入位置编码。位置编码与embeddings具有相同的维数d因此可以将两者求和。位置编码有多种选择有学习的也有固定的。
Positional Encoding就是句子中词语相对位置的编码让Transformer保留词语的位置信息。
理想状态下编码方式应该要满足以下几个条件
对于每个位置的词语它都能提供一个独一无二的编码词语之间的间隔对于不同长度的句子来说含义应该是一致的能够随意延伸到任意长度的句子
以下是Transformer中使用的位置编码的公式 PE将正弦函数用于偶数嵌入索引i余弦函数用于奇数嵌入索引i。 代码实现
class PositionalEncoding(nn.Module):def __init__(self, d_model, max_len5000):super(PositionalEncoding, self).__init__() # 初始化位置编码矩阵 pe torch.zeros(max_len, d_model)# 计算位置编码position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1)div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))pe[:, 0::2] torch.sin(position * div_term)pe[:, 1::2] torch.cos(position * div_term)# 将位置编码矩阵转换为张量pe pe.unsqueeze(0).transpose(0, 1)#pe.requires_grad Falseself.register_buffer(pe, pe)def forward(self, x):return x self.pe[:x.size(0), :]这里再引申一下为什么位置嵌入会有用 得出一个结论 P E p o s k PE~posk~ PE posk 可以被 P E p o s PE~pos~ PE pos 线性表示。从这一点来说位置编码是可以反应一定的相对位置信息。
但是这种相对位置信息会在注意力机制那里消失。
2.多头注意力机制
为什么Transformer 需要进行 Multi-head Attention
最终的原因可以通过两句话来概括
①为了解决模型在对当前位置的信息进行编码时会过度的将注意力集中于自身位置的问题
②一定程度上ℎ越大整个模型的表达能力越强越能提高模型对于注意力权重的合理分配。
self-Attention自注意力机制
所谓自注意力机制就是通过某种运算来直接计算得到句子在编码过程中每个位置上的注意力权重然后再以权重和的形式来计算得到整个句子的隐含向量表示。最终Transformer架构就是基于这种的自注意力机制而构建的Encoder-Decoder模型。 从上图可以看出自注意力机制的核心过程就是通过Q和K计算得到注意力权重然后再作用于V得到整个权重和输出。具体的对于输入Q、K和V来说其输出向量的计算公式为 q i W q ⋅ a i k i W k ⋅ a i v i W v ⋅ a i α i , j q i ⋅ k j A softmax ( Q K T d k ) Attention ( Q , K , V ) A V \\\begin{align*}q^{i} W^{q} \cdot a^{i} \\k^{i} W^{k} \cdot a^{i} \\v^{i} W^{v} \cdot a^{i} \\\alpha_{i,j} q^{i} \cdot k^{j} \\A \text{softmax}\left(\frac{QK^{T}}{\sqrt{d_k}}\right) \\\text{Attention}(Q, K, V) AV\\\end{align*} qikiviαi,jAAttention(Q,K,V)Wq⋅aiWk⋅aiWv⋅aiqi⋅kjsoftmax(dk QKT)AV
首先对于输入序列中的每个元素我们计算查询向量Query、键向量Key和值向量Value其中Wq、Wk和Wv是需要学习的参数矩阵ai是输入序列的第i个元素。然后我们利用查询向量和键向量来计算注意力得分Attention Score这里αi,j表示第i个元素和第j个元素之间的注意力得分。接着我们对注意力得分进行缩放处理并应用softmax函数以得到注意力权重。其中Q和K分别是所有查询向量和键向量构成的矩阵dk是键向量的维度。最后我们利用注意力权重和值向量来计算自注意力机制的输出。其中V是所有值向量构成的矩阵A是注意力权重矩阵。 通过这种自注意力机制的方式确实解决了“传统序列模型在编码过程中都需顺序进行的弊端”的问题有了自注意力机制后仅仅只需要对原始输入进行几次矩阵变换便能够得到最终包含有不同位置注意力信息的编码向量。
MultiHeadAttention
模型在对当前位置的信息进行编码时会过度的将注意力集中于自身的位置因此提出了通过多头注意力机制来解决这一问题。同时使用多头注意力机制还能够给予注意力层的输出包含有不同子空间中的编码表示信息从而增强模型的表达能力。 所谓的多头注意力机制其实就是将原始的输入序列进行多组的自注意力处理过程然后再将每一组自注意力的结果拼接起来进行一次线性变换得到最终的输出结果。 Q i W i Q ⋅ X K i W i K ⋅ X V i W i V ⋅ X α i , j Q i ⋅ K j d k A i softmax ( α i , j ) head i A i ⋅ V i MultiHead ( Q , K , V ) Concat ( head 1 , . . . , head H ) ⋅ W O \begin{align*}Q_i W_i^Q \cdot X \\K_i W_i^K \cdot X \\V_i W_i^V \cdot X \\\alpha_{i,j} \frac{Q_i \cdot K_j}{\sqrt{d_k}} \\A_i \text{softmax}(\alpha_{i,j}) \\\text{head}_i A_i \cdot V_i \\\text{MultiHead}(Q, K, V) \text{Concat}(\text{head}_1, ..., \text{head}_H) \cdot W^O\end{align*} QiKiViαi,jAiheadiMultiHead(Q,K,V)WiQ⋅XWiK⋅XWiV⋅Xdk Qi⋅Kjsoftmax(αi,j)Ai⋅ViConcat(head1,...,headH)⋅WO
为什么要使用多头
根据下图多头注意力计算方式我们可以发现在dm固定的情况下不管是使用单头还是多头的方式在实际的处理过程中直到进行注意力权重矩阵计算前两者之前没有任何区别。当进行进行注意力权重矩阵计算时ℎ越大那么QKV就会被切分得越小进而得到的注意力权重分配方式越多。图源
This post is all you need上卷——层层剥开Transformer 从上图可以看出如果ℎ1那么最终可能得到的就是一个各个位置只集中于自身位置的注意力权重矩阵如果ℎ2那么就还可能得到另外一个注意力权重稍微分配合理的权重矩阵ℎ3同理如此。因而多头这一做法也恰好是论文作者提出用于克服模型在对当前位置的信息进行编码时会过度的将注意力集中于自身的位置的问题。这里再插入一张真实场景下同一层的不同注意力权重矩阵可视化结果图 同时当ℎ不一样时dk的取值也不一样进而使得对权重矩阵的scale的程度不一样。例如如果dm768那么当ℎ12时则dk64当ℎ1时则dk768。
所以当模型的维度dm确定时一定程度上ℎ越大整个模型的表达能力越强越能提高模型对于注意力权重的合理分配。
3.Encoder层
对于Encoder部分来说其内部主要由两部分网络所构成多头注意力机制和两层前馈神经网络。同时对于这两部分网络来说都加入了残差连接并且在残差连接后还进行了层归一化操作。这样对于每个部分来说其输出均为LayerNorm(xSublayer(x))并且在都加入了Dropout操作。对于第2部分的两层全连接网络来说其具体计算过程为 F F N ( x ) m a x ( 0 , x W 1 b 1 ) W 2 b 2 FFN(x) max(0, xW1 b1)W2 b2 FFN(x)max(0,xW1b1)W2b2 4.Decoder层
对于Decoder部分来说其整体上与Encoder类似只是多了一个用于与Encoder输出进行交互的多头注意力机制。 不同于Encoder部分在Decoder中一共包含有3个部分的网络结构。最上面的和最下面的部分暂时忽略Mask与Encoder相同只是多了中间这个与Encoder输出Memory进行交互的部分作者称之为“Encoder-Decoder attention”。对于这部分的输入Q来自于下面多头注意力机制的输出K和V均是Encoder部分的输出Memory经过线性变换后得到。
在Decoder对每一个时刻进行解码时首先需要做的便是通过Q与 K进行交互query查询并计算得到注意力权重矩阵然后再通过注意力权重与V进行计算得到一个权重向量该权重向量所表示的含义就是在解码时如何将注意力分配到Memory的各个位置上。
Decoder解码预测过程 Decoder训练解码过程
在介绍完预测时Decoder的解码过程后下面就继续来看在网络在训练过程中是如何进行解码的。在真实预测时解码器需要将上一个时刻的输出作为下一个时刻解码的输入然后一个时刻一个时刻的进行解码操作。显然如果训练时也采用同样的方法那将是十分费时的。因此在训练过程中解码器也同编码器一样一次接收解码时所有时刻的输入进行计算。这样做的好处一是通过多样本并行计算能够加快网络的训练速度二是在训练过程中直接喂入解码器正确的结果而不是上一时刻的预测值因为训练时上一时刻的预测值可能是错误的能够更好的训练网络。
例如在用平行预料我 是 谁who am i对网络进行训练时编码器的输入便是我 是 谁而解码器的输入则是s who am i对应的正确标签则是who am i e
但是模型在实际的预测过程中只是将当前时刻之前包括当前时刻的所有时刻作为输入来预测下一个时刻也就是说模型在预测时是看不到当前时刻之后的信息。因此Transformer中的Decoder通过加入注意力掩码机制来解决了这一问题。
左边依旧是通过Q和K计算得到了注意力权重矩阵此时还未进行softmax操作而中间的就是所谓的注意力掩码矩阵两者在相加之后再乘上矩阵V便得到了整个自注意力机制的输出。 5.残差和LayerNorm
残差
在Transformer中数据过Attention层和FFN层后都会经过一个Add Norm处理。其中Add为residule block残差模块数据在这里进行residule connection残差连接。
在transformer的encoder和decoder中各用到了6层的attention模块每一个attention模块又和一个FeedForward层简称FFN相接。对每一层的attention和FFN都采用了一次残差连接即把每一个位置的输入数据和输出数据相加使得Transformer能够有效训练更深的网络。在残差连接过后再采取Layer Nomalization的方式。具体的操作过程见下图 LayerNorm
为什么在NLP任务中Transformer使用LayerNorm而不是BatchNorm。
首先BN不适合RNN这种动态文本模型有一个原因是因为batch中的长度不一致导致有的靠后的特征的均值和方差不能估算。
但是这个问题不是大问题可以在处理数据的适合使句子的长度相近的在一个batch所以这不是NLP中不用BN的核心原因。
BN是对每个特征的batch_size上求得均值和方差就是对每个特征这些特征都有明确得含义比如身高、体重等。但是想象以下如果BN应用到NLP任务就变成了对每个单词也就是默认了在同一个位置得单词对应的是同一个特征比如“我/期待/全新/AGI”和“今天/天气/真/不错“,使用BN代表着”我“和”今天“是对应同一个维度的特征才能做BN。
LayNorm做的事针对每一个样本做特征的缩放。也就是它认为“我/期待/全新/AGI”这四个词在同一个特征下基于此做归一化。这样做和BN的区别在于一句话中的每个单词都可以归到这句话的”语义信息“这个特征中。所以这里不再对应batch_size而是文本长度。 引申-为什么BN在CNN中可以而在NLP中不行 浅浅理解BN在NLP中是对词向量进行缩放词向量是我们学习出来的参数来表示词语语义的参数不是真实存在的。而图像像素是真实存在的像素中包含固有的信息。
6.问题解析
Transformer的并行化
Decoder不用多说没有并行只能一个一个解码很类似于RNN这个时刻的输入依赖于上一个时刻的输出。
Encoder首先6个大的模块之间是串行的一个模块的计算结果作为下一个模块的输入互相之间有依赖关系。对于每个模块注意力层和前馈神经网络这两个子模块单独看都是可以并行的不同的单词之间是没有依赖关系的当然对于注意力层在做attention时会依赖别的时刻的输入。然后注意力层和前馈网络之间时串行的必须先完成注意力层计算再做前馈网络。
为什么Q和K使用不同的权重矩阵生成不能使用同一个值进行自身的点乘
简单回答使用QKV不相同可以保证在不同空间进行投影增强了表达能力提高泛化能力。
详细解释
transformer中为什么使用不同的K 和 Q 为什么不能使用同一个值 - 知乎
Transformer计算attention时候为什么选择点乘而不是加法在计算复杂度和效果上有什么区别
实验分析时间复杂度相似效果上和dk相关dk越大加法效果越显著。 transformer中的attention为什么scaled
根据上一个问题在dk即 attention-dim较小的时候两者的效果接近。但是随着dk增大Add 开始显著超越 Mul。作者分析 Mul 性能不佳的原因认为是极大的点积值将整个 softmax 推向梯度平缓区使得收敛困难。
这才有了 scaled。所以Add 是天然地不需要 scaledMul 在dk较大的时候必须要做 scaled。个人认为Add 中的矩阵乘法和 Mul 中的矩阵乘法不同。前者中只有随机变量 X 和参数矩阵W 相乘但是后者中包含随机变量 X 和随机变量 X 之间的乘法。
下面附上在知乎看到的代码数值实验就很清楚
from scipy.special import softmax
import numpy as npdef test_gradient(dim, time_steps50, scale1.0):# Assume components of the query and keys are drawn from N(0, 1) independentlyq np.random.randn(dim)ks np.random.randn(time_steps, dim)x np.sum(q * ks, axis1) / scale # x.shape (time_steps,) y softmax(x)grad np.diag(y) - np.outer(y, y)return np.max(np.abs(grad)) # the maximum component of gradientsNUMBER_OF_EXPERIMENTS 5
# results of 5 random runs without scaling
print([test_gradient(100) for _ in range(NUMBER_OF_EXPERIMENTS)])
print([test_gradient(1000) for _ in range(NUMBER_OF_EXPERIMENTS)])# results of 5 random runs with scaling
print([test_gradient(100, scalenp.sqrt(100)) for _ in range(NUMBER_OF_EXPERIMENTS)])
print([test_gradient(1000, scalenp.sqrt(1000)) for _ in range(NUMBER_OF_EXPERIMENTS)])# 不带 scaling 的两组可以看到 dim1000 时很容易发生梯度消失
[1.123056543761436e-06, 0.14117211040878508, 0.24896212285554725, 0.000861296669097178, 0.24717750591081689]
[1.2388028380883043e-09, 3.630249703743676e-18, 1.4210854715202004e-14, 1.1652900866465643e-12, 5.045026175709566e-07]
# 带 scaling 的两组dim1000 时梯度流依然稳定
[0.11292476415310426, 0.08650727907448993, 0.11307320939056421, 0.2039260641245917, 0.11422935801305531]
[0.12367058336991178, 0.15990586501130605, 0.1701746604359328, 0.10761820500367032, 0.07591586878293968]