上一讲第 7 讲:循环神经网络 下一讲第 9 讲:目标检测、图像分割与可视化理解

第 8 讲:注意力机制与 Transformer

Attention and Transformers —— 从 RNN 机器翻译的"固定长度瓶颈"出发,沿着历史的轨迹看注意力机制如何诞生:对齐分数、softmax 加权、query/key/value、缩放点积、自注意力、多头注意力、位置编码与掩码,最终拼装成统治当今深度学习的 Transformer 架构,并把同一套架构搬到图像上得到 Vision Transformer

本讲概览:

本讲讲两大主题:一是注意力(attention)——一个本质上作用于向量集合的全新神经网络基元(primitive);二是 Transformer——以自注意力为核心组件搭起来的网络架构。剧透一下:今天深度学习中几乎所有的最大规模应用——图像分类、图像生成、文本生成、文本分类、音频处理——背后几乎清一色是 Transformer。老师特意按历史发展顺序来讲:虽然 Transformer 在 2017 年才正式诞生,但注意力的思想早在几年前就已经从 RNN 中生长出来了。回望历史,Transformer 横空出世的那一刻其实并不像想象中那样天翻地覆——因为自注意力、注意力这些想法当时已经在领域里酝酿了好几年。

一、动机:RNN 机器翻译的上下文向量瓶颈

先快速回顾上一讲。RNN(recurrent neural network,循环神经网络)是处理序列的架构,它让我们能处理超出"一对一"(输入一张图、输出一个分类)的更多问题:一对多(图像描述/image captioning:输入图像、输出一串单词)、多对一(输入一段视频帧序列、输出一个分类),等等。本讲继续沿着"更强架构 → 更新问题"的主线前进。

作为引子,考虑序列到序列(sequence to sequence)的机器翻译问题:输入一句英文,输出一句意大利语。两种语言的词数可能不同、词序可能完全不同,不能假设任何逐词对应关系——这正是序列架构的用武之地。用 RNN 做翻译可以追溯到 2014 年甚至更早,人们用 RNN 处理序列问题已经超过十年。

经典的做法是编码器—解码器(encoder–decoder)结构。编码器是一个 RNN:循环单元接收两个输入——当前时间步的输入 \( x_t \) 和上一时间步的隐状态 \( h_{t-1} \),吐出下一个隐状态 \( h_t \);同一组权重沿时间步反复复用,就能处理任意长度的序列。例子(为了幻灯片放得下,句子故意很短):"We see the sky",每个词喂给 RNN 一个 tick。编码器的目标是处理完所有词之后,把整句话的内容总结成一个向量,称为上下文向量(context vector)\( C \)。实现细节不一而足,最直接的理解:\( C \) 就是编码器 RNN 的最后一个隐状态——由于循环结构,最后那个隐状态已经吸收了整个输入序列的信息,可以看作整句话的"编码摘要"。这个向量可能是 128 个或 1024 个浮点数——关键是它的长度在设定网络时就被固定死了。

解码器是第二个 RNN(通常结构相同但权重 \( u \) 不同),每个时间步接收三个输入:上一个输出词 \( y_{t-1} \)、上一个解码器隐状态 \( s_{t-1} \)、以及上下文向量 \( C \),逐词地生成目标语言的句子。

问题来了:输入序列与输出序列之间的全部通信,都要挤过 \( C \) 这一个固定长度的向量。翻译"We see the sky"这种四词短句,把所有需要的信息塞进 1024 个浮点数或许还行;可如果要翻译一整段、一整本书呢?随着输入序列变长,要求网络把全部内容总结进一个定长向量迟早不再合理——这就是通信瓶颈(communication bottleneck)

直觉上的解法:与其强迫信息挤过一个固定长度的瓶颈,不如改变架构——在解码器生成每个词的时刻,都允许它回头看一眼整个输入序列。这样不再有瓶颈,模型也能扩展到长得多的序列。注意力、Transformer 以及今天的这一切,都源于解决这个 RNN 瓶颈问题。

二、注意力的诞生:让解码器每一步都能"回头看"

2.1 对齐分数:解码器状态和每个输入词有多匹配

编码器保持不变,仍然产出各时间步的隐状态 \( h_1 \ldots h_4 \)。解码器需要一个初始隐状态 \( s_0 \)(马上细说怎么设)。关键的新步骤是:拿 \( s_0 \) 与输入序列的每一个隐状态比较,为每个输入位置算一个标量对齐分数(alignment score),衡量"当前解码器状态与这个输入 token 有多匹配"。四个输入 token,就算四个对齐分数。

怎么算?实现方式很多,课上给的是一个简单版本:一个小的线性层 \( f_{\mathrm{att}} \)——把解码器隐状态 \( s \) 与某个编码器隐状态 \( h \) 拼接成一个向量,过一次线性变换压成一个标量。它只是一个普通的线性算子,可以直接放进计算图,和网络其他参数一起通过梯度下降端到端地学习。

2.2 softmax:把无界的分数变成概率分布

得到的对齐分数是任意实数,取值范围从负无穷到正无穷,毫无约束。回忆几讲之前的 softmax 函数:它把任意一组分数变成一个概率分布——每个输出在 0 到 1 之间、全部加起来等于 1。于是把四个对齐分数过一遍 softmax,就得到了一个"在给定解码器状态下、对输入 token 的分布"。

2.3 加权求和:按注意力权重混合编码器状态

接下来用这个分布对编码器隐状态 \( h_1 \ldots h_4 \) 做线性组合,权重就是刚算出的注意力分数 \( a_{11} \ldots a_{14} \)(每个都在 0、1 之间且和为 1)。得到的结果就是本时间步的上下文向量 \( c_1 \)——一个由注意力权重调制的、对输入序列的全新摘要。解码器 RNN 本身的结构毫无变化:仍然把上下文向量与 \( y_0 \) 拼接喂给循环单元,得到下一个隐状态和第一个输出词。唯一改变的是 \( C \) 的计算方式。

$$ e_i = f_{\mathrm{att}}(s, h_i), \quad a_i = \mathrm{softmax}(e)_i, \quad c = \sum_i a_i h_i $$
深入

一个走通流程的具体数字例子。设输入四个词的编码器隐状态都是 2 维向量:\( h_1 = (1, 0) \)("We")、\( h_2 = (0.8, 0.6) \)("see")、\( h_3 = (0, 1) \)("the")、\( h_4 = (-0.5, 0.5) \)("sky");解码器当前状态 \( s_0 = (1, 0.1) \),此刻正要生成对应"we see"的意大利语词 vediamo。假设 \( f_{\mathrm{att}} \) 算出的对齐分数为 \( e = (3.2,\ 2.8,\ 0.1,\ -1.0) \)。做 softmax:分母 \( = e^{3.2} + e^{2.8} + e^{0.1} + e^{-1.0} \approx 24.5 + 16.4 + 1.1 + 0.37 \approx 42.4 \),于是注意力权重约为 \( a = (0.58,\ 0.39,\ 0.026,\ 0.009) \)。上下文向量 \( c_1 \approx 0.58 \times (1, 0) + 0.39 \times (0.8, 0.6) + 0.026 \times (0, 1) + 0.009 \times (-0.5, 0.5) \approx (0.89,\ 0.25) \)——几乎全部由"We"和"see"两个词的状态加权而成,"the sky"的贡献被压到接近零。这正是我们期望的行为:生成 vediamo 时,网络把注意力放在 we see 上,而不在乎 the sky。注意这些权重不是人给的,是 \( e \) 经 softmax 自动涌现的。

2.4 一切可微、无需监督:梯度下降自己学会"看哪里"

老师反复强调最关键的一点:这整套机制全部可微。我们从不需要告诉网络"输出哪个词该对应输入哪些词"——那类对齐标注数据几乎无法获得。整个机制只是一个由可微操作组成的计算图,最终仍在输出序列上算 softmax 交叉熵损失;网络为了预测对输出 token,自己在训练过程中学会该注意输入序列的哪些部分。有同学问:怎么初始化解码器?老师澄清这里的"初始化(initialize)"一词有歧义:其一,解码器作为神经网络,其权重照例随机初始化、靠梯度下降优化;其二,处理每个句子时需要设定初始隐状态 \( s_0 \) 的规则——可以直接取编码器最后一个隐状态、可以接一个可学习的线性投影(从编码器末状态映射到解码器初状态)、甚至干脆全零——都行,只要网络在训练中"预期"到这种设定即可。还有同学问"否定、异或这类依赖远距离组合的语义会不会出问题"——老师笑答:也许会,这是难题,需要很多数据和很多算力,指望网络自己把它解开。

之后一切迭代:拿新的解码器状态 \( s_1 \),用同一个 \( f_{\mathrm{att}} \) 重新和所有编码器状态比对,重新 softmax,重新加权求和得到新的上下文向量 \( c_2 \),再跑一个解码器 tick……如此以往,解码器每走一步,都为这一步现算一个新的上下文向量——不再是挤过一个固定向量,而是每一步都回头重看整个输入序列并"就地"重新摘要。瓶颈问题就此解决。之所以叫"注意力",正是因为网络在输出的每个时刻都在 attend(注视)输入序列的不同位置。

三、注意力的可视化:网络自己学会了软对齐

注意力还送来一份意外的礼物:可解释性(introspection)。我们从没教过网络输入输出之间的对齐关系,但把网络预测的注意力权重画出来,就能看到它解题时"在看哪里"。把每个输出词的注意力分布画成矩阵的一行(输入词排成列),就得到一张二维注意力热图。课上的例子是英译法:"The agreement on the European Economic Area was signed in August 1992."。

有同学问:它是怎么搞懂语法的?老师的回答很坦率:"This is the mystery of deep learning(深度学习的奥秘)"。我们没有教它任何语法,只是:给了海量"输入句—输出句"配对;凭人类设计者的直觉,认为"词与词之间应当存在某种对应",于是把注意力这个机制烘焙进架构;剩下的全靠梯度下降。网络在端到端任务中自己想清楚该如何使用这个机制——而它居然真的有效,这本身就相当神奇。

拓展

这套"编码器—解码器 + 注意力"的方案来自 Bahdanau、Cho 与 Bengio 2015 年的论文 Neural Machine Translation by Jointly Learning to Align and Translate——注意力机制在机器学习中的首次亮相。这篇论文刚刚在 ICLR 2025 上获得 Test of Time Award 亚军,足见其影响深远。两年后(2017),Vaswani 等人在 Google 推出 Attention Is All You Need,索性把 RNN 整个扔掉,只留注意力——这就是 Transformer。

四、把注意力从 RNN 中剥离:一般化的注意力算子

以上我们是为了修 RNN 才引入注意力,但这个机制本身其实藏着一个更一般的算子。接下来就把它单独拎出来,让它脱离 RNN 独立存在。重新表述:有一堆数据向量(data vectors)——想被摘要的数据(原来是编码器隐状态);有一堆查询向量(query vectors)——我们要为之产出输出的东西(原来是解码器隐状态)。注意力算子做的事:对每个查询向量,回到数据向量、以该查询专属的方式把它们重新摘要成一个输出向量(输出向量正是原来喂给 RNN 下一 tick 的上下文向量)。查询向量从哪来?注意力算子根本不关心——这正是剥离的关键。下面做四步推广。

4.1 推广一:用(缩放)点积当相似度函数

\( f_{\mathrm{att}} \) 原则上可以是任何"两个向量进、一个标量出"的函数。但真正的问题反过来:最简单的这种函数是什么?——点积(dot product)。事实证明点积作为相似度分数已经足够好用。但它和 softmax 之间有一个微妙且致命的交互:点积的量级随向量维度增长。设想两个全 1 向量:10 维时点积为 10,100 维时点积为 100——softmax 指数里被除以的分母随之暴涨,输出分布被压得越来越平(squashed),梯度随之消失(正是上一讲见过的梯度消失问题),整个机制学不动。对策是一个小小的"补丁":不用纯点积,而是除以向量维度的平方根——缩放点积(scaled dot product)。这让 softmax 在很宽的维度范围内都有健康的梯度流。老师强调这绝非小事:网络越做越大、向量维度越做越高是必然趋势(更高维度 = 更多算力、更多容量),架构必须考虑向上向下的可扩展性(scalability)。

深入

除以 \( \sqrt{d} \) 到底救了什么?数字算给你看。假设 \( q \) 和 \( k \) 的每个分量独立、均值 0、方差 1,那么 \( q \cdot k = \sum_{i=1}^{d} q_i k_i \) 的期望为 0、方差为 \( d \)。取 \( d = 64 \):点积的标准差约 8,原始分数动辄 \( \pm 20 \);softmax 中 \( e^{20} \) 与 \( e^{-20} \) 相差约 \( e^{40} \approx 2.4 \times 10^{17} \) 倍——分布退化成 one-hot,梯度几乎为 0。除以 \( \sqrt{64} = 8 \) 后,分数方差被拉回 1,softmax 输入回到温和区间,梯度顺畅。课上"全 1 向量"的例子是同一件事的直观版:10 维全 1 点积 \( = 10 \),100 维 \( = 100 \),softmax 指数级放大差异、分布越压越平;除以 \( \sqrt{10} \approx 3.16 \)、\( \sqrt{100} = 10 \) 后,两种维度下的分数量级重新可比。一句话:\( \sqrt{d} \) 是让点积方差与维度解耦的归一化因子

4.2 推广二:多个查询向量 → 一切都是矩阵乘法

第二步:不止处理一个查询,而是一次处理一整组查询。记查询矩阵 \( Q \) 形状为 \( N_q \times D_q \)(\( N_q \) 个查询,每个 \( D_q \) 维),数据矩阵 \( X \) 形状为 \( N_x \times D_q \)。现在需要所有查询与所有数据向量的两两相似度——每个都是一次(缩放)点积。什么运算能一次性算出两组向量的所有点积?矩阵乘法:\( QX^T \) 的第 \( (i, j) \) 个元素恰好是第 \( i \) 个查询与第 \( j \) 个数据向量的内积。得到相似度矩阵后,softmax 沿"数据"那一维逐行(每个查询独立地)做,得到注意力矩阵 \( A \);输出 = 数据向量的加权和,权重是 \( A \) 的每一行——而"用一个矩阵的行去线性组合另一个矩阵的行"又是矩阵乘法的看家本领:\( Y = AX \)(转置位置摆对即可)。于是整组查询的注意力计算归结为寥寥几次矩阵乘法

$$ E = QK^T / \sqrt{d_k} \quad \rightarrow \quad A = \mathrm{softmax}(E,\ \text{按行}) \quad \rightarrow \quad Y = AV $$

4.3 推广三:把数据向量的两重身份拆开——Key 与 Value

还有一个别扭之处:数据向量 \( X \) 在计算中出现在两个完全不同的场合——既参与和查询算相似度("你跟查询对不对得上"),又作为被加权求和的对象产出输出("取回什么内容")。用同一组向量干两件事显得很奇怪,也限制了表达力。解法:给这两种用途各配一个可学习的线性投影。键矩阵 \( W_k \) 把每个数据向量从 \( D_x \) 维投影到 \( D_q \) 维(必须与查询同维才能做点积),得到键向量(key) \( K = XW_k \),专门用来跟查询比对;值矩阵 \( W_v \) 把数据向量投影到 \( D_v \) 维(原则上可与查询维度不同),得到值向量(value) \( V = XW_v \),专门用来被加权求和产出输出。

直觉类比搜索引擎:你上 Google(或者现在问 ChatGPT——老师的玩笑)输入"世界上最好的学校是什么?"——这是查询;它需要与后端无数网页字符串()去匹配;而匹配之后你真正想拿回的东西()是 Stanford——和查询本身完全是两码事。查询是"我在找什么";数据后端里可能只希望拿一部分去匹配,取回的又是另一部分——键和值就像对数据向量的两种过滤器。有同学问:"数据向量到底怎么划分成键和值?"老师答:妙就妙在我们不需要说怎么分——只提供机制(两个投影矩阵),不提供规则;\( W_k \)、\( W_v \) 只是普通可学习参数,和当年不教英语法语的对齐一样,网络靠梯度下降自己学出"怎样分才对当前任务有用"。

注意力的一般形式:输入查询 \( Q \)、数据 \( X \);数据经两个可学习投影变成键 \( K \) 与值 \( V \);输出 \( = \mathrm{softmax}(QK^T / \sqrt{d}) \cdot V \)。它已经是一个独立的神经网络层:两个输入、两组可学习参数(\( W_k \)、\( W_v \))、输入一组向量、输出一组向量——可以插进任何架构里。因为查询和数据来自两个不同来源,这种形态叫交叉注意力(cross-attention)层。

五、自注意力:一组向量自己和自己交流

更常见甚至可以说当今最主流的形态是自注意力(self-attention)。这里只有一组输入向量 \( x_1 \ldots x_N \),没有天然的"数据 vs 查询"之分:输入一组向量,输出同样数量的一组向量。做法:给每个输入向量配三个可学习投影,分别得到它的查询、键、值

$$ Q = XW_Q, \quad K = XW_K, \quad V = XW_V \quad \Longrightarrow \quad \mathrm{SelfAttention}(X) = \mathrm{softmax}\left( QK^T / \sqrt{d_k} \right) \cdot V $$

之后的计算与交叉注意力分毫不差——只是 \( Q \)、\( K \)、\( V \) 恰好来自同一组输入向量的三个不同线性投影。每个查询和所有键比相似度、按列做 softmax 得到分布、用分布加权求和值向量得到对应的输出 \( y_i \)。有同学问 \( D_{\mathrm{in}} \)、\( D_{\mathrm{out}} \) 怎么定:它们是层的架构超参数,和普通线性层的输入输出维度一样;原则上可以不同,但实践中几乎总取相同——老师自嘲在记号上写得"过度一般"了。工程上还有一个常用技巧:三个投影融合成一次大矩阵乘(把三个权重矩阵沿维度拼接),因为硬件上"少数大矩阵乘"远比"多次小矩阵乘"高效。

自注意力 vs 交叉注意力,什么时候用哪个?有些问题天然有两种东西要互相比较:机器翻译(输入句 vs 输出句)、图像描述(图像区域 vs 生成的词)——用交叉注意力。有些问题只有一种东西:图像分类只有一张图,我们想让图的各个部分互相看——用自注意力。关键是同一套机器、同一个算子可以在两类问题间复用。另外,读过 Transformer 论文的同学会见到 encoder/decoder 的划分——这种"单组输入的自注意力"对应所谓 decoder-only attention;老师指出这恰是如今最常用的形态(GPT 类模型皆然),而且它已经完全不能塞回开头那个 RNN 解码器里了——我们确实已经和 RNN 分道扬镳。

六、置换等变性与位置编码

自注意力有一个漂亮的数学性质。把输入向量随机打乱顺序再喂进去,会发生什么?逐步追:\( Q \)、\( K \)、\( V \) 是逐行的线性投影,只会跟着打乱;相似度是点积,结果跟着打乱;softmax 根本不在乎输入顺序;加权求和同理——最终输出向量与原来完全相同,只是按同样的方式被打乱了顺序。这叫置换等变性(permutation equivariance)(可与卷积的平移等变性类比)。换言之,自注意力层的计算不依赖输入的顺序:它真正作用的不是"序列",而是无序的向量集合(set)——这正是开场说的"作用于集合的全新基元"的确切含义。(注意前提是固定权重只谈前向计算,不涉及训练。)

但顺序常常是重要信息!"我打他"和"他打我"不能一视同仁。快速修补方案:位置编码(positional embedding)——在每个输入向量上额外拼接一段编码位置的数据,告诉网络"这个是第 1 个、这个是第 2 个……"。具体机制有很多种(原始 Transformer 用不同频率的正弦函数,现代模型多用可学习向量,详见拓展块)。

拓展

原始 Transformer 用固定不学习的正弦位置编码 \( \mathrm{PE}(\mathrm{pos}, 2i) = \sin(\mathrm{pos} / 10000^{2i/d}) \)、\( \mathrm{PE}(\mathrm{pos}, 2i+1) = \cos(\cdots) \),任意相对偏移都对应一个线性变换,便于模型学到相对位置关系;后来的 GPT、ViT 等改用直接可学习的位置向量;支持外推的方案还有 RoPE(旋转位置编码,如今 LLM 的主流)、ALiBi(直接给注意力分数加距离惩罚)等。

七、掩码自注意力:不许偷看未来

有时我们想对计算结构加约束:不让每个输入都看所有其他输入,而是只允许看其中一部分。实现方式极其优雅:掩码自注意力(masked self-attention)——在算出对齐分数矩阵 \( E \) 之后,把想要屏蔽的位置直接改写成负无穷,再做 softmax。\( e^{-\infty} = 0 \),softmax 之后这些位置权重恰好为 0,输出就完全不依赖对应的值向量。

典型用途是语言建模(language modeling):现在不需要 RNN 也能直接做上一讲的语言模型任务——输入"attention is very",输出"is"只许依赖第一个词、"very"只许依赖前两个词……如果允许网络看到序列后方的词,它就会"作弊"。上三角掩码一盖,每个位置只能回头看,不能向前看。这也是 GPT 类自回归生成的核心组件。

八、多头自注意力:并行跑 H 份,各学各的

实践标配的最后一层加强:多头自注意力(multi-head self-attention)。把输入 \( X \) 路由到 \( H \) 份彼此独立的自注意力副本并行计算,每份产出自己的输出,把这些输出沿特征维拼接,最后再过一个线性输出投影把各头信息融合。为什么要这么做?老师的回答很直白:"更多的计算、更多的 flops、更多的参数——深度学习永远想要更多更大。"这是给这一层增加容量(capacity)的又一个手段。

有同学问:各头的输入 \( x_1 \)、\( x_2 \)、\( x_3 \) 不是完全一样的吗?对——架构完全相同、计算完全相同,唯一的区别是权重:每个头有自己独立的一套 \( W_Q \)、\( W_K \)、\( W_V \),初始化时随机到不同的值,训练后自然学出各不相同的"关注模式"(有的头盯句法、有的盯指代等——这是后续可解释性研究反复观察到的现象)。

深入

整个(多头)自注意力其实就是四次批量矩阵乘。①QKV 投影:一次大矩阵乘把 \( X \) 投影成 \( Q \)、\( K \)、\( V \)(工程上把三个投影融合、把 \( H \) 个头拼成批量维);②QK 相似度:\( QK^T / \sqrt{d} \),批量矩阵乘一次算出所有头的全部两两分数;③V 加权:注意力矩阵 \( A \) 乘 \( V \);④输出投影:拼接各头后乘 \( W_O \) 混合头间信息。没有循环、没有 for 循环——而矩阵乘是可分布式、可优化、高度并行的硬件友好原语,这正是 Transformer 能横扫天下的工程根基。

九、三大序列基元对比:RNN、CNN 与自注意力

至此课程已见过三种处理序列/结构化数据的基元,老师做了一次全面对比:

基元作用对象信息混合方式可并行性软肋
RNN一维有序序列沿时间逐步传递隐状态本质串行:每个隐状态依赖上一个隐状态,无法沿序列并行,难以做大不可并行;定长瓶颈
卷积\( N \) 维网格(1D/2D/3D/4D,图像即 2D)局部混合:核在网格上滑动高:每个核位置可并行计算难建大感受野——要么超大核,要么堆很多层,引入串行性
自注意力向量集合一层之内任意两向量直接交互高:整算子≈四次矩阵乘计算量与显存均 \( O(N^2) \)

自注意力天然适配长序列:没有 RNN 式瓶颈,也不必堆很多层才能让所有向量互相看见——一层自注意力就让每个向量看到其他所有向量。唯一代价是贵:长度 \( N \) 的序列需要 \( O(N^2) \) 计算与 \( O(N^2) \) 显存;\( N \) 到 10 万、100 万、1000 万时平方项非常吓人。"怎么解决?买更多 GPU。"——老师原话,这也是业界事实上的解法。

有同学问"可并行到底有什么好处?"老师给出了一段计算机历史的回答:过去二十年,单个处理器提速已经撞上物理极限,但获得成百上千个处理器很容易。计算力的增长来自设计"不必跑在单个快处理器上"的算法——能用 10 个、100 个、1000 个、100 万个处理器协同(老师:"我想把整个斯坦福校园铺满处理器一起算")的算法才能持续扩展。另一位同学追问"\( O(N^2) \) 是不是坏事?"老师的回答出人意料:"\( N^2 \) 其实是好事。"计算机科学教育教我们平方复杂度是坏蛋,但对神经网络而言,更多计算 = 网络有更多"思考"空间、更强的处理能力,也许能给出更好的答案——更贵不等于更糟。三选一用哪个?答案早写在了那篇论文的标题里:Attention is all you need——只靠注意力,就能走得很远。

拓展

\( O(N^2) \) 的攻防战。标准自注意力的显存墙催生了两条路线:一是改进实现——FlashAttention(2022)不改数学,通过分块计算(tiling)与在线 softmax 重排运算顺序,避免显式物化 \( N \times N \) 注意力矩阵,把显存降到 \( O(N) \) 并大幅提速,如今已是训练标配(FlashAttention-2/3 持续优化);二是改结构——稀疏注意力、线性注意力、状态空间模型(Mamba)等"高效注意力"家族,试图把平方降到线性,但在大规模语言建模上至今仍难全面替代标准注意力。长上下文(百万 token)仍是当前最活跃的战场之一。

十、Transformer:把自注意力包成一个块

零件齐了,现在组装。Transformer 就是把自注意力置于一切核心的网络架构。一个Transformer 块(block)的流水线:

  1. 多头自注意力:输入一组向量 X,让所有向量互相交流、比对——这是"向量之间"的计算;
  2. 残差连接(residual connection):把自注意力包在残差里,理由与几讲之前 ResNet 完全相同——让深层网络易于优化;
  3. 层归一化(LayerNorm):归一化让训练更稳定(第 6 讲已详述其原理);
  4. 逐向量前馈网络(MLP / FFN):一个两层小神经网络,对每个向量独立地施加同构的计算——这是"向量内部"的处理。自注意力负责让向量们对话,MLP 负责让每个向量自己消化,二者配合缺一不可;
  5. MLP 外面同样套残差连接 + LayerNorm,整体打一个框——这就是一个完整的 Transformer 块。

Transformer = Transformer 块的堆叠序列。自 2017 年问世以来架构几乎没大改,规模却天翻地覆:原始 Transformer 约 12 个块、2 亿(200M)参数;今天人们训练着数百块、上万亿参数的 Transformer——同一张蓝图在算力与参数上横跨多个数量级地扩展了八年。老师评论:2017 年之后只有一些小改动,因为时间关系留作课外阅读;并且"我看 Transformer 短期内不会退场"。

拓展

老师略过的"小改动"里最值得知道的是归一化位置:原始设计是"后归一化"(LayerNorm 在残差之后),现代大模型普遍改为预归一化(Pre-LN)——先 LayerNorm 再进子层,深网络训练更稳;GPT-2 之后的 decoder-only 模型还把激活函数从 ReLU 换成 GELU、把上下文相关的 FFN 中间层加宽到约 4 倍宽度等。此外现代 LLM 在多头注意力上普遍采用分组查询注意力(GQA)等变体以降低 KV 缓存的推理开销。

十一、Vision Transformer:把图像切成 patch

Transformer 能用于图像吗?相当直白——这就是 Vision Transformer(ViT)。流程:把输入图像切成若干小块(patch),把每个 patch 分别线性投影成一个向量(再拼上位置编码),这些向量作为输入喂给 Transformer;输出端每个 patch 得到一个对应向量。要做分类,就把所有输出向量做一次池化(pooling),接一个线性层预测类别分数。

注意这里没有用到任何卷积先验——图像不再是"网格",而是"一组 patch 向量",正契合自注意力"作用于向量集合"的本性。于是同一套 Transformer 架构通吃语言、图像以及更多模态——这也是它统治当代深度学习的原因。

拓展

ViT 出自 2020 年论文 An Image is Worth 16×16 Words:把 \( 224 \times 224 \) 图像切成 \( 16 \times 16 \) 的 patch(196 块)即可直接套用标准 Transformer。关键经验:在中等数据集上从头训 ViT 不及 CNN,但在 JFT-300M 级别的大数据(或先大规模预训练再迁移)上,ViT 反超最优 CNN,且随数据与规模持续扩展——再次印证"Transformer 是随算力与数据扩展的架构"。patch 大小是关键超参数(越小 token 越多、计算越贵)。后续的 DeiT 用知识蒸馏缓解数据饥渴;Swin Transformer 引入层级化与滑窗注意力以兼顾密集预测任务。今天几乎所有视觉—语言模型(VLM)——CLIP 的双塔、LLaVA/Qwen-VL 等多模态大模型——都是"ViT 视觉编码器 + Transformer 语言模型"的组合,用交叉注意力或拼接 token 的方式把两个模态接在一起:老师在课上埋的伏笔"Transformer 可用于很多其他东西"在此尽数兑现。

本讲要点回顾

上一讲第 7 讲:循环神经网络 下一讲第 9 讲:目标检测、图像分割与可视化理解