注意力机制我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.
摘自论文原文: An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is computed as a weighted sum of the values, where the weight assigned to each value is computed by a compatibility function of the query with the corresponding key.
注意力机制的核心公式为:
对于注意力机制来说,我们需要三个基本的输入: 。
在Transformer中Encoder使用的 ,其实都是从输入矩阵 经过线性变化得来的。
简单来说就是:
img
在这张图中, 与 经过MatMul,生成了相似度矩阵。对相似度矩阵每个元素除以 ,其为 的维度大小。这个除法被称为Scale。
注意力的计算过程:
img
query 和 key 进行相似度计算,得到一个query 和 key 相关性的分值
将这个分值进行归一化(softmax),得到一个注意力的分布
使用注意力分布和 value 进行计算,得到一个融合注意力的更好的 value 值
为了增强拟合性能,Transformer对Attention继续扩展,提出了多头注意力(Multi-Head Attention)。
img
对于同样的输入 ,我们定义多组不同的 ,计算得到多组 ,然后学习到不同的数据。
比如我们定义8组参数,同样的输入 ,将得到8个不同的输出 ,在输出到下一层前,我们需要将8个输出拼接到一起,进行一次线性变换,将维度降低到我们想要的维度。
Self-AttentionSelf-attention就本质上是一种特殊的attention。
attention和self attention 其具体计算过程是一样的,只是计算对象发生了变化而已。
attention是source对target的attention,
而self attention 是source 对source的attention。
即输入的Q=K=V。
在翻译任务中,如果源句子≠目标句子,那么你用目标句子中的词去query源句子中的所有词的key,再做相应运算,这种方式就是Attention;如果你的需求不是翻译,而是对当前这句话中某几个词之间的关系更感兴趣,期望对他们进行计算,这种方式就是Self-Attention。
从范围上来讲,注意力机制是包含自注意力机制的。注意力机制给定K、Q、V,其中Q和V可以是任意的,而K往往等于V(不相等也可以);而自注意力机制要求K=Q=V。
Transformer模型基于Encoder-Decoder架构。一般地,在Encoder-Decoder中,Encoder部分将一部分信息抽取出来,生成中间编码信息,发送到Decoder中。
img
我们可以将整个架构抽象为四个组成部分:
输入部分
输出部分
编码器部分
解码器部分
输入部分img
源文本嵌入层及其位置编码器
目标文本嵌入层及其位置编码器
关于位置编码器 Positional Encoding:
Transformer模型的输入为一系列词,词需要转化为词向量。一般的语言模型都需要使用Embedding层,用以将词转化为词向量。Transformer没有采用RNN的结构,不能利用单词的顺序信息,但顺序信息对于NLP任务来说非常重要。在此基础上,Transformer增加了位置编码(Positional Encoding)。
代表单词在句子中的位置, 表示词向量的维度, 表示偶数维度, 表示奇数维度。生成的是[−1,1]区间内的实数。
Embedding层和Positional Encoding层的代码实现:x的大小为 (batch_size, sequence_length, embedding_dim) pe的大小为 (max_len, embedding_dim) 这里的 sequence_length max_len,需要匹配形状。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import torchimport torch.nn as nnimport mathfrom torch.autograd import Variableclass Embeddings (nn.Module): def __init__ (self, d_model, vocab ): """ d_model: 指词嵌入的维度 vocab: 指词表的大小 """ super (Embeddings, self).__init__() self.lut = nn.Embedding(vocab, d_model) self.d_model = d_model def forward (self, x ): return self.lut(x) * math.sqrt(self.d_model) class PositionalEncoding (nn.Module): def __init__ (self, d_model, dropout, max_len=5000 ): """ d_model: 词嵌入维度, dropout: 置0比率, max_len: 每个句子的最大长度 """ super (PositionalEncoding, self).__init__() self.dropout = nn.Dropout(p=dropout) pe = torch.zeros(max_len, d_model) position = torch.arange(0 , max_len).unsqueeze(1 ) div_term = torch.exp(torch.arange(0 , d_model, 2 ) * -(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 ) self.register_buffer('pe' , pe) def forward (self, x ): x = x + self.pe[:, :x.size(1 )].detach() return self.dropout(x)
PYTHON
编码器部分
由N个编码器层堆叠而成
每个编码器层由两个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
Add & Norm层由 Add 和 Norm 两部分组成。Add 类似 ResNet 提出的残差连接,以解决深层网络训练不稳定的问题。Norm 为归一化层,即 Layer Normalization ,通常用于 RNN 结构。
Feed Forward层由两个全连接层构成,第一层的激活函数为 ReLu,第二层不使用激活函数。
Multi-Head Attention 采用了 Mask 操作,即掩码张量,因为在翻译的过程中是顺序翻译的,即翻译完第 i 个单词,才可以翻译第i+1 个单词。
Mask
0到4即代表按顺序的前5个单词。
Mask1 2 3 4 5 6 7 8 9 10 11 12 def subsequent_mask (size ): """ size是掩码张量最后两个维度的大小,形成一个方阵 """ attn_shape = (1 , size, size) subsequent_mask = np.triu(np.ones(attn_shape), k=1 ).astype('uint8' ) return torch.from_numpy(1 - subsequent_mask)
PYTHON
注意力的计算实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import torch.nn.functional as Fdef attention (query, key, value, mask=None , dropout=None ): """ 输入分别是query, key, value mask: 掩码张量, dropout:置零 """ d_k = query.size(-1 ) scores = torch.matmul(query, key.transpose(-2 , -1 )) / math.sqrt(d_k) if mask is not None : scores = scores.masked_fill(mask == 0 , -1e9 ) p_attn = F.softmax(scores, dim = -1 ) if dropout is not None : p_attn = dropout(p_attn) return torch.matmul(p_attn, value), p_attn
PYTHON
多头注意力机制实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import copydef clones (module, N ): """ 用于生成相同网络层的克隆函数, 它的参数module表示要克隆的目标网络层, N代表需要克隆的数量""" return nn.ModuleList([copy.deepcopy(module) for _ in range (N)])class MultiHeadedAttention (nn.Module): def __init__ (self, head, embedding_dim, dropout=0.1 ): """ head——头数 embedding_dim——词嵌入的维度, dropout——置0比率,默认是0.1 """ super (MultiHeadedAttention, self).__init__() assert embedding_dim % head == 0 self.d_k = embedding_dim // head self.head = head self.linears = clones(nn.Linear(embedding_dim, embedding_dim), 4 ) self.attn = None self.dropout = nn.Dropout(p=dropout) def forward (self, query, key, value, mask=None ): if mask is not None : mask = mask.unsqueeze(0 ) batch_size = query.size(0 ) query, key, value = \ [model(x).view(batch_size, -1 , self.head, self.d_k).transpose(1 , 2 ) for model, x in zip (self.linears, (query, key, value))] x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout) x = x.transpose(1 , 2 ).contiguous().view(batch_size, -1 , self.head * self.d_k) return self.linears[-1 ](x)
PYTHON
contiguous() 是 PyTorch 中的一个方法,用于返回一个具有连续内存的新张量,即将张量的存储重新排列为连续的内存块,使得张量的元素在内存中的布局是连续的。 在上面,由于转置操作,储存内存变得不连续了,所以需要重新规划。
前馈全连接层1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class PositionwiseFeedForward (nn.Module): def __init__ (self, d_model, d_ff, dropout=0.1 ): """ d_model——线性层的输入维度,也是第二个线性层的输出维度 d_ff——第二个线性层的输入维度和第一个线性层的输出维度 dropout=0.1 """ super (PositionwiseFeedForward, self).__init__() self.w1 = nn.Linear(d_model, d_ff) self.w2 = nn.Linear(d_ff, d_model) self.dropout = nn.Dropout(dropout) def forward (self, x ): return self.w2(self.dropout(F.relu(self.w1(x))))
PYTHON
规范化层1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class LayerNorm (nn.Module): def __init__ (self, features, eps=1e-6 ): """ features——表示词嵌入的维度, eps——它是一个足够小的数, 在规范化公式的分母中出现,防止分母为0.默认是1e-6. """ super (LayerNorm, self).__init__() self.a2 = nn.Parameter(torch.ones(features)) self.b2 = nn.Parameter(torch.zeros(features)) self.eps = eps def forward (self, x ): mean = x.mean(-1 , keepdim=True ) std = x.std(-1 , keepdim=True ) return self.a2 * (x - mean) / (std + self.eps) + self.b2
PYTHON
self.a2 是一个用 nn.Parameter 封装的可学习参数张量,它的形状为 (features,),其中 features 表示输入特征的维度。这个参数控制归一化后的结果的缩放比例。在初始化时,我们将其初始化为一个全为1的张量,表示初始时不进行缩放。
self.b2 同样是一个用 nn.Parameter 封装的可学习参数张量,形状也为 (features,)。这个参数控制归一化后的结果的平移偏移。在初始化时,我们将其初始化为一个全为0的张量,表示初始时不进行平移。
在进行 Layer Normalization 过程中,我们先计算输入张量 x 沿着最后一个维度的均值和标准差,然后对输入进行归一化。归一化的结果为 (x - mean) / (std + eps),其中 eps 是一个足够小的数,用于防止分母为0的情况。然后,我们将归一化后的结果乘以 self.a2(缩放)并加上 self.b2(平移),得到最终的归一化结果。
子层连接结构(Add)1 2 3 4 5 6 7 8 9 10 11 12 class SublayerConnection (nn.Module): def __init__ (self, size, dropout=0.1 ): super (SublayerConnection, self).__init__() self.norm = LayerNorm(size) self.dropout = nn.Dropout(p=dropout) def forward (self, x, sublayer ): return x + self.dropout(sublayer(self.norm(x)))
PYTHON
编码器1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class EncoderLayer (nn.Module): def __init__ (self, size, self_attn, feed_forward, dropout ): """ size,词嵌入维度的大小,它也将作为编码器层的大小 self_attn,多头自注意力子层实例化对象,自注意力机制 feed_froward,前馈全连接层实例化对象 """ super (EncoderLayer, self).__init__() self.self_attn = self_attn self.feed_forward = feed_forward self.sublayer = clones(SublayerConnection(size, dropout), 2 ) self.size = size def forward (self, x, mask ): x = self.sublayer[0 ](x, lambda x: self.self_attn(x, x, x, mask)) return self.sublayer[1 ](x, self.feed_forward)class Encoder (nn.Module): def __init__ (self, layer, N ): super (Encoder, self).__init__() self.layers = clones(layer, N) self.norm = LayerNorm(layer.size) def forward (self, x, mask ): for layer in self.layers: x = layer(x, mask) return self.norm(x)
PY
解码器部分
由N个解码器层堆叠而成
每个解码器层由三个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器层Decoder Block 的第一个 Multi-Head Attention 采用了 Mask 操作,第二个 Multi-Head Attention 主要的区别在于 Attention 的 K, V 矩阵不是来自上一个 Decoder Block 的输出计算的,而是来自Encoder的编码信息矩阵C。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class DecoderLayer (nn.Module): def __init__ (self, size, self_attn, src_attn, feed_forward, dropout ): ''' size——词嵌入的维度大小,解码器的尺寸 self_attn——多头自注意力对象(Q=K=V) src_attn——多头注意力对象(Q!=K=V) feed_forward——前馈全连接层 dropout——置零比率 ''' super (DecoderLayer, self).__init__() self.size = size self.self_attn = self_attn self.src_attn = src_attn self.feed_forward = feed_forward self.sublayer = clones(SublayerConnection(size, dropout), 3 ) def forward (self, x, memory, source_mask, target_mask ): x = self.sublayer[0 ](x, lambda x: self.self_attn(x, x, x, target_mask)) x = self.sublayer[1 ](x, lambda x: self.src_attn(x, memory, memory, target_mask)) return self.sublayer[2 ](x, self.feed_forward)
PYTHON
解码器1 2 3 4 5 6 7 8 9 10 11 class Decoder (nn.Module): def __init__ (self, layer, N ): super (Decoder, self).__init__() self.layers = clones(layer, N) self.norm = LayerNorm(layer.size) def forward (self, x, memory, source_mask, target_mask ): for layer in self.layers: x = layer(x, memory, source_mask, target_mask) return self.norm(x)
PYTHON
输出部分output
线性层&softmax层通过对上一步的线性变化得到指定维度的输出,并将向量进行归一化操作。
1 2 3 4 5 6 7 class Generator (nn.Module): def __init__ (self, d_model, vocab_size ): super (Generator, self).__init__() self.project = nn.Linear(d_model, vocab_size) def forward (self, x ): return F.log_softmax(self.project(x), dim=-1 )
PYTHON
最终模型构建1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def make_model (source_vocab, target_vocab, N=6 , d_model=512 , d_ff=2048 , head=8 , dropout=0.1 ): c = copy.deepcopy attn = MultiHeadedAttention(head, d_model) ff = PositionwiseFeedForward(d_model, d_ff, dropout) position = PositionalEncoding(d_model, dropout) model = EncoderDecoder( Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N), Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), N), nn.Sequential(Embeddings(d_model, source_vocab), c(position)), nn.Sequential(Embeddings(d_model, target_vocab), c(position)), Generator(d_model, target_vocab)) for p in model.parameters(): if p.dim() > 1 : nn.init.xavier_uniform(p) return model
PYTHON
nn.Sequential 是 PyTorch 中的一个容器,用于按顺序组合多个神经网络模块(如层、激活函数等),形成一个整体的神经网络模型。它可以简化模型的构建过程,使代码更加简洁易读。
具体地,nn.Sequential 接受一个包含多个神经网络模块的列表或序列作为参数,然后将这些模块按顺序组合在一起,形成一个完整的神经网络模型。当输入数据进入 nn.Sequential 时,它会按照列表中模块的顺序依次进行前向传播,将每个模块的输出作为下一个模块的输入,直到所有模块都被处理完毕,最终得到整个模型的输出。
最终我们得到的模型(下方较长):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 EncoderDecoder( (encoder): Encoder( (layers): ModuleList( (0-5): 6 x EncoderLayer( (self_attn): MultiHeadedAttention( (linears): ModuleList( (0-3): 4 x Linear(in_features =512, out_features =512, bias =True ) ) (dropout): Dropout(p =0.1, inplace =False ) ) (feed_forward): PositionwiseFeedForward( (w1): Linear(in_features =512, out_features =2048, bias =True ) (w2): Linear(in_features =2048, out_features =512, bias =True ) (dropout): Dropout(p =0.1, inplace =False ) ) (sublayer): ModuleList( (0-1): 2 x SublayerConnection( (norm): LayerNorm() (dropout): Dropout(p =0.1, inplace =False ) ) ) ) ) (norm): LayerNorm() ) (decoder): Decoder( (layers): ModuleList( (0-5): 6 x DecoderLayer( (self_attn): MultiHeadedAttention( (linears): ModuleList( (0-3): 4 x Linear(in_features =512, out_features =512, bias =True ) ) (dropout): Dropout(p =0.1, inplace =False ) ) (src_attn): MultiHeadedAttention( (linears): ModuleList( (0-3): 4 x Linear(in_features =512, out_features =512, bias =True ) ) (dropout): Dropout(p =0.1, inplace =False ) ) (feed_forward): PositionwiseFeedForward( (w1): Linear(in_features =512, out_features =2048, bias =True ) (w2): Linear(in_features =2048, out_features =512, bias =True ) (dropout): Dropout(p =0.1, inplace =False ) ) (sublayer): ModuleList( (0-2): 3 x SublayerConnection( (norm): LayerNorm() (dropout): Dropout(p =0.1, inplace =False ) ) ) ) ) (norm): LayerNorm() ) (src_embed): Sequential( (0): Embeddings( (lut): Embedding(11, 512) ) (1): PositionalEncoding( (dropout): Dropout(p =0.1, inplace =False ) ) ) (tgt_embed): Sequential( (0): Embeddings( (lut): Embedding(11, 512) ) (1): PositionalEncoding( (dropout): Dropout(p =0.1, inplace =False ) ) ) (generator): Generator( (project): Linear(in_features =512, out_features =11, bias =True ) ) )
ROUTEROS
参考:
https://lulaoshi.info/deep-learning/attention/transformer-attention.html#self-attention%E4%B8%AD%E7%9A%84q%E3%80%81k%E3%80%81v
https://juejin.cn/post/7125629962769399838
http://121.199.45.168:13008/04_mkdocs_transformer/3%20%E8%BE%93%E5%85%A5%E9%83%A8%E5%88%86%E5%AE%9E%E7%8E%B0.html