查看全课大纲(第 49 / 69 节)
- 1.NLP 的研究领域、应用与学习路径
- 2.自然语言的基本术语
- 3.信息论基础:信息熵与交叉熵
- 4.语言的进化与语义变迁
- 5.主观概率、客观概率与条件概率
- 6.贝叶斯原理与推理
- 7.随机变量:二项式、期望方差与联合概率
- 8.常见概率分布
- 9.辛普森悖论与贝叶斯解题实例
- 10.语言模型:语言概率与词袋模型
- 11.n-gram 语言模型(二元 / 三元 / N-Gram)
- 12.语言模型评价:交叉熵、困惑度与插值
- 13.朴素贝叶斯文本分类:垃圾邮件
- 14.拼写检查
- 15.中文分词:构词法、最大匹配、最优路径与 Jieba
- 16.词表示问题与语义相似度
- 17.TF-IDF 权重处理
- 18.One-Hot 表达与词表示的局限
- 19.Word2Vec 核心思想
- 20.Word2Vec 训练过程
- 21.Skip-gram 模型详解
- 22.词性标注任务与标注方法(规则 + N-Gram)
- 23.EM 算法与混合模型
- 24.高斯混合模型 GMM
- 25.从混合模型到隐马尔可夫模型
- 26.隐马尔可夫模型(HMM)推导与维特比解码
- 27.线性链条件随机场 CRF
- 28.贝叶斯网络与图模型
- 29.隐马尔可夫链与 HMM 推导
- 30.PLSA 主题模型
- 31.LSI 与 SVD 向量表达
- 32.LDA 与狄利克雷分布
- 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
- 34.上下文无关文法(CFG)与句法分析
- 35.概率上下文无关文法 PCFG
- 36.统计机器翻译:词对齐与翻译模型
- 37.统计机器翻译中的对齐模型与参数估计
- 38.从统计机器翻译到神经机器翻译
- 39.神经元与反向传播速览(指向深度学习课)
- 40.CNN 原理及其在 NLP 中的应用
- 41.循环神经网络的基本原理与 Elman / Jordan 网络
- 42.LSTM:长短期记忆网络的核心思想与分步实现
- 43.Encoder-Decoder 框架
- 44.Seq2Seq 与早期注意力机制
- 45.注意力机制的背景与动机
- 46.注意力模型实现原理与匹配函数
- 47.记忆网络:组成与计算
- 48.注意力机制的应用与历史延展
- 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
- 50.Multi-Head Attention、FFNN 与位置编码
- 51.层归一化、损失函数与 Universal Transformer 延展
- 52.预训练范式:从 ELMo 到 BERT
- 53.BERT 与掩码语言模型
- 54.BERT 下游任务微调与 GLUE 口径
- 55.GPT 与自回归语言建模
- 56.GPT-1 到 GPT-4 的演进
- 57.指令微调与 RLHF
- 58.分词器:BPE、WordPiece 与中文词表
- 59.上下文窗口与长文本处理
- 60.能力边界、成本与适用场景
- 61.文本分类实战:从朴素贝叶斯到逻辑回归
- 62.命名实体识别实战
- 63.文本生成与摘要实战
- 64.语义检索与向量库实战
- 65.RAG 知识库问答
- 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
- 67.幻觉与事实性问题
- 68.文本与知识结合(知识图谱压缩)
- 69.跨媒体信息检索与未来展望
Transformer 结构:Encoder-Decoder 栈与 Self-Attention
小象实战讲义 · 自然语言处理
在掌握了注意力机制的基本思想后,我们迎来了自然语言处理领域一个里程碑式的模型架构——Transformer。它摒弃了传统的循环结构,完全基于注意力机制来建模序列数据,不仅在机器翻译任务上取得了突破性进展,更成为后续所有预训练大模型(如 BERT、GPT)的核心骨架。本节我们将深入剖析 Transformer 的宏观架构,并重点讲解其核心运算单元:自注意力(Self-Attention)。
💡 核心导读
本节你将掌握:
- Transformer 的宏观架构:理解其作为编码器-解码器(Encoder-Decoder)栈的整体设计。
- 编码器与解码器的内部结构:拆解单个编码器和解码器的组成,特别是自注意力层与前馈神经网络层。
- 自注意力机制的原理与计算:从直观理解到公式推导,掌握 Query、Key、Value 矩阵的计算以及注意力权重的生成过程。
- 自注意力的作用与意义:理解它如何让模型动态地关注输入序列中不同部分的信息,并解决长距离依赖和指代消解等问题。
从序列到序列到 Transformer
在上一节中,我们学习了基于 RNN/LSTM 的序列到序列(Seq2Seq)模型以及注意力机制。这种模型虽然强大,但仍存在一些局限:RNN 的串行计算导致训练效率低,且长距离依赖信息在传递过程中容易被“稀释”或遗忘。
Transformer 的提出(Vaswani et al., 2017)带来了一个革命性的思路:完全抛弃循环结构,仅依赖注意力机制来建立输入与输出序列中任意两个位置之间的直接关联。这使得模型能够并行处理整个序列,极大地提升了训练速度,并更有效地捕获长程依赖关系。
Transformer 最初被应用于机器翻译任务。我们可以将其视为一个“黑盒”,输入一种语言的句子,输出另一种语言的翻译结果。当然,它的应用远不止于此,文本摘要、词性标注、问答等任务都可以被建模为序列到序列的转换问题,并套用 Transformer 架构。
Transformer 的宏观架构:编码器-解码器栈
打开 Transformer 这个“黑盒”,我们看到的是一个非常经典的编码器-解码器(Encoder-Decoder) 结构,这与我们之前学过的 Seq2Seq 模型在思想上一脉相承。
输入序列 (源语言) -> [编码器组件] -> [解码器组件] -> 输出序列 (目标语言)然而,Transformer 的独特之处在于,它的编码器和解码器都不是单一的网络层,而是由多个完全相同的层堆叠(Stack)而成。
编码器组件 = Stack of N Encoder Layers (N=6 in original paper)
解码器组件 = Stack of N Decoder Layers (N=6 in original paper)数据流动过程:
- 编码阶段:输入序列(例如一个法语句子)经过词嵌入(Word Embedding) 和位置编码(Positional Encoding) 后,送入第一个编码器层。该层的输出作为第二个编码器层的输入,如此依次传递,直到最后一个(第N个)编码器层。最后一个编码器层的输出,包含了输入序列经过层层抽象和整合后的上下文感知表示。
- 解码阶段:解码器接收目标序列(例如一个英语句子,在训练时通常是右移一位的真实标签)的嵌入表示和位置编码。解码过程也是自回归的(autoregressive):
- 第一个解码器层接收编码器最终输出的信息(通过一种特殊的“编码器-解码器注意力”层)以及当前已生成的部分目标序列,产生第一个解码状态。
- 该状态与编码器输出一起,送入第二个解码器层,产生第二个解码状态。
- 此过程持续进行,直到最后一个解码器层输出最终的解码状态。
- 输出层:最后一个解码器层的输出通过一个线性层(全连接层)和一个 Softmax 层,转换为目标语言词汇表上的概率分布,从而预测下一个词。
这种堆叠结构使得模型能够构建不同层次的抽象表示,底层编码器可能捕获词汇、短语级别的信息,而高层编码器则能捕获更复杂的句法和语义关系。
拆解编码器:自注意力与前馈网络
让我们聚焦于构成编码器栈的基本单元——单个编码器层(Encoder Layer)。每个编码器层主要由两个子层构成:
- 多头自注意力层(Multi-Head Self-Attention Layer)
- 前馈神经网络层(Position-wise Feed-Forward Network)
每个子层周围都应用了残差连接(Residual Connection) 和层归一化(Layer Normalization)。数据流可以概括为:子层输出 = LayerNorm(x + Sublayer(x))。
我们首先深入理解最核心的自注意力(Self-Attention) 机制,特别是“自(Self)”的含义。
理解“自注意力”
在之前的注意力机制中,我们有一个查询(Query,例如解码器的隐藏状态)和一组键值对(Key-Value pairs,例如编码器的所有隐藏状态),通过计算 Query 与每个 Key 的相似度来对 Values 进行加权求和。
自注意力,顾名思义,就是 Query、Key、Value 都来自同一个序列。对于输入序列中的每一个词(或位置),自注意力机制会计算它与序列中所有词(包括它自己)的关联程度(注意力权重),然后根据这些权重对所有词的 Value 表示进行加权求和,从而得到该词新的、融合了全局上下文信息的表示。
一个经典例子: 考虑句子:“The animal didn‘t cross the street because it was too tired.” 我们想知道代词“it”指代的是什么?是“animal”还是“street”?一个理想的自注意力机制在计算“it”的新表示时,应该给“animal”分配很高的注意力权重,而给“street”分配很低的权重。这样,“it”的表示中就更多地包含了“animal”的信息,从而帮助模型进行正确的指代消解。
自注意力的计算步骤
自注意力的计算可以分解为以下几个清晰的步骤。假设我们有一个输入序列,包含 n 个词,每个词已被表示为 d_model 维的向量(例如 512 维)。我们将这些向量堆叠成矩阵 X,其形状为 (n, d_model)。
自注意力的目标是产生一个输出矩阵 Z,单头时其形状为 (n, d_v)(列数由 Value 投影维度 d_v 决定,不一定等于 d_model;多头拼接后再经线性层投影回 d_model),其中每一行是对应输入词经过上下文信息增强后的新表示。
步骤 1:创建 Query, Key, Value 矩阵 我们通过三个不同的可学习权重矩阵 W_Q, W_K, W_V(每个形状为 (d_model, d_k) 或 (d_model, d_v),通常令 d_k = d_v)将输入 X 线性投影到三个不同的空间:
Q = X * W_Q(Query)K = X * W_K(Key)V = X * W_V(Value)
这里,Q, K, V 的每一行分别对应输入序列中一个词的 Query、Key、Value 向量。投影的目的是让模型能够学习在不同语义子空间中进行注意力计算。
步骤 2:计算注意力分数 注意力分数衡量 Query 和 Key 的相似度。对于序列中的第 i 个词(Query q_i),我们计算它与所有词(Keys k_1, ..., k_n)的点积:score_i_j = q_i · k_j^T。将所有分数组合起来,就是矩阵乘法:Scores = Q * K^T,形状为 (n, n)。矩阵中第 i 行第 j 列的元素,表示第 i 个词对第 j 个词的注意力分数。
步骤 3:缩放与 Softmax 点积的结果可能数值很大,尤其是当 d_k 较大时,这会导致 Softmax 函数的梯度非常小。因此,Transformer 对分数进行缩放:ScaledScores = Scores / sqrt(d_k)。 接着,对 ScaledScores 的每一行应用 Softmax 函数,使得每一行的所有权重之和为 1,得到注意力权重矩阵 AttentionWeights。
步骤 4:加权求和 最后,用注意力权重对 Value 矩阵 V 进行加权求和,得到输出 Z:Z = AttentionWeights * V。Z 的每一行就是对应输入词的新表示,它融合了序列中所有词的信息,融合的强度由注意力权重决定。
公式总结: 单个注意力头的计算可以浓缩为一个公式: Attention(Q, K, V) = softmax( (Q * K^T) / sqrt(d_k) ) * V
为了让概念更具体,我们用一个极简的数值例子来演示上述计算流程。请注意,实际模型中的维度远大于此,且权重是学习得到的。
import numpy as np
# 步骤1: 定义极小规模的输入和权重(仅为演示)
# 假设输入序列有2个词,每个词嵌入维度为4
np.random.seed(42) # 固定随机种子保证可复现
X = np.random.randn(2, 4).round(4) # 输入矩阵,形状(2,4)
print("输入矩阵 X (2个词,每个词4维向量):")
print(X)
# 初始化可学习的权重矩阵(这里随机生成以演示计算)
# 假设我们将维度从4投影到3(即 d_k = d_v = 3)
W_Q = np.random.randn(4, 3).round(4)
W_K = np.random.randn(4, 3).round(4)
W_V = np.random.randn(4, 3).round(4)
print("\n权重矩阵 W_Q:")
print(W_Q)
print("\n权重矩阵 W_K:")
print(W_K)
print("\n权重矩阵 W_V:")
print(W_V)
# 计算 Q, K, V
Q = X @ W_Q
K = X @ W_K
V = X @ W_V
print("\nQuery 矩阵 Q = X * W_Q:")
print(np.round(Q, 4))
print("\nKey 矩阵 K = X * W_K:")
print(np.round(K, 4))
print("\nValue 矩阵 V = X * W_V:")
print(np.round(V, 4))输入矩阵 X (2个词,每个词4维向量):
[[ 0.4967 -0.1383 0.6477 1.523 ]
[-0.2342 -0.2341 1.5792 0.7674]]
权重矩阵 W_Q:
[[-0.4695 0.5426 -0.4634]
[-0.4657 0.242 -1.9133]
[-1.7249 -0.5623 -1.0128]
[ 0.3142 -0.908 -1.4123]]
权重矩阵 W_K:
[[ 1.4656 -0.2258 0.0675]
[-1.4247 -0.5444 0.1109]
[-1.151 0.3757 -0.6006]
[-0.2917 -0.6017 1.8523]]
权重矩阵 W_V:
[[-0.0135 -1.0577 0.8225]
[-1.2208 0.2089 -1.9597]
[-1.3282 0.1969 0.7385]
[ 0.1714 -0.1156 -0.3011]]
Query 矩阵 Q = X * W_Q:
[[-0.8075 -1.511 -2.7725]
[-2.2639 -1.7685 -2.1268]]
Key 矩阵 K = X * W_K:
[[-0.2648 -0.7099 2.4502]
[-2.0512 0.3119 0.4312]]
Value 矩阵 V = X * W_V:
[[-0.4371 -0.6028 0.6993]
[-1.677 0.421 1.2013]]
# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import numpy as np
np.random.seed(42) # 固定随机种子,确保输出可复现
X = np.random.randn(2, 4).round(4) # 输入矩阵,形状(2,4)
W_Q = np.random.randn(4, 3).round(4)
Q = X @ W_Q
W_K = np.random.randn(4, 3).round(4)
K = X @ W_K
# 步骤2: 计算注意力分数矩阵 Scores = Q * K^T
scores = Q @ K.T
print("原始注意力分数矩阵 Scores = Q * K^T:")
print(np.round(scores, 4))
# 步骤3: 缩放并应用 Softmax
d_k = K.shape[1] # d_k = 3
scale = np.sqrt(d_k)
scaled_scores = scores / scale
print(f"\n缩放因子 sqrt(d_k) = sqrt({d_k}) ≈ {scale:.4f}")
print("缩放后的分数矩阵:")
print(np.round(scaled_scores, 4))
def softmax(x):
# 数值稳定的 Softmax 实现
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
attention_weights = softmax(scaled_scores)
print("\n注意力权重矩阵 AttentionWeights (每行和为1):")
print(np.round(attention_weights, 4))原始注意力分数矩阵 Scores = Q * K^T:
[[-5.5067 -0.0105]
[-3.3562 3.175 ]]
缩放因子 sqrt(d_k) = sqrt(3) ≈ 1.7321
缩放后的分数矩阵:
[[-3.1793 -0.0061]
[-1.9377 1.8331]]
注意力权重矩阵 AttentionWeights (每行和为1):
[[0.0402 0.9598]
[0.0225 0.9775]]
# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import numpy as np
np.random.seed(42) # 固定随机种子,确保输出可复现
X = np.random.randn(2, 4).round(4) # 输入矩阵,形状(2,4)
W_Q = np.random.randn(4, 3).round(4)
Q = X @ W_Q
W_K = np.random.randn(4, 3).round(4)
K = X @ W_K
scores = Q @ K.T
d_k = K.shape[1] # d_k = 3
scale = np.sqrt(d_k)
scaled_scores = scores / scale
def softmax(x):
# 数值稳定的 Softmax 实现
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
attention_weights = softmax(scaled_scores)
W_V = np.random.randn(4, 3).round(4)
V = X @ W_V
# 步骤4: 加权求和得到输出 Z = AttentionWeights * V
Z = attention_weights @ V
print("自注意力输出矩阵 Z = AttentionWeights * V:")
print(np.round(Z, 4))
print(f"\nZ 的形状: {Z.shape}")
print("解释:Z 有 2 行(对应序列中两个词),每一行是一个 3 维向量(d_v=3);它是原始输入 X 对应行的词,经过自注意力机制聚合了序列中两个词的信息后得到的新表示。")自注意力输出矩阵 Z = AttentionWeights * V:
[[-1.6272 0.3799 1.1811]
[-1.6491 0.398 1.19 ]]
Z 的形状: (2, 3)
解释:Z 有 2 行(对应序列中两个词),每一行是一个 3 维向量(d_v=3);它是原始输入 X 对应行的词,经过自注意力机制聚合了序列中两个词的信息后得到的新表示。多头注意力(Multi-Head Attention)
上面的计算描述的是单头注意力(Single-Head Attention)。Transformer 实际使用的是多头注意力。其思想是:将 d_model 维的 Query、Key、Value 投影到 h(例如 8)个不同的、维度更低的子空间(d_k, d_v = d_model / h),然后在每个子空间内并行地执行自注意力计算。最后,将 h 个头的输出拼接起来,再通过一个线性层投影回 d_model 维。
这样做的好处是允许模型在不同的表示子空间里共同关注来自不同位置的不同类型的信息。例如,一个头可能专注于指代关系,另一个头可能关注局部短语结构,等等。
解码器的结构与掩码自注意力
解码器层比编码器层稍微复杂一些,它包含三个子层:
- 掩码多头自注意力层(Masked Multi-Head Self-Attention):与编码器的自注意力类似,但为了防止在训练时“偷看”未来的信息(即保证自回归性质),在计算注意力分数后,会将当前位置之后的所有位置屏蔽掉(设置为一个极大的负数,使得 Softmax 后权重为 0)。
- 编码器-解码器注意力层(Encoder-Decoder Attention):这里的 Query 来自解码器上一层的输出,而 Key 和 Value 则来自编码器最终的输出。这使得解码器在生成每一个词时,都能有选择地关注输入序列中最相关的部分,其机制与我们之前学习的 Seq2Seq 注意力完全相同。
- 前馈神经网络层(Position-wise Feed-Forward Network):与编码器中的相同。
每个子层同样被残差连接和层归一化所包围。
📝 动手练一练
- 概念辨析:请简述“自注意力(Self-Attention)”与之前 Seq2Seq 模型中“注意力(Attention)”机制的核心区别。在 Transformer 的解码器中,这两种注意力是如何被结合使用的?
👉 点击查看参考答案
核心区别:
- 来源不同:Seq2Seq 注意力中,Query 来自解码器的隐藏状态,Key 和 Value 来自编码器的所有隐藏状态,是跨序列的注意力。而自注意力中,Query、Key、Value 都来自同一个序列,是序列内部的注意力。
- 目的不同:Seq2Seq 注意力旨在让解码器在生成目标词时聚焦于源序列的相关部分。自注意力旨在为序列中的每个词构建一个融合了序列内所有词信息的上下文表示。
在 Transformer 解码器中的结合: 解码器使用了两种注意力:
- 掩码自注意力:作用于目标序列自身,让每个目标词关注它之前的所有目标词,用于建模目标语言的内在依赖关系。
- 编码器-解码器注意力:Query 来自解码器的掩码自注意力层输出,Key 和 Value 来自编码器的最终输出。这正是 Seq2Seq 注意力机制,用于在生成目标词时对齐源序列信息。 因此,Transformer 解码器巧妙地融合了内部(自注意力)和外部(跨序列注意力)的上下文信息。
- 手算理解:假设在一个极简的自注意力计算中,我们得到某个 Query 向量
q与两个 Key 向量k1,k2的点积分数分别为3和1,d_k=4。请计算缩放后的分数,并应用 Softmax 求出对应的注意力权重a1和a2(保留4位小数)。如果对应的 Value 向量v1 = [1, 0],v2 = [0, 1],那么加权求和后的输出向量z是什么?
👉 点击查看参考答案
计算缩放分数:
scale = sqrt(d_k) = sqrt(4) = 2scaled_score1 = 3 / 2 = 1.5scaled_score2 = 1 / 2 = 0.5应用 Softmax:
exp(1.5) ≈ 4.4817exp(0.5) ≈ 1.6487sum_exp = 4.4817 + 1.6487 = 6.1304a1 = 4.4817 / 6.1304 ≈ 0.7311a2 = 1.6487 / 6.1304 ≈ 0.2689加权求和输出
z:z = a1 * v1 + a2 * v2 = 0.7311 * [1, 0] + 0.2689 * [0, 1] = [0.7311, 0.2689]
因此,注意力权重约为 [0.7311, 0.2689],输出向量 z 约为 [0.7311, 0.2689]。这表明模型更关注与 k1 对应的信息 v1。
本章小结
本节我们深入探讨了 Transformer 模型的核心架构。Transformer 通过堆叠编码器和解码器层构建了一个强大的序列到序列模型。其最关键的创新在于完全依赖注意力机制,特别是自注意力,来建模序列内部和序列之间的依赖关系。
自注意力机制通过为序列中的每个词计算 Query、Key、Value 向量,并基于 Query 与所有 Key 的相似度(注意力权重)对 Value 进行加权求和,从而动态地、并行地为每个词生成融合了全局上下文的表示。多头注意力进一步扩展了这一能力,允许模型在不同的子空间中关注不同类型的信息。
📋 行动清单
- 在白板或纸上画出 Transformer 编码器-解码器栈的宏观数据流图,标出编码器和解码器的层数(N)。
- 推导并记忆自注意力计算的完整公式:
Z = softmax( (Q * K^T) / sqrt(d_k) ) * V,并理解 Q, K, V 的来源。 - 思考:如果不使用位置编码,Transformer 的自注意力机制在处理序列时会缺失什么关键信息?这会导致什么问题?(我们将在下一节详细讨论位置编码)
—— 小象教研组