transformer架构(带公式与代码)
本文最后更新于 2025年3月20日 晚上
NLP的救星:transformer架构
当然不只是NLP,如今transformer的注意力机制几乎可以做任何事。
一,传统RNN的缺陷
- 只能串行,对于较长序列的问题效率较低。
- 在处理长序列时难以捕捉到长期依赖关系,只能有效利用较短的上下文信息。
- 反向传播时,由于参数共享和多次连乘的特性,容易出现梯度消失或梯度爆炸的问题,导致模型难以训练或无法收敛。
二,transformer(编码器)的核心——(self-attention,自注意力机制)
假设输入序列是 $ n $ 个词,每个词被编码为维度 $ d_{model} $(如 512)的向量,输入矩阵为:
$$
X \in \mathbb{R}^{n \times d_{\text {model }}}
$$自注意力通过三个可学习的权重矩阵,将输入映射为 $ Query(Q)、Key(K)、Value(V)$:
$$
Q=X \cdot W^{Q}, \quad K=X \cdot W^{K}, \quad V=X \cdot W^{V}
$$
$ W^{Q},W^{K},W^{V} \in \mathbb{R}^{d_{\text {model}} \times d_{k}} ,d_{k} $是每个头的维度(如 64)。- 每个词生成对应的 Q、K、V 向量,用于后续计算。
- 在自注意力机制中,求的是序列内各词的关联度。
- 在普通注意力机制中,计算一个序列(如源序列)与另一个序列(如目标序列)之间的关系。
对每个 Query 向量$Q_i$,计算它与所有 key 向量$K_j$的相似度:
$$ \text{Attention Score}(Q_i,K_j)=\frac{Q_i\cdot K_j^T}{\sqrt{d_k}} $$- 点积:衡量两个向量的相似度(值越大越相关)。
- 缩放因子$\sqrt{d_k}$:防止点积结果过大导致梯度消失(尤其在维度较高时)。
最后对注意力分数进行 Softmax 即可得到权重分布。
用注意力权重对 Value 向量加权求和,得到当前词的最终表示:
$$
\mathrm{Output}=\text{Attention Weights}\cdot V
$$多头注意力(Multi-Head Attention)
为了捕捉不同类型的依赖关系,Transformer 使用 多个独立的注意力头:- 将 Q、K、V 拆分为 h 个头(如 8 个头),每个头维度为均分总维度。
- 每个头独立计算注意力,得到 h 个输出矩阵。
- 拼接所有头的输出,并通过线性变换合并为最终结果。
三,总体结构
- Add:残差连接
- Norm:层归一化
输入序列 → 词嵌入 + 位置编码 → 编码器(多头自注意力 + FFN) → 编码器输出
↓
解码器输入 → 词嵌入 + 位置编码 → 掩码自注意力 → 编码器-解码器注意力 → FFN → 输出概率
3.1 整体架构
- 由 编码器(Encoder) 和 解码器(Decoder) 组成,堆叠多层(原始论文中为 6 层)。
- 完全依赖自注意力机制,无需循环(RNN)或卷积(CNN)。
- Q,K,V的权重矩阵通过解码器的输出进行反向传播。
3.2 编码器(Encoder)
作用:将输入序列转换为一系列富含上下文信息的表示。
每层编码器结构:
- 多头自注意力层(Multi-Head Self-Attention)
- 计算输入序列内部关系,拆分多个头并行处理。
- 前馈神经网络(FFN)
- 每个位置独立处理,维度扩展后压缩(如
512 → 2048 → 512
)。
- 每个位置独立处理,维度扩展后压缩(如
每小层附加操作:
- 残差连接:每个子层输出与输入相加。
- 层归一化:对残差结果进行归一化。
3.3 解码器(Decoder)
作用:根据编码器的输出和已生成的部分输出序列,逐步生成目标序列。
每层解码器结构:
- 掩码多头自注意力层(Masked Multi-Head Self-Attention)
- 通过掩码避免模型看到未来信息。就是给注意力分数加上值,后面的变量减无穷大使得分数被抑制,否则加0。
- 同时还可以调整模型动态输入不同长度的序列。
- 编码器-解码器注意力层(Cross-Attention)
- Key 和 Value 来自编码器输出,Query 来自解码器输入。
- 前馈神经网络(FFN):与编码器结构相同。
每小层附加操作:
- 残差连接 + 层归一化(每个子层后应用)。
3.4 输入处理
- 词嵌入(Embedding)
- 将词转换为固定维度向量(如 512 维)。
- 位置编码(Positional Encoding)
- 使用正弦/余弦函数或可学习参数生成位置信息。
- 公式示例(正弦编码):$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right), \quad PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right)$
3.5 注意力机制
单头注意力公式
$$
\text{f}(Q) = \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$
多头注意力
- 将 (Q, K, V) 拆分到多个头(如 8 头),独立计算后拼接结果。
- Query(Q):表示当前词希望“查询”其他词的信息。
- Key(K):表示其他词提供的“索引标签”,用于与 Query 匹配。
- Value(V):表示其他词的实际内容,最终通过权重聚合到输出中。
场景 | 输入来源 | 不同值的原因 |
---|---|---|
自注意力 | 同一层的同一输入序列 | 投影矩阵不同 |
交叉注意力 | Q:解码器输入;K/V:编码器输出 | 来源不同层 |
3.6 输出生成
- 解码器最后一层输出通过线性层 + Softmax 生成概率分布。
- 训练:Teacher Forcing(输入真实历史词)。
- 推理:自回归生成(逐步预测词)。
3.7 任务类型
- 编码器-解码器架构(如机器翻译)
- 仅编码器架构(如文本分类)
- 仅解码器架构(如文本生成)
- 对于不同长度的输入序列,分别生成 Q、K、V 矩阵,并计算自注意力。或使用“填充”与“掩码”。
四,具体结构问题
- 编码器和解码器是独立的模块,但解码器的每一层都会通过 交叉注意力(Cross-Attention) 访问编码器的最终输出。
- 输入序列 → [编码器层1 → 编码器层2 → … → 编码器层N] → 编码器最终输出 ↓↓↓
解码器层1 → 解码器层2 → … → 解码器层N → 输出概率
- 输入序列 → [编码器层1 → 编码器层2 → … → 编码器层N] → 编码器最终输出 ↓↓↓
- 编码器与解码器宏观上都只有一个,但是它们里面都可以添加多层,例如编码器中有多个(多头自注意力+FFN),解码器中有多个(掩码多头自注意力+交叉注意力+FFN)
五,利用编码器对中文句子进行分类
- 这里的代码只用到了编码器,并不牵扯解码器。因为两者都用一般用于语言翻译、对话等任务,这里只进行分类,因此没必要。
- 用解码器时输出会变得很难,本人技术还不到位。有待提高。
1 |
|
测试
1 |
|