前言
Hello,大家好,我是
GISer Liu
😁,一名热爱AI技术的GIS开发者,本系列文章是作者参加DataWhale2025年1月份学习赛,旨在讲解Transformer模型的理论和实践。😲
本文将从Attention机制的原理出发,详细解析Transformer模型的核心组件——编码器(Encoder)的结构与工作流程。我们将重点探讨自注意力机制(Self-Attention)和多头注意力机制(Multi-Head Attention)的设计思想,以及它们在序列数据处理中的作用。此外,本文还将对比Transformer与传统模型(如CNN、RNN)在计算复杂度、并行性和长程依赖处理上的差异,帮助读者更好地理解Transformer模型的优势与局限性。
一、编码器(Encoder)概述
1. 编码器的作用
+ **将输入序列转换为高维表示,捕捉序列中的语义和结构信息。** - 编码器的主要任务是将输入序列(如文本中的单词序列)转换为高维向量表示,这些向量能够捕捉序列中的语义信息和结构信息。通过这种转换,模型能够理解输入序列中各个元素之间的关系。 + **为解码器(Decoder)提供上下文信息。** - 在Encoder-Decoder架构中,编码器的输出会作为解码器的输入,为解码器提供上下文信息。解码器利用这些上下文信息生成输出序列(如翻译后的文本)。2. 编码器的结构
- 由多个相同的编码器层(Encoder Layer)堆叠而成。
- 编码器通常由多个相同的编码器层堆叠而成,每个编码器层都包含两个主要子层:多头自注意力层和前馈神经网络。通过堆叠多个编码器层,模型能够逐步提取输入序列中的高层次特征。
- 每个编码器层包含两个主要子层:多头自注意力层(Multi-Head Self-Attention)和前馈神经网络(Feed-Forward Network)。
- 多头自注意力层:通过自注意力机制计算输入序列中不同位置之间的关联关系,生成自注意力输出。
- 前馈神经网络:对自注意力层的输出进行非线性变换,提取更复杂的特征。
二、编码器(Encoder)工作流程
1. 输入阶段
- 初始输入
- 整个编码器部分由多个相同的子模块(Encoder Layer)按顺序连接构成。第一个Encoder子模块接受来自**输入嵌入(Input Embedding)和位置编码(Position Embedding)**组合后的输入。
- 输入嵌入:将输入的原始数据(如文本中的单词)转化为词向量表示。词向量是固定长度的向量,通常通过嵌入层(Embedding Layer)生成。
- 位置编码:为了让模型能够捕捉到输入序列中元素的位置信息,位置编码通常是一个与词向量长度相同的向量。位置编码和词向量通过相加的方式结合,形成最终的输入向量。
- 辅助信息:位置编码也是向量,长度与词向量相同。位置编码和词向量通过相加的方式结合,形成最终的输入向量。
- 辅助信息:第一个Encoder子模块传递给后面的子模块的是词向量和位置编码相加后得到的向量。这个向量是一个矩阵,因为输入序列通常包含多个词向量。
- 后续Encoder输入
- 除了第一个Encoder之外的其他Encoder子模块,它们从前一个Encoder接受相应的输入(inputs),这样就形成了一个顺序传递信息的链路。
- 辅助信息:第一个Encoder子模块传递给后面的子模块的是词向量和位置编码相加后得到的向量。这个向量是一个矩阵,因为输入序列通常包含多个词向量。
2. 核心处理阶段
- 多头自注意力层处理
- 每个Encoder子模块在接收到输入后,首先会将其传递到多头自注意力层。在这一层中,通过多头自注意力机制(即查询、键、值都来自同一个输入序列自身)计算输入序列不同位置之间的关联关系,生成自注意力输出。
- 辅助信息:编码器模块包含多个编码器层(Encoder Layer),每个编码器层都包含一个多头自注意力层和一个前馈神经网络。每个编码器层都接受前一个编码器层的输出,并输出处理后的向量。
- 每个Encoder子模块在接收到输入后,首先会将其传递到多头自注意力层。在这一层中,通过多头自注意力机制(即查询、键、值都来自同一个输入序列自身)计算输入序列不同位置之间的关联关系,生成自注意力输出。
- 前馈层处理
- 自注意力层的输出紧接着被传递到前馈层(Feedforward Layer)。前馈层一般是由全连接网络构成,对自注意力层输出的特征做进一步的非线性变换,提取更复杂、高层次的特征,然后将其输出向上发送到下一个编码器(如果不是最后一个Encoder的话),以便后续Encoder子模块继续进行处理。
- 辅助信息:编码器模块包含多个编码器层,每个编码器层都包含一个前馈层和一个多头自注意力层。每个编码器层都接受前一个编码器层的输出,并输出处理后的向量。
3. 残差和归一化阶段
- 残差连接
- 自注意力层和前馈子层均配备了残差连接。从网络拓扑结构来看,这种连接方式构建了一种并行链路,使得输入信号能够以残差的形式参与到每一层的输出计算中。
- 辅助信息:残差连接是指将输入信号直接加到输出信号上。假设自注意力层的输入为
x
,输出为y
,经过残差连接后变为x + y
。残差连接的目的是防止梯度消失,并帮助模型更好地学习深层特征。 - 辅助信息:残差连接的形式是向量相加,而不是标量相加。输入信号和输出信号都是向量,残差连接是将这两个向量相加。
- 辅助信息:残差连接是指将输入信号直接加到输出信号上。假设自注意力层的输入为
- 自注意力层和前馈子层均配备了残差连接。从网络拓扑结构来看,这种连接方式构建了一种并行链路,使得输入信号能够以残差的形式参与到每一层的输出计算中。
- 层归一化
- 在残差连接后,紧跟着会进行层归一化操作。层归一化是对每一层神经元的输入进行归一化处理,它可以加速网络的收敛速度、提高模型的泛化能力等,使得模型训练更加稳定、高效。经过层归一化后的结果就是当前Encoder子模块最终的输出,然后传递给下一个Encoder子模块或者后续的其他模块。
- 辅助信息:层归一化是对每一层的输入进行归一化处理,而不是对输出进行归一化。归一化后的结果会传递给下一个Encoder子模块。每个Encoder子模块都包含一个前馈层和一个多头自注意力层,并且每个子模块都会进行残差连接和层归一化操作。
- 在残差连接后,紧跟着会进行层归一化操作。层归一化是对每一层神经元的输入进行归一化处理,它可以加速网络的收敛速度、提高模型的泛化能力等,使得模型训练更加稳定、高效。经过层归一化后的结果就是当前Encoder子模块最终的输出,然后传递给下一个Encoder子模块或者后续的其他模块。
三、编码器(Encoder)的详细结构
1. 多头自注意力层(Multi-Head Self-Attention)
- Scaled Dot-Product Attention
- Scaled Dot-Product Attention 是自注意力机制的核心部分。它通过计算查询(Query)、键(Key)和值(Value)之间的点积来得到注意力权重,然后使用这些权重对值进行加权求和。
- 公式:
Attention(Q, K, V) = softmax(QK^T / sqrt(d_k))V
- 其中,
Q
、K
、V
分别表示查询、键和值,d_k
是键的维度。
- 公式:
- Scaled Dot-Product Attention 是自注意力机制的核心部分。它通过计算查询(Query)、键(Key)和值(Value)之间的点积来得到注意力权重,然后使用这些权重对值进行加权求和。
- Multi-Head Attention
- Multi-Head Attention 是将 Scaled Dot-Product Attention 并行执行多次,然后将结果拼接起来。通过这种方式,模型能够从不同的子空间中学习到不同的特征表示。
- 公式:
MultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h)W^O
- 其中,
head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
,W_i^Q
、W_i^K
、W_i^V
是可学习的参数矩阵,W^O
是输出层的参数矩阵。
- 公式:
- Multi-Head Attention 是将 Scaled Dot-Product Attention 并行执行多次,然后将结果拼接起来。通过这种方式,模型能够从不同的子空间中学习到不同的特征表示。
- Self-Attention
- Self-Attention 是指查询、键和值都来自同一个输入序列。通过 Self-Attention,模型能够捕捉输入序列中不同位置之间的依赖关系。
- Add & Norm
- 在多头自注意力层和前馈层之后,都会进行残差连接(Add)和层归一化(Norm)。残差连接将输入信号直接加到输出信号上,层归一化则对每一层的输入进行归一化处理。
2. 前馈神经网络(Feed-Forward Network)
- Position-wise Feed-Forward Network
- 前馈神经网络由两个全连接层组成,中间通过一个激活函数(如ReLU)连接。前馈神经网络对自注意力层的输出进行非线性变换,提取更复杂的特征。
- 公式:
FFN(x) = max(0, xW_1 + b_1)W_2 + b_2
- 其中,
W_1
、W_2
是可学习的参数矩阵,b_1
、b_2
是偏置项。
- 公式:
- 前馈神经网络由两个全连接层组成,中间通过一个激活函数(如ReLU)连接。前馈神经网络对自注意力层的输出进行非线性变换,提取更复杂的特征。
四、注意力机制详解
1. 自注意力机制(Self-Attention)
定义:自注意力是一种机制,允许序列中的每个元素与其他元素进行交互,捕捉序列内部的依赖关系。
工作原理:
- 输入序列通过线性变换生成查询(Query)、键(Key)和值(Value)向量:
- 输入序列 ( X ) 通过三个不同的线性变换矩阵 ( W_Q )、( W_K )、( W_V ) 分别生成查询向量 ( Q )、键向量 ( K ) 和值向量 ( V )。
- 公式表示为:
Q = X W Q , K = X W K , V = X W V Q = XW_Q, \quad K = XW_K, \quad V = XW_V Q=XWQ,K=XWK,V=XWV
- 计算每个元素的查询与所有键的点积,得到注意力分数:
- 计算查询向量 ( Q ) 与键向量 ( K ) 的点积,得到注意力分数矩阵 ( A )。
- 公式表示为:
A = Q K T d k A = \frac{QK^T}{\sqrt{d_k}} A=dkQKT
其中 ( d_k ) 是键向量的维度,用于缩放点积,防止梯度消失或爆炸。
3. 使用SoftMax对注意力分数归一化,得到权重:
- 对注意力分数矩阵 ( A ) 进行SoftMax操作,得到归一化的注意力权重矩阵 ( \alpha )。
- 公式表示为:
α = SoftMax ( A ) \alpha = \text{SoftMax}(A) α=SoftMax(A)
- 对值向量进行加权求和,生成上下文感知的表示:
- 使用注意力权重矩阵 ( \alpha ) 对值向量 ( V ) 进行加权求和,得到最终的上下文感知表示 ( Z )。
- 公式表示为:
Z = α V Z = \alpha V Z=αV
优点:
- 捕捉长距离依赖:自注意力机制能够捕捉序列中任意两个元素之间的依赖关系,无论它们之间的距离有多远。
- 并行计算,加速训练:与RNN不同,自注意力机制可以并行计算,从而加速训练过程。
缺点:
- 计算复杂度高(( O(n^2) )):自注意力机制的计算复杂度与序列长度的平方成正比,对于长序列来说计算量较大。
- 无法直接捕捉位置信息,需依赖位置编码:自注意力机制本身不包含序列的位置信息,因此需要通过位置编码来引入序列的顺序信息。
代码示例:
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttention(nn.Module):
def __init__(self, embed_size, heads):
"""
初始化多头自注意力机制。
参数:
embed_size (int): 输入向量的维度(例如 512)。
heads (int): 注意力头的数量(例如 8)。
"""
super(SelfAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads # 每个头的维度
# 检查 embed_size 是否能被 heads 整除
assert (
self.head_dim * heads == embed_size
), "Embedding size needs to be divisible by heads"
# 定义线性变换层
self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = nn.Linear(heads * self.head_dim, embed_size) # 输出线性变换
def forward(self, values, keys, query, mask=None):
"""
前向传播。
参数:
values (torch.Tensor): 值向量,形状为 (N, value_len, embed_size)。
keys (torch.Tensor): 键向量,形状为 (N, key_len, embed_size)。
query (torch.Tensor): 查询向量,形状为 (N, query_len, embed_size)。
mask (torch.Tensor, optional): 掩码矩阵,形状为 (N, 1, query_len, key_len)。
返回:
torch.Tensor: 多头注意力输出,形状为 (N, query_len, embed_size)。
"""
N = query.shape[0] # 批量大小
value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
# 检查输入形状
assert values.shape == (N, value_len, self.embed_size), "Values shape mismatch"
assert keys.shape == (N, key_len, self.embed_size), "Keys shape mismatch"
assert query.shape == (N, query_len, self.embed_size), "Query shape mismatch"
# 将输入拆分为多个头
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = query.reshape(N, query_len, self.heads, self.head_dim)
# 对 values、keys 和 queries 进行线性变换
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# 计算缩放点积注意力分数
energy = torch.matmul(queries, keys.transpose(-2, -1)) / (self.head_dim ** 0.5)
# 应用掩码(如果有)
if mask is not None:
energy = energy.masked_fill(mask == 0, float("-1e20"))
# 计算注意力权重
attention = F.softmax(energy, dim=-1)
# 对 values 进行加权求和
out = torch.matmul(attention, values) # 形状: (N, query_len, heads, head_dim)
out = out.reshape(N, query_len, self.heads * self.head_dim) # 拼接多头输出
# 通过线性变换映射回原始维度
out = self.fc_out(out)
return out
class SelfAttentionWithResidual(nn.Module):
def __init__(self, embed_size, heads):
"""
初始化带残差连接和层归一化的多头自注意力机制。
参数:
embed_size (int): 输入向量的维度(例如 512)。
heads (int): 注意力头的数量(例如 8)。
"""
super(SelfAttentionWithResidual, self).__init__()
self.attention = SelfAttention(embed_size, heads) # 多头自注意力层
self.norm = nn.LayerNorm(embed_size) # 层归一化
def forward(self, values, keys, query, mask=None):
"""
前向传播。
参数:
values (torch.Tensor): 值向量,形状为 (N, value_len, embed_size)。
keys (torch.Tensor): 键向量,形状为 (N, key_len, embed_size)。
query (torch.Tensor): 查询向量,形状为 (N, query_len, embed_size)。
mask (torch.Tensor, optional): 掩码矩阵,形状为 (N, 1, query_len, key_len)。
返回:
torch.Tensor: 多头注意力输出,形状为 (N, query_len, embed_size)。
"""
# 残差连接:将输入 query 与多头注意力输出相加
attention_output = self.attention(values, keys, query, mask)
out = self.norm(query + attention_output) # 层归一化
return out
2. 多头注意力机制(Multi-Head Attention)
定义:多头注意力是将自注意力机制扩展到多个子空间,每个子空间独立计算注意力,最后将结果拼接。
- Q:Query查询,它代表了正在询问的信息或关心的上下文。在自注意力机制中,每个序列元素都有一个对应的查询,它试图从其他部分找到相关信息。
- K:Key键,这些是可以查询的条目或“索引”。在自注意力机制中,每个序列元素都有一个对应的键
- V:Value值:对于每一个“键”,都有一个与之关联的“值”,它代表实际的信息内容。当查询匹配到一个特定的键时,其对应的值就会被选中并返回。
我是一名软件开发者,感觉其有点像前端搜索框输入值🤔🤔,然后后端数据库根据输入值做了一个关键词的模糊查询?按照相似性进行排序?然后见准确度最高查询结果的返回?
工作原理:
- 将输入序列映射到多个子空间,生成多组Q、K、V:
- 输入序列 ( X ) 通过多个线性变换矩阵生成多组查询、键和值向量。
- 每组查询、键和值向量分别在不同的子空间中计算自注意力。
- 在每个子空间中独立计算自注意力:
- 每个子空间独立计算自注意力,得到多个注意力头(Attention Head)的输出。
- 将所有子空间的结果拼接,并通过线性变换得到最终输出:
- 将所有注意力头的输出拼接在一起,然后通过一个线性变换矩阵得到最终的输出。
优点:
- 捕捉序列中不同子空间的信息:多头注意力机制允许模型在不同的子空间中捕捉不同的特征,增强了模型的表示能力。
- 增强模型的表示能力:通过多个注意力头的并行计算,模型能够捕捉到更丰富的特征。
缩放点击注意力:
这里的
d
k
d_k
dk表示每个头的维度,通常由总的模型维度
d
m
o
d
e
l
d_{model}
dmodel和多头注意力的头数h决定,具体而言,
d
k
=
d
m
o
d
e
l
h
d_k = \frac{d_{model}}{h}
dk=hdmodel
使用多头的意义:
多头注意力机制(Multi-Head Attention)通过将输入映射到多个子空间,允许模型从不同角度捕捉序列中的依赖关系。下面是其特征:
特性 | 说明 |
---|---|
多样性 | 每个头在不同子空间中学习,捕捉输入之间更丰富的关系。 |
并行计算 | 多个头可以并行计算,充分利用硬件加速(如GPU)。 |
增强表达能力 | 通过组合多个头的输出,模型能够学习更复杂的特征表示。 |
可解释性 | 每个头可以关注不同的语义或语法特征,便于分析和调试。 |
点乘 vs 加法注意力
点乘注意力(Dot-Product Attention)和加法注意力(Additive Attention)是两种常见的注意力计算方式。以下是它们的对比:
特性 | 点乘注意力 | 加法注意力 |
---|---|---|
计算方式 | 通过点积计算相似性: Q K T QK^T QKT。 | 通过全连接层和非线性激活函数计算相似性: tanh ( W q Q + W k K ) \text{tanh}(W_q Q + W_k K) tanh(WqQ+WkK)。 |
计算效率 | 高效,可通过矩阵乘法并行化,适合大规模计算。 | 计算复杂,涉及非线性操作,效率较低。 |
复杂度 | O ( d ) O(d) O(d),实际硬件中并行化加速明显。 | O ( d ) O(d) O(d),但非线性操作增加计算负担。 |
效果 | 在高维空间中,点乘能有效衡量向量相似性,缩放因子避免数值不稳定。 | 效果无明显优势,且计算更复杂。 |
适用场景 | 适合大规模训练和推理,广泛应用于Transformer模型。 | 适用于小规模任务或特定场景,如需要显式建模复杂关系时。 |
代码示例:
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, embed_size, heads):
"""
初始化多头注意力机制。
参数:
embed_size (int): 输入向量的维度(例如 512)。
heads (int): 注意力头的数量(例如 8)。
"""
super(MultiHeadAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads # 每个头的维度
# 检查 embed_size 是否能被 heads 整除
assert (
self.head_dim * heads == embed_size
), "Embedding size needs to be divisible by heads"
# 定义线性变换层
self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = nn.Linear(heads * self.head_dim, embed_size) # 输出线性变换
def forward(self, values, keys, query, mask=None):
"""
前向传播。
参数:
values (torch.Tensor): 值向量,形状为 (N, value_len, embed_size)。
keys (torch.Tensor): 键向量,形状为 (N, key_len, embed_size)。
query (torch.Tensor): 查询向量,形状为 (N, query_len, embed_size)。
mask (torch.Tensor, optional): 掩码矩阵,形状为 (N, 1, query_len, key_len)。
返回:
torch.Tensor: 多头注意力输出,形状为 (N, query_len, embed_size)。
"""
N = query.shape[0] # 批量大小
value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
# 将输入拆分为多个头
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = query.reshape(N, query_len, self.heads, self.head_dim)
# 对 values、keys 和 queries 进行线性变换
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# 计算缩放点积注意力分数
energy = torch.matmul(queries, keys.transpose(-2, -1)) / (self.head_dim ** 0.5)
# 应用掩码(如果有)
if mask is not None:
energy = energy.masked_fill(mask == 0, float("-1e20"))
# 计算注意力权重
attention = F.softmax(energy, dim=-1)
# 对 values 进行加权求和
out = torch.matmul(attention, values) # 形状: (N, query_len, heads, head_dim)
out = out.reshape(N, query_len, self.heads * self.head_dim) # 拼接多头输出
# 通过线性变换映射回原始维度
out = self.fc_out(out)
return out
3. 交叉注意力机制(Cross-Attention)
定义:交叉注意力是一种机制,允许一个序列(查询序列)中的元素关注另一个序列(键-值序列)中的元素。
工作原理:
- 查询(Query)来自一个序列,键(Key)和值(Value)来自另一个序列:
- 查询向量 ( Q ) 来自一个序列,键向量 ( K ) 和值向量 ( V ) 来自另一个序列。
- 计算查询与键的点积,得到注意力分数:
- 计算查询向量 ( Q ) 与键向量 ( K ) 的点积,得到注意力分数矩阵 ( A )。
- 公式表示为:
A = Q K T d k A = \frac{QK^T}{\sqrt{d_k}} A=dkQKT
- 使用SoftMax归一化,对值向量进行加权求和:
- 对注意力分数矩阵 ( A ) 进行SoftMax操作,得到归一化的注意力权重矩阵 ( \alpha )。
- 使用注意力权重矩阵 ( \alpha ) 对值向量 ( V ) 进行加权求和,得到最终的上下文感知表示 ( Z )。
- 公式表示为:
Z = α V Z = \alpha V Z=αV
应用场景:
- Transformer解码器中,解码器的查询与编码器的键-值进行交互:在Transformer解码器中,交叉注意力机制用于将解码器的查询与编码器的键-值进行交互,从而生成目标序列。
- 多模态任务中,如图像与文本的对齐:在多模态任务中,交叉注意力机制可以用于将不同模态的数据(如图像和文本)进行对齐。
代码示例:
class CrossAttention(torch.nn.Module):
def __init__(self, embed_size, heads):
super(CrossAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads
assert (
self.head_dim * heads == embed_size
), "Embedding size needs to be divisible by heads"
self.values = torch.nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = torch.nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = torch.nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = torch.nn.Linear(heads * self.head_dim, embed_size)
def forward(self, values, keys, query, mask):
N = query.shape[0]
value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
# Split the embedding into self.heads different pieces
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = query.reshape(N, query_len, self.heads, self.head_dim)
values = self.values(values)
keys = self.keys(keys)
queries = self.queries(queries)
# Scaled dot-product attention
energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])
if mask is not None:
energy = energy.masked_fill(mask == 0, float("-1e20"))
attention = F.softmax(energy / (self.embed_size ** (1 / 2)), dim=3)
out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(
N, query_len, self.heads * self.head_dim
)
out = self.fc_out(out)
return out
4. 自注意力、多头注意力与交叉注意力的区别
特性 | 自注意力(Self-Attention) | 多头注意力(Multi-Head Attention) | 交叉注意力(Cross-Attention) |
---|---|---|---|
输入来源 | 查询、键、值来自同一个序列。 | 查询、键、值来自同一个序列,但通过多个子空间计算。 | 查询来自一个序列,键和值来自另一个序列。 |
信息交互对象 | 序列内部的元素之间进行交互。 | 序列内部的元素之间进行交互,但通过多个子空间捕捉不同特征。 | 一个序列的元素与另一个序列的元素进行交互。 |
应用场景 | 捕捉序列内部的依赖关系,如句子中单词之间的关系。 | 增强模型的表示能力,捕捉序列中不同子空间的信息。 | 用于两个序列之间的交互,如机器翻译中的编码器-解码器交互。 |
计算复杂度 | ( O(n^2) ) | ( O(n^2) ),但由于并行计算,实际计算速度更快。 | ( O(n^2) ) |
捕捉全局信息 | 是 | 是 | 是 |
捕捉位置信息 | 需要位置编码 | 需要位置编码 | 需要位置编码 |
5.多头注意力和多头自注意力的区别
特性 | 多头注意力(Multi-Head Attention) | 多头自注意力(Multi-Head Self-Attention) |
---|---|---|
输入来源 | 查询(Q)、键(K)和值(V)可以来自不同的输入源。 | 查询(Q)、键(K)和值(V)都来自同一个输入序列。 |
功能重点 | 融合不同来源的信息,建立跨序列依赖关系。 | 挖掘输入序列自身的内在结构和关系。 |
应用场景 | - 机器翻译:解码器的查询(Q)与编码器的键(K)和值(V)交互。 - 多模态任务:如图像与文本的对齐。 | - 文本编码:捕捉句子内部的语义和语法关系。 - 文本生成:理解当前生成文本的内部结构。 |
输出信息 | 包含不同输入序列之间相互作用后的特征。 | 反映输入序列自身内部关系的特征表示。 |
计算流程 | 1. 线性变换得到 Q、K、V。 2. 计算注意力分数。 3. 加权求和。 4. 拼接多头并线性变换。 | 1. 线性变换得到 Q、K、V。 2. 计算注意力分数。 3. 加权求和。 4. 拼接多头并线性变换。 |
示例 | - 机器翻译:解码器通过多头注意力机制参考编码器的输出,生成目标语言句子。 - 多模态任务:图像与文本对齐。 | - 文本分类:捕捉句子中单词之间的依赖关系。 - 文本生成:理解当前生成文本的主题和情节发展。 |
核心区别 | 查询(Q)、键(K)和值(V)可以来自不同序列,用于跨序列信息融合。 | 查询(Q)、键(K)和值(V)来自同一序列,用于挖掘序列内部关系。 |
五、前馈神经网络(Feed-Forward Network, FFN)
1. 前馈神经网络的结构
前馈神经网络(FFN)是Transformer模型中的一个重要组件,通常由两个全连接层(FC)和一个ReLU激活函数组成。它的主要作用是对自注意力层的输出进行非线性变换,提取更复杂的特征。结构:
- 第一层全连接层(FC1):
- 输入 ( x ) 通过一个线性变换矩阵 ( W_1 ) 和偏置 ( b_1 ),将输入从低维映射到高维。
- 公式表示为:
h = x W 1 + b 1 h = xW_1 + b_1 h=xW1+b1
- ReLU激活函数:
- 对第一层的输出 ( h ) 应用ReLU激活函数,引入非线性。
- 公式表示为:
h relu = max ( 0 , h ) h_{\text{relu}} = \max(0, h) hrelu=max(0,h)
- 第二层全连接层(FC2):
- 将ReLU激活后的输出 ( h_{\text{relu}} ) 通过另一个线性变换矩阵 ( W_2 ) 和偏置 ( b_2 ),将高维特征映射回低维。
- 公式表示为:
y = h relu W 2 + b 2 y = h_{\text{relu}}W_2 + b_2 y=hreluW2+b2
整体公式:
FFN ( x ) = max ( 0 , x W 1 + b 1 ) W 2 + b 2 \text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2 FFN(x)=max(0,xW1+b1)W2+b2
2. 前馈神经网络的作用
特征提取:
- 通过非线性变换(ReLU激活函数),FFN能够捕捉输入序列中的复杂模式和特征,增强模型的表达能力。
维度变换:
- FFN通常先将输入序列的维度从低维映射到高维(通过FC1),然后再映射回$$低维(通过FC2)。这种先升维后降维的操作有助于扩充中间层的表示能力,从而抵抗ReLU带来的模型表达能力的下降。
数学公式:
FFN ( x ) = max ( 0 , x W 1 + b 1 ) W 2 + b 2 \text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2 FFN(x)=max(0,xW1+b1)W2+b2
其中:
- ( x ) 是输入序列,维度为 ( n × d model n \times d_{\text{model}} n×dmodel)。
- ( W 1 W_1 W1) 是第一个全连接层的权重矩阵,维度为 d model × d ff d_{\text{model}} \times d_{\text{ff}} dmodel×dff。
- ($ b_1 ) 是第一个全连接层的偏置,维度为 ) 是第一个全连接层的偏置,维度为 )是第一个全连接层的偏置,维度为d_{\text{ff}}$。
- ( W 2 W_2 W2) 是第二个全连接层的权重矩阵,维度为 d ff × d model d_{\text{ff}} \times d_{\text{model}} dff×dmodel。
- ( b_2 ) 是第二个全连接层的偏置,维度为 d model d_{\text{model}} dmodel。
代码示例:
import torch
import torch.nn as nn
class FeedForwardNetwork(nn.Module):
def __init__(self, d_model, d_ff):
super(FeedForwardNetwork, self).__init__()
self.fc1 = nn.Linear(d_model, d_ff) # 第一个全连接层
self.fc2 = nn.Linear(d_ff, d_model) # 第二个全连接层
self.relu = nn.ReLU() # ReLU激活函数
def forward(self, x):
# 第一层全连接 + ReLU激活
h = self.relu(self.fc1(x))
# 第二层全连接
y = self.fc2(h)
return y
# 示例使用
d_model = 512 # 输入维度
d_ff = 2048 # 中间层维度
ffn = FeedForwardNetwork(d_model, d_ff)
# 输入序列 (batch_size, seq_len, d_model)
x = torch.randn(32, 10, d_model) # 假设batch_size=32, seq_len=10
output = ffn(x)
print(output.shape) # 输出形状: (32, 10, d_model)
代码解释:
fc1
:第一个全连接层,将输入从 ($d_{\text{model}} $) 维度映射到 d ff d_{\text{ff}} dff)维度。relu
:ReLU激活函数,引入非线性。fc2
:第二个全连接层,将中间层的 d ff d_{\text{ff}} dff维度映射回 d model d_{\text{model}} dmodel维度。forward
:前向传播函数,依次执行FC1、ReLU和FC2。
3. 前馈神经网络的作用总结
+ **特征提取**:通过非线性变换,FFN能够捕捉输入序列中的复杂模式和特征,增强模型的表达能力。 + **维度变换**:FFN通过先升维后降维的操作,扩充中间层的表示能力,从而抵抗ReLU带来的模型表达能力的下降。通过这种结构,FFN能够有效地增强Transformer模型的表示能力,使其在处理复杂任务时表现更优。
六、残差连接与层归一化
1. 残差连接(Residual Connection)
作用:
残差连接是一种深度学习技巧,通过将子层的输入与输出相加,形成短路连接。这种设计可以缓解深层网络中的梯度消失问题,使模型更容易训练。
数学表示:
残差连接的数学表达式为:
Residual = x + SubLayer ( x ) \text{Residual} = x + \text{SubLayer}(x) Residual=x+SubLayer(x)
其中:
- ( x ) 是子层的输入。
- ( SubLayer ( x ) \text{SubLayer}(x) SubLayer(x)) 是子层的输出。
优点:
- 缓解梯度消失:残差连接允许梯度直接流过网络层,减少了深层网络中梯度消失的风险。
- 加速训练:通过短路连接,模型可以更快地收敛。
- 增强模型表达能力:残差连接使得模型能够学习到更复杂的特征。
2. 层归一化(Layer Normalization))
作用:
层归一化对每一层的输入进行归一化,使得每个特征的均值为 0,标准差为 1。这有助于加速模型收敛,提高训练稳定性。
与批量归一化的区别:
- 批量归一化(Batch Normalization):依赖于批次中的其他样本,对每个特征在批次维度上进行归一化。
- 层归一化(Layer Normalization):不依赖于批次大小,对每个样本在特征维度上进行归一化。这使得层归一化适用于小批量或在线学习场景。
优点:
- 稳定训练过程:层归一化减少了内部协变量偏移(Internal Covariate Shift),使得每一层的输入分布更加稳定。
- 提高模型稳定性:通过归一化处理,减少了特征之间的尺度差异,避免了某些特征在学习过程中占据主导地位。
- 适应不同类型的网络:层归一化特别适用于循环神经网络(RNN)和Transformer模型。
- 减少梯度消失和爆炸:归一化处理可以减少梯度在传播过程中的消失或爆炸问题。
- 不受批量大小限制:层归一化不依赖于批次大小,因此在处理不同大小的批次时,不需要调整超参数。
数学表示:
层归一化的数学表达式为:
LayerNorm
(
x
)
=
γ
⋅
x
−
μ
σ
2
+
ϵ
+
β
\text{LayerNorm}(x) = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta
LayerNorm(x)=γ⋅σ2+ϵx−μ+β
其中:
- ( x ) 是输入。
- ( μ \mu μ) 是输入的均值。
- ( σ 2 \sigma^2 σ2) 是输入的方差。
- ( γ \gamma γ) 和 ( β \beta β) 是可学习的参数。
- ( ϵ \epsilon ϵ) 是一个小的常数,用于数值稳定性。
3. Add & Norm 操作
定义:
Add & Norm 是将残差连接(Addition)和层归一化(Normalization)结合在一起的操作,用于提高训练过程中的稳定性和性能。
步骤:
- 计算子层的输出: SubLayer ( x ) \text{SubLayer}(x) SubLayer(x)
- 执行残差连接: Residual = x + SubLayer ( x ) \text{Residual} = x + \text{SubLayer}(x) Residual=x+SubLayer(x)
- 应用层归一化: Output = LayerNorm ( Residual ) \text{Output} = \text{LayerNorm}(\text{Residual}) Output=LayerNorm(Residual)
数学表示:
Output = LayerNorm ( x + SubLayer ( x ) ) \text{Output} = \text{LayerNorm}(x + \text{SubLayer}(x)) Output=LayerNorm(x+SubLayer(x))
七、总结
Attention机制通过动态地为输入序列中的每个元素分配权重,解决了传统序列模型(如RNN)在处理长程依赖时的瓶颈问题。Transformer模型在此基础上引入了自注意力机制和多头注意力机制,进一步增强了模型的表达能力,使其能够并行处理序列数据并捕捉全局依赖关系。这种设计不仅显著提升了模型在NLP任务中的性能,还为其他领域(如计算机视觉、多模态学习)提供了新的思路。
OK!今天就学习到这里了!🙂
项目地址
如果觉得我的文章对您有帮助,三连+关注便是对我创作的最大鼓励!或者一个star🌟也可以😂.