MENU

Transformer详解

July 12, 2020 • Read: 141656 • Deep Learning阅读设置

B站视频讲解

Transformer是谷歌大脑在2017年底发表的论文attention is all you need中所提出的seq2seq模型。现在已经取得了大范围的应用和扩展,而BERT就是从Transformer中衍生出来的预训练语言模型

这篇文章分为以下几个部分

  1. Transformer直观认识
  2. Positional Encoding
  3. Self Attention Mechanism
  4. 残差连接和Layer Normalization
  5. Transformer Encoder整体结构
  6. Transformer Decoder整体结构
  7. 总结
  8. 参考文章

0. Transformer直观认识

Transformer和LSTM的最大区别,就是LSTM的训练是迭代的、串行的,必须要等当前字处理完,才可以处理下一个字。而Transformer的训练时并行的,即所有是同时训练的,这样就大大增加了计算效率。Transformer使用了位置嵌入(Positional Encoding)来理解语言的顺序,使用自注意力机制(Self Attention Mechanism)和全连接层进行计算,这些后面会讲到

Transformer模型主要分为两大部分,分别是EncoderDecoderEncoder负责把输入(语言序列)隐射成隐藏层(下图中第2步用九宫格代表的部分),然后解码器再把隐藏层映射为自然语言序列。例如下图机器翻译的例子(Decoder输出的时候,是通过N层Decoder Layer才输出一个token,并不是通过一层Decoder Layer就输出一个token)

本篇文章大部分内容在于解释Encoder部分,即把自然语言序列映射为隐藏层的数学表达的过程。理解了Encoder的结构,再理解Decoder就很简单了

上图为Transformer Encoder Block结构图,注意:下面的内容标题编号分别对应着图中1,2,3,4个方框的序号

1. Positional Encoding

由于Transformer模型没有循环神经网络的迭代操作, 所以我们必须提供每个字的位置信息给Transformer,这样它才能识别出语言中的顺序关系

现在定义一个位置嵌入的概念,也就是Positional Encoding,位置嵌入的维度为[max_sequence_length, embedding_dimension], 位置嵌入的维度与词向量的维度是相同的,都是embedding_dimensionmax_sequence_length属于超参数,指的是限定每个句子最长由多少个词构成

注意,我们一般以为单位训练Transformer模型。首先初始化字编码的大小为[vocab_size, embedding_dimension]vocab_size为字库中所有字的数量,embedding_dimension为字向量的维度,对应到PyTorch中,其实就是nn.Embedding(vocab_size, embedding_dimension)

论文中使用了sin和cos函数的线性变换来提供给模型位置信息:

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

上式中$pos$指的是一句话中某个字的位置,取值范围是$[0, \text{max_sequence_length})$,$i$指的是字向量的维度序号,取值范围是$[0, \text{embedding_dimension}/2)$,$d_{\text{model}}$指的是embedding_dimension​的值

上面有$\sin$和$\cos$一组公式,也就是对应着embedding_dimension维度的一组奇数和偶数的序号的维度,例如0,1一组,2,3一组,分别用上面的$\sin$和$\cos$函数做处理,从而产生不同的周期性变化,而位置嵌入在embedding_dimension​维度上随着维度序号增大,周期变化会越来越慢,最终产生一种包含位置信息的纹理,就像论文原文中第六页讲的,位置嵌入函数的周期从$2 \pi$到$10000 * 2 \pi$变化,而每一个位置在embedding_dimension​维度上都会得到不同周期的$\sin$和$\cos$函数的取值组合,从而产生独一的纹理位置信息,最终使得模型学到位置之间的依赖关系和自然语言的时序特性

如果不理解这里为何这么设计,可以看这篇文章Transformer中的Positional Encoding

下面画一下位置嵌入,纵向观察,可见随着embedding_dimension​序号增大,位置嵌入函数的周期变化越来越平缓

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math

def get_positional_encoding(max_seq_len, embed_dim):
    # 初始化一个positional encoding
    # embed_dim: 字嵌入的维度
    # max_seq_len: 最大的序列长度
    positional_encoding = np.array([
        [pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim)]
        if pos != 0 else np.zeros(embed_dim) for pos in range(max_seq_len)])
    
    positional_encoding[1:, 0::2] = np.sin(positional_encoding[1:, 0::2])  # dim 2i 偶数
    positional_encoding[1:, 1::2] = np.cos(positional_encoding[1:, 1::2])  # dim 2i+1 奇数
    return positional_encoding

positional_encoding = get_positional_encoding(max_seq_len=100, embed_dim=16)
plt.figure(figsize=(10,10))
sns.heatmap(positional_encoding)
plt.title("Sinusoidal Function")
plt.xlabel("hidden dimension")
plt.ylabel("sequence length")

plt.figure(figsize=(8, 5))
plt.plot(positional_encoding[1:, 1], label="dimension 1")
plt.plot(positional_encoding[1:, 2], label="dimension 2")
plt.plot(positional_encoding[1:, 3], label="dimension 3")
plt.legend()
plt.xlabel("Sequence length")
plt.ylabel("Period of Positional Encoding")

2. Self Attention Mechanism

对于输入的句子$X$,通过WordEmbedding得到该句子中每个字的字向量,同时通过Positional Encoding得到所有字的位置向量,将其相加(维度相同,可以直接相加),得到该字真正的向量表示。第$t$个字的向量记作$x_t$

接着我们定义三个矩阵$W_Q,W_K.W_V$,使用这三个矩阵分别对所有的字向量进行三次线性变换,于是所有的字向量又衍生出三个新的向量$q_t,k_t,v_t$。我们将所有的$q_t$向量拼成一个大矩阵,记作查询矩阵$Q$,将所有的$k_t$向量拼成一个大矩阵,记作键矩阵$K$,将所有的$v_t$向量拼成一个大矩阵,记作值矩阵$V$(见下图)

为了获得第一个字的注意力权重,我们需要用第一个字的查询向量$q_1$乘以键矩阵K(见下图)

            [0, 4, 2]
[1, 0, 2] x [1, 4, 3] = [2, 4, 4]
            [1, 0, 1]

之后还需要将得到的值经过softmax,使得它们的和为1(见下图)

softmax([2, 4, 4]) = [0.0, 0.5, 0.5]

有了权重之后,将权重其分别乘以对应字的值向量$v_t$(见下图)

0.0 * [1, 2, 3] = [0.0, 0.0, 0.0]
0.5 * [2, 8, 0] = [1.0, 4.0, 0.0]
0.5 * [2, 6, 3] = [1.0, 3.0, 1.5]

最后将这些权重化后的值向量求和,得到第一个字的输出(见下图)

  [0.0, 0.0, 0.0]
+ [1.0, 4.0, 0.0]
+ [1.0, 3.0, 1.5]
-----------------
= [2.0, 7.0, 1.5]

对其它的输入向量也执行相同的操作,即可得到通过self-attention后的所有输出

矩阵计算

上面介绍的方法需要一个循环遍历所有的字$x_t$,我们可以把上面的向量计算变成矩阵的形式,从而一次计算出所有时刻的输出

第一步就不是计算某个时刻的$q_t,k_t,v_t$了,而是一次计算所有时刻的$Q,K$和$V$。计算过程如下图所示,这里的输入是一个矩阵$X$,矩阵第$t$行为第$t$个词的向量表示$x_t$

接下来将$Q$和$K^T$相乘,然后除以$\sqrt{d_k}$(这是论文中提到的一个trick),经过softmax以后再乘以$V$得到输出

Multi-Head Attention

这篇论文还提出了Multi-Head Attention的概念。其实很简单,前面定义的一组$Q,K,V$可以让一个词attend to相关的词,我们可以定义多组$Q,K,V$,让它们分别关注不同的上下文。计算$Q,K,V$的过程还是一样,只不过线性变换的矩阵从一组$(W^Q,W^K,W^V)$变成了多组$(W^Q_0,W^K_0,W^V_0)$ ,$(W^Q_1,W^K_1,W^V_1)$,…如下图所示

对于输入矩阵$X$,每一组$Q$、$K$和$V$都可以得到一个输出矩阵$Z$。如下图所示

Padding Mask

上面Self Attention的计算过程中,我们通常使用mini-batch来计算,也就是一次计算多句话,即$X$的维度是[batch_size, sequence_length],sequence_length​是句长,而一个mini-batch是由多个不等长的句子组成的,我们需要按照这个mini-batch中最大的句长对剩余的句子进行补齐,一般用0进行填充,这个过程叫做padding

但这时在进行softmax就会产生问题。回顾softmax函数$\sigma(z_i)=\frac{e^{z_i}}{\sum_{j=1}^K e^{z_j}}$,$e^0$是1,是有值的,这样的话softmax中被padding的部分就参与了运算,相当于让无效的部分参与了运算,这可能会产生很大的隐患。因此需要做一个mask操作,让这些无效的区域不参与运算,一般是给无效区域加一个很大的负数偏置,即

$$ \begin{align*} &Z_{illegal}=Z_{illegal}+bias_{illegal}\\ &bias_{illegal}→-∞ \end{align*} $$

3. 残差连接和Layer Normalization

残差连接

我们在上一步得到了经过self-attention加权之后输出,也就是$\text{Self-Attention}(Q, \ K, \ V)$,然后把他们加起来做残差连接

$$ X_{embedding} + \text{Self-Attention}(Q, \ K, \ V) $$

Layer Normalization

Layer Normalization的作用是把神经网络中隐藏层归一为标准正态分布,也就是$i.i.d$独立同分布,以起到加快训练速度,加速收敛的作用

$$ \mu_{j}=\frac{1}{m} \sum^{m}_{i=1}x_{ij} $$

上式以矩阵的列(column)为单位求均值;

$$ \sigma^{2}_{j}=\frac{1}{m} \sum^{m}_{i=1}(x_{ij}-\mu_{j})^{2} $$

上式以矩阵的列(column)为单位求方差

$$ LayerNorm(x)=\frac{x_{ij}-\mu_{j}}{\sqrt{\sigma^{2}_{j}+\epsilon}} $$

然后用每一列每一个元素减去这列的均值,再除以这列的标准差,从而得到归一化后的数值,加$\epsilon$是为了防止分母为0

下图展示了更多细节:输入$x_1,x_2$经self-attention层之后变成$z_1,z_2$,然后和输入$x_1,x_2$进行残差连接,经过LayerNorm后输出给全连接层。全连接层也有一个残差连接和一个LayerNorm,最后再输出给下一个Encoder(每个Encoder Block中的FeedForward层权重都是共享的)

4. Transformer Encoder整体结构

经过上面3个步骤,我们已经基本了解了Encoder的主要构成部分,下面我们用公式把一个Encoder block的计算过程整理一下:

1). 字向量与位置编码

$$ X = \text{Embedding-Lookup}(X) + \text{Positional-Encoding} $$

2). 自注意力机制

$$ Q = \text{Linear}_q(X) = XW_{Q}\\ K = \text{Linear}_k(X) = XW_{K}\\ V = \text{Linear}_v(X) = XW_{V}\\ X_{attention} = \text{Self-Attention}(Q,K,V) $$

3). self-attention残差连接与Layer Normalization

$$ X_{attention} = X + X_{attention}\\ X_{attention} = \text{LayerNorm}(X_{attention}) $$

4). 下面进行Encoder block结构图中的第4部分,也就是FeedForward,其实就是两层线性映射并用激活函数激活,比如说$ReLU$

$$ X_{hidden} = \text{Linear}(\text{ReLU}(\text{Linear}(X_{attention}))) $$

5). FeedForward残差连接与Layer Normalization

$$ X_{hidden} = X_{attention} + X_{hidden}\\ X_{hidden} = \text{LayerNorm}(X_{hidden}) $$

其中

$$ X_{hidden} \in \mathbb{R}^{batch\_size \ * \ seq\_len. \ * \ embed\_dim} $$

5. Transformer Decoder整体结构

我们先从HighLevel的角度观察一下Decoder结构,从下到上依次是:

  • Masked Multi-Head Self-Attention
  • Multi-Head Encoder-Decoder Attention
  • FeedForward Network

和Encoder一样,上面三个部分的每一个部分,都有一个残差连接,后接一个 Layer Normalization。Decoder的中间部件并不复杂,大部分在前面Encoder里我们已经介绍过了,但是Decoder由于其特殊的功能,因此在训练时会涉及到一些细节

Masked Self-Attention

具体来说,传统Seq2Seq中Decoder使用的是RNN模型,因此在训练过程中输入$t$时刻的词,模型无论如何也看不到未来时刻的词,因为循环神经网络是时间驱动的,只有当$t$时刻运算结束了,才能看到$t+1$时刻的词。而Transformer Decoder抛弃了RNN,改为Self-Attention,由此就产生了一个问题,在训练过程中,整个ground truth都暴露在Decoder中,这显然是不对的,我们需要对Decoder的输入进行一些处理,该处理被称为Mask

举个例子,Decoder的ground truth为"<start> I am fine",我们将这个句子输入到Decoder中,经过WordEmbedding和Positional Encoding之后,将得到的矩阵做三次线性变换($W_Q,W_K,W_V$)。然后进行self-attention操作,首先通过$\frac{Q\times K^T}{\sqrt{d_k}}$得到Scaled Scores,接下来非常关键,我们要对Scaled Scores进行Mask,举个例子,当我们输入"I"时,模型目前仅知道包括"I"在内之前所有字的信息,即"<start>"和"I"的信息,不应该让其知道"I"之后词的信息。道理很简单,我们做预测的时候是按照顺序一个字一个字的预测,怎么能这个字都没预测完,就已经知道后面字的信息了呢?Mask非常简单,首先生成一个下三角全0,上三角全为负无穷的矩阵,然后将其与Scaled Scores相加即可

之后再做softmax,就能将-inf变为0,得到的这个矩阵即为每个字之间的权重

Multi-Head Self-Attention无非就是并行的对上述步骤多做几次,前面Encoder也介绍了,这里就不多赘述了

Masked Encoder-Decoder Attention

其实这一部分的计算流程和前面Masked Self-Attention很相似,结构也一摸一样,唯一不同的是这里的$K,V$为Encoder的输出,$Q$为Decoder中Masked Self-Attention的输出

6. 总结

到此为止,Transformer中95%的内容已经介绍完了,我们用一张图展示其完整结构。不得不说,Transformer设计的十分巧夺天工

下面有几个问题,是我从网上找的,感觉看完之后能对Transformer有一个更深的理解

Transformer为什么需要进行Multi-head Attention?

原论文中说到进行Multi-head Attention的原因是将模型分为多个头,形成多个子空间,可以让模型去关注不同方面的信息,最后再将各个方面的信息综合起来。其实直观上也可以想到,如果自己设计这样的一个模型,必然也不会只做一次attention,多次attention综合的结果至少能够起到增强模型的作用,也可以类比CNN中同时使用多个卷积核的作用,直观上讲,多头的注意力有助于网络捕捉到更丰富的特征/信息

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

  1. RNN系列的模型,无法并行计算,因为 T 时刻的计算依赖 T-1 时刻的隐层计算结果,而 T-1 时刻的计算依赖 T-2 时刻的隐层计算结果
  2. Transformer的特征抽取能力比RNN系列的模型要好

为什么说Transformer可以代替seq2seq?

这里用代替这个词略显不妥当,seq2seq虽已老,但始终还是有其用武之地,seq2seq最大的问题在于将Encoder端的所有信息压缩到一个固定长度的向量中,并将其作为Decoder端首个隐藏状态的输入,来预测Decoder端第一个单词(token)的隐藏状态。在输入序列比较长的时候,这样做显然会损失Encoder端的很多信息,而且这样一股脑的把该固定向量送入Decoder端,Decoder端不能够关注到其想要关注的信息。Transformer不但对seq2seq模型这两点缺点有了实质性的改进(多头交互式attention模块),而且还引入了self-attention模块,让源序列和目标序列首先“自关联”起来,这样的话,源序列和目标序列自身的embedding表示所蕴含的信息更加丰富,而且后续的FFN层也增强了模型的表达能力,并且Transformer并行计算的能力远远超过了seq2seq系列模型

7. 参考文章

Last Modified: June 6, 2021
Archives Tip
QR Code for this page
Tipping QR Code
Leave a Comment

122 Comments
  1. lawson lawson

    softmax([2,4,4,])的结果是 ([0.0634, 0.4683, 0.4683])

  2. lawson lawson

    tql,真的tql

  3. vip vip

    我也觉得,tql,感觉清北大佬的水平

  4. yu yu

    position_encoding 应该是2*(i//2)吧

    1. mathor mathor

      @yu不是的,就是我上面的公式

    2. mor mor

      @mathor您好,关于position_encoding 代码里面 i (10000的指数 2i/dmodel) 的取值,我有一些看法。
      为了方便描述,我们假设encoding_dim=6,如果我们按照[2 * i for i in range(6)]来生成,得到的会是[0, 2, 4, 6, 8, 10],而从论文的公式和您的笔记来看,我们想要的i应是[0, 0, 2, 2, 4, 4]。
      也许可以这样:[2 * i for i in range(3)] 得到 [0, 2, 4],再进一步得到[0, 0, 2, 2, 4, 4]。
      当然,我省略了np.power()。

    3. mathor mathor

      @mor这个不太容易跟您解释,就连我自己都想了一晚上我是否错了,不过我在上面代码第13行加了一个print语句,打印positional_encoding,并且对照公式算了一下,发现我没有错,要不您也试一下@(太开心)

    4. how哇you how哇you

      @mor同意@mor。希望博主再仔细考证一下。

    5. Dionysus Dionysus

      @mor可以(i//2)for i in range(embed_dim)

    6. Yao Yao

      @Dionysus我最后试出来是2*np.floor(I/2) for I in range(embed_dim)

    7. xhhdsh xhhdsh

      @mathor公式没问题,但是代码确实写错了

    8. wang wang

      @yu博主既然是先计算的temp=pos/10000^(2i/d),那就应该保证2i位置和2i+1位置的temp值是相等的,但是代码中很明显是不同的呢@(委屈)

    9. 剪烛西窗 剪烛西窗

      @mor我理解了博主没有错。论文和这篇文章的描述是正确的,因为他是针对PE(pos,2i)和PE(pos,2i+1)分别描述的,所以范围是(2,encoding_dim/2)。而这篇文章中的代码是先求出来所有的,然后再分别求奇数位置和偶数位置。

    10. 4daJKong 4daJKong

      @mor(2 * i / embed_dim) for i in range(embed_dim)
      同意,这个嵌入生成确实有点问题,希望楼主能看下

    11. 4daJKong 4daJKong

      @4daJKong [(2 * (hid_j // 2)) for hid_j in range(hidden_dim)]
      这样生成的才对

  5. NLPer NLPer

    您好,我想转载该文,希望能都得到授权

    1. mathor mathor

      @NLPer请问转载到什么地方?

    2. NLPer NLPer

      @mathor您好,微信公众号:python遇见NLP

    3. mathor mathor

      @NLPer可以,请在开头注明出处

    4. NLPer NLPer

      @mathor好的,感谢

    5. cc cc

      @mathor博主 你上面的图显示不出来

    6. 喵子 喵子

      @cc我这边开了梯子就可以显示出来了

  6. 孙暖暖 孙暖暖

    给up主打call 太厉害了!

    1. mathor mathor

      @孙暖暖谢谢

  7. zfx zfx

    受益匪浅,谢谢博主!!

  8. Liam Liam

    醍醐灌顶,谢谢博主,tql@(大拇指)

  9. CCZ CCZ

    博主,我是在你写给贺老师的邮件中读到你目前的状态,来到了这里,曾经我也是贺老师的学生;博主你的能力应该是挺强的,贺老师给你的回复也很贴合;我目前也是一名工科211在读研究生

    1. mathor mathor

      @CCZ这么有缘吗,您居然是贺老师的学生!

  10. Vermouth Vermouth

    LayerNorm,是沿Embedding的方向进行标准化的吧?

    1. mathor mathor

      @Vermouth是的

    2. gq gq

      @mathor为什么使用Layer Normalization,而不使用Batch Normalization ?

    3. mathor mathor

      @gq效果不好呗,还能是什么原因

    4. 肖恩懒洋杨 肖恩懒洋杨

      @gqbatchNorm要指定L(句子长度), 但是每个batch的最大句子长短是不固定的。

    5. 66 66

      @gqbatch 的只能是相当于同一维度的 但是因为长短不一 所以layer 相当于同一个东西 来进行nor 所以会更好

  11. Hang Zhou Hang Zhou

    请问训练的时候decoder是如何处理dec_input,因为我看你在greedy search中用了了for-loop, 一步步更新dec_input. 但是这个操作,在训练模型的时候不需要吗? 我指训练的时候如何一步步利用dec_input里面的真实值,好像在代码中没找到相关内容

    1. Hang Zhou Hang Zhou

      @Hang Zhou好像找到相关解释了,,是并行处理了。避开了loop

    2. idealcp idealcp

      @Hang Zhou你好 请问在哪里看到相关解释了 这个问题我也没搞明白 非常感谢!

  12. obsidian obsidian

    加油,讲的很棒!

  13. phac123 phac123

    大佬你好,我想问一个问题,下面这个地方i的范围是不是应该除以二,
    [pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim)]

    即为:[pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim // 2)]

    1. fantasy fantasy

      @phac123那一步只是算了sin或者cos里面的值,真正取值是按照步长为2来取得

    2. 阿木 阿木

      @phac123我也觉得这步好像有些问题

  14. solthx solthx

    总结的太好了,赞!

  15. wisley wisley

    非常棒! 就是LN的地方图表示的不是很清楚感觉,对应的应该是batch_size,max_leng,hidden的张量来画图表示可能会更好!

    1. 喵子 喵子

      @wisley说的太对了,我一直卡在这了

  16. hellopbc hellopbc

    可见随着 序号增大,位置嵌入函数的周期变化越来越平缓;
    请问这个是怎么看的?

  17. hellopbc hellopbc

    第t个字、t时刻有什么说法嘛?没看太明白

  18. hellopbc hellopbc

    这个mask讲的绝,一下就清楚了,要是每个论文都和作者一样写,我就笑死了

  19. hellopbc hellopbc

    博主你好,我想请问一下,有的bert预训练模型似乎有一个输入序列长度的限制,请问这是出于什么目的才这样做呢,不限制序列长度不是更加灵活吗

    1. mathor mathor

      @hellopbc显存是有限的

    2. 肖恩懒洋杨 肖恩懒洋杨

      @hellopbc不限制序列长度的话, 每次都要重新计算位置嵌入

  20. bin bin

    请问decoder哪个outputs(shifted right) 是groundtruth吗,那在inference的时候这部分是怎么处理的呀

  21. Jarvis Jarvis

    请问这个输入到Decoder的X_hidder是怎么变换成K和V矩阵的?

  22. XD XD

    博主讲的好清晰,打call一下

  23. LL人 LL人

    老哥我想转载你的文章在CSDN

  24. T T

    同样是研一,差距为啥这么大

  25. king king

    博主,你好。因为decoder部分每个层都有两个输入,请问Transformer反向传播的路径是怎样的

  26. yuka yuka

    tql,真的tql@(太开心)

  27. salute salute

    tql原来是太强了的意思

  28. Olyvar Olyvar

    谢谢,谢谢,受益匪浅

  29. 杰

    楼主写的很好,认真学习后,有个问题问题:1.在训练的时候目标函数定义的是什么呢?我在原论文中好像没看到。2.对于QK来自Encoder V来自Decode 这句表述,是每一层Encoder Bolck 和Decode Block对应的吗,即第一层的Encoder Bolck QK对应 第一层的Decode Bolck中的V,还是只用最外一层的Encoder Bolck的QK对应于每一层的Decode Block种的V呢@(呵呵)

    1. mathor mathor

      @杰1. 目标函数(损失函数)为CrossEntropyLoss

      只用Encoder最后一层对应每一层Decoder
  30. 六月 六月

    讲的太清楚了,赞

  31. 烂人一个 烂人一个

    我很好奇,MASK三角矩阵那里到底应该是按位相乘,还是相加,还是两者都可以,好像别的地方都说的按位相乘,但我觉得你讲解的更正确,按位相乘会存在一些问题,元素符号不确定,相加似乎更稳妥,总是可以得到负无穷,望解答!

    1. mathor mathor

      @烂人一个感觉都可以,但我觉得相加还是要简单一点

  32. 烂人一个 烂人一个

    我还想请教大佬一个问题,为什么字编码和位置编码是直接相加,不是拼接。直觉上直接相加两者不就混在一起了吗?看到一种说法是两者拼接在一起再乘以一个转换矩阵得到的结果就是两者相加,不知道对不对?望解答!

    1. mathor mathor

      @烂人一个拼接肯定不好,假设一个batch就一句话,这一句话就两个词,那么向量的维度就是[1,2,emb_dim],前面的1我们省略掉,那么就是[2, emb_dim]。现在相当于有两个[emb_dim]向量,拼接成了[2,emb_dim]这样一个矩阵,但是这两个[emb_dim]向量是有顺序的,不能随便乱拼,而这个顺序的体现,其实就是通过在[emb_dim]向量中融入位置编码

      至于你说的另一种说法,我没看到过,好像也不是目前业界主流

    2. zc zc

      @mathor大佬,我也有和层主相似的疑问,即是否可以用拼接。按照您的例子,用相加是[2,emb_dim],用拼接就是变成了[2, 2*emb_dim](word_emb + position_emb),维度扩展了一倍。
      谢谢大佬,看您的博客收获很多!

  33. Pluto Pluto

    请问训练过程decoder的输入是不是就是groundtruth?还有我记得测试阶段decoder的输入好像和训练阶段不一样,博主能解释一下为什么嘛?

  34. 银

    请问大佬几个关于decoder测试阶段加速的问题 :
    1、在不损失太多精度的情况下是不是减少decoder ff_units 或者decoder layer数量 或者 seq lenth 来提速?
    2、有没有一种方法可以不让decoder 的预测串行 比如之前是预测完第一个字再预测第二个 能不能直接把全部的encoder的信息拿来并行预测?

  35. 亭亭玉立 亭亭玉立

    Layer Normalization 部分应该是矩阵的行吧,应该不是列

  36. Yang Yang

    博主您好,在多头注意力部分,attentiona is all your need中是将已经得到的一组Q,K,V通过不同的线性变换得到不同的Q,K,V。您在博客中写的,是直接使用不同的Wq, Wk, Wv,去处理输入,得到不同的Q,K,V。
    我个人觉得这两者似乎没有任何差别,不知道想的对不对? 在相关的论文里,大家是习惯用第一种方式来描述,还是第二种哎?
    感谢!

  37. 阿木 阿木

    博主您好,我认为[pos / np.power(10000, 2 * (i//2) / embed_dim) for i in range(embed_dim)]

    1. 河东第一萌新 河东第一萌新

      @阿木确实,这个才符合公式

  38. NLP工程师 NLP工程师

    您好,可否转载您的系列文章?公众号:算法工程师,会声明出处并加上原文链接!

    1. mathor mathor

      @NLP工程师no

  39. JuniorSummer JuniorSummer

    博主你好有些数学公式失效变成[Math Processing Error],直接就看不懂了,能麻烦博主重新更新下吗@(乖)

    1. JuniorSummer JuniorSummer

      @JuniorSummer回复完发现突然能看了,那没事了,不麻烦博主啦~

    2. mathor mathor

      @JuniorSummer强制刷新

  40. gasol gasol

    博主好厉害!新nlper看了醍醐灌顶!

  41. su人 su人

    这动图是怎么做的呀

    1. mathor mathor

      @su人这不是我做的,是我从某个外文博客上download下来的

  42. hhhttt hhhttt

    浩哥?

  43. Feirou Feirou

    太强了@(大拇指)@(大拇指)@(大拇指)

  44. yiyan yiyan

    只有我想问W_Q,W_K,W_V是怎么来的吗?看了好多资料还是不清楚是怎么这三个系数矩阵是怎么设计的,哪位好心人帮一下啊?

    1. mathor mathor

      @yiyan学过pytorch吗,学过的话就是nn.Linear()直接产生的

      没学过的话,你可以理解为随机产生的三个矩阵

  45. 问刘十九 问刘十九

    博主,有一点不明白的位置,前面几层的decoder的input,你视频你说的是不经过linear,直接送到下一个decoder的。那图中的output embedding,在训练过程中是只在第一层decoder上对ground truth进行编码,其他层的decoder就没有用到output embedding了。那在inference的时候,是不是第一次是对<SOS>进行embedding,送到模型,然后预测出下一个单词了,再进行一次embedding,是这样理解吗?

    1. mathor mathor

      @问刘十九我画了一张图,不知道能否解答你的疑问

      https://z3.ax1x.com/2021/11/24/oCH0Qf.jpg

    2. 问刘十九 问刘十九

      @mathor懂了,懂了,谢谢博主

  46. 阿QQ 阿QQ

    博主讲的太好了,疑惑顿消。请问可以转载么?转到CSDN博客,只用于自己学习

    1. mathor mathor

      @阿QQsure

  47. fightingX fightingX

    通俗易懂

  48. 123456 123456

    请问经过自注意力机制后的矩阵 是一个对称矩阵吗?

  49. Mr.Anonymous Mr.Anonymous

    博主的文章写的好棒!希望加个markdown下载的功能,方便将您的博文收藏和添加注释。谢谢分享~@(笑眼)

    1. mathor mathor

      @Mr.Anonymous以国内的环境来看,这样只会更方便大家互相抄

  50. ML ML

    博主这里可以添加一个细节,Q,K,V的维度,很多博客都未涉及这点,新手看transformer,认为三个矩阵维度都是一样的

  51. jshn jshn

    大佬好厉害!!!!!!

  52. zhy zhy

    大佬您好,一直有个疑惑。请问encoder输出给decoder的k和v就是Xhidden吗?谢谢

  53. Foreverythin Foreverythin

    太强了

  54. nlp-learning nlp-learning

    博主你好,自注意机制那里好像有点问题,单个词向量得到的权重分别和各个值向量相乘再求和,但是在矩阵计算中是将经过softmax的权重矩阵直接和值矩阵相乘,这两种计算方法应该是不一样的吧?

  55. aaxym aaxym

    太厉害了,大佬@(真棒)

  56. Liisu Liisu

    图片是不是都挂了

  57. 怂怂 怂怂

    写得是真的好啊@(花心)

  58. try try

    在用矩阵方法计算注意力的时候提到了论文中除以根号下d_k的trick,博客里好像没有对这个d_k进行说明,我查了一下原论文,发现是key矩阵的dimension,希望作者有时间可以补充说明一下

  59. Cqiang Cqiang

    我想问一个题外话,我是CSDN过来的,请问一下这个博客,是什么网站或者说是什么名字,这排版看着好舒服啊!!!

    1. mathor mathor

      @Cqiang这个博客用的是typecho框架搭建的,你所看到的排版或者界面是用的一个叫Mirages主题,就在博客最底下有个超链接,点进去就可以了解了

  60. 小鱼 小鱼

    “为了获得第一个字的注意力权重,我们需要用第一个字的查询向量 乘以键矩阵 K(见下图)”,这里K得写成K^T,可能作者一下写错了吧。总体来说,作者这篇文章写得太棒了,学到许多,也让我弄懂了这个transformer中的运算以及原理,感谢。

  61. 小鱼 小鱼

    Wq,Wk,Wv三个矩阵大小都是字向量的维数m*字向量的维数m形式的。

    1. 小鱼 小鱼

      @小鱼加上这个让内容更加充分。

    2. 小白 小白

      @小鱼多头注意力机制输入是一个batch,请问是否是也计算句子与句子之间的权重呢

    3. 小鱼 小鱼

      @小白只计算句子内部。

  62. yyer yyer

    tql太强了

  63. ebgert ebgert

    您好,想请教一下文章中的动图是用什么做的

  64. AI_magician AI_magician

    这里的 layer normalization 应该也可以是和 batch norm 一样的,可以学习偏移和平移变化的参数吧,而不是单纯用z-score吧?@(哈哈)

  65. nunu nunu

    大佬,请收下我的膝盖。

  66. heron heron

    大佬

  67. Neon Neon

    两年前看的时候研0,为了接触实验室的科研任务看的第一个大型深度学习模型,跟着复现了一遍代码,看得懵懵懂懂;转眼间研二,做科研一年多,用了无数次Transformer,今天为了背八股又翻回来看数学家写的Transformer博客,感觉有了更透彻的理解,真是厉害啊。

    1. mathor mathor

      @Neon你有了更深的理解,是你厉害

  68. 北风 北风

    你的文章写的很棒很棒

  69. zql zql

    这篇文章在Positional Encoding部分就征服我了

  70. kjz kjz

    24年研0,cv方向,老板让搞vit。天杀的,本科四年就没搞过nlp,查阅了很多资料,论文也读了好几遍,对于流程一知半解,最开始的decoder中输入名称是outputs就一直不明白,看来博主的帖子,豁然开朗,万分感谢。(对于自己的方向有一种94年入.......的感觉,不知道大佬们有什么看法)。

  71. 步行者 步行者

    厉害

  72. 白桦林 白桦林

    真是一个牛逼了得啊,写的太好了,无敌。