敞开成长之旅!这是我参加「日新方案 12 月更文挑战」的第5天,点击检查活动详情

关于transformer中的attention部分中的矩阵运算,在上一篇文章 Transformer原理解读篇<一> – () 浅浅解析了一番。

本文就Transformer剩余的部分做个了解。


参阅资料

Transformer【原文】 :Attention is all you need

李宏毅 【seq2seq】 :seq2seq.pdf

李宏毅 【Youtube】 Self-Attention :YouToBe-Transformer

知乎图解 【Transformer】 :知乎图解Transformer

哈佛团队注解版 Transformer 【Code】 github1s.com/harvardnlp/…


布景

这儿再补充一下Transformer呈现的布景。

The goal of reducing sequential computation also forms the foundation of the Extended Neural GPU, ByteNet and ConvS2S, all of which use convolutional neural networks as basic building block, computing hidden representations in parallel for all input and output positions.

削减次序核算的目标也构成了扩展神经GPU、ByteNet和ConvS2S的基础,一切这些都运用卷积神经网络作为基本构建块,并行核算一切输入和输出方位的躲藏表明。

In these models, the number of operations required to relate signals from two arbitrary input or output positions grows in the distance between positions, linearly for ConvS2S and logarithmically for ByteNet. This makes it more difficult to learn dependencies between distant positions.

在这些模型中,将来自两个恣意输入或输出方位的信号关联起来所需的操作数量随着方位之间的间隔增加,ConvS2S为线性增加,ByteNet为对数增加。这使得学习长途方位之间的依赖关系变得更加困难。

In the Transformer this is reduced to a constant number of operations, albeit at the cost of reduced effective resolution due to averaging attention-weighted positions, an effect we counteract with Multi-Head Attention.

在 Transformer 中,这个操作数现已削减到指定的数量,虽然价值是由于留意力加权方位的均匀而下降了有用分辨率,咱们用多头留意力抵消了这种影响

End-to-end memory networks are based on a recurrent attention mechanism instead of sequencealigned recurrence and have been shown to perform well on simple-language question answering and language modeling tasks.

端到端记忆网络根据循环留意力机制,而不是序列对齐的循环,并已被证明在简略言语问答和言语建模任务中表现良好。

To the best of our knowledge, however, the Transformer is the first transduction model relying entirely on self-attention to compute representations of its input and output without using sequence aligned RNNs or convolution.

Transformer 是第一个彻底依托自我关注来核算其输入和输出表明的转导模型,而无需运用序列对齐的RNN或卷积。

仍然回到Transformer架构总图:

「论文研读」Transformer原理解读篇

Most competitive neural sequence transduction models have an encoder-decoder structure (cite). Here, the encoder maps an input sequence of symbol representations (x1,…,xn)(x_1, …, x_n) to a sequence of continuous representations z=(z1,…,zn)\mathbf{z} = (z_1, …, z_n). Given z\mathbf{z}, the decoder then generates an output sequence (y1,…,ym)(y_1,…,y_m) of symbols one element at a time. At each step the model is auto-regressive (cite), consuming the previously generated symbols as additional input when generating the next.

大多数竞争性神经序列转导模型都具有编码器-解码器结构。编码器: 将输入序列(x1,…,xn)(x_1, …, x_n)映射到连续的序列z=(z1,…,zn)\mathbf{z} = (z_1, …, z_n)。 根据给定的zz,编码器会生成一个输出序列(y1,…,ym)(y_1,…,y_m)。一个元素一个元素的生成。在每个步骤中,模型都是自回归的,在生成下一个输出符号时需求消耗先前生成的符号作为附加输入。

规范的Encoder-Decoder架构代码如下:

class EncoderDecoder(nn.Module):
    """
    A standard Encoder-Decoder architecture. Base for this and many
    other models.
    """
    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
        super(EncoderDecoder, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = src_embed
        self.tgt_embed = tgt_embed
        self.generator = generator
    def forward(self, src, tgt, src_mask, tgt_mask):
        "Take in and process masked src and target sequences."
        return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)
    def encode(self, src, src_mask):
        return self.encoder(self.src_embed(src), src_mask)
    def decode(self, memory, src_mask, tgt, tgt_mask):
        return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)

Transformer也遵循这种Encoder-Decoder全体架构。

The Transformer follows this overall architecture using stacked self-attention and point-wise, fully connected layers for both the encoder and decoder。

Transformer 遵循这种全体架构,编码器和解码器均运用堆叠的self-attention和point-wise、全衔接层。

这儿的point-wise,我猜是逐点相乘的意思,即前文讲解的点积运算。假如有更科学的解释,请留言嗷


Encoder

Add & Norm

Encoder中的Add & Norm表明的是:a (Input)+ b (Output) = b’ ,再将b’进行LayerNorm。

关于残差衔接,最熟悉的网络结构莫过于残差网络 ResNet了吧。

长下面这样:

「论文研读」Transformer原理解读篇

在forword函数中一般:

def forward(self, x):
    identity = x
    out = self.layer1(x)
    ...
    out += identity  # 残差衔接
    out = self.relu(out)

Transformer中的Add操作也是如此,在运用Multi-Head Attention之前先保存输入值,然后通过多头之后在与保存值相加,在运用LayerNorm进行层归一化。

这儿拓宽一下LayerNorm和BatchNorm的差异:

BN & LN

参阅链接:

1.zhuanlan.zhihu.com/p/441573901 Batch Norm详解之原理及为什么神经网络需求它

2.zhuanlan.zhihu.com/p/521535855 超细节的 BatchNorm/BN/LayerNorm/LN 知识点

3.blog.csdn.net/qq_37541097… 强推!Batch Normalization详解以及pytorch试验

?定义

BatchNorm: 对batch个样本的每一个特征维度进行归一化

BN的意图是使咱们的一批feature map满足均值为0,方差为1的散布规则。

LayerNorm: 对于每一个样本的的一切特征进行归一化,(能够理解为将二维矩阵进行转置之后进行BatchNorm,在转置回来)

LN 一般调配RNN来运用 (transformer顶用的归一化也是LN)

Mean均值:ui=1M∑Aiu_i = \frac 1M \sum A_i

Std Dev规范差:=1M∑(Ai−)2\sigma = \sqrt {\frac 1M \sum(A_i – \mu )^2}

Normalize Ai=Ai−ii2+\hat A_i = \frac {A_i – \mu_i}{\sqrt{\sigma_i^2 + \epsilon}}

Batch Normalize BNi~=⊙Ai+\tilde{BN_i} = \gamma \odot \hat A_i + \beta

( \gamma缩放参数,\beta 平移参数)

2\mu \ \sigma^2 在正向传达中统计得到

\gamma \beta 在反向传达过程中练习得到, \gamma 默认值 1, \beta 默认值为 0

\epsilon 是为了防止分母为0的情况呈现

?差异

batch 和 layer归一化的差异:

「论文研读」Transformer原理解读篇

batchNorm是对一个batch中的一切的样本的同一个维度进行归一化;LayerNorm是对一个样本中一切特征维度进行归一化。

说简略点,其实深度学习里的正则化办法便是”通过把一部分不重要的杂乱信息损失掉,以此来下降拟合难度以及过拟合的危险,然后加速模型收敛“。Normalization意图便是让散布稳定下来(下降各维度数据的方差)。

?BN和LN 有哪些差异

1)两者做Norm的维度不一样,BN是在Batch维,而LN一般是在终究一维

2)BN需求在练习过程中,滑动均匀累积每个神经元的均值和方差,并保存在模型文件顶用于推理过程,而LN不需求

3)由于Norm维度的差异,使得它们适用的范畴也有差异,BN更多用于CV范畴,LN更多用于NLP范畴

?为什么Transformer/BERT运用LN,而不运用BN

  1. layer normalization 有助于得到一个球体空间中契合0均值1方差高斯散布的 embedding, batch normalization不具备这个功用。
  2. layer normalization能够对transformer学习过程中由于多词条embedding累加可能带来的“尺度”问题施加约束,相当于对表达每个词一词多义的空间施加了约束,有用下降模型方差。batch normalization也不具备这个功用。

LN代码完结

class LayerNorm(nn.Module):
    "Construct a layernorm module (See citation for details)."
    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(features))
        self.b_2 = 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.a_2 * (x - mean) / (std + self.eps) + self.b_2

BN代码完结

直接运用torch的内置函数:

import torch.nn as nn
import torch
bn = nn.BatchNorm2d(2, eps=1e-5)
# 随机生成一个batch为2,channel为2height=width=2的特征向量
# [batch, channel, height, width]
feature1 = torch.randn(2, 2, 2, 2)
output = bn(feature1)
print(output)

自定义完结:

import numpy as np
import torch.nn as nn
import torch
def bn_process(feature, mean, var):
    feature_shape = feature.shape
    for i in range(feature_shape[1]):
        # [batch, channel, height, width]
        feature_t = feature[:, i, :, :]
        mean_t = feature_t.mean()
        # 整体规范差
        std_t1 = feature_t.std()
        # 样本规范差
        std_t2 = feature_t.std(ddof=1)
        # bn process
        # 这儿记住加上eps和pytorch保持共同
        feature[:, i, :, :] = (feature[:, i, :, :] - mean_t) / np.sqrt(std_t1 ** 2 + 1e-5)
        # update calculating mean and var
        mean[i] = mean[i] * 0.9 + mean_t * 0.1
        var[i] = var[i] * 0.9 + (std_t2 ** 2) * 0.1
    print(feature)
# 随机生成一个batch为2,channel为2,height=width=2的特征向量
# [batch, channel, height, width]
feature1 = torch.randn(2, 2, 2, 2)
# 初始化统计均值和方差
calculate_mean = [0.0, 0.0]
calculate_var = [1.0, 1.0]
# print(feature1.numpy())
# 留意要运用copy()深复制
bn_process(feature1.numpy().copy(), calculate_mean, calculate_var)

其他归一化办法

除了LN和BN归一化,还有IN(Instance Normalization)、 GN(Group Normalization)

举个比如:

核算机视觉(CV)范畴的数据xxx一般是4维方式,假如把x∈RNCHW x \in \mathbb{R}^{N \times C \times H \times W}类比为一摞书,这摞书总共有 N 本,每本有 C 页,每页有 H 行,每行 W 个字符。

核算均值时:

  • BN 相当于把这些书按页码一一对应地加起来(例如:第1本书第36页,加第2本书第36页…),再除以每个页码下的字符总数:NHW,因而能够把 BN 看成求“均匀书”的操作(留意这个“均匀书”每页只有一个字)
  • LN 相当于把每一本书的一切字加起来,再除以这本书的字符总数:CHW,即求整本书的“均匀字”
  • IN 相当于把一页书中一切字加起来,再除以该页的总字数:HW,即求每页书的“均匀字”
  • GN 相当于把一本 C 页的书均匀分红 G 份,每份成为有 C/G 页的小册子,对这个 C/G 页的小册子,求每个小册子的“均匀字”

  • Switchable Normalization 即:将 BN、LN、IN 结合,赋予权重,让网络自己去学习归一化层应该运用什么办法

相关论文:

  1. Batch Normalization,其论文:arxiv.org/pdf/1502.03…

  2. Layer Normalizaiton,其论文:arxiv.org/pdf/1607.06…

  3. Instance Normalization,其论文:arxiv.org/pdf/1607.08…

  4. Group Normalization,其论文:arxiv.org/pdf/1803.08…

  5. Switchable Normalization,其论文:arxiv.org/pdf/1806.10…

运用BN需求留意的问题

运用BatchNorm的时分不需求运用bias(偏置

「论文研读」Transformer原理解读篇

通过Add & Norm 之后 FFN便是Encoder终究一个未解之谜了。

FFN – FeedForward Network

Feed Forward 层比较简略,是一个两层的全衔接层,第一层的激活函数为 Relu,第二层不运用激活函数,对应的公式如下:

「论文研读」Transformer原理解读篇

X是输入,Feed Forward 终究得到的输出矩阵的维度与X共同。

在详细代码完结的时分运用Linear层即可达到意图。

class PositionwiseFeedForward(nn.Module):
    "Implements FFN equation."
    # d_ff=2048  d_model=512
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionwiseFeedForward, self).__init__()
        self.w_1 = nn.Linear(d_model, d_ff)
        self.w_2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)
    def forward(self, x):
        return self.w_2(self.dropout(self.w_1(x).relu()))

整个Encoder便是由多个Encoder Block堆叠而成。

以下代码仅供参阅:

class EncoderLayer(nn.Module):
    "Encoder is made up of self-attn and feed forward (defined below)"
    def __init__(self, size, self_attn, feed_forward, dropout):
        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):
        "Follow Figure 1 (left) for connections."
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
        return self.sublayer[1](x, self.feed_forward)
# 残差衔接
class SublayerConnection(nn.Module):
    """
    A residual connection followed by a layer norm.
    Note for code simplicity the norm is first as opposed to last.
    """
    def __init__(self, size, dropout):
        super(SublayerConnection, self).__init__()
        self.norm = LayerNorm(size)
        self.dropout = nn.Dropout(dropout)
    def forward(self, x, sublayer):
        "Apply residual connection to any sublayer with the same size."
        return x + self.dropout(sublayer(self.norm(x)))
# LN归一化
class LayerNorm(nn.Module):
    "Construct a layernorm module (See citation for details)."
    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(features))
        self.b_2 = 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.a_2 * (x - mean) / (std + self.eps) + self.b_2

Decoder

介绍完Encoder,Decoder就很好理解了。

不同点:

  • Decoder包括两个 Multi-Head Attention 层。
    • 第一个 Multi-Head Attention 层采用了 Masked 操作。
    • 第二个 Multi-Head Attention 层的K, V矩阵运用 Encoder 的编码信息矩阵C进行核算,而Q运用上一个 Decoder block 的输出核算。这样做的好处是在 Decoder 的时分,每一位单词都能够利用到 Encoder 一切单词的信息 (这些信息无需Mask)。
  • 终究输出有一个 Softmax 层核算下一个翻译单词的概率。

Decoder中的Attention是Masked Attention,是想阐明在解码阶段,终究的输出是一个一个产生的。即先翻译完第 i 个单词,才能够翻译第 i+1 个单词,所以需求掩码来去掉后面词的影响。而在Encoder阶段,是提早知道大局的信息,所以没有mask。 详细比如可检查【Transformer模型详解(图解最完整版) – 知乎 (zhihu.com)】

Cross Attention

「论文研读」Transformer原理解读篇

「论文研读」Transformer原理解读篇

练习时:第i个decoder的输入 = encoder输出 + ground truth embeding
猜测时:第i个decoder的输入 = encoder输出 + 第(i-1)个decoder输出

练习时由于知道ground truth embedding,相当于知道正确答案,网络能够一次练习完结。
猜测时,首先输入start,输出猜测的第一个单词 然后start和新单词组成新的query,再输入decoder来猜测下一个单词,循环往复直至end

# 生成带掩码块的N层decoder block
class Decoder(nn.Module):
    "Generic N layer decoder with masking."
    def __init__(self, layer, N):
        super(Decoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)
    def forward(self, x, memory, src_mask, tgt_mask):
        for layer in self.layers:
            x = layer(x, memory, src_mask, tgt_mask)
        return self.norm(x)
class DecoderLayer(nn.Module):
    "Decoder is made of self-attn, src-attn, and feed forward (defined below)"
    def __init__(self, size, self_attn, src_attn, 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, src_mask, tgt_mask):
        "Follow Figure 1 (right) for connections."
        m = memory
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
        x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
        return self.sublayer[2](x, self.feed_forward)

掩码详细完结能够参阅代码

def subsequent_mask(size):
    "Mask out subsequent positions."
    attn_shape = (1, size, size)
    subsequent_mask = torch.triu(torch.ones(attn_shape), diagonal=1).type(
        torch.uint8
    )
    return subsequent_mask == 0
"""
参数 diagonal 操控要考虑的对角线。假如 diagonal = 0,则保留主对角线上和上方的一切元素。正值扫除主对角线和对角线上方的部分元素。
>>> a = torch.randn(4, 4)
>>> a
tensor([[ 0.5144,  0.5091, -0.3698,  0.3694],
        [-1.1344, -0.2793,  1.6651, -1.3632],
        [-0.3397, -0.1468, -0.0300, -1.1186],
        [-2.1449,  1.3087, -0.1409,  2.4678]])
>>> torch.triu(a, diagonal=1)
tensor([[ 0.0000,  0.5091, -0.3698,  0.3694],
        [ 0.0000,  0.0000,  1.6651, -1.3632],
        [ 0.0000,  0.0000,  0.0000, -1.1186],
        [ 0.0000,  0.0000,  0.0000,  0.0000]])
"""

杂乱度剖析

下降Transformer的核算杂乱度 – 郑之杰的个人网站

「论文研读」Transformer原理解读篇

核算查询矩阵Q,键矩阵K,值矩阵V

「论文研读」Transformer原理解读篇

核算留意力矩阵A

「论文研读」Transformer原理解读篇

归一化留意力矩阵A

「论文研读」Transformer原理解读篇

加权求和核算输出H

「论文研读」Transformer原理解读篇

自留意力的运算杂乱度

根据上述剖析可知,自留意力机制的全体运算杂乱度为:

O(2DkNDx+DvNDx+N2Dk+1+DxN2)=O(N2)O(2D_k N D_x + D_v N D_x + N^2 D_k + 1 + D_x N^2)=O(N^2)

其间 NN 是输入序列长度。一般地,选择向量的特征维度Dk=Dv=Dx=dD_k = D_v = D_x = d, 进而等价于

O(2dNd+dNd+N2d+1+dN2)=O(3Nd2+2N2d)O(2d N d + d N d + N^2 d + 1 + d N^2)=O(3Nd^2+2N^2d)

在自然言语处理等任务中一般有N>dN > d

全衔接层的核算杂乱度

通常认为Transformer的核算量首要来源于自留意力层,但是全衔接层的核算量也不可疏忽。全衔接层一般为两层,第一层的特征维度由 d→4dd \to 4d, 第二层的特征维度为 4d→d4d \to d。 所以全衔接层的核算杂乱度为: O(Nd4d+N4dd)=O(8Nd2)O(Nd4d + N4dd)=O(8Nd^2)

「论文研读」Transformer原理解读篇

番外:改善杂乱度

关于如何改善杂乱度,能够移步至杂乱度开篇的链接,这儿为截取图片。

「论文研读」Transformer原理解读篇

综上,这篇文章还存在许多不成熟的当地,尽请指出!引证的文章均在文章中给了链接,如有侵权请联络删除~