精华内容
下载资源
问答
  • Transformer-Tensorflow2 用于分类的Transformer架构 要求:Tensorflow 2.0
  • Google 2017年论文Attention is all you need提出了...1. Transformer架构Transformer解释下这个结构图。首先,Transformer模型也是使用经典的encoder-decoder架构,由encoder和decoder两部分组成。上图左侧用Nx...

    Google 2017年论文Attention is all you need提出了Transformer模型,完全基于Attention mechanism,抛弃了传统的CNN和RNN。

    1. Transformer架构

    Transformer

    解释下这个结构图。首先,Transformer模型也是使用经典的encoder-decoder架构,由encoder和decoder两部分组成。

    上图左侧用Nx框出来的,就是我们encoder的一层。encoder一共有6层这样的结构。

    上图右侧用Nx框出来的,就是我们decoder的一层。decoder一共有6层这样的结构。

    输入序列经过word embedding和positional embedding相加后,输入到encoder中。

    输出序列经过word embedding和positional embedding相加后,输入到decoder中。

    最后,decoder输出的结果,经过一个线性层,然后计算softmax。

    2. Encoder

    encoder由6层相同的层组成,每一层分别由两部分组成:

    第一部分是multi-head self-attention mechanism

    第二部分是position-wise feed-forward network,是一个全连接层。

    两部分,都有一个残差连接(residual connection),然后接着一个Layer Normalization。

    3. Decoder

    与encoder类似,decoder也是由6个相同层组成,每一个层包括以下3个部分:

    第一部分是multi-head self-attention mechanism

    第二部分是multi-head context-attention mechanism

    第三部分是position-wise feed-forward network

    同样,上面三部分中每一部分,都有一个残差连接(residual connection),后接着一个Layer Normalization。

    4. Attention机制

    Attention是指对于某个时刻的输出y,它在输入x上各个部分的注意力。这个注意力可以理解为权重。

    attention机制有很多计算方式,下面是一张比较全面的表格:

    image.png

    seq2seq模型中,使用的是加性注意力(addtion attention)较多。

    为什么这种attention叫做addtion attention呢?很简单,对于输入序列隐状态

    和输出序列的隐状态

    ,它的处理方式很简单,直接合并为

    但是transformer模型使用的不是这种attention机制,使用的是另一种,叫做乘性注意力(multiplicative attention)。

    那么这种乘性注意力机制是怎么样的呢?从上表中的公式也可以看出来:两个隐状态进行点积!

    4.1 Self-attention是什么?

    上面我们说的attention机制的时候,都会提到两个隐状态,分别是

    ,前者是输入序列第

    个位置产生的隐状态,后者是输出序列在第

    个位置产生的隐状态。

    所谓self-attention实际上就是输出序列就是输入序列,因此计算自己的attention得分,就叫做self-attention!

    4.2 Context-attention是什么?

    context-attention是encoder和decoder之间的attention!,所以,也可以成为encoder-decoder attention!

    不管是self-attention还是context-attention,它们计算attention分数的时候,可以选择很多方式,比如上面表中提到的:

    additive attention

    local-base

    general

    dot-product

    scaled dot-product

    那么Transformer模型,采用的是哪种呢?答案是:scaled dot-product attention。

    4.3 Scaled dot-product attention是什么?

    论文Attention is all you need里面对于attention机制的描述是这样的:

    An attention function can be described as 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 of the query with the corresponding key.

    这句话描述得很清楚了。翻译过来就是:通过确定Q和K之间的相似程度来选择V!

    用公式来描述更加清晰:

    scaled dot-product attention和dot-product attention唯一区别是,scaled dot-product attention有一个缩放因子

    上面公式中

    表示的是

    的维度,在论文中,默认是64。

    那么为什么需要加上这个缩放因子呢?论文中给出了解释:对于

    很大时,点积得到的结果维度很大,使得结果处理softmax函数梯度很小的区域。

    我们知道,梯度很小时,这对反向传播不利。为了克服这个负面影响,除以一个缩放因子,在一定程度上减缓这种情况。

    为什么是

    呢?论文没有进一步说明。个人觉得你可以使用其他缩放因子,看看模型效果有没有提升。

    论文中也提供了一张很清晰的结果图,供大家参考:

    image.png

    首先说明一下我们的

    是什么:

    在encoder的self-attention中,Q、K、V都来自同一个地方(相等),他们是上一层encoder的输出。对于第一层encoder,它们就是word embedding和positional encoding相加得到的输入。

    在decoder的self-attention中,Q、K、V都来自同一个地方(相等),他们是上一层decoder的输出。对于第一层decoder,它们就是word embedding和positional encoding相加得到的输入。但是对于decoder,我们不希望它能获得下一个time step,因此我们需要进行sequence masking。

    在encoder-decoder attention中,Q来自于decoder的上一层的输出,K和V来自于encoder的输出,K和V是一样的。

    三者的维度一样,即

    4.4 Scaled dot-product attention代码实现

    import numpy as np

    import torch

    import torch.nn as nn

    import torch.nn.functional as F

    class ScaledDotProductAttention(nn.Module):

    """

    Scaled dot-product attention mechanism.

    """

    def __init__(self, attention_dropout=0.0):

    super(ScaledDotProductAttention, self).__init__()

    self.dropout = nn.Dropout(attention_dropout)

    self.softmax = nn.Softmax(dim=2)

    def forward(self, q, k, v, scale=None, attn_mask=None):

    """

    前向传播

    args:

    q: Queries张量,形状[B, L_q, D_q]

    k: keys张量, 形状[B, L_k, D_k]

    v: Values张量,形状[B, L_v, D_v]

    scale: 缩放因子,一个浮点标量

    attn_mask: Masking张量,形状[B, L_q, L_k]

    returns:

    上下文张量和attention张量

    """

    attention = torch.bmm(q, k.transpose(1, 2))

    if scale:

    attention = attention * scale

    if attn_mask:

    # 给需要mask的地方设置一个负无穷

    attention = attention.masked_fill_(attn_mask, -np.inf)

    # 计算softmax

    attention = self.softmax(attention)

    # 添加dropout

    attention = self.dropout(attention)

    # 和V做点积

    context = torch.bmm(attention, v)

    return context, attention

    5. Multi-head attention是什么呢?

    理解了Scaled dot-product attention,Multi-head attention也很简单了。论文提到,他们发现将Q、K、V通过一个线性映射之后,分成

    份,对每一份进行scaled dot-product attention效果更好。然后,把各个部分的结果合并起来,再次经过线性映射,得到最终的输出。这就是所谓的multi-head attention。上面的超参数

    就是heads数量。论文默认是8。

    multi-head attention的结构图如下:

    image.png

    值得注意的是,上面所说的分成

    份是在

    维度上面进行切分的。因此,进入到scaled dot-product attention的

    实际上等于未进入之前的

    Multi-head attention允许模型加入不同位置的表示子空间的信息。

    Multi-head attention的公式如下:

    其中,

    论文中,

    所以scaled dot-product attention里面的

    5.1 Multi-head attention代码实现

    class MultiHeadAttention(nn.Module):

    def __init__(self, model_dim=512, num_heads=8, dropout=0.0):

    super(MultiHeadAttention, self).__init__()

    self.dim_per_head = model_dim / num_heads

    self.num_heads = num_heads

    self.linear_q = nn.Linear(model_dim, self.dim_per_head * num_heads)

    self.linear_k = nn.Linear(model_dim, self.dim_per_head * num_heads)

    self.linear_v = nn.Linear(model_dim, self.dim_per_head * num_heads)

    self.dot_product_attention = ScaledDotProductAttention(dropout)

    self.linear_final = nn.Linear(model_dim, model_dim)

    self.dropout = nn.Dropout(dropout)

    # multi-head attention之后需要做layer norm

    self.layer_num = nn.LayerNorm(model_dim)

    def forward(self, query, key, value, attn_mask=None):

    # 残差连接

    residual = query

    batch_size = key.size(0)

    # linear projection

    query = self.linear_q(query) # [B, L, D]

    key = self.linear_k(key) # [B, L, D]

    value = self.linear_v(value) # [B, L, D]

    # split by head

    query = query.view(batch_size * num_heads, -1, dim_per_head) # [B * 8, , D / 8]

    key = key.view(batch_size * num_heads, -1, dim_per_head) #

    value = value.view(batch_size * num_heads, -1, dim_per_head)

    if attn_mask:

    attn_mask = attn_mask.repeat(num_heads, 1, 1)

    # scaled dot product attention

    scale = (key.size(-1) // num_heads) ** -0.5

    context, attention = self.dot_product_attention(

    query, key, value, scale, attn_mask

    )

    # concat heads

    context = context.view(batch_size, -1, dim_per_head * num_heads)

    # final linear projection

    output = self.linear_final(context)

    # dropout

    output = self.dropout(output)

    # add residual and norm layer

    output = self.layer_num(residual + output)

    return output, attention

    上面代码中出现了 Residual connection和Layer normalization。下面进行解释:

    5.1.1 Residual connection是什么?

    残差连接其实比较简单!看图就会比较清晰:

    image.png

    假设网络中某个层对输入x作用后的输出为

    ,那么增加residual connection之后,变成:

    这个

    操作被称为shotcut。

    残差结构因为增加了一项

    ,该层网络对

    求偏导时,为常数项1!所以可以在反向传播过程中,梯度连乘,不会造成梯度消失!

    5.1.2 Layer normalization是什么?

    归一化层,主要有这几种方法,BatchNorm(2015年)、LayerNorm(2016年)、InstanceNorm(2016年)、GroupNorm(2018年);

    将输入的图像shape记为[N,C,H,W],这几个方法主要区别是:

    BatchNorm:batch方向做归一化,计算NHW的均值,对小batchsize效果不好;(BN主要缺点是对batchsize的大小比较敏感,由于每次计算均值和方差是在一个batch上,所以如果batchsize太小,则计算的均值、方差不足以代表整个数据分布)

    LayerNorm:channel方向做归一化,计算CHW的均值;(对RNN作用明显)

    InstanceNorm:一个batch,一个channel内做归一化。计算HW的均值,用在风格化迁移;(因为在图像风格化中,生成结果主要依赖于某个图像实例,所以对整个batch归一化不适合图像风格化中,因而对HW做归一化。可以加速模型收敛,并且保持每个图像实例之间的独立。)

    GroupNorm:将channel方向分group,然后每个group内做归一化,算(C//G)HW的均值;这样与batchsize无关,不受其约束。

    Normalization layers

    6. Mask是什么?

    mask顾名思义就是掩码,大概意思是对某些值进行掩盖,使其不产生效果.

    需要说明的是,Transformer模型中有两种mask。分别是padding mask和sequence mask。其中,padding mask在所有的scaled dot-product attention里都需要用到,而sequence mask只在decoder的self-attention中用到。

    所以,我们之前的ScaledDotProductAttention的forward方法里的参数attn_mask在不同的地方有不同的含义。

    6.1 Padding mask

    什么是padding mask呢?回想一下,我们的每个批次输入序列长度是不一样的!也就是说,我们要对输入序列进行对齐!具体来说,就是给较短序列后面填充0。因为这些填充位置,其实没有意义,所以我们的attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。

    具体做法是:把这些位置的值加上一个非常大的负数(可以是负无穷),这样的话,经过softmax,这些位置的概率就会接近0。

    而我们的padding mask实际上是一个张量,每个值都是一个Boolean,值为False的地方就是我们要进行处理的地方。

    下面是代码实现:

    def padding_mask(seq_q, seq_k):

    # seq_k和seq_q的形状都是[B,L]

    len_q = seq_q.size(1)

    # `PAD` is 0

    pad_mask = seq_k.eq(0)

    pad_mask = pad_mask.unsqueeze(1).expand(-1, len_q, -1) # shape [B,L_q,L_k]

    [B,L]->[B,1,L]->[B,L,L]

    F

    F

    T

    T

    F

    F

    T

    T

    F

    F

    T

    T

    F

    F

    T

    T

    6.2 Sequence mask

    sequence mask是为了使得decoder不能看到未来的信息。也就是对于一个序列,在time step为t的时刻,我们的解码输出只能依赖于t时刻之前的输出,而不能依赖t之后的输出。因此我们需要想一个办法,把t之后的信息给隐藏起来。

    那具体如何做呢?也很简单:产生一个上三角矩阵,上三角矩阵的值全为1,下三角的值全为0,对角线值也为0。把这个矩阵作用在每一个序列上,就可以达到我们的目的。

    具体代码如下:

    def sequence_mask(seq):

    batch_size, seq_len = seq.size()

    mask = torch.triu(torch.ones((seq_len, seq_len), dtype=torch.uint8),

    diagonal=1)

    mask = mask.unsqueeze(0).expand(batch_size, -1, -1) # [B, L, L]

    return mask

    [B,L,L]

    0

    1

    1

    1

    0

    0

    1

    1

    0

    0

    0

    1

    0

    0

    0

    0

    image.png

    值得注意的是,本来mask只需要二维矩阵即可,但是考虑到我们的输入序列都是批量的,所以我们需要把原本二维矩阵扩张成3维张量。上面代码中,已经做了处理。

    回到本节开始的问题,attn_mask参数有几种情况?分别是什么意思?

    对于decoder的self-attention,里面使用的scaled dot-product attention,同时需要padding mask和sequence mask作为attn_mask,具体实现就是两个mask相加作为attn_mask。

    其它情况,attn_mask都等于padding mask。

    7. Positional encoding是什么?

    就目前而言,Transformer架构似乎少了点东西。没错,那就是它对序列的顺序没有约束!我们知道序列的顺序是一个很重要的信息,如果缺失了这个信息,可能我们的结果就是:所有词语都对了,但是无法组成有意义的语句。

    为了解决这个问题,论文中提出了positional encoding。一句话概括就是:对序列中的词语出现的位置进行编码!如果对位置进行编码,那么我们的模型就可以捕捉顺序信息。

    那么具体怎么做呢?论文的实现是使用正余弦函数。公式如下:

    其中,pos是指词语在序列中的位置。可以看出,在偶数位置,使用正弦编码,在奇数位置,使用余弦编码。

    上面公式中的

    是模型的维度,论文默认是512。

    这个编码公式的意思就是:给定词语的位置pos,我们可以把它编码成

    维的向量!也就是说,位置编码的每一个维度对应正弦曲线,波长构成了从

    的等比序列。

    Postional encoding是对词汇的位置编码。

    7.1 Positional encoding代码实现

    class PositionalEncoding(nn.Module):

    def __init__(self, d_model, max_seq_len):

    """

    初始化

    args:

    d_model: 一个标量。模型的维度,论文默认是512

    max_seq_len: 一个标量。文本序列的最大长度

    """

    super(PositionalEncoding, self).__init__()

    # 根据论文给出的公式,构造出PE矩阵

    position_encoding = np.array([

    [pos / np.pow(10000, 2.0 * (j // 2) / d_model) for j in range(d_model)]

    for pos in range(max_seq_len)

    ])

    # 偶数列使用sin,奇数列使用cos

    position_encoding[:, 0::2] = np.sin(position_encoding[:, 0::2])

    position_encoding[:, 1::2] = np.cos(position_encoding[:, 1::2])

    # 在PE矩阵的一次行,加上一个全是0的向量,代表这`PAD`的positional_encoding

    # 在word embedding中也会经常加上`UNK`,代表位置单词的word embedding,两者十分类似

    # 那么为什么需要这个额外的PAD的编码呢?很简单,因为文本序列的长度不易,我们需要对齐,

    # 短的序列我们使用0在结尾不全,我们也需要这些补全位置的编码,也就是`PAD`对应的位置编码

    pad_row = torch.zeros([1, d_model])

    position_encoding = torch.cat((pad_row, position_encoding))

    # 嵌入操作,+1是因为增加了`PAD`这个补全位置的编码

    # word embedding中如果词典增加`UNK`,我们也需要+1。

    self.position_encoding = nn.Embedding(max_seq_len+1, d_model)

    self.position_encoding.weight = nn.Parameter(position_encoding, requires_grad=False)

    def forward(self, input_len):

    """

    神经网络前向传播

    args:

    input_len: 一个张量,形状为[BATCH_SIZE, 1]。每一个张量的值代表这一批文本序列中对应的长度。

    returns:

    返回这一批序列的位置编码,进行了对齐。

    """

    # 找出这一批序列的最大长度

    max_len = torch.max(input_len)

    # 对每一个序列的位置进行对齐,在原序列位置的后面补上0

    # 这里range从1开始也是因为要避开PAD(0)的位置

    input_pos = torch.LongTensor(

    [list(range(1, len+1)) + [0] * (max_len-len) for len in input_len]

    )

    return self.position_encoding(input_pos)

    8. Word embedding是什么?

    Word embedding是对序列中的词汇的编码,把每一个词汇编码成

    维的向量!它实际上就是一个二维浮点矩阵,里面的权重是可训练参数,我们只需要把这个矩阵构建出来就完成了word embedding的工作。

    embedding = nn.Embedding(vocab_size, embedding_size, padding_idx=0)

    上面vocab_size是词典大小,embedding_size是词嵌入的维度大小,论文里面就是等于

    。所以word embedding矩阵就是一个vocab_size*embedding_size的二维张量。

    9. Position-wise Feed-Forward netword是什么?

    这是一个全连接网络,包含连个线性变换和一个非线性函数(ReLU)。公式如下:

    这个线性变换在不同的位置都是一样的,并且在不同的层之间使用不同的参数。

    论文提到,这个公式还可以用两个核大小为1的一维卷积来解释,卷积的输入输出都是

    ,中间层维度是

    代码如下:

    class PositionalWiseFeedForward(nn.Module):

    def __init__(self, model_dim=512, ffn_dim=2048, dropout=0.0):

    super(PositionalWiseFeedForward, self).__init__()

    self.w1 = nn.Conv1d(model_dim, ffn_dim, 1)

    self.w2 = nn.Conv2d(model_dim, ffn_dim, 1)

    self.dropout = nn.Dropout(dropout)

    self.layer_norm = nn.LayerNorm(model_dim)

    def forward(self, x):

    output = x.transpose(1, 2)

    output = self.w2(F.relu(self.w1(output)))

    output = self.dropout(output.transpose(1, 2))

    # add residual and norm layer

    output = self.layer_norm(x + output)

    return output

    10. 完整代码

    至此,所有的细节都解释完了。

    import numpy as np

    import torch

    import torch.nn as nn

    import torch.nn.functional as F

    class ScaledDotProductAttention(nn.Module):

    """

    Scaled dot-product attention mechanism.

    """

    def __init__(self, attention_dropout=0.0):

    super(ScaledDotProductAttention, self).__init__()

    self.dropout = nn.Dropout(attention_dropout)

    self.softmax = nn.Softmax(dim=2)

    def forward(self, q, k, v, scale=None, attn_mask=None):

    """

    前向传播

    args:

    q: Queries张量,形状[B, L_q, D_q]

    k: keys张量, 形状[B, L_k, D_k]

    v: Values张量,形状[B, L_v, D_v]

    scale: 缩放因子,一个浮点标量

    attn_mask: Masking张量,形状[B, L_q, L_k]

    returns:

    上下文张量和attention张量

    """

    attention = torch.bmm(q, k.transpose(1, 2))

    if scale:

    attention = attention * scale

    if attn_mask:

    # 给需要mask的地方设置一个负无穷

    attention = attention.masked_fill_(attn_mask, -np.inf)

    # 计算softmax

    attention = self.softmax(attention)

    # 添加dropout

    attention = self.dropout(attention)

    # 和V做点积

    context = torch.bmm(attention, v)

    return context, attention

    class MultiHeadAttention(nn.Module):

    def __init__(self, model_dim=512, num_heads=8, dropout=0.0):

    super(MultiHeadAttention, self).__init__()

    self.dim_per_head = model_dim / num_heads

    self.num_heads = num_heads

    self.linear_q = nn.Linear(model_dim, self.dim_per_head * num_heads)

    self.linear_k = nn.Linear(model_dim, self.dim_per_head * num_heads)

    self.linear_v = nn.Linear(model_dim, self.dim_per_head * num_heads)

    self.dot_product_attention = ScaledDotProductAttention(dropout)

    self.linear_final = nn.Linear(model_dim, model_dim)

    self.dropout = nn.Dropout(dropout)

    # multi-head attention之后需要做layer norm

    self.layer_num = nn.LayerNorm(model_dim)

    def forward(self, query, key, value, attn_mask=None):

    # 残差连接

    residual = query

    batch_size = key.size(0)

    # linear projection

    query = self.linear_q(query) # [B, L, D]

    key = self.linear_k(key) # [B, L, D]

    value = self.linear_v(value) # [B, L, D]

    # split by head

    query = query.view(batch_size * num_heads, -1, dim_per_head) # [B * 8, , D / 8]

    key = key.view(batch_size * num_heads, -1, dim_per_head) #

    value = value.view(batch_size * num_heads, -1, dim_per_head)

    if attn_mask:

    attn_mask = attn_mask.repeat(num_heads, 1, 1)

    # scaled dot product attention

    scale = (key.size(-1) // num_heads) ** -0.5

    context, attention = self.dot_product_attention(

    query, key, value, scale, attn_mask

    )

    # concat heads

    context = context.view(batch_size, -1, dim_per_head * num_heads)

    # final linear projection

    output = self.linear_final(context)

    # dropout

    output = self.dropout(output)

    # add residual and norm layer

    output = self.layer_num(residual + output)

    return output, attention

    def padding_mask(seq_q, seq_k):

    # seq_k和seq_q的形状都是[B,L]

    len_q = seq_q.size(1)

    # `PAD` is 0

    pad_mask = seq_k.eq(0)

    pad_mask = pad_mask.unsqueeze(1).expand(-1, len_q, -1) # shape [B,L_q,L_k]

    def sequence_mask(seq):

    batch_size, seq_len = seq.size()

    mask = torch.triu(torch.ones((seq_len, seq_len), dtype=torch.uint8),

    diagonal=1)

    mask = mask.unsqueeze(0).expand(batch_size, -1, -1) # [B, L, L]

    return mask

    class PositionalEncoding(nn.Module):

    def __init__(self, d_model, max_seq_len):

    """

    初始化

    args:

    d_model: 一个标量。模型的维度,论文默认是512

    max_seq_len: 一个标量。文本序列的最大长度

    """

    super(PositionalEncoding, self).__init__()

    # 根据论文给出的公式,构造出PE矩阵

    position_encoding = np.array([

    [pos / np.pow(10000, 2.0 * (j // 2) / d_model) for j in range(d_model)]

    for pos in range(max_seq_len)

    ])

    # 偶数列使用sin,奇数列使用cos

    position_encoding[:, 0::2] = np.sin(position_encoding[:, 0::2])

    position_encoding[:, 1::2] = np.cos(position_encoding[:, 1::2])

    # 在PE矩阵的一次行,加上一个全是0的向量,代表这`PAD`的positional_encoding

    # 在word embedding中也会经常加上`UNK`,代表位置单词的word embedding,两者十分类似

    # 那么为什么需要这个额外的PAD的编码呢?很简单,因为文本序列的长度不易,我们需要对齐,

    # 短的序列我们使用0在结尾不全,我们也需要这些补全位置的编码,也就是`PAD`对应的位置编码

    pad_row = torch.zeros([1, d_model])

    position_encoding = torch.cat((pad_row, position_encoding))

    # 嵌入操作,+1是因为增加了`PAD`这个补全位置的编码

    # word embedding中如果词典增加`UNK`,我们也需要+1。

    self.position_encoding = nn.Embedding(max_seq_len+1, d_model)

    self.position_encoding.weight = nn.Parameter(position_encoding, requires_grad=False)

    def forward(self, input_len):

    """

    神经网络前向传播

    args:

    input_len: 一个张量,形状为[BATCH_SIZE, 1]。每一个张量的值代表这一批文本序列中对应的长度。

    returns:

    返回这一批序列的位置编码,进行了对齐。

    """

    # 找出这一批序列的最大长度

    max_len = torch.max(input_len)

    # 对每一个序列的位置进行对齐,在原序列位置的后面补上0

    # 这里range从1开始也是因为要避开PAD(0)的位置

    input_pos = torch.LongTensor(

    [list(range(1, len+1)) + [0] * (max_len-len) for len in input_len]

    )

    return self.position_encoding(input_pos)

    # embedding = nn.Embedding(vocab_size, embedding_size, padding_idx=0)

    # 获得输入的词嵌入编码

    # seq_embedding = seq_embedding(inputs) * np.sqrt(d_model)

    class PositionalWiseFeedForward(nn.Module):

    def __init__(self, model_dim=512, ffn_dim=2048, dropout=0.0):

    super(PositionalWiseFeedForward, self).__init__()

    self.w1 = nn.Conv1d(model_dim, ffn_dim, 1)

    self.w2 = nn.Conv2d(model_dim, ffn_dim, 1)

    self.dropout = nn.Dropout(dropout)

    self.layer_norm = nn.LayerNorm(model_dim)

    def forward(self, x):

    output = x.transpose(1, 2)

    output = self.w2(F.relu(self.w1(output)))

    output = self.dropout(output.transpose(1, 2))

    # add residual and norm layer

    output = self.layer_norm(x + output)

    return output

    class EncoderLayer(nn.Module):

    """Encoder的一层。"""

    def __init__(self, model_dim=512, num_heads=8, ffn_dim=2048, dropout=0.0):

    super(EncoderLayer, self).__init__()

    self.attention = MultiHeadAttention(model_dim, num_heads, dropout)

    self.feed_forward = PositionalWiseFeedForward(model_dim, ffn_dim, dropout)

    def forward(self, inputs, attn_mask=None):

    # self attention

    context, attention = self.attention(inputs, inputs, inputs, attn_mask)

    # feed forward network

    output = self.feed_forward(context)

    return output, attention

    class Encoder(nn.Module):

    """多层EncoderLayer组成的Encoder"""

    def __init__(self,

    vocab_size,

    num_layers=6,

    model_dim=512,

    num_heads=8,

    ffn_dim=2048,

    dropout=0.0):

    super(Encoder, self).__init__()

    self.encoder_layers = nn.ModuleList(

    [EncoderLayer(model_dim, num_heads, ffn_dim, dropout) for _ in range(num_layers)]

    )

    self.seq_embedding = nn.Embedding(vocab_size + 1, model_dim, padding_idx=0)

    self.pos_embedding = PositionalEncoding(model_dim, max_seq_len)

    def forward(self, inputs, inputs_len):

    output = self.seq_embedding(inputs)

    output += self.pos_embedding(inputs_len)

    self_attention_mask = padding_mask(inputs, inputs)

    attentions = []

    for encoder in self.encoder_layers:

    output, attention = encoder(output, self_attention_mask)

    attentions.append(attention)

    return output, attentions

    class DecoderLayer(nn.Module):

    def __init__(self, model_dim, num_heads=8, ffn_dim=2048, dropout=0.0):

    super(DecoderLayer, self).__init__()

    self.attention = MultiHeadAttention(model_dim, num_heads, dropout)

    self.feed_forward = PositionalWiseFeedForward(model_dim, ffn_dim, dropout)

    def forward(self,

    dec_inputs,

    enc_outputs,

    self_attn_mask=None,

    context_attn_mask=None):

    # self attention, all inputs are decoder inputs

    dec_output, self_attention = self.attention(dec_inputs, dec_inputs, dec_inputs, self_attn_mask)

    # context attention

    # query is decoder's outputs, key and value are encoder's inputs

    dec_output, context_attention = self.attention(dec_output, enc_outputs, enc_outputs, context_attn_mask)

    # decoder's output, or context

    dec_output = self.feed_forward(dec_output)

    return dec_output, self_attention, context_attention

    class Decoder(nn.Module):

    def __init__(self,

    vocab_size,

    max_seq_len,

    num_layers=6,

    model_dim=512,

    num_heads=8,

    ffn_dim=2048,

    dropout=0.0):

    super(Decoder).__init__()

    self.num_layers = num_layers

    self.decoder_layers = nn.ModuleList(

    [DecoderLayer(model_dim, num_heads, ffn_dim, dropout) for _ in range(num_layers)]

    )

    self.seq_embedding = nn.Embedding(vocab_size + 1, model_dim, padding_idx=0)

    self.pos_embedding = PositionalEncoding(model_dim, max_seq_len)

    def forward(self, inputs, inputs_len, enc_output, context_attn_mask=None):

    output = self.seq_embedding(inputs)

    output += self.pos_embedding(inputs_len)

    self_attention_padding_mask = padding_mask(inputs, inputs)

    seq_mask = sequence_mask(inputs)

    self_attn_mask = torch.gt((self_attention_padding_mask + seq_mask), 0)

    self_attentions = []

    context_attentions = []

    for decoder in self.decoder_layers:

    output, self_attn, context_attn = decoder(

    output, enc_output, self_attn_mask, context_attn_mask)

    self_attentions.append(self_attn)

    context_attentions.append(context_attn)

    return output, self_attentions, context_attentions

    class Transformer(nn.Module):

    def __init__(self,

    src_vocab_size,

    src_max_len,

    tgt_vocab_size,

    tgt_max_len,

    num_layers=6,

    model_dim=512,

    num_heads=8,

    ffn_dim=2048,

    dropout=0.0):

    super(Transformer).__init__()

    self.encoder = Encoder(src_vocab_size, src_max_len, num_layers, model_dim, num_heads, ffn_dim, dropout)

    self.decoder = Decoder(tgt_vocab_size, tgt_max_len, num_layers, model_dim, num_heads, ffn_dim, dropout)

    self.linear = nn.Linear(model_dim, tgt_vocab_size, bias=False)

    self.softmax = nn.Softmax()

    def forward(self, src_seq, src_len, tgt_seq, tgt_len):

    context_attn_mask = padding_mask(tgt_seq, src_seq)

    output, enc_self_attn = self.encoder(src_seq, src_len)

    output, dec_self_attn, ctx_attn = self.decoder(tgt_seq, tgt_len, output, context_attn_mask)

    output = self.linear(output)

    output = self.softmax(output)

    return output, enc_self_attn, dec_self_attn, ctx_attn

    展开全文
  • Transformer架构解析

    2016-01-30 12:51:00
    基于这个理念,我们提出了Transformer架构。 基本概念定义 Transformer。 我们的每一个服务应用,都是一个数据转换器。数据在这些Transformer之间进行流动和转换。流动的过程就是Pipeline形成的过程(Pipeline的概念...
        

    核心观点: 服务的本质是数据的流转与变换

    数据的变换依赖于数据的流转,只有流转的数据才能够被变换。基于这个理念,我们提出了Transformer架构。

    基本概念定义

    • Transformer。 我们的每一个服务应用,都是一个数据转换器。数据在这些Transformer之间进行流动和转换。流动的过程就是Pipeline形成的过程(Pipeline的概念在后续会有定义)。典型的例子比如你开发的一个Spark Streaming程序,一个Storm程序,一个Tomcat Web服务,都是一个Transformer。

    • Estimator 。它是一类问题的抽象与实现。现实生活中,我们要解决的问题举例有,实时计算问题,离线批量问题,缓存问题,Web服务问题等。对这些问题,我们都有一些可扩张,灵活动态的,具有平台性质的Estimator。比如MR 可以解决大部分离线批量问题,比如Spark则可以解决实时计算,离线批量等多个方面的问题。比如Storm则可以解决实时计算问题,比如Tomcat。并不是所有的Estimator 都能够实现平台特质,隔离底层的。譬如 基于Spark的Transformer 可以实现以资源为需求的动态部署。但基于Tomcat的Transormer则不行,因为Tomcat本身并没有做到分布式的,以资源为粒度的提供给上层Transormer使用的特质。

    • Parameter 。 每个Transformer都有自己的参数,每个Estimator有自己的参数。Parameter就是所有参数的集合。如果进行扩展,他可以包括Transformer/Estimator/Pipeline/Core/OS 等各个层次的参数。

    • Pipeline。 数据在Transfomer之间流动的形成了Pipeline。每一个Transformer 以自己作为Root节点,都会向下延伸出一个树状结构。

    • DataFrame。数据框。数据需要被某种形态进行表示。可以是byte数组,可以一个son字符串。这里我们用DataFrame 对 数据表示( Data Represention )。 它是各个Transformer之间交换数据的表示和规范。

    Transformer架构概览

    1063603-bba37227eac90139.png
    Transformer架构概览

    什么是资源需求为主导的Estimator

    在前文中,我们在对Estimator进行第一的时候,我们提到了平台特质以资源为导向等概念。那么这些指的是什么呢?

    如果上层的Transformer可以按资源进行申请,并且被提交到Estimator上运行,则我们认为该Estimator 是具有平台特质以资源为导向的。典型的比如Spark。

    但是譬如Tomcat,他本身虽然可以运行Web类的Transformer,但是Transformer无法向Tomcat提出自己的资源诉求,比如CPU/内存等,同时Tomcat本身也没办法做到很透明的水平扩展(在Transformer不知情的情况下)。所以我们说Tomcat 是不具备平台特质,并且不是以资源为导向的Estimator。

    但是,当我们基于Core层开发了一套容器调度系统(Estimator),则这个时候Tomcat则只是退化成了Transfomer的一个环境,不具备Estimator的概念。

    在Transformer架构中,我们努力追求Estimator 都是具备平台特质,并且以资源为导向的服务平台。

    Transformer/Estimator/Pipeline的关系

    下面以搜索为例子,简单画了个三者之间的关系。特定的Transformer依赖于特定的Estimator运行,不同的Transformer 构建了Pipeline实现了数据的流动,数据流动到具体的Transformer后发生数据的transform行为。

    1063603-711c2cba3fb75e64.png
    Transformer/Estimator/Pipeline的关系

    Transformer 架构可以对互联网也进行建模

    Transformer 和Pipeline构建了一个复杂的网络拓扑。在Pipeline流动的的DataFrame则实现了信息的流动。如果我们跳出公司的视野,你会发现整个公司的网状服务体系只是全世界网络体系的一小部分。整个互联网是一张复杂的大网。而整个互联网其实也是可以通过上面五个概念进行涵盖的。

    利用Transformer概念去理解我们已经存在的概念

    譬如,我们部署服务到底是一件什么样的事情?

    你可能觉得这个问题会比较可笑。然而,如果之前我们提出的概念是正确或者合理的,让我们离真理更近了一步的话,那么它应该能够清晰的解释,我们部署或者下线一个服务,或者一个服务故障,到底是什么?

    所谓部署服务,不过是新建一个Transformer,并且该Transformer和已经存在的的Transformer通过Pipeline建立了联系,在网络拓扑中形成一个新的节点。这个新的Transformer无论业务有多复杂,不过是实现了一个对数据transform的逻辑而已。

    Transformer 的优势

    前文我们提到了具有平台特质,以资源为导向的Estimator,可以给我们带来如下的好处:

    • 这些Estimator 底层共享 Yarn/Mesos这个大资源池,可以提高资源利用率

    • Estimator如果已经实现了Adaptive Resource Allocation,则根据Transformer的运行情况,可以动态添加或者缩进对应的资源

    • Transformer 部署变得异常简单,申明资源即可。开发人员无需关心起如何运行。一切由Estimator来解决。

    • 有了Estimator的规范和限制,Transformer开发变得成为套路,真正只要关注如何transform,和哪些Transformer建立Pipline

    • 平台组和应用组只能划分清晰。平台组总结数据处理模式,提供抽象的Estimator供应用组进行开发和运行

    除了这些,对我们进行架构设计也具有极大的知道意义。让我们换了一种思考模式去思考面对新的需求,如何设计的问题。

    我们不希望每次遇到一个新的业务问题,都需要根据自己的聪明才智,通过经验,得到一个解决方案。任何事情都是有迹可循的。正如吴文俊提出的机器证明,可以通过流程化的方式让计算机来证明几何问题。当面临一个新的业务问题的时候,我们应该有标准的流程可以走。

    当设计一个平台的时候,我们只要关注Estimator就好,我们必须已经有大量的以及随时具备上线新的Estimator的能力。 之后面对实际的各种业务需求,应该由基于这些Estimator的Transformer去应对,构建Transformer 按如下方式思考去获得答案:

    • 哪个Estimator 最适合这个Transformer?
    • 从已经存在的Transformer中找出我们需要建立Pipeline的Transformer
    • 针对业务逻辑,定义好如何对数据进行Transform

    一个复杂的业务必定是由多个Transfomer进行构建的,每个Transfomer的构建流程都可以遵循这个方式。

    用Transformer架构思考样例

    假设我现在有个搜索服务,我要新接入一个产品,再次假设新产品的数据已经远远不断的放到了Kafka里。

    这个时候,我们需要新建立一个Transformer。

    哪个Estimator 最适合这个Transformer?

    数据进入索引,必然有个吞吐量和实时性的权衡。如果你追求实时性,譬如要达到毫秒级,这个时候实时计算里的Estimator Storm是个更好的选择。而如果是秒级的,可能Spark Streaming是个更好的选择。假设我们选择了Spark Streaming,则说明我们的Transformer是个Spark Streaming程序。

    从已经存在的Transformer中找出我们需要建立Pipeline的Transformer

    这里我们要连接的Transformer 非常清晰,就是搜索和Kafka。 他们之间需要通过我们新的Transformer将数据进行流转。为了解决他们的数据表示的不一致性(DataFrame的不一致),所以我们需要新的Transformer 能够做两次转换,将Kafka的数据转换为搜索能够认识的数据表示形态。

    针对业务逻辑,定义好如何对数据进行Transform

    你需要调研Kafka里的DataFrame以及搜索需要的DataFrame,实现transform逻辑。

    程序员根据这三点进行是靠,按照Estmator的规范(这里是Spark Streaming 的编程规范),写了几十行(或者百余杭代码),然后提出资源要求,譬如:

    • 10颗核
    • 10G内存
    • 无磁盘要求

    这个时候他package好后,通过一个简单的submit 命令(或者如果你有web提交任务的界面),带上资源要求,将服务进行提交。

    过了几秒,你就会发现数据已经神奇的从Kafka流入到搜索,通过搜索的API我们已经能够检索的数据了。

    整个过程从设计,从实现,我们都是严格按照规范来做的。我们无需有所谓的服务器。我们只要知道根据Transformer架构去思考,然后提出自己需要的资源,就可以实现一个新的业务逻辑。可能一到两小时就搞定了整件事情。

    个人感觉

    Transformer 架构,不仅仅能建模我们的数据平台,也能建模我们传统的Web服务,还能对机器学习流程进行建模。

    展开全文
  • 理解Transformer架构 .02

    2020-03-13 12:07:57
    通过提问题的方式,学习一下Bert中的Transformer架构,其中这篇文章与《理解Transformer架构 .01》的内容互为补充: 目录 1. Transformer的结构是什么样? 1.1 encoder端与decoder端总览 1.2 encoder端各个子...

    通过提问题的方式,学习一下Bert中的Transformer架构,其中这篇文章与《理解Transformer架构 .01》的内容互为补充:

    目录

    1. Transformer的结构是什么样?

    1.1  encoder端与decoder端总览

    1.2 encoder端各个子模块

    1.2.1 多头 self-attention 模块

    1.2.2 前馈神经网络层

    1.3 decoder端各个子模块

    1.3.1 多头self-attention 模块

    1.3.2 多头encoder-decoder attention 交互模块

    1.3.3 前馈神经网络模块(Feed-Forward Net,FFN)

    1.4 Add &Norm 模块

    1.5 position encoding(位置编码)

    2. Transformer中一直强调的self-attention是什么?self-attention的计算过程?为什么它能发挥如此重大的作用?self-attention为什么要用Q、K、V,仅仅使用Q、V/K、V或者V不行吗?

    2.1 self-attention是什么?

    2.2 self-attention的计算过程?

    2.3 关于self-attention为什么它能发挥如此大的作用?

    2.4 关于self-attention为什么要使用Q、K、V,仅仅使用Q、K/V、V或者V为什么不行?

    3. 为什么要使用多头self-attention?

    4. Transformer相比于RNN/LSTM,有什么优势?

    5. Transformer的训练过程

    6. self-attention公式中的归一化有什么作用?


    1. Transformer的结构是什么样?

    Transformer本身是一个典型的encoder-decoder模型,如果从模型层面来看,Transformer实际上就像一个seq2seq attention的模型:

                                                             

    1.1  encoder端与decoder端总览

    • encoder端由N个相同的大模块堆叠而成(论文中N=6),其中每个大模块又由两个子模块组成,分别是self-attention模块以及一个前馈神经网络层。

    注意:encoder端每个大模块的输入是不一样的,第一个大模块的输入的输入序列的embedding(可以通过word2vec训练而来,再加上位置编码),其余模块的输入均是前一个模块的输出,最后一个模块的输出作为整个endocer端的输出

    • decoder端同样由N个相同的大模块堆叠而成,其中每个大模块由3个子模块组成,分别是self-attention模块、encoder-decoder attention交互模块以及一个前馈神经网络模块。

    注意:decoder端每个大模块接收的输入是不一样的,其中第一个大模块(最底下那个)在模型训练和测试时接收的输入是不一样的,并且每次训练时接收的输入是不一样的(因为最底层的输入有 "shifted right"这一限制);其余模块的输入都是前一个模块的输出;最后一个模块的输出作为整个decoder端的输出

    对于decoder端第一个大模块,其训练及测试时的输入为:

    训练时每次的输入为上次的输入加上输入序列向后移一位的ground truth(例如,每向后移一位就是一个新的单词,那么就加上其对应的embedding),特别的,当decoder的time step为1时,也就是第一次接收输入,其输入为一个特殊的token,可能是目标序列开始的token如[CLS],也可能是源序列结尾的token如[SEP],其目标是预测下一个位置的单词是什么;如当time step为1时,就是预测输入序列的第一个单词是什么

    注意:在实际现实中可能不会这样每次动态的输入,而是一次性把目标序列的embedding统统输入第一个大模块中,然后在多头attention模块中对序列进行mask即可。二在测试的时候,先生成第一个位置的输出,有了这个之后,第二次预测时,再将其加入输入序列,以此类推直至预测结束。

    1.2 encoder端各个子模块

    1.2.1 多头 self-attention 模块

    在介绍多头self-attention之前,先看看self-attention模块,其结构图如下所示:

                                                                               

    上面的self-attention可以被描述为将query和key-value键值对的一组集合映射到输出,query、keys、values和输出都是向量,其中,query和keys的维度均为 d_k ,values的维度是 d_v (论文中  d_k = d_v =d_{model}/h=64 ),输出为被计算为values的加权和,其中分配给每个value的权重由query 与对应key的相似性函数计算得来。这种attention的形式被称为 “Scaled Dot-Product Attention”,对应到公式的形式为:

                                                                       Attention(Q,K,V)=softmax({QK^T \over \sqrt{d_k} })V

    而多头self-attention模块,则是将Q,K,V通过参数矩阵映射后(映射方式就是在Q,K,V后分别接一个全连接层),然后在做self-attention,将这个过程重复h次(论文中h=8),最后再将所有的结果拼接起来,再送入一个全连接层即可,图示如下:

                                                                   

    对应到公式的形式为:

                                          MultiHead(Q,K,V)=Concat(head_1,...,head_h)W^O \quad \quad where \quad head_i=Attention(QW_i^Q,KW_i^K,VW_i^V)

    其中,W_i^Q \in \mathbb{R}^{d_{model} \chi d_k } ,W_i^K \in \mathbb{R}^{d_{model} \chi d_k },W_i^V \in \mathbb{R}^{hd_v \chi d_{model} }

    1.2.2 前馈神经网络层

    前馈神经网络模块由两个线性变换组成,中间有一个ReLU激活函数,对应到公式的形式为:

                                                      FFN(x)=max(0,xW_1+b_1)W_2 +b_2

    论文中前馈神经网络模块输入和输出的维度均为 d_{model}=512 ,其内层的维度 d_{ff}=2048 。

    1.3 decoder端各个子模块

    1.3.1 多头self-attention 模块

    decoder端多头self-attention 模块与encoder端的一致,但是需要注意的是decoder端的多头self-attention需要mask,因为它在预测时,是“看不到未来的序列的”,所以要讲当前预测的单词(token)及其之后的单词token全部mask掉。

    1.3.2 多头encoder-decoder attention 交互模块

    多头encoder-decoderattention交互模块的形式与多头self-attention模块一致,唯一不同的是其Q,K,V矩阵的来源,其Q矩阵来源于下面子模块的输出(对应到图中即为masked多头self-attention模块经过Add&Norm后的输出),而K,V矩阵则来源于整个encoder端的输出,目的就是让decoder端的单词token给予encoder端对应的单词token更多的关注。

    1.3.3 前馈神经网络模块(Feed-Forward Net,FFN)

    该部分与encoder端的一致。

    1.4 Add &Norm 模块

    Add & Norm模块在encoder端和decoder端每个子模块的后面,其中Add是残差连接(残差连接可参考上一篇文章),Norm表示LayerNorm(在每一层计算每一个样本的均值与方差,而BatchNorm是为每一各batch计算每一层的样本均值与方差),因此encoder端和decoder端每个子模块的实际输出为:LayerNorm (x+ Sublayer(x)),其中Sublayer(x)为子模块的输出,而x+Sublayer(x)代表残差。

    1.5 position encoding(位置编码)

    position encoding 添加到encoder端和decoder端最底部的输入embedding。position encoding具有与embedding相同的维度 d_{model} :

                                              PE_{(pos,2i)}=sin(pos/10000^{2i/d_{model}})PE_{(pos.2i+1)}=cos(pos/10000^{2i/d_{model}})

    ,因此可以对两者进行求和。

    具体做法是使用不同频率的正弦和余弦函数,公式如下:

                                                              PE(pos,2i)=sin(pos/10000^{2i/d_{model}})

                                                           PE(pos,2i+1)=cos(pos/10000^{2i/d_{model}})

    其中pos为位置,i为维度,之所以选择这个函数是因为任意位置 PE_{pos+k} 可以表示为 PE_{pos} 的线性函数,因为三角函数有这样的特性:

                                                          sin(\alpha +\beta)=sin(\alpha)cos(\beta) +cos(\alpha)sin(\beta)

                                                         cos(\alpha+\beta)=cos(\alpha)cos(\beta) -sin(\alpha)sin(\beta)

    注意:transformer中的positional encoding 不是通过网络学习得来的,而是直接使用上述公式计算得来,论文中也实验了利用网络学习positional encoding ,发现结果与上述基本一致,但是论文中选择了正弦和余弦函数版本,因为三角函数不受序列长度的限制,也就是可以对 更长的序列进行表示。

    2. Transformer中一直强调的self-attention是什么?self-attention的计算过程?为什么它能发挥如此重大的作用?self-attention为什么要用Q、K、V,仅仅使用Q、V/K、V或者V不行吗?

    2.1 self-attention是什么?

    self-attention,也叫intra-attention,是一种通过自身和自身相关联的attention机制,从而得到一个更好的representation来表达自身,self-attention可以看成一般attention的一种特殊情况。在self-attention中,Q=K=V,序列中的每个单词token和该序列中其余单词token进行attention计算。self-attention的特点在于可以无视token之间的距离直接计算依赖关系,从而能够学习到序列的内部结构,实现起来也比较简单。

    2.2 self-attention的计算过程?

    参考1.2.1节的内容。

    2.3 关于self-attention为什么它能发挥如此大的作用?

    self-attention是一种自身和自身相关联的attention机制,这样能够得到一个更好的representation来表达自身,在多数情况下,自然会对下游任务有一定的促进作用,但是Transformer效果显著及其强大的特征抽取能力是否完全归功于其self-attention模块,还是存在 一定争议的。

    很明显,模型中引入self-attention后会更容易捕获句子中长距离的相互依赖的特征,因为如果是RNN或者LSTM,需要依次序列计算,对于远距离的相互依赖的特征,需要经过若干时间步骤的信息累积才能将两者联系起来,而距离越远,有效捕获的可能性越小。

    但self-attention在计算过程中会直接将句子中任意两个单词的联系通过一个计算步骤直接联系起来,所以远距离依赖特征之间的距离被极大缩减,有利于有效地利用这些特征。除此之外,self-attention对于增加计算的并行性也有直接帮助作用。

    2.4 关于self-attention为什么要使用Q、K、V,仅仅使用Q、K/V、V或者V为什么不行?

    self-attention使用Q、K、V,这样三个参数独立,模型的表达能力和灵活性显然比只用Q、V或者只用V要好些,当然主流attention的做法还有很多种,比如说seq2seq with attention也就只有hidden state 来做相似性的计算,处理不同的任务,attention的做法有细微的不同,但是主题思想还是一致的。

    3. 为什么要使用多头self-attention?

    论文中说到进行multi-headed self-attention的原因是将模型分为多个头,形成多个子空间,可以让模型去关注不同方面的信息,最后再将各个方面的信息综合起来

    4. Transformer相比于RNN/LSTM,有什么优势?

    RNN系列的模型T时刻隐层状态的计算,依赖两个输入,一个T时刻的句子输入单词 X_t ,另一个是T-1时刻的隐层状态的输出 S_{T-1} ,这是最能体现RNN本质特征的一点,RNN的历史信息是通过这个信息传输渠道往后传输的。而RNN 并行计算的问题就出在这里,因为t时刻的计算依赖t-1时刻的隐层计算结果,而t-1时刻的计算依赖t-2时刻的隐层计算结果,如此下去就形成了所谓的序列依赖关系。

    5. Transformer的训练过程

    1. 首先,encoder端得到输入的encoding表示,将其输入到decoder端做交互式attention;
    2. 之后再decoder端接收器相应的输入,经过多头self-attention之后,结合encoder端的输出;
    3. 再经过FFN,得到decoder端的输出之后,最后经过一个线性全连接层,就可以通过softmax来预测下一个单词token,
    4. 然后根据softmax多分类的损失函数,将loss反向传播即可。

    注意:encoder端可以并行计算,一次性将输入序列全部encoding出来,如对于某个序列x_1,x_2,...,x_n ,self-attention可以直接计算x_i,x_j的点乘结果,而RNN系列的模型就必须按照顺序从x1计算到xn;但是decoder端不是这样的,它需要将被预测单词一个接一个预测出来。

    6. self-attention公式中的归一化有什么作用?

    计算过程中,随着 d_k 的增大,q\cdot k 点积的结果也随之增大,这样会将softmax函数推入梯度非常小的区域,使得收敛困难(可能会出现梯度消失的情况)。

     

     

     

    展开全文
  • 理解Transformer架构 .01

    2020-03-12 20:56:20
    本文参考自知乎专栏:...1. Transformer架构 2. 编码器 3. 解码器 4. Transformer中的张量 5. 编码过程 5.1 从宏观上看自注意力机制 5.2 从微观上看自注意力机制的计算步骤 5.3 自注意力的矩阵运算 6. Tran...

    本文参考自知乎专栏:https://zhuanlan.zhihu.com/p/54356280,并结合自己的一些理解记录下来方便以后查看。

    目录

    1. Transformer架构

    2. 编码器

    3. 解码器

    4. Transformer中的张量

    5. 编码过程

    5.1 从宏观上看自注意力机制

    5.2 微观上看自注意力机制的计算步骤

     5.3 自注意力的矩阵运算

    6. Transformer中的多头注意力机制

    7.  使用位置编码表示序列的顺序

    8. 残差模块

    9. 解码过程中的一些细节

    10. 最终的线性变换和softmax层


    1. Transformer架构

    首先,Transformer架构理论来源是《Attention is All You Need》,它主要有编码组件、解码组件以及它们之间的连接组成。

                                                           

    编码组件部分由一堆编码器(encoders)构成(论文中是将6个编码器叠在一起,6个编码器的效果要好于1个编码器,就像CNN中使用多个卷积核效果优于只使用一个),如下图:

                                                      

    2. 编码器

    所有的编码器在结构上都是相同的,它们之间没有共享参数(可以理解为它们是6个独立的参数空间);而每个解码器都可以分为自注意力层和前馈网络层:

                                                      

    从编码器输入的句子首先会经过一个自注意力层(self-attention),这层的作用是帮助编码器在对每个单词编码时关注输入句子的其他单词(主要是计算Q、K、V的过程)。

    自注意力层的输出会传递到前馈神经网络中(feed-forward),每个位置的单词对应的前馈神经网络都完全一样。

    3. 解码器

    解码器中也有编码器的自注意力层(self-attention)和前馈层(feed-forward)。除此之外,这两个层之间还有一个编码-解码自注意力层,用来关注输入句子的相关部分。

                                                 

    4. Transformer中的张量

    张量,是矢量概念的推广,简单理解为矢量是一阶张量,矩阵是二阶张量。

    首先将输入单词通过词嵌入算法转换为词向量:

                                                    

    每个单词都被嵌入512维的向量,简单用上面的方框表示这些向量。

    词嵌入过程只发生在最底层的编码器中。所有的编码器都有一个相同的特点,即它们接收一个向量列表,列表中的每个向量大小为512维。在底层编码器中它就是词向量,在其他编码器中,它就是下一层编码器的输出。向量列表大小是我们可以设置的超参数(一般是训练集中最长句子的长度)。

    将输入序列进行词嵌入之后,每个单词都会流经编码器中的两个子层。

                                                      

    其中,Transformer的一个核心特性就是,在输入序列中每个位置的单词都有自己独特的路径流入编码器。在自注意力层中,这些路径之间存在依赖关系(各个单词之间存在序列关系,会有位置编码);而前馈层没有这些依赖关系,因此在前馈层可以并行执行各种路径。

    5. 编码过程

    一个编码器(从最底层的那一个算起)接收向量列表作为输入,接着将向量列中中的向量传递到自注意力层进行处理,然后传递到前馈神经网络中,将输出结果传递到下一个编码器中。

                                                      

    5.1 从宏观上看自注意力机制

    举个例子:

    下列句子是需要翻译的输入句子:

    The animal didn't cross the street because it was too tired

    思考:这个 it 在这个句子中是指什么呢? 它指的是street还是animal呢?这对于人类来说是一个简单的问题,但是对于算法则不是。

    当模型处理这个单词 it 的时候,自注意力机制会允许 it 与 animal建立联系。随着模型处理输入序列的每个单词,自注意力会关注整个输入序列的所有单词,帮助模型对本单词更好地进行编码。

    如RNN网络,它会将已经处理过的前面所有的单词/向量的表示与它正在处理的当前单词/向量结合起来;而自注意力机制会将所有相关单词的理解融入到我们正在处理的单词中。

                                                                   

    当我们在编码器#5(栈中最上层编码器)中编码it这个单词时,注意力机制的部分会去关注 The Animal ,将它的表示的一部分编入it的编码中。

    5.2 微观上看自注意力机制的计算步骤

    通过了解如何使用向量来计算自注意力,然后使用矩阵来实现。

    计算自注意力的第一步就是从每个编码器的输入向量(词向量)中生成三个向量,也就说对于每个单词,我们创造一个查询向量、一个键向量和一个值向量。这三个向量是通过词嵌入与三个权重矩阵后相乘创建的。

    可以发现这些向量在维度上比词嵌入向量维度更低,它们的维度是64,而词嵌入和编码器的输入/输出向量的维度是512。但实际上不强求维度更小,这只是一种基于架构上的选择,它可以使多头注意力(multiheaded attention)的大部分计算保持不变。

                                                  

    注意:W^Q 、W^K 、W^V属于模型中的参数,一开始随机初始化,通过模型逐渐收敛而固定。

    X_1 与 W^Q 权重矩阵相乘得到 q_1 ,就是与这个单词相关的查询向量。最终使得输入序列的每个词向量创建一个查询向量、一个键向量和一个值向量。

    计算自注意力的第二步计算得分,假设我们在为这个例子中的第一个词 ''Thinking'' 计算自注意力向量,我们需要拿输入句子中的每个单词 对 ''Thinking'' 打分。这些分数决定了在编码单词 ''Thingking'' 的过程中有多重视句子的其他部分。

    这些分数是通过打分单词(所有输入句子的单词)的键向量与 ''Thinking'' 的查询向量相点积来计算的。所以如果我们是处理位置最靠前的词的自注意力的话,第一个分数是q1和k1的点积,第二个分数是q1和k2的点积。

                                                           

    第三步和第四步将分数除以8(8是论文中使用的键向量的维数64的平方分,这会让梯度更稳定),也可以使用其他值,8是默认值;然后通过softmax将所有单词的分数归一化,得到的分数都是正值且和为1.

                                                       

    这个softmax分数决定了每个单词对编辑当下位置(Thinking)的贡献。

    第五步将每个值向量乘以softmax分数(这是为了准备之后将它们求和)。

    第六步对加权值向量求和,然后即得到自注意力层在该位置的输出(例子中是对应第一个单词)。注意:自注意力的另一种解释是在编码某个单词时,将所有单词的值向量进行加权求和,权重是该词的键向量与被编码单词的查询向量的点击并softmax得到。

    这六步可以用下图表示:

                                                       

    这样,自注意力的计算就完成了,得到的向量就可以传给前馈神经网络了。

     5.3 自注意力的矩阵运算

    第一步是生成查询矩阵、键矩阵和值矩阵。为此,我们将输入句子的词嵌入装进矩阵X中(X的维度通常是:batch_size*dimension),将其程以我们训练的权重矩阵(W^Q 、 W^KW^V)。

                                                        

    这里,X中的每一行对应输入句子中一个单词的词向量。q/k/v向量的维度是64维,词向量是512维。

    最后,可以将5.2节中的第2到第6步合并为一个公式来计算自注意力层的输出:

                                                        

    6. Transformer中的多头注意力机制

    通过增加一种叫做“多头”注意力的机制(multi-headed attention),论文进一步完善了自注意力层,并在两方面提高了注意力层的性能:

    1. 它扩展了模型专注于不同位置的能力。在上面的例子中,虽然每个编码都在z1中有或多或少的体现,但是它可能被实际的单词本身所支配。如果我们翻译一个句子,比如,"The animal didn't cross the street because it was too tired",我们会想到 "it"指的是哪个词,这时模型的"多头"注意力机制会起作用。
    2. 它给出了多头注意力层的多个“表示子空间”(representation suspaces)。接下来我们将看到,对于 多头注意力机制,我们有多个查询/键/值权重矩阵集(Transformer中有8个注意力头,所以每个编码器/解码器有8个矩阵集合)。这些集合中的矩阵参数都是随机初始化的,在训练之后,每个集合都被用来将输入词嵌入(除了第一层是词嵌入,其他层来自较低编码器/解码器的向量)投影到不同的表示子空间中。

                                                  

    在多头注意力机制下,我们为每个头保持独立的 查询/键/值权重矩阵,从而产生不同的 查询/键/值矩阵。

    如果我们做与5.2节相同的自注意力计算,只需8次不同的权重矩阵运算,我们就会得到8个不同的Z矩阵:

                                                 

    注意:这种计算给我们带来了一点挑战,因为前馈层不需要8个矩阵,它只需要一个矩阵,我们只需要将这些每个头产生的Z矩阵拼接在一起,然后用一个附加的权重矩阵 W^O 与它们相乘即可:

                                         

    下面尝试用一张图概括自注意力的所有计算过程:

                                

    这就是自注意力的全部计算过程,还算简单吧,不过思路确实巧妙。

    7.  使用位置编码表示序列的顺序

    为了解决输入单词顺序的表示问题,Transformer为每个输入的词嵌入添加了一个向量。这些向量遵循模型学习到的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的解决方法是将位置向量直接添加到词嵌入中使得它们在接下来的运算中,能够更好地表达词与词之间的距离。

                                               

    如果我们假设词嵌入的维数是4,则实际的位置编码如下:

                                                    

    举个例子:

    在下图中,每一行对应一个词向量的位置编码,所以第一行对应着输入序列的第一个词。每行包含512个值,每个值介于-1和1之间。

                                                        

    20字的位置编码实例,词嵌入大小为512.可以看到它从中间分裂成两半,左半部分的值是由一个正弦函数生成;右半部分由余弦函数生成,然后将它们拼接在一起得到每一个位置编码向量

    原始论文里描述了位置编码的公式(第3.5节)。你可以在 get_timing_signal_1d()中看到生成位置编码的代码。这不是唯一可能的位置编码方法。然而,它的优点是能够扩展到未知的序列长度(例如,当我们训练出的模型需要翻译远比训练集里的句子更长的句子时)

    8. 残差模块

    在编码器的每个子层(包括自注意力、前馈网络层)的周围都有一个残差连接,并且都跟随着一个 “层-归一化”步骤。(Bert中是 Add&Norm 模块)

    层-归一化步骤(Add&Norm):

                                                  

    如果我们去可视化这些向量以及这个和自注意力相关联的层-归一化操作,那么看起来就像下面这张描述一样:

                                                      

    解码器的子层也是这样的,比如一个2层的编码-解码结构的transformer:

                                    

    9. 解码过程中的一些细节

    编码器通过处理输入序列开启工作。顶端编码器的输出之后会转化为一个包含向量K(键向量)和V(值向量)的注意力向量集。这些向量将被每个解码器用于自身的“编码-解码注意力层”,而这些层可以帮助解码器关注输入序列哪些位置合适:

                                                             

    在完成编码阶段后,则开始解码阶段。解码阶段的每个步骤都会输出一个输出序列的元素。

    接下来的步骤重复了这个过程,直到到达一个特殊的终止符号,它表示transformer的解码器已经完成了它的输出。每个步骤的输出在下一个时间步被提供给低端解码器,并且就像编码器之前做的那样,这些解码器会输出它们的解码结果。另外,就像我们对编码器的输入所做的那样,我们会嵌入并添加位置编码给那些解码器,来表示每个单词的位置。

    而那些解码器中的自注意力层表现的模式与编码器不同:在解码器中自注意力层只被允许处理输出序列中更靠前的那些位置,在softmax步骤前,它会把后面的位置给隐去(把它们设置为-inf)。

    这个“编码-解码注意力层”工作方式基本就像多头自注意力层一样,只不过它是通过下面的层来创造查询矩阵,并且从编码器的输出中取得键/值矩阵。

    10. 最终的线性变换和softmax层

    解码组件最后会输出一个实数向量,我们如何把浮点数变成一个单词?这便是线性变换层要做的工作,它之后就是softmax层。

    线性变换是一个简单的全连接神经网络,它可以把解码组件产生的向量投射到一个比它大得多的、被称为对数几率(logits)的向量里。

    不妨假设我们的模型从训练集中学习一万个不同的英语单词(模型的“输出词表”)。因此对数几率向量为一万个单元格长度的向量-----每个单元格对应一个单词的分数。

    接下来的softmax层便会把那些分数变成概率(都为正数,上限是1.0).概率最高的单元格被选中,并且它对应的单词被作为这个时间步的输出。

                                               

    上图中,以解码器组件产生的输出向量开始,之后会转化为一个输出单词。

     

    展开全文
  • 微软AI&Research今天分享了有史以来最大的基于Transformer架构的语言生成模型Turing NLG(下文简称为T-NLG),并开源了一个名为DeepSpeed的深度学习库,以简化对大型模型的分布式培训。 基于Transformer的架构,...
  • 2020-02-11 18:50 ...而今天,微软研究院重磅发布了有史以来最大的基于Transformer架构的语言生成模型 Turing-NLG。此模型的参数高达170亿,是英伟达的Megatron(也就是现在第二大Transformer模型)的两倍,是Op...
  • 介绍基于注意力的Transformer网络被广泛用,为了提高性能,模型通常通过增加隐藏层的尺寸(例如,T5[2]使用65K的尺寸)来扩大,或者通过堆叠更多的Transformer块(例如,GPT-3[3]使用96个Transformer块)来加深。...
  • transformer架构的理解

    2019-05-06 09:40:00
    架构图: 分为两大块,编码层与解码层; 本质上来说编码层会为每一个输入单词输出一个新的representation;可类比于lstm模型中的每个input中对应的hidden state值;而不是一个context vector; 解码层会根据...
  • Transformer模型 Transformer干什么的? Transformer目前主要应用于NLP领域中,它是17年Google团队在论文《Attentionisallyourneed》中提出的。 读完这篇论文,总结了Transformer的三个主要亮点: 1.不同于以往主流...
  • 作者:Alireza Dirafzoon编译:ronghuaiyang导读在单GPU上就可以运行的Transformer模型,而且几乎不损失精度,了解一下?如果你一直在开发机器学习算法用于处理连续数据 —— 例如语言处理中的文本,语音信号,或...
  • 机器之心报道机器之心编辑部考虑到 Transformer 对于机器学习最近一段时间的影响,这样一个研究就显得异常引人注目了。Transformer 有着巨大的内存和算力需求,因为它构造了一个注意力矩阵,需求与输入呈平方关系。...
  • 文档:6.2.1 认识Transformer架构.note 了解Transformer模型的作用. 了解Transformer总体架构图中各个组成部分的名称. 文档:6.2.2 输入部分实现.note 了解文本嵌入层和位置编码的作用. 掌握文本...
  • 但是通过这篇文章,你会从GNN的角度看待Transformer架构,对于原理有更清楚的认知。选自NTU Graph Deep Learning Lab,作者:Chaitanya Joshi,机器之心编译,参与:一鸣、杜伟、Jamin。有的工程师会问这个问题:...
  • 机器之心报道机器之心编辑部在计算机视觉...近日,一篇匿名的 ICLR 2021 投稿论文将标准 Transformer 直接应用于图像,提出了一个新的 Vision Transformer 模型,并在多个图像识别基准上实现了接近甚至优于当前 SOT...
  • 【新智元导读】为了探索AutoML在序列域中的应用是否能够取得的成功,谷歌的研究团队在进行基于进化的神经架构搜索(NAS)之后,使用了翻译作为一般的序列任务的代理,并找到了Evolved Transformer这一新的...
  • 之前有被人问到过为什么transformer要采取这样的架构。当然transformer的细节很多,self attention, fully connected layer, residual connection, layer normalization, etc, etc。如果真的要问为什么必须要做这样...
  • 作者丨苏剑林单位丨追一科技研究方向丨NLP,神经网络个人主页丨kexue.fm相信近一年来(尤其是近半年来),大家都能很频繁地看到各种 Transformer 相关工作(比如 BERT、GPT、XLNet 等等)的报导,连同各种基础评测...
  • Transformer 有着巨大的内存和算力需求,因为它构造了一个注意力矩阵,需求与输入呈平方关系。...这一方法超越了注意力机制,甚至可以说为下一代深度学习架构打开了思路。自面世以来,Transformer 模型已经在多个领域取
  • Transformer架构 可视化Transformer 代码实现 概述 深度学习做NLP的方法,基本上都是先将句子分词,然后每个词转化为对应的词向量序列。这样一来,每个句子都对应的是一个矩阵X=(x1,x2,…,xt),其中xi都代表着第...
  • (一)机器翻译及其相关技术 1. 机器翻译(MT):将一段文本从一种语言自动翻译为另一种语言,用神经网络解决这个问题通常称为神经机器翻译(NMT)。 主要特征:输出是单词序列而不是单个单词。...
  • 来源:新智元本文约1600字,建议阅读8分钟。Evolved Transformer不仅实现了最先进的翻译结果,与原始的Transformer相比,它还展示了语言建模的改...
  • 机器之心报道机器之心编辑部考虑到 Transformer 对于机器学习最近一段时间的影响,这样一个研究就显得异常引人注目了。Transformer 有着巨大的内存和算力需求,因为它构造了一个注意力矩阵,需求与输入呈平方关系。...
  • 问题陈述 给定Company Name和Business Description ,任务是将公司分类。 共有62个类别 方法 删除重复的条目 跨类别的Wordcloud可视化 具有BOW和多项朴素贝叶斯的基线模型分别获得F-1和AUC分数分别为0.59和0.685 。...
  • 谷歌最新博客表示,此前在语言建模和翻译等序列任务中,Transformer架构已经展现出了极大的优势,但这些架构几乎均为手动设计,与视觉领域差异巨大。 能不能用更自动的方式应用这一高效的架构? 谷歌研究人员就此一...
  • Transformer模型的架构,与上述seq2seq模型相似,Transformer同样基于编码器-解码器架构,其区别主要在于以下三点: (1)Transformer blocks:将seq2seq模型重的循环网络替换为了Transformer Blocks,该模块包含...
  • transformer

    2020-09-06 09:56:45
    transformer架构介绍 首先把transformer当成一个黑盒子,输入是一个序列,输出是另外一个序列,如下图: transformer由两部分组成,一个是encoders,另外一个是decoders,两者是相连的。 encoders包含了多个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 610
精华内容 244
关键字:

transformer架构