[论文速通]旧文重读之Attention Is All You Need
也是突然想起了这篇老文章。
可以说是现代大模型的鼻祖级文章之一。
之前大四闲得没事的时候看过一次,但是完全不知所云。
什么 QKV,什么 Attention,完全看不懂。
看了一两天之后就放弃了。
现在回头看,连带着数学,一个小时就看得差不多了。
可以说是成功对 Transformer 祛魅。
或者说,这一年以来的工程实践和学习,让我对很多以前不知道的东西有了新的认知。
那学术小白兼工程蒟蒻就来献丑了,浅谈一下自己读 *Attention Is All You Need* 的过程与感受吧。
纯属脸滚键盘,自己读完之后留档罢了。
如果能够帮助到某些读者,那自然是极好的。
---
# 0. 写在前面
本文只讨论最原始的 Transformer,也就是论文中提到的翻译任务。
后续那些花里胡哨的东西,不在本文讨论范围之内。
首先,翻译问题。
词表翻译太呆了,但是要解决一个词在不同上下文中语义不同的问题,一直是很费劲的事情。
LSTM、RNN 等模型尝试维护一个不断更新的隐藏状态,根据前面的状态继续处理或生成下一个单元。
但随着序列变长,远距离信息需要经过很多步状态传递,长期依赖仍然很难处理。
同时,这种一步接一步的结构也天然难以并行。
一个天然的想法就是:
**改变历史信息的权重。**
当年的一种思路,就是给 RNN 加入 Attention,以区分不同信息的重要程度。
重要的信息权重大一点,不重要的信息权重小一点。
并且,还有另外一种方法:
把 RNN 换成 CNN,不就能跨区域提取特征了吗?
虽然实现方法不同,但 Attention、CNN、RNN 本质上都在试图解决一个问题:
> **跨区域整合信息。**
RNN 靠状态一步一步传递;
CNN 靠不断堆叠卷积,逐渐扩大感受野;
那能不能直接让任意位置之间交换信息?
于是,一步到位:
> **Attention Is All You Need!**
---
# 1. Attention 与 Transformer
## 1.1 什么是 Attention
先看论文里的公式:
$$
\operatorname{Attention}(Q,K,V)
=
\operatorname{softmax}
\left(
\frac{QK^T}{\sqrt{d_k}}
\right)V
$$
这就是 Attention。
很多文章都在写什么 Query、Key、Value,看完之后反而更加不明所以。
其实我觉得可以先用一句话理解:
> **Attention 就是当前 token 看整个序列时,按照重要程度,把自己需要的信息拿回来。**
从数学形式上看,它很像一种对信息的**加权期望**。
为什么这么说呢?
首先拆开来看,Q、K、V 分别是什么。
### Q:Query
Q 是查询。
它表示:
> **当前 token 想要寻找什么信息。**
### K:Key
K 是 Key。
它表示:
> **当前 token 拿出来,用于和别人的 Q 进行匹配的“索引”或者“标签”。**
### V:Value
V 是 Value。
它表示:
> **当前 token 真正能够提供出去的信息。**
于是先计算:
$$
QK^T
$$
学过矩阵乘法的都知道,两组向量做点积以后,可以得到一个矩阵。
这个矩阵表示的就是:
> **每个 Query 与其他 token 的 Key 的匹配程度。**
或者换个我自己比较习惯的说法:
**它看起来很像一个协方差矩阵,尤其是在归一化以后。**
文章中的描述其实也很直接,就是计算每个 Query 与 Key 的 compatibility,也就是匹配程度。
接下来再经过:
$$
\operatorname{softmax}
\left(
\frac{QK^T}{\sqrt{d_k}}
\right)
$$
把这些匹配程度变成 Attention Weight。
这里为什么还要除一个:
$$
\sqrt{d_k}
$$
原因也很简单。
维度比较高的时候,Q 和 K 的点积数值可能会变得很大,容易把 Softmax 推到梯度很小的区域。
所以除以 $\sqrt{d_k}$,把数值压一压。
这也是为什么论文中的 Attention 全名叫:
> **Scaled Dot-Product Attention**
得到 Attention Weight 之后,再乘上:
$$
V
$$
也就是:
> **当前 token 根据 Attention Weight,从其他位置的 V 中把信息拿回来。**
如果整个序列有 $n$ 个 token,那么 Attention 之后仍然有 $n$ 个位置。
例如单个 Attention Head 的 Value 是 $64$ 维,那么最终输出就是:
$$
n\times64
$$
也就是说:
**token 数量没有改变,但每个 token 的表示已经融合了其他 token 的信息。**
所以 Attention 干的事情其实非常朴素:
> **决定当前 token 应该去哪里拿信息,以及拿多少。**
这就是 Attention。
---
## 1.2 什么是 QKV
其实上面已经说得差不多了。
但是还是得单独说一下:
**QKV 到底是怎么计算出来的?**
首先,文字进入 Tokenizer,被转换成一个个 token ID。
例如:
```text
我 -> 123
喜欢 -> 456
苹果 -> 789
这些数字本身没有办法直接做 Attention。
所以接下来还需要经过 Embedding,把每个 token 转换成一个向量。
在原始 Transformer 中:
\[ d_{\text{model}}=512 \]
假设一句话有 $n$ 个 token,那么经过 Embedding 和位置编码之后,可以得到:
\[ X\in\mathbb{R}^{n\times512} \]
然后通过可训练的线性投影矩阵计算 Q、K、V。
对于第 $i$ 个 Attention Head:
\[ Q_i=XW_i^Q \]\[ K_i=XW_i^K \]\[ V_i=XW_i^V \]
其中原始 Transformer 使用:
\[ W_i^Q,W_i^K,W_i^V \in \mathbb{R}^{512\times64} \]
所以:
\[ n\times512 \rightarrow n\times64 \]
注意,这里的 $512\rightarrow64$:
不是把 512 维硬生生切出前 64 维。
而是用一个 $512\times64$ 的矩阵,把完整的 512 维信息重新投影到一个 64 维空间。
所以 $W_Q$、$W_K$、$W_V$ 是 Transformer 中非常重要的一组可训练参数。
模型通过反向传播逐渐学会:
应该怎样查询信息;
应该怎样匹配信息;
应该怎样传递信息。
但 Transformer 当然不只训练这些 W。
后面还有 FFN、$W_O$、Embedding、LayerNorm 等大量可训练参数。
1.3 什么是 Encoder 和 Decoder
首先还是要回到 Attention 的作用:
Attention 负责跨 token 的信息组合。
那么问题来了:
从其他 token 那里把信息拿过来以后,应该怎么处理?
作者用了一个非常简单粗暴的方法:
全连接网络,FFN。
对,就是这么简单粗暴。
Attention 已经负责把其他 token 的信息拿回来了。
而 FFN 的作用,就是对当前 token 已经融合好的信息继续进行非线性加工。
所以我觉得可以这样理解:
Attention 负责“信息从哪里来”。
FFN 负责“这些信息拿回来以后怎么算”。
原论文中的 FFN 是:
\[ \operatorname{FFN}(x) = \max(0,xW_1+b_1)W_2+b_2 \]
也就是:
\[ 512 \rightarrow 2048 \rightarrow 512 \]
中间经过一次 ReLU。
所以 FFN 并不会继续让 token 之间交流。
它是对每个 token 分别进行特征加工。
也就是:
Attention:token 与 token 之间交流
FFN:单个 token 内部的特征加工
Transformer 由:
6 层 Encoder
6 层 Decoder
组成。
Encoder 每一层主要包括:
Multi-Head Self-Attention
↓
Add & Norm
↓
FFN
↓
Add & Norm
这里还有残差连接和 LayerNorm,不过不是本文重点,就不展开了。
Decoder 稍微复杂一点。
除了自己的 Masked Self-Attention 和 FFN 之外,还多了一层:
Encoder-Decoder Attention
用来读取 Encoder 的输出。
同时 Decoder 的 Self-Attention 需要加 Mask。
原因也很简单:
生成当前 token 的时候,不能偷看后面的答案。
所以 Encoder 和 Decoder 结构并不完全一样。
但整个 Transformer Layer 干的事情,在我看来其实非常直观:
不断进行跨 token 的信息交换,再对每个 token 内部的信息进行非线性加工。
一层不够?
那就再来一层。
再来一层。
最后让这些表示逐渐变成某种人类很难直接解释的高维特征。
这个感觉其实和 CNN 等深度网络也有一点异曲同工之妙。
1.4 多头注意力
如果只有一个 Attention Head,那么所有的信息关系都得挤在同一个 Attention 空间里面。
确实很专注。
但是也容易丢失 Attention 的多样性。
所以论文提出了:
Multi-Head Attention
原始 Transformer 使用了:
\[ h=8 \]
也就是 8 个 Attention Head。
这里一开始非常容易产生一个误解:
是不是把原来的 512 维向量切成:
1~64 -> head 1
65~128 -> head 2
...
其实完全不是。
每一个 Head 都会看到完整的 512 维输入。
只是每个 Head 有自己独立的一套:
\[ W_i^Q,\quad W_i^K,\quad W_i^V \]
把同一个 512 维输入分别投影到不同的 64 维空间:
\[ 512\rightarrow64 \]
所以本质上可以理解为:
在 8 个不同的表示空间里面分别做 Attention。
每个 Head:
\[ \operatorname{head}_i = \operatorname{Attention} \left( QW_i^Q, KW_i^K, VW_i^V \right) \]
最终得到一个 64 维输出。
然后把 8 个 Head 拼起来:
\[ \operatorname{Concat} ( \operatorname{head}_1, \ldots, \operatorname{head}_8 ) \]
于是:
\[ 8\times64=512 \]
整个过程大概就是:
\[ 512 \rightarrow 64 \rightarrow 64\times8 \rightarrow 512 \]
拼接以后还会再经过一个输出矩阵:
\[ W^O \]
完整公式就是:
\[ \operatorname{MultiHead}(Q,K,V) = \operatorname{Concat} ( \operatorname{head}_1, \ldots, \operatorname{head}_h ) W^O \]
这样,不同 Attention Head 就有机会学习不同的信息关系。
可能某个 Head 更关注局部关系;
某个 Head 更关注远距离关系;
某个 Head 又学到了其他东西。
当然,模型并没有规定:
“你负责语法,你负责指代,你负责位置。”
它们只是拥有不同的参数和不同的表示空间。
至于最后会不会分工、怎么分工,全靠训练自己学。
所以多头注意力在我看来,本质上就是:
用多个不同的视角,同时观察同一个序列。
1.5 位置编码
Attention 确实可以让任意两个 token 之间直接交换信息。
但是它却有一个硬伤:
它自己不知道顺序。
“我吃苹果”和“苹果吃我”,如果只有 token 本身,而没有位置信息,那么 Attention 并没有天然的时间顺序可以依赖。
RNN 的顺序是写死在网络结构里面的:
1 -> 2 -> 3 -> 4
Transformer 把这个结构扔掉以后,就必须通过另外一种方式告诉模型位置。
所以 Transformer 在 Embedding 上额外加入了:
Positional Encoding
也就是告诉模型:
你不仅要知道“你是谁”,还要知道“你在第几个位置”。
原论文使用的是正弦和余弦位置编码:
\[ PE_{(pos,2i)} = \sin \left( \frac{pos}{10000^{2i/d_{\text{model}}}} \right) \]\[ PE_{(pos,2i+1)} = \cos \left( \frac{pos}{10000^{2i/d_{\text{model}}}} \right) \]
最后直接加在 Embedding 上:
\[ X = X_{\text{embedding}} + X_{\text{position}} \]
于是 Transformer 虽然扔掉了 RNN 的递归结构,但没有扔掉“顺序”本身。
只是把顺序:
从网络结构里面拿了出来,显式编码进输入。
1.6 如何训练
曾经,我以为 Attention 是某种神奇的东西。
真的会像莫队或者 LCA 一样,在数据里面跳来跳去。
但是并非如此。
拆开以后会发现:
Transformer 依旧脱离不开:
矩阵乘法;
全连接层;
Softmax;
激活函数;
残差连接;
LayerNorm。
那就很好理解了。
掏出我们的:
反向传播大法。
Transformer 中需要学习的东西其实很多。
比如:
Embedding;
$W_Q$;
$W_K$;
$W_V$;
Multi-Head 后面的 $W_O$;
FFN 中的 $W_1$、$W_2$;
LayerNorm 中的参数;
最后的输出层。
这些东西说到底全部都是参数。
模型完成一次翻译,计算 Loss,然后反向传播。
梯度一路从输出往前传:
Loss
↓
输出层
↓
Decoder
↓
FFN / Attention
↓
WQ / WK / WV / WO
↓
Embedding
然后更新这些参数。
再来一遍。
再来一遍。
重复很多很多遍。
于是最后,这些看起来完全没有人工规定语义的矩阵,居然真的学会了:
什么信息应该被什么 token 查询、匹配和利用。
所以 Transformer 虽然看起来很神秘,但真正拆到底以后:
依旧是矩阵,依旧是神经网络,依旧是反向传播。
2. 一些碎碎念
经过这一年以来的积累。
或者说强行积累吧。
也是对很多概念有了更加直观的认识。
所以现在回看 Transformer,会觉得非常亲切。
很多以前零散的东西也终于串起来了。
什么:
KV Cache;
上下文窗口;
稀疏注意力;
多头注意力;
Embedding;
Position Encoding。
以前看到这些名词,感觉都是一个个独立的黑盒。
现在再回头看,会发现很多东西其实都能从最原始的 Transformer 一路顺下来。
想当年大四想啃强化学习,部署了一下午环境没部署好。
然后跑去看理论。
看了半天也没明白到底在干什么。
现在回头看,好像也就是云淡风轻了。
当然,能看懂一篇经典论文,也不代表真正懂了多少东西。
求学之路还很长。
同志仍需努力啊。
```

浙公网安备 33010602011771号