Transformer算法

Transformer由Google的研究团队提出;论文发表于2017年。主要致力于在序列建模中提升并行性与长距离依赖建模能力,摆脱对循环卷积的依赖。Transformer的主要工作如下:

  1. 结构:利用编码器与解码器的结构
  2. 三种注意力:编码器多头自注意力、交叉注意力、解码器多头自注意力(含因果掩码)
  3. 位置信息:用位置编码赋予词向量序列信息
  4. 残差连接+层归一化+前馈网络(FFN):形成标准层块,稳定深层训练

Transformer整体架构

image

Transformer由Encoder(编码器)和Decoder(解码器)组成。首先,我们要将自然语言Inputs转换成词向量,使用位置编码表示时间关系,将词向量和位置编码相加输入编码器提取信息;Outputs shifted right为Outputs的偏移,第一次输入为起始符,与位置编码相加,输入解码器,再依次偏移输出字符。

编码器(Encoder)

编码器每层包含两个子层:

  1. 多头自注意力机制(Multi-Head Attention)

    • 并行计算多个注意力头,捕捉不同位置的特征关系

    • 输入:Q(Query)、K(Key)、V(Value)均来自同一输入序列

  2. 前馈神经网络(Feed-Forward Network)

    • 两层全连接网络,对每个位置独立处理

每个子层后都有残差连接+层归一化(Add & Norm)

解码器(Decoder)

解码器每层包含三个子层:

  1. 掩码多头自注意力(Masked Multi-Head Attention)

    • 防止当前位置“看到”未来位置的信息(用于自回归生成)
  2. 多头交叉注意力机制(Multi-Head Attention)

    • Q来自解码器上一层,K/V来自Encoder输出

    • 实现“关注输入序列相关部分”的能力

  3. 前馈神经网络

每个子层后都有残差连接+层归一化(Add & Norm)

输入文本数据数值转换

在Transformer中,所有计算都基于数值,因此文本进入模型前必须被转换为数字表示,输出后再将数字映射回文字。这一转换的核心是词嵌入(Word Embedding):它将每个单词或子词映射为一个固定维度的稠密向量,使语义相近的词在向量空间中距离更近。如图所示,词嵌入矩阵就像一本“字典”,将“独热向量”乘以“词嵌入矩阵”,本质上就是查表,得到“篮球”和“鸡”的词嵌入向量。

注释:词嵌入矩阵是经训练得到的权重表,将词ID映射成向量;语义相近聚类、差异分散,便于在向量空间做相似度计算。至于词嵌入的方法,这里不做讨论。

image

位置编码

位置编码是通过不同频率的正弦和余弦函数生成的。对于词嵌入向量中的每一个维度,计算规则如下:

  • 偶数维度(2i):使用正弦函数sin

  • $$ PE(pos, 2i) = \sin\left( \frac{pos}{10000^{\frac{2i}{d}}} \right) $$

  • 奇数维度(2i+1):使用余弦函数cos

    $$ PE(pos, 2i + 1) = \cos \left( \frac{pos}{10000^{\frac{2i}{d}}} \right) $$

其中pos代表当前词在序列中的位置索引,i代表维度的索引,d则是词嵌入向量的总维度大小。

image

自注意力机制

自注意力机制在做什么,主要分为三步:

  1. 计算X中不同token之间的关联程度
  2. 通过关联程度提取X的有用信息
  3. 输出包含不同注意力分配的信息

输入与投影

  • 输入向量a,底部的绿色方块$a^1, a^2, a^3, a^4$代表输入序列中每个词的嵌入向量(通常已经加上了位置编码)。

  • 生成 Query 和 Key

    • Query ( q ):对于我们要关注的当前词(这里是第1个词),通过权重矩阵$W^q$将其转换为查询向量$q^1$,公式为:$q^1=W^qa^1$

    • Key ( k ):对于序列中的每一个词(包括当前词自己),通过权重矩阵$W^k$将其转换为查询向量$j^1$,公式为:$k^1=W^ka^1$

image

点积计算

  • 这是注意力的核心部分。为了知道第1个词应该“关注”其他词多少,我们需要计算它的查询向量 $q^1$ 与其他所有词的键向量 $k$ 之间的相似度。

  • 计算公式:$\alpha = q \cdot k$

  • 具体操作

    • $\alpha_{1,1} = q^1 \cdot k^1$ (词1对自己的关注度)

    • $\alpha_{1,2} = q^1 \cdot k^2$ (词1对词2的关注度)

    • $\alpha_{1,3} = q^1 \cdot k^3$ (词1对词3的关注度)

    • $\alpha_{1,4} = q^1 \cdot k^4$ (词1对词4的关注度)

  • 图中的黄色方块 $\alpha_{1,1}$ 到 $\alpha_{1,4}$ 就是这些原始的相关性分数。

Softmax 归一化

  • 原始的分数 $\alpha$ 可能是任意实数,为了将其转化为概率分布(即注意力权重),需要通过 Softmax 层。

  • 作用:将这一组分数转化为总和为 1 的数值。数值越大,表示模型在处理第1个词时,越应该“注意”那个位置的词。

  • 输出:顶部的灰色方块 $\alpha’{1,1}$ 到 $\alpha’{1,4}$ 就是最终的注意力权重。

image

上图展示了 Transformer 模型中**自注意力机制(Self-Attention)**的最后一步:加权求和生成输出向量

在此之前,我们已经计算出了每个词对其他词的“注意力权重”(即 $\alpha’$),现在需要利用这些权重来整合信息。

核心概念

图中的绿色方块 $b^1$ 和 $b^2$ 代表最终输出的上下文向量(Context Vectors)。

  • $b^1$:是第1个词在考虑了句子中所有其他词的信息后,生成的新的表示向量。

  • $b^2$:是第2个词在考虑了句子中所有其他词的信息后,生成的新的表示向量。

左侧部分:$b^1$ 的计算过程

这部分演示了如何计算序列中第一个词的输出向量 $b^1$。

  • 输入

    • 注意力权重 ($\alpha’_{1,i}$):来自前一步 Softmax 层的输出(蓝色方块),表示第1个词对第 $i$ 个词的关注程度。

    • 值向量 ($v^i$):每个输入词 $a^i$ 通过权重矩阵 $W^v$ 转换得到的 Value 向量(深蓝色方块)。公式为 $v^i = W^v a^i$。

  • 操作

    • 将第1个词对所有词的注意力权重 $\alpha’_{1,i}$ 分别乘以对应的值向量 $v^i$。

    • 例如:$\alpha^{'}{1,1} \times v^1$,$\alpha^{'}{1,2} \times v^2$ 等。

  • 求和

    • 将所有乘积结果相加,得到最终的输出向量 $b^1$。

    • 公式:$b^1 = \sum_{i} \alpha^{‘}{1,i} v^i = \alpha^{'}{1,1}v^1 + \alpha^{’}{1,2}v^2 + \alpha^{'}{1,3}v^3 + \alpha^{'}_{1,4}v^4$

右侧部分:$b^2$ 的计算过程

这部分演示了如何计算序列中第二个词的输出向量 $b^2$。原理与左侧完全相同,只是查询的主体变成了第2个词。

  • 变化点

    • 使用的是第2个词的注意力权重 $\alpha’_{2,i}$(注意下标变成了2)。

    • 值向量 $v^i$ 保持不变(因为 Value 是基于原始输入生成的,不随查询对象改变)。

  • 操作

    • 将第2个词对所有词的注意力权重 $\alpha^{'}_{2,i}$ 分别乘以对应的值向量 $v^i$。
  • 求和

    • 公式:$b^2 = \sum_{i} \alpha^{‘}{2,i} v^i = \alpha^{'}{2,1}v^1 + \alpha^{’}{2,2}v^2 + \alpha^{'}{2,3}v^3 + \alpha^{'}_{2,4}v^4$

步骤展示了如何利用计算好的注意力权重,对 Value 向量进行加权求和,从而生成包含上下文信息的输出向量。

这就是自注意机制的计算过程,总结为如下公式:

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

多头注意力机制

下图为自注意力机制的矩阵理解方式,正如图所描述的参数含义,A为最后算出的注意力矩阵。

image

多头注意力机制是Transformer的核心组件,简单来说,它并不是只算一次注意力,而是把输入拆成多份,并行地做多次“自注意力”计算,最后再把结果拼接起来。具体流程是:

  1. 输入先被拆成h个头,每个头独立计算Q,K,V
  2. 每个头分别做缩放点积注意力
  3. 所有头的输出拼接起来,再经过一个线性变换得到最终结果

image

import torch
import torch.nn as nn
import math

class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
"""
:param d_model: 模型总维度 (例如 512)
:param num_heads: 头的数量 (例如 8)
"""
super(MultiHeadAttention, self).__init__()
assert d_model % num_heads == 0, "d_model must be divisible by num_heads"

self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # 每个头的维度 (d_k)

# 定义 W^q, W^k, W^v 线性层
# 这里我们直接生成一个大矩阵,后面通过 reshape 拆分
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)

# 最后的输出线性变换 W^o
self.W_o = nn.Linear(d_model, d_model)

def scaled_dot_product_attention(self, Q, K, V, mask=None):
"""
核心公式: softmax(QK^T / sqrt(d_k))V
"""
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))

attn_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights

def forward(self, X):
batch_size, seq_len, _ = X.size()

# 1. 线性投影并拆分多头
# [batch, seq_len, d_model] -> [batch, seq_len, num_heads, d_k] -> [batch, num_heads, seq_len, d_k]
Q = self.W_q(X).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(X).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(X).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)

# 2. 并行计算注意力 (所有头同时计算)
# output shape: [batch, num_heads, seq_len, d_k]
attn_output, weights = self.scaled_dot_product_attention(Q, K, V)

# 3. 拼接多头结果
# [batch, num_heads, seq_len, d_k] -> [batch, seq_len, num_heads, d_k] -> [batch, seq_len, d_model]
concat_attn = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)

# 4. 最终线性变换
output = self.W_o(concat_attn)

return output, weights

层归一化

层归一化是在特征维度(即 d_model)上对每个样本的激活值进行标准化,使其均值为 0、方差为 1,然后通过可学习的缩放和平移参数恢复模型的表达能力。
在 Transformer 等深层架构中,层归一化能稳定训练过程,避免内部协变量偏移,加速收敛,并允许使用更大的学习率。它拼接在多头注意力和前馈网络之后,通过对残差连接前的输出进行归一化,保证梯度流动顺畅,防止梯度消失或爆炸。

image

归一化对象:每个样本的每一层(特征维度)进行归一化

计算方式:对单个样本的每一层的所有神经元进行归一化

$$\text{LayerNorm}(x) = \frac{x - \mu}{\sigma} \cdot \gamma + \beta$$

$\mu$和$\sigma$是当前样本的均值和方差,$\gamma$和$\beta$是学习的缩放因子和偏置

前馈神经网络

前馈神经网络是一种无循环的多层神经网络,信息从输入到输出单向流动。前馈神经网络是一种结构类型而不是特定网络

image

Transformer的推理过程

这张图展示了 Transformer 模型在机器翻译任务中的完整推理流程,以将中文句子"我是一条狗"翻译成英文"I am a dog"为例。整个过程始于左侧的编码器,得到编码信息,编码信息随后作为关键输入传递给右侧的解码器,解码过程采用自回归模式,从起始标记开始逐步生成目标语言序列。在每一步生成过程中,解码器首先利用掩码自注意力机制处理已生成的部分译文(如当前已生成"I"),确保预测下一个词时只能看到历史信息而不能窥探未来;接着通过交叉注意力机制,将解码器的中间状态与编码器输出的"编码信息"进行对齐和融合,从而让模型在生成每个英文单词时都能关注到源句子中最相关的中文词汇;最后经过线性层和 Softmax 归一化得到词表上的概率分布,选出概率最高的词作为当前步的输出(如依次预测出"I"、“am”、“a”、“dog”)。这一过程不断循环迭代,直到模型预测出结束标记为止,此时完整的翻译结果"I am a dog"便生成完毕。

image

总结

一、整体架构

Transformer 采用 Encoder-Decoder 结构,两者各由 6 层相同模块堆叠而成:

  • Encoder:负责将输入序列编码为富含上下文的向量表示

  • Decoder:负责自回归地逐词生成输出序列

image

二、核心组件一览

组件 核心作用 关键细节
词嵌入(Embedding) 将离散词映射为稠密向量 语义相近的词在向量空间中距离更近
位置编码(Positional Encoding) 注入位置/顺序信息 使用不同频率的 sin/cos 函数,弥补注意力无顺序感知的缺陷
多头自注意力(Multi-Head Self-Attention) 捕捉序列内部任意两个位置之间的依赖 Q/K/V 来自同一输入;并行多个"头"从不同子空间学习
掩码自注意力(Masked Self-Attention) Decoder 中防止看到未来信息 保证自回归生成的因果性
交叉注意力(Cross-Attention) 连接 Encoder 与 Decoder Q 来自 Decoder,K/V 来自 Encoder 输出
前馈神经网络(FFN) 对每个位置做非线性变换 两层全连接,ReLU 激活
层归一化 + 残差连接(Add & Norm) 稳定深层训练 每个子层后均添加,缓解梯度消失/爆炸

三、核心公式

自注意力(缩放点积注意力):

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

其中 $\sqrt{d_k}$ 为缩放因子,防止点积值过大导致 softmax 梯度消失。

四、数据流

输入 → 词嵌入 + 位置编码 → [多头自注意力 → Add&Norm → FFN → Add&Norm] × 6 (Encoder)

输出 ← Softmax ← 线性层 ← [掩码自注意力 → 交叉注意力 → FFN] × 6 (Decoder)