Transformer
说明
- 我在看bert和gpt模型的时候,都出现了transformer,所以写一篇博客,记录下学习的内容,方便以后忘了可以回想起来。
- 一个Transformer层:6个(其实可以自己改数量)encoder层加上6个decoder层。
- 粗略的结构如下图,该图片来自龙心尘-CSDN博客_transformer
- 本文还参考了原论文attention is all your need,transformer的个人实现代码
- 由于transformer的个人实现代码的例子不太友好,我这里修改了下代码,统一了这篇博客的例子一致。
- 首先写一个数据流,再介绍结构,最后展示代码
- 全文以
Je suis etudiant
为输入,任务为翻译,输出值为I am a student
- 那么transformer是怎么做这个翻译的呢
- 首先encoder将输入句子
Je suis etudiant
编码,也就是提取特征 - 下一步,我们将这个特征,和
[EOS]
输入到decoder中,翻译出I
- 接着,再把特征和
[EOS] I
输入到decoder中,翻译出am
- 接着,把特征和
[EOS] I am
输入到decoder中,翻译出a
- 最后,把特征和
[EOS] I am a
输入到decoder中,翻译出student
- 把已经翻译了合在一起,就是最终输出
I am a student
- 具体的结结构图如下,图片来自attention is all your need原文
- 注意上面的图,左边是encoder,右边是decoder。左边的encoder的方框旁边有个$\text{N}\times$,意思是这样的结构重复$\text{N}$次。
- decoder的第一个自注意力层和其他的不一样,这个有$Masked$,意思是用来掩码作用的。
- 实际上,整个模型中会出现两次掩码
- 第一种掩码是padding mask,因为每个句子长度是不一样的,所以我们需要将这个句子补充到一样长。但是这些补充的
[PAD]
没有啥意义,所以需要掩盖掉[PAD]
这个token,让自注意力矩阵不要关注这个token,因为这个[PAD]
只是填充作用 - 第二种掩码是sequence mask,在decoder中的掩码,这个掩码是为了让decoder看不到未来的信息,下面会举例子
- 第一种掩码是padding mask,因为每个句子长度是不一样的,所以我们需要将这个句子补充到一样长。但是这些补充的
数据流
第一步:处理数据
- 将上面的句子tokenize化,也就是切分成一个一个单词,如下:
x = [Je, suis, etudiant]
y = [I, am, a, student]
- 注意:这里的语料库只有一个样例,实际上我们处理的句子长度不一样。这样,我们就需要把所有的句子补齐长度,变成一样长
- 不妨假设补齐后的$x$和$y$的长度是$n=5$和$m=4$,那么补齐之后的$x$和$y$就是
Je, suis, etudiant, [PAD], [PAD]
[BOS] I am a student
- 这里的
[BOS]
的作用,是告诉序列模型开始输出了,也对应上面的图中的shifted right,所以这里的输出序列是多一个长度,所以一般在代码里面,都是$m \leftarrow m+1$
- 对于上述切分好的token,要形成字典,输入值
1 | src_vocab = {'[PAD]': 0, 'Je': 1, 'suis': 2, 'etudiant': 3} |
第二步:encoder
- 第一个encoder之前,要计算一个
[PAD]
的掩码矩阵get_attn_pad_mask
- 如
enc_inputs: tensor([[1, 2, 3, 0, 0]])
,也就是Je, suis, etudiant, [PAD], [PAD]
- 会形成一个$1nn$的矩阵
- 如
1 | # 这个矩阵的意思是:第一行,在我们提取特征的时候,不应该把`Je`的注意力放在第五列上(即`[PAD]`) |
- 第一个encoder进来的
enc_inputs
要经过词向量和位置向量的计算,转成了$\text{batch_size} \times n\times d_{model}$的矩阵,不妨记为$\mathbf{z}$ - 每个encoder有两个子层,分别是多头注意力层、前馈神经网络
- 每一层的encoder计算流程如下
- $\mathbf{z}$乘一个$W_x, W_v, W_k$矩阵,分别得到每个$\mathbf{z_i}$的查询向量$q_i=\mathbf{z_i}W_x$、键向量$k_i=\mathbf{z_i}W_k$、值向量$v_i=\mathbf{z_i}W_v$,这里的$\mathbf{z_i}$默认是batch中的一个token,也就是$\mathbf{z_i}$的维度是$1\times d_{model}$
- 矩阵乘法就是$Q=\mathbf{z}W_x$,以此类推
- 计算$\text { Attention }(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V$,注意这里的$QK^{T}$就是注意力得分矩阵
- 这个得分矩阵的维度是$\text{batch_size} \times n_{heads} \times n \times n$,这个矩阵要和pad掩码矩阵做点积
- 计算完$\text { Attention }(Q, K, V)$后乘一个权重矩阵$W^{O}$,得到一个$\text{batch_size} \times n \times d_{model}$的矩阵
- 第5步的矩阵加上第1步的矩阵,作为残差层,再进行一个layernorm,这就完成了多头注意力
- 假设多头注意力出来的值为$\mathbf{z}_{atten}$
- $\mathbf{z}_{atten}$经过一个前馈神经网络的值,再加上$\mathbf{z}_{atten}$,作为残差层,进行layernorm,这就完成了第二个子层
- 经过第8步输出的值,作为下一个encoder的输入
符号 | 维度 | 真实维度 | 含义 |
---|---|---|---|
$\mathbf{z}$ | $\text{batch_size} \times n \times d_{model}$ | $14512$ | 一批输入的数据量,n是输入的最大长度,$d_{model}$是模型的维度 |
$W_x$ | $\text{batch_size} \times n \times (d_{x}*n_{heads})$ | $14(64*8)$ | 8个scaled dot product层并行计算 |
$W_k$ | $\text{batch_size} \times n \times (d_{k}*n_{heads})$ | $14(64*8)$ | 键向量矩阵 |
$W_v$ | $\text{batch_size} \times n \times (d_{v}*n_{heads})$ | $14(64*8)$ | 值向量矩阵 |
$Q$ | $\text{batch_size} \times n_{heads} \times n \times d_{x}$ | $184*64$ | 每个token的查询值 |
$K$ | $\text{batch_size} \times n_{heads} \times n \times d_{k}$ | $184*64$ | |
$V$ | $\text{batch_size} \times n_{heads} \times n \times d_{v}$ | $184*64$ | |
$\text {Attention }(Q, K, V)$ | $\text{batch_size} \times n_{heads} \times n \times d_{v}$ | $184*64$ | 注意力值 |
$W^{O}$ | $n_{heads} d_{v} d_{model}$ | $864512$ | 就是个全连接层 |
第三步:decoder
- 第一个的decoder需要将
dec_input
输入,就是dec_inputs: tensor([[5, 1, 2, 3, 4]])
- 并进行词向量化,和位置向量相加,即变成一个$\text{batch_size} \times m \times d_{model}$的矩阵,记为$\mathbf{z}$
- 这里要计算两个掩码矩阵,
dec_self_attn_mask
和dec_enc_attn_mask
1 | # dec_self_attn_mask: batch_size * m * m |
每一个encoder的都会接受相同的
dec_inputs, enc_inputs, enc_outputs
每一层的encoder计算流程为:dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask
- 开始计算Masked多头注意力,接受一个dec_outputs和dec_self_attn_mask
将dec_outputs分别与$W_x$,$W_k$,$W_v$矩阵相乘,得到查询向量、键向量、值向量
计算$\text { Attention }(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V$,注意这里的$QK^{T}$就是注意力得分矩阵
这个得分矩阵的维度是$\text{batch_size} \times n_{heads} \times m \times m$,这个矩阵要和dec_self_attn_mask矩阵做点积
计算完$\text { Attention }(Q, K, V)$后乘一个权重矩阵$W^{O}$,得到一个$\text{batch_size} \times n \times d_{model}$的矩阵
第5步的矩阵加上第1步的矩阵,作为残差层,再进行一个layernorm,这就完成了第一个多头注意力子层
第6步的输出值与$W_x$,最后一个encoder的输出值与$W_k$,$W_v$矩阵相乘,得到查询向量、键向量、值向量
计算$\text { Attention }(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V$,注意这里的$QK^{T}$就是注意力得分矩阵
这个得分矩阵的维度是$\text{batch_size} \times n_{heads} \times m \times m$,这个矩阵要和dec_enc_attn_mask矩阵做点积
计算完$\text { Attention }(Q, K, V)$后乘一个权重矩阵$W^{O}$,得到一个$\text{batch_size} \times n \times d_{model}$的矩阵第5步的矩阵加上第1步的矩阵,作为残差层,再进行一个layernorm,这就完成了第二个多头注意力子层
假设第10步计算出来的值为$\mathbf{z}_{atten}$
$\mathbf{z}_{atten}$经过一个前馈神经网络的值,再加上$\mathbf{z}_{atten}$,作为残差层,进行layernorm,这就完成了第二个子层
经过第12步输出的值,作为下一个decoder的输出
- 注意到,上面的2~6步和7~10步,都是两个多头注意力层,这两个层的区别如下
- 第一个多头注意力层,查询、键、值都是decoder的输出值
- 第二个多头注意力层,查询是decoder的输出值,但是键、值是最后一个encoder的输出值
第一个子层 | 第二个子层 | |
---|---|---|
query | 上一个decoder的输出值 | 第一个子层的输出值 |
key | 上一个decoder的输出值 | 最后encoder的输出值 |
value | 上一个decoder的输出值 | 最后encoder的输出值 |
Mask矩阵维度 | $\text{batch_size} \times m \times m$ | $\text{batch_size} \times n \times m $ |
第四步:预测序列
经过decoder出来的数值,维度为$\text{batch_size} \times m \times d_{model}$
使用一个线性层,$d_{model} \times \text{vocab_output}$,$\text{vocab_output}$是输出词表的大小
- 即最后输出经过softmax,得到词表中每个token的概率
嵌入和位置向量
- 第一个encoder和decoder层的输入,都需要将上面的$1\times 4$和$1\times 5$的向量,转成词向量
- 还需要记录每个字符串的位置
- 下面的$p=512$,是文中的给出的一个维度,用于方便计算,$\mathbf{y}$是一样的做法。
- 下面的词向量其实可以看成一个权重矩阵,即一个$5 \times 512$的矩阵,5是x的序列长度,每一行对应一个token。
序号 | token | 词向量 | 维度 |
---|---|---|---|
1 | Je | $[x_{11}, x_{12}, …, x_{1p}]$ | $1\times 512$ |
2 | suis | $[x_{21}, x_{22}, …, x_{2p}]$ | $1\times 512$ |
3 | etudiant | $[x_{31}, x_{32}, …, x_{3p}]$ | $1\times 512$ |
0 | [PAD] | $[x_{01}, x_{02}, …, x_{0p}]$ | $1\times 512$ |
0 | [PAD] | $[x_{01}, x_{02}, …, x_{0p}]$ | $1\times 512$ |
- 除了词向量,还要加入位置的信息。就是每个token在句子中的位置,有两种计算方法,attention的计算方式如下:
- 如
suis
这个token,它在句子中的位置是2,那么$p o s= 2$ - 公式里面的$i$应该是$i=1,\dots, 256$,而且这个$PE$向量,偶数位是$\sin$,奇数位是$\cos$
- 但是torch和tensorflow里面的向量,前256维是$\sin$,后256维是$\cos$,源代码是
get_timing_signal_1d
- 如
- 计算完词向量和位置向量之后,将两个向量求和,$\mathbf{z} = \mathbf{x} + PE$,这个$PE$矩阵是不变的,而词向量矩阵$\mathbf{x}$是变化的
- 这个$\mathbf{z}$就是encoder的输入
注意力层
在讲注意力层之前,要讲讲现实中的一些东西
attention机制就是:人在阅读书籍、查看图片,并不会把所有的信息都看完,而是在句子、图片中找到重点辅助认知
现在序列到序列普遍的做法:encoder+decoder的结构
- encoder将输入的序列$x=(x_1, \dots, x_n)$编码成$z=F(x_1, \dots, x_n)$,这个$z$可以理解为是从原始序列中提取出来的语义
- decoder的任务就是,根据已有的语义$z$和已经输出的语序$(y_1, \dots, y_{i-1})$,生成第$i$个单词
- 每个$y_i$都是这么生成的,特别是在生成$y_1$时,原有的输出序列只会有
[EOS]
- 上面的公式并没有体现出attention的机制,就是,生成$y_1, y_2, y_3$等,使用的语义都是$z$,也就是关注了原始句子中每一个单词
- 那么怎么从这个$z$中,找出即将输出的$y_i$所对应重点呢?只要每次生成$y_i$采用的$z$不一样就ok啦,如下
- 这个$z_{(i)}$,可以看做是,现在要生成第$i$个单词,应该注意原始句子中每个单词的程度(就是一个概率分布)
- 我们可以将一个句子,看成一组
(K, V)
对,就是$x=(x_1, \dots, x_n)$有$n$个(K, V)
对,即[(K_1, V_1), (K_2, V_2), ...]
- 当我们要生成某个单词时,把这个单词转换成$Query$,分别与$n$个
(K, V)
对做查询,这里的$smilarity(Query, k_i)$在transformer里面,就是一个内积,得到一个数值,这个数值就代表了$Query$应该关注第$i$个单词的程度 在计算机中,K可以看做是地址,V看做是值,通过找到地址K,取出值V;也就是计算机寻址中,只会找一个值,但是在attention机制里面,会把所有的值取出来,进行加权求和
下图已经被展示到烂了的图,展示了注意力是怎么计算的。。。。。
scaled dot product attention
- 第一层的encoder的输入值,是词向量和位置向量的向量和,输入的矩阵形状是$\text{batch_size} \times \text{len_input} \times p = 1\times 5\times 512$
第二到六层的encoder的输入值,是上一层的输出值,同样也是$1\times 5\times 512$
现在这个输入值$\mathbf{z}$放到encoder中,每个encoder的做法是一样的,下面以一个encoder来说明。
- 将$\mathbf{z} = [\mathbf{z_1}, \dots, \mathbf{z_{5}}]^{T}$放进encoder
- 将每个$\mathbf{z_i}$计算分别与$W_x, W_v, W_k$做矩阵乘法,分别得到每个$\mathbf{x_i}$的查询向量$q_i=\mathbf{z_i}W_x$、键向量$k_i=\mathbf{z_i}W_k$、值向量$v_i=\mathbf{z_i}W_v$
- 这里查询向量、键向量、值向量都是$1\times64$维
- 其实可以分别设置维度$d_q, d_k, d_v$,但是attention的文章里面将维度统一了,方面计算。
- 那么$W_x, W_k$就是$512\times64$的矩阵,$W_v$是$512\times d_v$
- 注意,这里每一个$q_i$会和所有的$k_j \quad (j=1, \dots, 11)$做乘法运算和softmax,就得到了一个长度为$4$的分数向量
- 这个分数向量,就是第$i$个token,对5个token的注意力得分
- 比如,这个注意力得分计算之后为
[0.1, 0.4, 0.5, -1e9, -1e9]
,那么第$i$个token的注意力就放到了得分为$0.5$的那个token上去了 - 我们使用$\mathbf{x}$与$W_x, W_v, W_k$做矩阵乘法,就是上面的做法的矩阵表达形式
- 不妨把$Q,K,V$记为三种向量的表示,则三个矩阵的维度是$5\times 64$,下面的$d_k=64$
- MatMul就是矩阵乘积,这里有个Mask(opt.),这里的掩码和注意力得分矩阵有关
Multi-Head Attention
注意途中的紫色方框,
Scaled Dot-Product Attention
就是上面的步骤这里我们要关注$h$这个参数,$h$是head的个数,也就是
Scaled Dot-Product Attention
的个数,这里可以进行并行计算的- 文章中$h=8$,就是一个encoder层里面,有8个这个样的attention。即有八个矩阵集合
- 当一个输入值$\mathbf{z}$进入时,这个$\mathbf{x}$分别计算出$Q,K,V$后
- $Q,K,V$同时进入8个attention层
- 每个$\text{head_i}$的维度是$\text{len_seq} \times d_k$
- 下面的Concat是拼接的意思,即8个head拼成了$\text{len_seq} \times (64*8)$的矩阵
前馈神经网络
- 这里网络接受一个输入值,$\mathbf{z}$的形状如$\text{batch_size} \times n \times d_{model}$或$\text{batch_size} \times m \times d_{model}$
- 表达式如下
- 这里的权重矩阵$W_1$的维数是$512\times 2048$,就是文章中的$d_{ff}=2048$,不知道为啥要这么设置
权重 | 维度 | 实际 |
---|---|---|
$W_1$ | $d_{model} \times d_{ff}$ | $512\times 2048$ |
$b_1$ | $1 \times d_{ff}$ | $1 \times 2048$ |
$W_2$ | $d_{ff} \times d_{model}$ | $2048 \times 512$ |
$b_2$ | $1 \times d_{model}$ | $1\times 512$ |
参数个数的估计
- 这里计算下参数的个数
- 总共出现了这些超参数
超参数 | 含义 |
---|---|
$n$ | 输入序列长度的最大值 |
$m$ | 输出序列长度的最大值 |
$d_{model}$ | 模型的维度,也是词向量的维度 |
$d_{ff}$ | 前馈神经网络的权重维度 |
$d_x, d_k, d_v$ | 查询、键、值的维度 |
$h_{heads}$ | 多头的数量 |
$\text{batch_size}$ | 数据量的大小 |
- 分别计算参数的个数,这里不加入batch_size,即假定为1,一个token进去
- 词向量矩阵:$n \times d_{model}$和$m \times d_{model}$
- 多头注意力层:
- 查询、键、值权重矩阵,$d_{model} \times d_x$,$d_{model} \times d_k$,$d_{model} \times d_v$,即总共有$n_{heads} \times d_{model} \times (d_x + d_k + d_v)$个参数
- 权重矩阵$W^{O}$,计算完attention,进行拼接之后的权重矩阵,$(n_{heads}*d_v) \times d_{model}$
- 前馈神经网络:
- encoder、decoder会输入一个$1 \times d_{model}$的值,那么输出就是$\operatorname{FFN}(x)=\max \left(0, x W_{1}+b_{1}\right) W_{2}+b_{2}$
- 那么这个网络的权重$W_1$,$b_1$,$W_2$,$b_2$的维度就是$d_{model} \times d_{ff}$,$1\times d_{ff}$,$d_{ff} \times d_{model}$,$1 \times d_{model}$
- 总结:一个transformers有6个encoder、6个decoder
- 那么encoders共有,$6 \times n_{heads} \times d_{model} \times (d_x + d_k + d_v) + 6\times (n_{heads}*d_v) \times d_{model} + 12 \times d_{ff} \times d_{model} + 6 \times d_{model} + 6 \times d_{ff}$
- 那么decoders共有,$12 \times n_{heads} \times d_{model} \times (d_x + d_k + d_v) + 12\times (n_{heads}*d_v) \times d_{model} + 12 \times d_{ff} \times d_{model} + 6 \times d_{model} + 6 \times d_{ff}$
- 假设batch_size为1,n=1,m=1,那么这个transformer总共有42506240个参数
- 正确的参数是44152832,我这里算的有出入,是因为没有算bias项
代码展示
- 看了一些别人的代码,综合自己的理解,写了一些注释
- 下面的代码有几个不足之处:
- 没有分别给出输入序列、输出序列的最大长度,而是统一成了5
- 位置向量那里写得不够好
self.pos_emb
这里偷懒了,在encoder和decoder直接输入了torch.LongTensor([[1,2,3,0,0]])
和torch.LongTensor([[5,1,2,3,4]])
,这里应该输入enc_inputs
和dec_inputs
- 实现的例子应该给出不同的序列长度,以此来区分序列的长度
1 | # %% |