全民 Transformer (二): Transformer在深度学习和NLP中如何发挥作用

《How Transformers work in deep learning and NLP: an intuitive introduction》

  2020年的确是 Transformer 年,从 NLP 进军到 CV。本文介绍一下 Transformer在 NLP 领域的应用。2017年的一篇非常有名的文章 “Attention is all you need”改变了我们对 Attention 的看法,有了充足的数据,矩阵乘法,线性层以及layer normalization 我们能够在机器翻译上达到 SOTA 的水平。

Key Words:Self-attention、Transformer、NLP


Beijing, 2021.01

作者:RaySue

Code:

Agile Pioneer  

  Attention 是如何转变为 self-attention 的呢?为什么 Transformer 效果这么霸道?让它成功的决定因素是什么?我们来一起揭开其神秘的面纱


  Transformer 不是很难理解。它是把包括 Attention 及其周围所有的概念合并到一起了,这块可能有点困惑。我们将慢慢的构建其周边的基础概念。

  使用 RNN 网络,我们习惯于顺序地处理序列以保持句子的顺序处在恰当的位置。为满足这样的设计,每层 RNN 需要前一个(hidden) 的输出。这样堆叠的 LSTM 的计算就是顺序执行的。

  开始介绍 Transformer 前先回顾一下序列处理,循环和LSTM。

输入序列的表示

Sets 和 词语切分(tokenization)

  Transformer 革命开始于一个简单的问题:为什么我们不能把整个输入序列作为输入呢?让隐藏层的状态彼此间不依赖!

  考虑一个例句:“hello, I love you”:

上图的操作被称之为 tokenization,是我们输入模型前三个步骤的第一步,词语切分。

Sets 是不同元素的集合,元素的位置在集合中无关紧要。

  换句话说,这个步骤之后,顺序就无关了。我们把输入的集合表示为 X = x 1 , x 2 , x 3 . . . , x N X = x_1, x_2, x_3 ..., x_N X=x1,x2,x3...,xN 其中 x ∈ R N × d i n x \in R^{N \times d_{in}} xRN×din。序列中的元素 x i x_i xi 表示 tokens。

  分词之后,我们需要把单词映射到一个几何分布的空间中,或者简单的构建word embeddings。

Word Embeddings

  通常来说,一个 embedding 是一个符号(单词,字符,句子) 在一个连续值向量的低维度空间分布的表示。

  单词不是离散的符号。他们是彼此强相关的。这就是我们把他们映射到一个连续的欧几里得空间中的原因,为了发现他们之间的联系。

  然后,依赖于任务,我们能够让 word embeddings 距离更远或者把他们聚在一起。

在理想的情况下,一个 embdding 能够通过把语义相似的输入在 embedding 空间中放置在一起,来捕获输入的语义。

  在自然语言中,我们能够发现相似单词的意义甚或是相似语法的结构(例如,聚类在一起的目标)。在任何情况下,当你把他们映射到 2D 或 3D 空间中,你能够从视觉上识别出一些簇。

  继续,我们将介绍一个有趣的技巧在集合中赋予顺序的概念。

正弦位置编码

如果觉得这块理解的不好,移步我的博客,里面有详细的讲解和证明。

  如果不使用 RNN 结构,那么如何表示位置信息呢?当你使用分词(tokenization)把序列变为一个集合的时候,就失去了顺序的概念。

  你能从序列 “Hello I love you” 中找到单词(tokens)的顺序吗?当然可以,但是如果是 30 个无序的单词呢?

  记住,机器学习都是关于 泛化性 的。神经网络确定无法理解集合中的顺序的。

因为 Transformers 把序列当做集合来处理,理论上,排列是不变的。

  所以我们需要提供给 embedding 一个顺序的概念,通过基于位置来微调 embedding 向量来实现。正式的说,positional encoding (PE)是一个由小常量组成的集合,用于输入到第一层 self-attention 层之前加在 word embedding 向量中。

  所以,引入 PE 后如果相同的单词出现在了不同的位置,那么实际上词向量表示上会有微小的改变,具体取决于 word 在输入序列的位置

  在 Transformer 的文章中,作者提出了正弦函数用于位置编码。正弦函数告诉模型关注于一个特殊的波长 λ \lambda λ。给定一个正弦函数 y ( x ) = s i n ( k x ) y(x) = sin(kx) y(x)=sin(kx) 波长就是 λ = 2 π k \lambda = \frac{2\pi}{k} λ=k2π在我们的场景下, λ \lambda λ 取决于在序列中的位置。i 用于区别奇数和偶数的位置。 公式如下:

P E ( p o s , 2 i ) = s i n ( p o s 1000 0 2 i d m o d e l ) PE_{(pos, 2i)} = sin(\frac{pos}{10000^{\frac{2i}{d_{model}}}}) PE(pos,2i)=sin(10000dmodel2ipos)

P E ( p o s , 2 i + 1 ) = c o s ( p o s 1000 0 2 i d m o d e l ) PE_{(pos, 2i + 1)} = cos(\frac{pos}{10000^{\frac{2i}{d_{model}}}}) PE(pos,2i+1)=cos(10000dmodel2ipos)

其中 d m o d e l d_{model} dmodel 表示 embedding 向量的维度。这样的话,位置编码的每个维度都相应于正弦曲线在不同维度上的不同的波长,从 2 π 2\pi 2π 10000 ⋅ 2 π 10000 \cdot 2\pi 100002π

  下图中序列长度 L = 32,可以理解为有 32 个词, 模型的 embedding 维度为 128,正弦函数的值域是[-1, 1],所以下图的值都是在 [-1, 1],-1(黑色),1(白色),0 是灰色。

  这样的做法和 循环 模型恰恰相反,循环模型中我们有顺序但是我们努力把注意力集中于那些距离不够近的 tokens。

Transformer 的基本概念

基于特征的 Attention:Key,Value 和 Query

   Key-value-query 的概念来自于信息检索系统。我们以在 youtube 上搜索一个视频为例。

  当你搜索(query)一个特定的视频时,搜索引擎将把你的 query 映射到一系列的 keys(视频标题,描述,等等),这些 keys 与可能存储的视频相关联。算法将提供给你最匹配的视频(values)。这就是基于内容/特征索引的基础概念。

  在单个视频检索任务中,Attention 就是有最大相关得分的视频。

  但我们可以泛化这个概念。Attention 和检索系统的重要差异是我们引入了一个更抽象而且平滑的‘检索’对象的概念。通过在我们候选的对象(youtube的视频)定义一个相似度(weight)我们可以对我们的 query 进行加权。

  所以,我们进一步把数据分为 key-value 对。我们使用 keys 来定义 attention weight 来作用于数据,然后我们实际将会得到的信息是 values。

  对于所谓的 映射,我们需要量化相似度,接下来介绍一下高维空间向量相似度的计算。

高维度空间向量的相似度

  在几何空间中,向量的内积被解释为向量的映射。一种定义向量相似度的方式是计算标准化的内积。在低维空间,如下的2D空间,内积就是相应的余弦距离。

s i m i l a r i t y ( a , b ) = c o s ( a , b ) = a ⋅ b ∣ a ∣ ∣ b ∣ = 1 s a ⋅ b similarity(a,b) = cos(a,b) = \frac{a \cdot b}{|a||b|} = \frac{1}{s}a \cdot b similarity(a,b)=cos(a,b)=abab=s1ab

  我们能够通过计算 scaled dot product(即余弦角度) 来计算向量间的相似度。

  在 Transformer 中,这是最基础的操作并且被使用在 self-attention 层中。

Transformer的encoder:Self-attention

  什么是 self-attention? 原文的回答是这样的,Self-attention 有时也被称为 intra-attention,是关于序列上不同位置的 attention 机制用来计算序列的表示。

  Self-attention 能够让我们发现输入的不同单词的相关性,表示句子的句法和上下文结构。

  例如,我们把 “Hello I love you” 作为输入序列。一个训练好的 Self-attention 层将会把 “love” 和单词 “I” 和 “you” 以更高的权重联系到一起,而和单词 “Hello” 的权重就比较低。对于语言学来说,我们知道这些单词具有主谓宾的关系而且能够直观的来理解 self-attention 捕获了哪些内容。

  实际上,Transformer 对 embedding 矩阵使用 3 个不同的代表即QueriesKeysValues。 这样可以很容易的把我们的输入 X ∈ R N × d k X \in R^{N \times d_k} XRN×dk 使用 3 个不同的权重矩阵 W Q , W K , W V ∈ R d k × d m o d e l W_Q,W_K,W_V \in R^{d_k \times d{model}} WQ,WK,WVRdk×dmodel 本质上,这就是在原始的 word embedding 上做了一个矩阵乘法。

  上图是一个示意图,有了 Q u e r y Query Query V a l u e Value Value K e y 矩 阵 Key 矩阵 Key,我们能够利用如下公式构造 self-attention 层:

A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

  在原始的文章中,这个 scaled dot-product attention 被选用为一种得分函数来表示两个单词之间的相关性(attention 权重)。当然,我们也可以使用其他的相似函数。 d k \sqrt{d_k} dk 在这里只是简单的作为一个缩放因子来确保向量不会激增,其中 d k d_k dk 就是 word embedding 的长度。

  参考前面介绍的数据库检索的范例,attention 这一项就是简单的寻找数据库中和搜索的 query 相似的条目。最后我们使用一个 softmax 函数来得到最终的 attention 权重的概率分布。

  记住我们已经让 Keys(K) 和 Values(V)作为有区别的表示了。因此最终的表示是 Self-attention 矩阵 s o f t m a x ( Q K T d k ) softmax(\frac{QK^T}{\sqrt{d_k}}) softmax(dk QKT) 与 Value(V) 相乘的结果。

  我们可以把 Self-attention 矩阵看作是模型关注哪些地方,而 Values 是我们实际得到了哪些信息。

跳跃短链接

  前面介绍完了 Self-attention 层,我们接下来需要介绍一下 归一化的方式, 及 跳跃短链接,就像一个张量通过卷积层或者RNN之后的处理类似。

  在语言中有一个重要的概念,即对世界有更广泛的理解,能够让我们具有把一些观点合并的能力。人类广泛的利用这些自顶向下的作用(我们的期望)来合并不同语境的词语。以一种粗糙的方法,跳远连接能够给 Transformer 一个微小的能力来让处理过程中不同层级的表示进行交互。

  随着多条路径的形成,我们能够把最后一层的高层次的理解“传递”到前层上。这就能够然我们重新调制如何理解输入。同样这与人类自上而下的思考方式一样,无非就是期望。

跳跃连接在深度学习的作用可以参加文章https://theaisummer.com/skip-connections/

layer Normalization

  BN 层是通过 batch size 和空间维度进行归一化的,所以如果batchsize太小,则计算的均值、方差不足以代表整个数据分布,一些场景下是不允许 batch size 过大的。而且 BN 不适用于 RNN,因为 sequence 的长度是不一致的,这就导致了 RNN 的 time step 不一致。如果存在一个 sequence 比其他的 sequence 长很多,就无法找到已存储的归一化的统计量了,如下图所示。

  而在 Layer Normalization (LN),均值和方差是通过 channels 和 空间维度计算的。在语言中每个单词是一个向量。因为我们正在处理的是向量,所以空间维度为 1。Layer Normalizationo 计算公式如下:

μ n = 1 K ∑ k = 1 K x n k \mu_n = \frac{1}{K}\sum^{K}_{k=1}x_{nk} μn=K1k=1Kxnk

σ n 2 = 1 K ∑ k = 1 K ( x n k − μ n ) 2 \sigma^2_n = \frac{1}{K}\sum^K_{k=1}(x_{nk} - \mu_n)^2 σn2=K1k=1K(xnkμn)2

  x   ^ n k = x n k − μ n σ n 2 + ϵ ,   x   ^ n k ∈ R \hat{\space x \space}_{nk} = \frac{x_{nk} - \mu_n}{\sqrt{\sigma^2_n + \epsilon}},\hat{\space x \space}_{nk} \in R  x ^nk=σn2+ϵ xnkμn x ^nkR

L N γ , β ( x n ) = γ   x   ^ n + β , x n ∈ R K LN_{\gamma,\beta}(x_n) = \gamma \hat{\space x \space}_{n} + \beta, x_n \in R^{K} LNγ,β(xn)=γ x ^n+β,xnRK

上面的公式中 K 是隐藏层节点的个数, x n x_n xn 表示第 n 个样本的特征。

  在一个合并了空间维度的 4D 张量的图上,我们可以可视化,LN的计算维度。

  加入归一化层和一个残差连接后,我们得到的结构如下:

  尽管这可以是一个独立构建的模块,Transformer 的作者在上面又加了额外的线性层对该层结果和另一个跳跃连接的结果进行重新标准化。

线性层

  线性层的表达式非常简单,如下:

y = x W T + b y = xW^T + b y=xWT+b

其中, W W W 是权重矩阵, y y y x x x b b b 表示向量。

利用 Pytorch 实现代码如下,就是两个全连接层,并在他们之间使用利用 dropout 和 非线性变换:

import torch.nn as nn

dim = 512
dim_linear_block = 512*4

linear = nn.Sequential(
    nn.Linear(dim, dim_linear_block),
    nn.ReLU,
    nn.Dropout(dropout),
    nn.Linear(dim_linear_block, dim),
    nn.Dropout(dropout))

这么做的目的主要是为了把 Self-attention 的输出映射到更高维度的空间中(文章中是4倍)。这样就解决了参数初始化不好以及秩崩溃(模型表达能力损失,纯自注意力机制随着层数的增多会以双指数级速率丢失秩)的问题。所以,Transformer 三个重要组件:跳过连接、MLP 和层归一化,结果表明,跳过连接能够有效地缓解秩崩溃(rank collapse),MLP 则通过增加利普希茨常数来降低收敛速度。

  下图大致解释了 Transformer 的编码器部分,由 N 个一样的模块连接组成。实际上,这和 Transformer 唯一的不同之处就是 attention 部分,Transformer 使用的是 多头 Attention

核心组件:多头Attention 和 并行实现

  在原始的论文中,作者把 Self-attention 的思想扩展为了 multi-attention。本质上,就是我们跑了多次 Self-attention 机制。

  每次我们把不相关的 Key,Query,Value 矩阵集合映射到低维空间,并计算 attention,每组对应的输出称之为 “head”。映射是通过每个矩阵乘以一个单独的权重矩阵来实现的,表示为 W i K , W i Q ∈ R d m o d e l × d k , W i V ∈ R d m o d e l × d k W_i^K,W_i^Q \in R^{d_{model} \times d_k},W_i^V \in R^{d_{model} \times d_k} WiKWiQRdmodel×dkWiVRdmodel×dk

  为了减少额外产生的复杂度,输出向量的 size 除以了头的个数。具体来说,在普通的 Transformer 中,使用 d m o d e l = 512 d_{model}=512 dmodel=512 并且 h = 8 h=8 h=8 的头数,每个头输出的向量的 size ( d k d_k dk) 为 64,现在,模型有多条路(8个头)来理解输入。

  然后把所有的输出 heads concat 到一起然后通过一个方形的权重矩阵转换一下, W O ∈ R d m o d e l × d m o d e l W^O \in R^{d_{model} \times d_{model}} WORdmodel×dmodel,其中 d m o d e l = h ⋅ d k d_{model} = h \cdot d_k dmodel=hdk

多头 Attention 整体公式如下:

M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , . . . , h e a d h ) W O MultiHead(Q,K,V) = Concat(head_1, ..., head_h)W^O MultiHead(Q,K,V)=Concat(head1,...,headh)WO

其中 h e a d i = A t t e n t i o n ( Q W i O , K W i K , V W i V ) head_i = Attention(QW_i^O, KW_i^K, VW_i^V) headi=Attention(QWiO,KWiK,VWiV),并且 W i Q , W i K , W i V ∈ R d m o d e l × d k W_i^Q,W_i^K,W_i^V \in R^{d_{model} \times d_k} WiQ,WiK,WiVRdmodel×dk

  由于每个 head 都是彼此独立的,所以我们可以并行的执行 self-attention 计算。

  但是为什么要这么繁琐呢?多头 Attention 背后的动机是,能够让模型每次注意到序列的不同部分。实际意义如下:

  • 模型能够更好的捕捉 位置信息,因为每个 head 将会注意到输入的不同片段。把他们合并后将会给我们一个更鲁棒的表示。

  • 每个头以一种不同的方式来计算单词间的相关性,来捕捉不同的上下文信息。引用原文的一句话:“Multi-head attention allows the model to jointly attend to information from different representation subspaces at different positions. With a single attention head, averaging inhibits this.”

Transformer的encoder 总结

  处理一个句子,我们需要这样三个步骤:

  • 输入序列的 word embedding 是同时计算的
  • 位置编码被应用在每个嵌入向量结果中,即词向量中也包含位置信息
  • 将向量传递到第一个 Encoder 模块

  每个 block 有如下的层组成,且顺序相同:

  • 一个用于发现词之间相关性的 multi-head self-attention
  • 一个归一化层
  • 一个两个子层间的残差连接
  • 一个线性层(全连接层)
  • 第二个归一化的层
  • 第二个残差连接

  注意上面的模块可以被重复很多次来形成编码器。在原始的论文中编码器有 6 个一样的模块组成。

Transformer 的 decoder有什么不同吗?

  解码器由前面提及的组件组成外加了两个新的组件,其余的和之前一样:

  • 输出序列整个填入到解码器并且计算词嵌入向量 注意概念:shifted right
  • 位置编码再次被应用
  • 并将向量传递到第一个 Decoder 模块

  每个解码模块包括:

  • 一个 Masked multi-head self-attention 层,目的是在解码第 i 个词的时候只能看到前 i-1 个输入,mask 矩阵是一个上三角矩阵 https://zhuanlan.zhihu.com/p/127774251
  • 然后接一层 归一化层 和一个残差连接
  • 一个新的 multi-head attention 层(被称为 编解码 attention)
  • 第二个 归一化层 和一个残差连接
  • 一个线性层和第三个残差连接

解码模块重复出现 6 次。最终的输出通过一个线性层转换到类别数量上然后通过标准的softmax 层得到分类的概率值。

输出的概率值是输出句子下一个 token 的类别概率。具体怎么做的呢?本质上,我们选取在法语上的每个词中概率值最大的那个词而已。

  原版的模型在 WMT 2014 英语-法语的翻译数据集上训练的,包含 36M 的句子和 32000 个词。

Masked Multi-head attention

  或许你还没有意识到,在解码的阶段,我们是一个接一个的预测每个词的。这在NLP的很多任务中,比如机器翻译,序列标注问题上都是不可避免的。因此,self-attention 层需要被修改以适用于在解码当前 token 的时候只能看见该 token 之前产生的结果。

  以翻译问题为例,在解码第三个单词时候的输入应该是 “Bonjour”, “je”。

  可以看出,目前的差距是我们无法知道整个句子,因为没有预测出来的我们是看不到的。所以这就是我们需要屏蔽未出现单词的原因。否则,模型直接复制下一个单词就好了,达不到训练的目的了。我们通过将下一个单词的embedding赋值为-inf的方式来达到这个目的。

数学上表达如下:

M a s k e d A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T + M d k ) V MaskedAttention(Q,K,V) = softmax(\frac{QK^T+M}{\sqrt{d_k}})V MaskedAttention(Q,K,V)=softmax(dk QKT+M)V

其中矩阵 M 由 0 和 -inf 组成。

  0通过指数运算得到的结果是1,而-inf经过指数运算的结果是0。

  这实际上与删除相应的连接具有相同的效果。其余的主要部分和编码器的attention是完全一致的。同样,我们可以通过并行的执行它们来加速计算。

  显然,这个 mask 对于我们计算的每个新的 token 将都会改变。

Encoder-Decoder attention: 奇迹发生的地方

  这里就是decoder处理encoded 表征的地方。通过 encoder 得到的 attention 矩阵 与之前Masked Multi-head attention block的结果一起通过另一个 attention 层。

  encoder-decoder 的 attention 层的目的就是为了合并输入和输出序列。encoder 的输出将输入序列压缩成了最终的 embedding。这就像是我们的数据库一样。我们将使用 encoder 的输出结果来产生 Key 和 Value 矩阵。另一方面,Masked Multi-head attention 模块包含当前解码的新序列,用于表示attention 层中的 Query 矩阵,再一次的在数据库中做“检索”。

encoder-decoder 的 attention 被训练用于联系输入序列和相应的输出单词间的关系。

  这一部分决定了每个英文单词和法语单词间是如何关联的。这部分对于英语和法语产生映射是必要的。

注意,encoder最后一层的输出将被用在每一个解码模块。

Transformer 为什么这么好的直观解释

  1. **每个块的分布的和独立的表征:**每个 transformer 模块有 h=8 个上下文表征。直觉来说你可以认为多个 head 就像一个卷积层的多个特征映射图用于捕捉来自图片的不同特征。和卷积不同的是,在这里我们有多个视角(线性重投影)到其他空间。当然这是可能的,通过先表征单词作为欧式空间中的向量(而不是离散符号),所以多头可能是在不同的空间对单词进行不同的表征从而提取更多丰富的特征。
  2. **意思严重依赖于上下文:**这是准确的对 self-attention 是什么的描述。我们通过attention 权重联系了单词表征表达之间的关系。没有局部的概念,我们自然的让模型建立全局的联系。
  3. **?多个编码器和解码器模块:**随着层数的增多,模型会做更多抽象的表征。和堆叠卷积或循环神经网络一样,我们可以堆叠多个transformer模块。第一个模块用于建立单词向量间对间的联系。第二个模块用于建立两个对之间的联系。第三个模块用于建立两个两对之间的联系,等等。同时,多头聚焦于每个对的不同部分。这就类似于感受野一样,但是是关于分布表征对的感受野
  4. **合并高低水平的信息:**当然是使用跳跃连接了!这样能够自顶向下的理解,能够让梯度沿着多个路径反向传播。

Self-attention VS 线性层 VS 卷积

问题:Self-attention 和前馈层有什么区别?线性层不也是对输入数据做了一个注意力吗这有什么区别?

答案:这个回答是完全不一样的,如果你深入研究这个概念。

Self-attention 的注意力机制的权重是随着不同的输入数据进行动态计算的。它是数据依赖的动态权重因为他们相应于输入数据进行动态改变(也称快速权重)。仍然以翻译问题为例,输入数据的每个单词将会相应的注意到不同的输入上。

另一方面全连接层的权重使用随机梯度下降的改变是非常缓慢的。在卷积中,我们进一步的限制了模型的权重(慢权重)有一个固定的size,即 kernel size。

总结

  1. 利用 self-attention 机制进行特征间关系的交互建模
  2. Transformer 中利用多头机制进行多维度的特征提取
  3. Mask multi-head 用来解决生成问题

参考

https://lilianweng.github.io/lil-log/2020/04/07/the-transformer-family.html

https://blog.csdn.net/weixin_47196664/article/details/114860630

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值