注:作者为初学者,有些知识不太熟悉,可能描述有误,望见谅。
此文章借鉴了这位博主的内容,感谢其提供的优质学习内容:
Transformer简介
Transformer是一种基于自注意力机制(Self-Attention)的深度学习模型,最初由Vaswani等人于2017年提出。其核心优势在于并行化处理能力和对长距离依赖关系的有效建模,广泛应用于自然语言处理(NLP)、计算机视觉(CV)和多模态任务。Transformer的整体架构:

左侧为 Encoder block,右侧为 Decoder block。Multi-Head Attention是由多个 Self-Attention组成的,可以看到 Encoder block 包含一个 Multi-Head Attention,而 Decoder block 包含两个 Multi-Head Attention (其中有一个用到 Masked)。Multi-Head Attention 上方还包括一个 Add & Norm 层,Add 表示残差连接 (Residual Connection) 用于防止网络退化,Norm 表示 Layer Normalization,用于对每一层的激活值进行归一化。
Transformer 本质上是一个 Encoder-Decoder 架构。因此 Transformer 可以分为两个部分:编码组件和解码组件。

其中,编码组件由多层编码器(Encoder)组成(在论文中作者使用了 6 层编码器,在实际使用过程中你可以尝试其他层数)。解码组件也是由相同层数的解码器(Decoder)组成(在论文也使用了 6 层)。

每个编码器由两个子层组成:Self-Attention 层和前馈网络 (FFN)。每个编码器的结构都是相同的,但是它们使用不同的权重参数。

解码器也有编码器中这两层,但是它们之间还有一个注意力层(即 Encoder-Decoder Attention),其用来帮忙解码器关注输入句子的相关部分。

下面说一下 Transformer的核心部分:
自注意力机制(Self-Attention)
让序列里每个元素直接“扫视”整句,把与自己相关的信息加权搬到自己身上,无视距离长短。就比如有一句话,其中有很多难以理解的“它”,人可以直接判断出这个“它”指代的是什么,但是机器却不会判断。
注:表示序列中词顺序的方法。为了解决这个问题,Transformer 模型为每个输入的词嵌入向量添加一个向量。这些向量遵循模型学习的特定模式,有助于模型确定每个词的位置,或序列中不同词之间的距离。
当模型处理每个词(输入序列中的每个位置)时,Self-Attention 机制使得模型不仅能够关注当前位置的词,而且能够关注句子中其他位置的词,从而可以更好地编码这个词。

Self-Attention 接收的为输入(单词的表示向量x组成的矩阵X) 或者上一个 Encoder block 的输出。Q,K,V是通过 Self-Attention 的输入进行线性变换得到的矩阵。公式如下:

这其实是在计算注意力分数。我们用这个词对句子中的每个词都计算一个分数,这些分数决定了我们在编码这个词时,需要对句子中其他位置的每个词放置多少的注意力。
(1)得到 Q(查询)、K(键)、V(值)三个矩阵;
(2)Q 与 K 做点积 → 相似度,再 softmax 成“关注概率”(dk为 Key 向量的维度);
(3)用概率对 V 做加权和-->输出向量-->FFN。
多头注意力(Multi-Head Attention)
简单来说,MHA 就是多个 Self-Attention 并行计算,然后把结果拼接起来再线性变换。
首先,通过 h个不同的线性变换对 Query、Key 和 Value 进行映射,每一组注意力用于将输入映射到不同的子表示空间,这使得模型可以在不同子表示空间中关注不同的位置;然后,将不同的Attention 拼接起来;最后,再进行一次线性变换。
注意,MHA需要为每组注意力(为每个头)单独维护不同的 Query、Key 和 Value 权重矩阵W。通俗来讲就是每个头会从不同的视角或不同的语义子空间来理解输入序列,从而得到不同的 Query、Key 和 Value 矩阵。

前馈神经网络(Feed-Forward Network)
通常也可以说是位置前馈网络(Position-wise Feed-Forward Networks),一个全连接前馈网络,每个位置的词都单独经过这个完全相同的前馈神经网络。
其由两个线性变换组成,即两个全连接层组成,第一个全连接层的激活函数为 ReLU 激活函数。在每个编码器和解码器中,虽然这个全连接前馈网络结构在所有位置都是相同的,但不同层的权重不共享。
![]()
在 Attention 把信息“混”完之后,FFN 给每个位置做一次非线性映射,增强表达能力,可以把它看成对 Attention 输出的“特征加工”。
注:非线性映射是因为位置前馈网络(Position-wise Feed-Forward Networks,FFN)在形式上包含两个线性变换层,但从整体来看,两个线性变换之间使用了ReLU激活函数,从而引入了非线性。
层归一化(Layer Normalization)和残差连接(Residual Connection)
每个编码器的每个子层(Self-Attention 层和 FFN 层)都有一个残差连接,再执行一个层标准化操作。
![]()
Add(残差连接)
残差连接(Residual Connection)或跳跃连接(Skip Connection)最早由何凯明等人在2015年提出的ResNet(Residual Network)中引入,成为了解决深层网络网格退化的一种有效方法。
残差连接在构建深层神经网络时,被视为一种有效的兜底策略。 当网络已经达到或接近其性能的最优解时,如果继续增加网络深度(即添加更多的层),这些新增的层(被视为冗余层)不应该对网络的性能产生负面影响。
Skip connections的实现方式通常是将某一层的输出(通常经过一个恒等映射或简单的线性变换)直接加到下一层(或更深层)的输出上。 这样,网络的输出就可以表示为输入的非线性变换与输入的线性叠加,即y = f(x) + x,其中f(x)表示输入x经过一系列非线性变换后的输出,x表示直接传递的输入。
![]()
作用:
(1)缓解梯度消失问题
在深层网络中,梯度在反向传播时容易逐层衰减。残差连接提供了“捷径”,让梯度可以直接回传到前面的层,从而更有效地训练深层模型。
(2)保留原始信息,避免退化
网络可以通过残差连接保留输入信息,只学习“残差”部分(即变化量),降低学习难度,避免模型性能随层数增加反而下降(即“网络退化”问题)。
(3)促进信息流动
输入信息可以直接传递到更深的层,避免在多个非线性变换中被“稀释”或丢失。
层归一化
层归一化是对每个样本的特征维度进行归一化,使其均值为 0,方差为 1,即把数据拉回正态分布。
作用:
(1)稳定训练过程
归一化可以减小输入数据的分布变化(缓解“内部协变量偏移”),使模型对参数初始化不那么敏感,训练更稳定。
(2)加速收敛
归一化后的数值范围更小、更稳定,有助于优化器更快地找到最优解。
(3)适用于变长序列
与批归一化 BN(BatchNorm)不同,层归一化不依赖 batch 大小,适合 NLP 中常见的变长序列输入。
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.d_model = d_model
self.num_heads = num_heads
self.head_dim = d_model // num_heads
self.qkv = nn.Linear(d_model, 3 * d_model)
self.fc = nn.Linear(d_model, d_model)
def forward(self, x):
batch_size, seq_len, _ = x.shape
qkv = self.qkv(x).chunk(3, dim=-1)
q, k, v = [t.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) for t in qkv]
scores = torch.matmul(q, k.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.head_dim))
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, v).transpose(1, 2).reshape(batch_size, seq_len, -1)
return self.fc(out)
class TransformerEncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, ff_dim, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.ffn = nn.Sequential(
nn.Linear(d_model, ff_dim),
nn.ReLU(),
nn.Linear(ff_dim, d_model)
)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
attn_out = self.self_attn(x)
x = self.norm1(x + self.dropout(attn_out))
ffn_out = self.ffn(x)
return self.norm2(x + self.dropout(ffn_out))
# 示例调用
d_model = 512
num_heads = 8
ff_dim = 2048
encoder_layer = TransformerEncoderLayer(d_model, num_heads, ff_dim)
x = torch.randn(32, 10, d_model) # (batch_size, seq_len, d_model)
out = encoder_layer(x)
print(out.shape) # 输出: torch.Size([32, 10, 512])
一个简单的功能记忆:
自注意力机制:全局搬信息;
多头注意力MHA:多视角搬信息;
FFN:就地深加工;
残差连接:保留老本别丢;
层归一化:随时拉稳别飘。
解码器与编码器协同工作
第一个编码器的输入是一个词嵌入向量序列(如句子 “I love NLP” 经过嵌入层后的向量表示)。每个位置对应一个向量,整个序列作为输入传入第一个编码器层。最后一个编码器的输出是一个上下文感知的向量序列,每个向量对应输入序列中的一个词,会被解码器用作 Key 和 Value 的来源。
K 负责“打分”,V 负责“递内容”;两者配合,让解码器在每一步都知道该关注源句的哪部分信息。可以把 K、V 想成:
(1)K 是“索引”(key):告诉解码器“我这里有哪类信息”。
(2)V 是“内容”(value):当解码器觉得该索引匹配时,就把这部分信息拿过去用。
解码器比编码器多出的Encoder-Decoder Attention 层的工作原理和多头自注意力机制类似。不同之处是Encoder-Decoder Attention 层使用前一层的输出构造 Query 矩阵,而 Key 和 Value 矩阵来自于编码器栈的输出。
MASK
Mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。
Mask只在“注意力分数 → softmax”之间的那一行代码里起作用——把被掩掉的位置变成 −∞,softmax 后概率≈0,这些位置就彻底不参与后续加权和,也不参与反向传播。
注:两次用到MASK,两次都在 softmax 之前,各层参数更新时梯度不会流经被遮位置。:
第一次 Mask 发生在解码器自注意力的 QK^T 上,遮的是未来目标词。
第二次 Mask 发生在解码器-编码器交叉注意力的 QK^T 上,遮的是源序列的 <pad>。
Transformer 模型里面涉及两种 mask,分别是 Padding Mask 和 Sequence Mask。其中,Padding Mask 在所有的 scaled dot-product attention 里面都需要用到;Decoder里面使用到的 scaled dot-product attention,同时需要 Padding Mask 和 Sequence Mask,具体实现就是两个 Mask 相加。
最后的线性层和softmax层
解码器栈的输出是一个 float 向量。我们怎么把这个向量转换为一个词呢?通过一个线性层再加上一个 Softmax 层实现,最后输出概率分布向量,每个值就是“该词出现在这个位置”的概率。
注:训练前先做一次性的词汇表(vocabulary)→ 如把 30 000 个词/子词按字母或频率排序,给每个词一个索引号 0…29 999。这个顺序在模型里固定不变。
线性层是一个简单的全连接神经网络,其将解码器栈的输出向量映射到一个更长的向量,这个向量被称为 logits 向量。
假设我们的模型有 10000 个英文单词(模型的输出词汇表)。因此 logits 向量有 10000 个数字,每个数表示一个单词的分数。然后,Softmax 层会把这些分数转换为概率(把所有的分数转换为正数,并且加起来等于 1),最后选择最高概率所对应的单词,作为这个时间步的输出。
1555

被折叠的 条评论
为什么被折叠?



