Transformer

Transformer 是一种依据自留意力机制(self-attention mechanism)的深度神经网络,它是自然语言处理范畴中的一项重要技能。最早由 Google 提出,现已被广泛应用于机器翻译、文本生成、语言模型等使命中。

Transformer 的中心思维是运用自留意力机制来完成对输入序列的编码和对输出序列的解码。自留意力机制可以让模型对输入序列中的不同方位进行重视,并将不同方位的信息整合起来。这种重视机制可以看作是一种在序列中进行“跨步”衔接(skip connection)的方式,使得模型可以更好地捕捉序列中的长程依靠联系。

Self-Attention 自留意力机制

Self-attention 的意图是依据输入序列中各个方位之间的依靠联系,核算出每个方位的特征向量表明,然后得到一个表明整个序列的矩阵表明(每个元素特征向量的拼接)。

Self-attention机制是一种将输入序列的不同部分相关起来的办法,可以在不引进循环或卷积结构的情况下学习到序列中的全局依靠联系。在self-attention中,每个输入元素都会核算一个留意力得分,该得分衡量它与其他元素之间的相对依靠性,并用这些得分来核算一个加权和。

序列自留意力核算的详细进程

在序列自留意力机制中,每个输入元素都可以被视为一个向量。关于每个向量,都可以经过一个矩阵改换来生成三个新向量:查询向量、键向量和值向量。

  • 查询向量(query vector):表明要核算相关度的向量,正如它的名字,它代表这个词作为查询时候的表明,每个词语都有一个查询向量;
  • 键向量(key vector):表明这个单词当作被比较的对象的表明向量,每个词语也有一个键向量;
  • 值向量(value vector):表明查询向量相关的向量,这儿可以了解为一个更深层的表明,每个词语也有一个值向量。

咱们首要将查询向量与所有键向量进行点积运算,然后将成果除以一个可学习的缩放因子(为了使得梯度稳定),得到一组分数。这些分数可以视为查询向量与不同键向量之间的相似度分数,用于衡量它们之间的相关性。接下来,咱们可以运用分数对值向量进行加权会聚,以获得对查询向量的响应表明。

在序列自留意力机制中,每个输入元素都作为查询向量、键向量和值向量的来源,因而每个元素都可以被视为自身与序列中所有其他元素之间的联系的表明。经过这种方式,自留意力可以有效地捕捉序列中元素之间的长程依靠联系,然后在各种自然语言处理使命中取得了很好的作用。

假定咱们有一个输入序列 x=x1,x2,…,xnx = {x_1, x_2, …, x_n},其间每个 xix_i 都是一个向量,维度为 dd。咱们可以经过一个线性改换来将每个向量映射到三个不同的向量,即查询向量 qiq_i、键向量 kik_i和值向量 viv_i

qi=Wqxi, ki=Wkxi, vi=Wvxiq_i = W_q x_i, \ k_i = W_k x_i, \ v_i = W_v x_i

其间 Wq,Wk,Wv∈Rd×dW_q, W_k, W_v \in \mathbb{R}^{d \times d} 是可学习的权重矩阵。
image.png

接下来,咱们核算每对查询向量和键向量之间的点积得分,qikj,i=1,2,…n,j=1,2…nq_i k_j,i=1,2,…n,j=1,2…n

image.png

然后对值向量进行加权求和,以得到对查询向量的响应表明:

Attention(Q,K,V)=softmax(QKTd)V\mathrm{Attention}(Q, K, V) = \mathrm{softmax}(\frac{QK^T}{\sqrt{d}})V

其间 Q=[q1,q2,…,qn]∈Rd×nQ= [q_1, q_2, …, q_n] \in \mathbb{R}^{d \times n}是查询矩阵, K=[k1,k2,…,kn]∈Rd×nK = [k_1, k_2, …, k_n] \in \mathbb{R}^{d \times n}是键矩阵,V=[v1,v2,…,vn]∈Rd×nV = [v_1, v_2, …, v_n] \in \mathbb{R}^{d \times n}是值矩阵, softmax\mathrm{softmax}是对每行进行 softmax 操作, d\sqrt{d}是缩放因子,用于平衡点积得分的量级,这样就能有愈加稳定的梯度。

image.png

image.png

image.png
终究,关于输入序列中的每个元素 xix_i,咱们都可以经过自留意力机制它和序列中其他元素的留意力。

多头留意力

在序列模型里,输入序列被屡次核算序列的自留意力,每次的核算都叫做一个头,每个头部可以重视序列中的不同部分,并核算出一个对每个方位的表明。然后,这些表明被合并成一个全体表明,以便于后续的模型处理。就像有多个人的大脑头部去重视不同的信息。

多头留意力是一种强壮的留意力机制,可以协助模型更好地了解和表明输入数据。

多头留意力的核算进程

在多头留意力中,咱们需求屡次核算序列的每个元素的自留意力。多头自留意力层中的每个头的 Q、K、V 矩阵都是由输入序列经过不同循环的线性改换得到的,每次循环的线性改换都不相同。每个头学习到不同的表明,然后可以捕捉序列中不同的特征。

例如,在进行机器翻译时,输入序列是源语言的词语序列,输出序列是方针语言的词语序列。在多头留意力机制中,可以将源语言的输入序列别离映射到不同的子空间中,如词性、词性和方位、句法结构等,每个子空间中的留意力头可以重视输入序列中与该子空间特征相关的信息,然后更好地捕捉源语言与方针语言之间的语义对应联系。

image.png

下面是多头留意力的核算进程:

将输入序列 X∈Rn×dX \in \mathbb{R}^{n \times d}经过 hh 次不同的线性改换(称为“头”)转换为 hh个查询 Q1,Q2,…,QhQ_1, Q_2, …, Q_hhh个键 K1,K2,…,KhK_1, K_2, …, K_hhh 个值 V1,V2,…,VhV_1, V_2, …, V_h,其间每个头的维度为 d/hd/h

Qi=XWiQ,Ki=XWiK,Vi=XWiV,i=1,2,…,hQ_i = XW_i^Q,K_i = XW_i^K, V_i = XW_i^V, i=1,2,…,h

这儿 WiQ∈Rd×d/hW_i^Q \in \mathbb{R}^{d \times d/h}WiK∈Rd×d/hW_i^K \in \mathbb{R}^{d \times d/h}WiV∈Rd×d/hW_i^V \in \mathbb{R}^{d \times d/h}别离是用于将输入序列 XX 转换为查询 QiQ_i、键 KiK_i 和值 ViV_i 的线性改换矩阵, dd 是输入序列的维度, hh是头的数量。每个头的维度为 d/hd/h ,因而每个头可以重视输入序列中的不同部分。

image.png

接下来,关于每个头 ii,核算其留意力权重 ZiZ_i,该权重表明该头在输入序列中重视的重要程度。这儿选用前面说的点积留意力机制:

Zi=softmax(QiKiTd/h)Z_i = \text{softmax}(\frac{Q_iK_i^T}{\sqrt{d/h}})

d/h\sqrt{d/h}是用于缩放点积的常数,旨在防止点积过大或过小而导致的梯度问题。然后,将留意力权重 ZiZ_i与值 ViV_i 相乘并相加,得到头 ii 的输出向量 OiO_i

Oi=ZiViO_i = Z_iV_i

最终,将所有头的输出向量拼接在一起,得到多头留意力的输出向量 Z∈Rn×dZ \in \mathbb{R}^{n \times d}

Z=Concat(O1,O2,…,Oh)Z = \text{Concat}(O_1, O_2, …, O_h)

多头留意力的输出向量 OO 可以作为下一层模型的输入,例如 Transformer 模型中的前馈神经网络。多头留意力机制可以协助模型更好地了解序列数据中的信息,然后进步模型的功能。

image.png

Transformer

Transformer 包含两个首要模块:Encoder 和 Decoder。Encoder 模块将输入序列映射到一个高维空间中,而 Decoder 模块则依据 Encoder 模块生成的编码信息,逐渐生成方针序列。

Encoder 级包含两个子层:多头自留意力层(Multi-Head Self-Attention Layer)和全衔接前馈层(Fully Connected Feedforward Layer)。多头自留意力层用于对输入序列进行编码,全衔接前馈层用于对编码后的序列进行进一步处理。Decoder 模块包含三个子层:多头自留意力层、编码器-解码器留意力层(Encoder-Decoder Attention Layer)和全衔接前馈层。

Transformer 经过自留意力机制完成对序列的编码和解码,使得模型可以更好地捕捉序列中的依靠联系,然后进步自然语言处理等使命的作用。

image.png

Encoder

encoder首要用于将输入序列转换为一系列躲藏更深层的表明,这些躲藏深层表明可以用于进一步处理或生成输出序列。

多头自留意力层

多头自留意力层是 Transformer 的中心部分,前面现已有详细的述说,它经过对输入序列中每个元素与所有元素的相似度进行核算,得到每个元素关于其他元素的权重,并运用这些权重进行加权均匀,得到每个元素的向量表明。这个进程可以看做是将序列中的每个元素与其他元素进行“跨步”衔接(skip connection),然后更好地捕捉序列中的长程依靠联系。

全衔接前馈层

全衔接前馈层对应Transformer结构图中的Feed Forward,它是多头自留意力层的一个弥补,用于对编码后的序列进行进一步处理,增强模型的表明才能。详细地,全衔接前馈层包含两个线性改换,中心运用激活函数(如 ReLU)进行非线性改换,然后生成愈加杂乱的特征表明。

Transformer 的 Encoder 模块经过多头自留意力层和全衔接前馈层对输入序列进行编码,然后捕捉序列中的依靠联系和特征表明。这些编码信息可以传递给 Decoder 模块,用于生成方针序列。

Add&Norm

Transformer中的Add & Norm是指在每个Multi-Head Attention和Feedforward层之后进行的一种规范化技能,意图是加速模型收敛速度并进步模型功能。这种想法来自于ResNet。

在Multi-Head Attention和Feedforward层中,模型进行一些线性改换和非线性改换,这些改换可能会导致梯度消失或梯度爆炸问题。为了解决这个问题,Transformer在每个层后添加了一个残差衔接(residual connection),将输入和输出相加。在残差衔接后,运用Layer Normalization对成果进行规范化。Layer Normalization是一种对数据进行归一化的办法,经过对每个特征维度上的数据进行标准化,使得不同特征维度上的数据具有相同的分布。最终,将归一化的成果与残差衔接相加,得到该层的终究输出。

image.png
Add & Norm技能可以有效地减轻梯度消失和梯度爆炸问题,同时也有助于加速模型的收敛速度。最终编码器的输出是作为解码器第二个多头留意力模块的k,v。

Decoder

在Transformer模型中,outputs(shifted right)指的是模型输出序列中每个时间步的猜测值,可是这些猜测值与真实输出序列相比,都向右移动了一个时间步,由于这个序列需求先进入encoder。这种右移操作通常称为“右移一位”或“shifted right”。这种右移操作可以使得decoder的输入序列与encoder的输入序列相同。Decoder的作用是将编码器产生的拥有高档特征的向量(context vector)与方针序列中的单词一起,逐一地生成输出序列,将编码器输出的高档特征转化为人类可读的方式。

transformer_decoding_1.gif

transformer_decoding_2.gif

Masked Mult-Head Attention

Decoder的第一个子层是一个“masked”多头自留意力层,他的意思是掩盖,这意味着在核算留意力时,只答应当时方位之前的方位作为查询进行留意力核算,不答应当时方位之后的方位参加核算。这是由于在解码器中,咱们需求逐渐生成输出,而不是一次性生成所有输出。假如答应当时方位之后的方位参加核算,那么就相当于咱们在生成当时方位的输出时运用了后边方位的信息,这会导致模型走漏未来信息,使得模型在生成输出时产生过错。

这种未来信息走漏会导致模型在生成输出时产生过错,由于模型会过度依靠未来信息,而忽略当时方位及之前的信息,然后导致模型对输入的了解呈现偏差,输出的成果也就不准确了。

因而,为了防止这种情况,解码器中运用“masked”多头自留意力层来限制只运用当时方位及其之前的信息进行核算,确保每个方位的输出只受前面方位的影响,然后防止了未来信息的走漏。

image.png

方位编码

在Transformer模型中,为了将序列的方位信息引进模型中,需求对输入序列的每个方位进行编码。这是经过在输入序列中添加一个方位编码向量来完成的。方位编码向量可以被看作是一个与词向量同样维度的向量,其间每个元素的值是依据该方位以及每个维度的信息核算得到的。详细地,关于方位 pospos和维度 ii,方位编码向量 PEpos,iPE_{pos, i} 的核算方式如下:

PEpos,i={sin⁡(px100002/4dmodel)iis evencos⁡(pg2/4dmodel100002(i−1)/dmed)iis oddPE_{\mathrm{pos,}i}=\begin{cases}\sin\left(\frac{px}{10000^{2/4}\mathrm{dmodel}}\right)&i\text{is even}\\ \cos\left(\frac{pg^{2/4}\mathrm{dmodel}}{10000^{2(i-1)/d}\mathrm{med}}\right)&i\text{is odd}\end{cases}

其间, PEpos,iPE_{pos, i} 表明方位编码矩阵中方位 pospos上的第 ii 维元素, dmodeld_{\text{model}} 是词向量和方位编码向量的维度, pospos 是当时方位的索引。公式中的 sinsincoscos 函数别离代表正弦函数和余弦函数。它们可以给每个方位编码向量赋予一个共同的形式,然后区别不同方位的输入。在核算中,方位编码向量会被加到对应的词向量中,然后产生终究的输入向量。下面咱们用一张图来直观感受一下这些形式。鄙人面的图中,每一行对应一个向量的方位编码。因而,第一行将是咱们要添加到输入序列中第一个单词的嵌入中的向量。每行包含512512个值 – 每个值的取值范围在11−1-1之间。咱们现已用五颜六色编码来使形式可见。

image.png

需求留意的是,由于方位编码向量是经过正弦和余弦函数进行核算的,所以在核算中不需求额定的训练,也不需求对每个方位编码向量进行更新。方位编码向量只需求在模型的初始化阶段核算一次,然后在每次输入序列的编码中运用即可。

下面咱们用一个动画演示一下Transformer核算整个进程:

transform20fps.gif

Transformer的pytorch完成

首要,咱们需求导入所需的库和模块:

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

然后,咱们界说Transformer模型的首要组件,包含编码器、解码器和整个Transformer模型本身。

在编码器和解码器中,咱们完成了多头自留意力机制(multi-head self-attention)和前馈神经网络(feed-forward network)这两个中心组件。

在Transformer模型中,咱们将编码器和解码器组合在一起,并添加一些额定的组件,如嵌入层(embedding layer)、方位编码器(position encoding)和输出层(output layer)。

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super(MultiHeadAttention, self).__init__()
        self.d_model = d_model  # 模型的维度
        self.n_heads = n_heads  # 多头留意力的头数
        self.d_k = d_model // n_heads  # 每个头的维度,确保可以整除
        # 创建权重矩阵
        self.W_Q = nn.Linear(d_model, d_model)  # 查询向量的权重矩阵
        self.W_K = nn.Linear(d_model, d_model)  # 键向量的权重矩阵
        self.W_V = nn.Linear(d_model, d_model)  # 值向量的权重矩阵
        # 最终的线性层
        self.W_O = nn.Linear(d_model, d_model)  # 输出向量的权重矩阵
    def forward(self, Q, K, V, mask=None):
        batch_size = Q.size(0)  # 获取输入数据的批次大小
        # 经过线性层,别离核算 Q、K、V 的投影向量
        Q = self.W_Q(Q)
        K = self.W_K(K)
        V = self.W_V(V)
        # 将 Q、K、V 投影向量分裂为多个头
        Q = Q.view(batch_size, -1, self.n_heads, self.d_k)
        K = K.view(batch_size, -1, self.n_heads, self.d_k)
        V = V.view(batch_size, -1, self.n_heads, self.d_k)
        # 核算留意力得分
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        # ...

MultiHeadAttention类中,咱们完成了多头自留意力机制,其间包含了以下首要部分:

  1. __init__ 办法:初始化函数,界说了模型的维度、多头留意力的头数、每个头的维度,并创建了权重矩阵(查询、键、值、输出)。
  2. forward 办法:前向传播函数,用于核算多头自留意力机制的输出。在此函数中,咱们首要经过线性层,将输入的 Q、K、V 别离投影到 d_model 维度空间上。
  3. 接着,咱们将 Q、K、V 投影向量分裂为多个头,以便进行并行核算。
  4. 然后,咱们核算留意力得分 scores,经过将 Q 与 K 转置后相乘,再除以 math.sqrt(self.d_k)。留意力得分用于核算每个值向量的权重,以便对值向量进行加权求和。

在这儿,咱们只是核算了留意力得分,并没有进行权重的核算。接下来,咱们将运用 softmax 函数将留意力得分转换为权重:

# 对 scores 进行缩放和掩码操作
if mask is not None:
mask = mask.unsqueeze(1)
scores = scores.masked_fill(mask == 0, -1e9)
# 将留意力得分进行 softmax 核算
    attn_weights = F.softmax(scores, dim=-1)
    # 将权重与 V 向量相乘
    attn_output = torch.matmul(attn_weights, V)
    # 将多头留意力向量拼接在一起
    attn_output = attn_output.view(batch_size, -1, self.d_model)
    # 经过最终的线性层,得到终究的多头留意力向量
    attn_output = self.W_O(attn_output)
    return attn_output, attn_weights
class FeedForward(nn.Module):
def init(self, d_model, d_ff):
super(FeedForward, self).init()
 # 创建两个线性层
    self.linear_1 = nn.Linear(d_model, d_ff)
    self.linear_2 = nn.Linear(d_ff, d_model)
def forward(self, x):
    # 经过 ReLU 激活函数
    x = F.relu(self.linear_1(x))
    x = self.linear_2(x)
    return x
class EncoderLayer(nn.Module):
def init(self, d_model, n_heads, d_ff, dropout=0.1):
super(EncoderLayer, self).init()
self.multihead_attn = MultiHeadAttention(d_model, n_heads)
self.ff = FeedForward(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)

以上的代码是 Transformer 模型的一部分,包含了 MultiHeadAttention、FeedForward 和 EncoderLayer 三个类。下面是代码解说:

  • MultiHeadAttention:多头留意力机制,将输入的 Q、K、V 矩阵别离经过线性改换得到 Q、K、V 的查询矩阵、键矩阵和值矩阵,然后核算留意力得分,并经过 softmax 函数将留意力得分转换为权重,最终将权重与 V 向量相乘得到多头留意力向量。
  • FeedForward:前馈神经网络,经过两个线性层和 ReLU 激活函数对输入进行改换。
  • EncoderLayer:编码器层,包含多头留意力机制、前馈神经网络、LayerNormalization 和 Dropout 层,其间 LayerNormalization 是为了削减训练进程中的内部协变量偏移,Dropout 是为了防止过拟合。

这三个类是 Transformer 模型的重要组成部分,可以用于语言建模、机器翻译等使命中。其间 MultiHeadAttention 的思维也被广泛应用于其他范畴的深度学习模型中。

Refernce 引用

  1. Vaswani, Ashish et al. “Attention is All you Need.” ArXiv abs/1706.03762 (2017): n. pag.