46、缩放点积注意力模块
论文《Attention Is All You Need》
1、作用:
缩放点积注意力(Scaled Dot-Product Attention)是 Transformer 模型的核心组件,旨在解决序列建模中长距离依赖关系捕捉的问题。传统的循环神经网络(RNN)在处理长序列时存在梯度消失或爆炸的问题,且并行性较差。该模块通过计算查询(Query)、键(Key)和值(Value)之间的相似度,实现对输入序列中重要信息的聚焦,同时支持高效的并行计算,为 Transformer 在自然语言处理、计算机视觉等领域的成功奠定了基础。
2、机制
缩放点积注意力的核心机制是通过向量点积计算查询与键的相似度,再经过缩放和 softmax 归一化得到注意力权重,最后用权重对值进行加权求和。具体步骤如下:
- 计算查询(Q)与键(K)的点积,得到原始注意力分数:scores=QKT
- 对原始分数进行缩放,除以键维度的平方根(dk),避免因维度过高导致的分数值过大,从而稳定 softmax 的梯度:scaled_scores=scores/dk
- 对缩放后的分数应用 softmax 函数,得到归一化的注意力权重:attention_weights=softmax(scaled_scores)
- 用注意力权重对值(V)进行加权求和,得到最终的注意力输出:output=attention_weights×V
此外,为了处理掩码场景(如解码时的序列掩码),可在 softmax 前加入掩码(mask),将无效位置的分数设为负无穷。
3、独特优势
- 并行性强:相较于 RNN 的串行计算方式,缩放点积注意力可对序列中所有位置的关系进行并行计算,大幅提升训练和推理速度。
- 长距离依赖捕捉能力:通过直接计算任意两个位置之间的注意力权重,能够有效捕捉长序列中的依赖关系,优于 RNN 和卷积神经网络(CNN)的局部感受野限制。
- 灵活性高:可通过多头注意力(Multi-Head Attention)扩展为多个并行的注意力子空间,捕捉不同维度的特征关系,进一步提升模型性能。
在机器翻译任务中,基于该模块的 Transformer 模型在 WMT 2014 英德翻译任务上实现了 28.4 BLEU 的分数,显著优于当时的主流模型。
4、代码
import torch
import torch.nn as nn
import torch.nn.functional as Fclass ScaledDotProductAttention(nn.Module):"""缩放点积注意力模块(Scaled Dot-Product Attention)实现查询、键、值之间的注意力计算"""def __init__(self):super().__init__()def forward(self, q, k, v, mask=None):"""参数说明:q: 查询张量,形状为 [batch_size, n_heads, seq_len_q, d_k]k: 键张量,形状为 [batch_size, n_heads, seq_len_k, d_k]v: 值张量,形状为 [batch_size, n_heads, seq_len_v, d_v](通常seq_len_k = seq_len_v)mask: 掩码张量,形状为 [batch_size, 1, seq_len_q, seq_len_k] 或类似,用于掩盖无效位置返回:output: 注意力输出,形状为 [batch_size, n_heads, seq_len_q, d_v]attn_weights: 注意力权重,形状为 [batch_size, n_heads, seq_len_q, seq_len_k]"""d_k = q.size(-1) # 键的维度# 计算Q与K的点积并缩放scores = torch.matmul(q, k.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))# 应用掩码(若有)if mask is not None:scores = scores.masked_fill(mask == 0, -1e9) # 掩码位置设为负无穷# 计算注意力权重attn_weights = F.softmax(scores, dim=-1)# 加权求和得到输出output = torch.matmul(attn_weights, v)return output, attn_weights# 测试代码
if __name__ == '__main__':# 实例化注意力模块model = ScaledDotProductAttention()# 生成随机输入(batch_size=2, n_heads=8, seq_len=10, d_k=d_v=64)q = torch.randn(2, 8, 10, 64)k = torch.randn(2, 8, 10, 64)v = torch.randn(2, 8, 10, 64)# 生成掩码(掩盖后5个位置)mask = torch.ones(2, 1, 10, 10)mask[:, :, :, 5:] = 0# 前向传播output, attn_weights = model(q, k, v, mask)# 验证输出形状print(f"输出形状: {output.shape}") # 预期: torch.Size([2, 8, 10, 64])print(f"注意力权重形状: {attn_weights.shape}") # 预期: torch.Size([2, 8, 10, 10])
47、深度可分离卷积模块
论文《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》
1、作用:
深度可分离卷积(Depthwise Separable Convolution)是为移动端和嵌入式设备设计的高效卷积操作,旨在解决传统卷积神经网络计算量大、参数过多的问题。传统卷积在提取空间特征和通道特征时存在冗余计算,该模块通过将卷积操作分解为深度卷积(Depthwise Convolution)和逐点卷积(Pointwise Convolution),在保持相似特征提取能力的同时,大幅减少计算量(FLOPs)和模型参数,使神经网络能够在资源受限的设备上高效运行。
2、机制
深度可分离卷积由两个连续的操作组成,具体机制如下:
- 深度卷积:对输入特征图的每个通道单独应用一个卷积核(即每个通道对应一个卷积核),用于捕捉该通道内的空间特征。假设输入特征图的通道数为Cin,卷积核大小为K×K,则深度卷积的卷积核总数为Cin,输出特征图的通道数仍为Cin。其计算量为Cin×H×W×K×K(H、W为特征图的高和宽)。
- 逐点卷积:使用1×1的卷积核对深度卷积的输出进行跨通道特征融合。假设需要输出的通道数为Cout,则逐点卷积的卷积核数量为Cout,每个卷积核的输入通道数为Cin。其计算量为Cout×H×W×Cin×1×1。
相比之下,传统卷积的计算量为Cout×H×W×K×K×Cin,深度可分离卷积的计算量约为传统卷积的1/Cout+1/(K2),当K=3时,计算量可减少至约 1/9~1/8。
3、独特优势
- 高效性:在相同感受野的情况下,计算量和参数数量远低于传统卷积,例如 MobileNet 使用深度可分离卷积后,模型大小仅为 AlexNet 的 1/10,计算量减少至 1/8,却能保持相近的 ImageNet 分类准确率。
- 灵活性:深度卷积和逐点卷积可分别独立优化,例如在深度卷积后加入批归一化和激活函数,提升特征提取能力。
- 适用性广:不仅适用于移动端视觉任务(如图像分类、目标检测),还可用于轻量级模型设计,如 MobileNet 系列、Xception 等,在工业界得到广泛应用。
4、代码
import torch
import torch.nn as nnclass DepthwiseSeparableConv(nn.Module):"""深度可分离卷积模块(Depthwise Separable Convolution)由深度卷积和逐点卷积组成,减少计算量和参数"""def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1):"""参数说明:in_channels: 输入特征图的通道数out_channels: 输出特征图的通道数kernel_size: 深度卷积的卷积核大小stride: 深度卷积的步长padding: 深度卷积的填充"""super().__init__()# 深度卷积:每个通道单独卷积self.depthwise = nn.Conv2d(in_channels=in_channels,out_channels=in_channels, # 输出通道数与输入相同kernel_size=kernel_size,stride=stride,padding=padding,groups=in_channels, # 分组数等于输入通道数,实现每个通道单独卷积bias=False)# 逐点卷积:1x1卷积融合通道特征self.pointwise = nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=1,stride=1,padding=0,bias=False)# 批归一化和激活函数self.bn = nn.BatchNorm2d(out_channels)self.relu = nn.ReLU(inplace=True)def forward(self, x):# 深度卷积x = self.depthwise(x)# 逐点卷积x = self.pointwise(x)# 批归一化和激活x = self.bn(x)x = self.relu(x)return x# 测试代码
if __name__ == '__main__':# 实例化深度可分离卷积模块(输入通道32,输出通道64,3x3卷积)model = DepthwiseSeparableConv(in_channels=32, out_channels=64, kernel_size=3).cuda()# 创建随机输入张量 [batch_size=2, channels=32, height=64, width=64]input_tensor = torch.randn(2, 32, 64, 64).cuda()# 前向传播output_tensor = model(input_tensor)# 验证输出形状print(f"输入形状: {input_tensor.shape}")print(f"输出形状: {output_tensor.shape}") # 预期: torch.Size([2, 64, 64, 64])
48、视觉 Transformer(ViT)的 Patch Embedding 模块
论文《An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》
1、作用: Patch Embedding 是视觉 Transformer(Vision Transformer, ViT)中将图像转换为 Transformer 可处理序列的核心模块。传统的 Transformer 模型主要用于处理文本序列,而图像是二维网格结构,无法直接输入 Transformer。该模块通过将图像分割为固定大小的非重叠补丁(Patches),并将每个补丁线性投影为固定维度的向量,从而将图像转换为一维序列,使 Transformer 能够应用于图像识别等视觉任务,打破了卷积神经网络在视觉领域的垄断地位。
2、机制 Patch Embedding 的具体机制如下:
- 图像分块:将输入图像(形状为\(H \times W \times C\),H、W为图像高宽,C为通道数)分割为N个非重叠的补丁,每个补丁的大小为\(P \times P \times C\)。若\(H = W = P \times \sqrt{N}\)(通常取\(P=16\)),则补丁数量\(N = (H/P) \times (W/P)\)。
- 线性投影:将每个\(P \times P \times C\)的补丁展平为长度为\(P^2 \times C\)的向量,再通过一个线性层(全连接层)将其投影为维度为D的向量(即嵌入维度),得到N个嵌入向量,形成形状为\(N \times D\)的序列。
- 添加位置编码:由于 Transformer 的自注意力机制是位置无关的,需在补丁嵌入序列中加入位置编码(Positional Encoding),以提供补丁的空间位置信息。位置编码的形状为\(N \times D\),与补丁嵌入序列逐元素相加。
例如,对于 224x224x3 的图像,使用 16x16 的补丁分割,可得到\(14 \times 14 = 196\)个补丁,每个补丁展平后长度为\(16 \times 16 \times 3 = 768\),通过线性投影至\(D=768\)维度,最终得到 196+1(加上类别嵌入)个向量的序列。
3、独特优势
- 适配 Transformer:成功将二维图像转换为一维序列,使 Transformer 能够直接处理视觉数据,充分利用 Transformer 捕捉全局依赖的能力。
- 减少冗余计算:相较于卷积神经网络的局部感受野,Patch Embedding 通过分块投影,避免了卷积操作中大量的重叠计算,在大规模数据集(如 ImageNet-21k)上表现更优。
- 灵活性高:补丁大小和嵌入维度可灵活调整,以平衡模型性能和计算成本。例如,ViT-B 在 ImageNet-1k 上的 top-1 准确率达到 85.8%,超过同期的 ResNet-152。
4、代码
import torch
import torch.nn as nnclass PatchEmbedding(nn.Module):"""视觉Transformer的Patch Embedding模块将图像分割为补丁并转换为嵌入序列"""def __init__(self, img_size=224, patch_size=16, in_channels=3, embed_dim=768):"""参数说明:img_size: 输入图像的大小(假设为正方形)patch_size: 每个补丁的大小(假设为正方形)in_channels: 图像的通道数(如RGB图像为3)embed_dim: 补丁嵌入的维度"""super().__init__()self.img_size = img_sizeself.patch_size = patch_size# 计算补丁数量self.num_patches = (img_size // patch_size) **2# 定义补丁投影层(使用卷积实现,等价于线性投影)self.proj = nn.Conv2d(in_channels=in_channels,out_channels=embed_dim,kernel_size=patch_size,stride=patch_size # 步长等于补丁大小,确保非重叠分块)# 定义位置编码(可学习的位置嵌入)self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches, embed_dim))def forward(self, x):"""参数x: 输入图像张量,形状为 [batch_size, in_channels, img_size, img_size]返回: 补丁嵌入序列,形状为 [batch_size, num_patches, embed_dim]"""batch_size = x.shape[0]# 补丁投影:[batch_size, in_channels, H, W] -> [batch_size, embed_dim, num_patches^(1/2), num_patches^(1/2)]x = self.proj(x)# 展平为序列:[batch_size, embed_dim, num_patches] -> [batch_size, num_patches, embed_dim]x = x.flatten(2).transpose(1, 2)# 添加位置编码x = x + self.pos_embedreturn x# 测试代码
if __name__ == '__main__':# 实例化Patch Embedding模块(224x224图像,16x16补丁,3通道,768维嵌入)model = PatchEmbedding(img_size=224, patch_size=16, in_channels=3, embed_dim=768).cuda()# 创建随机输入张量 [batch_size=2, channels=3, height=224, width=224]input_tensor = torch.randn(2, 3, 224, 224).cuda()# 前向传播output_tensor = model(input_tensor)# 验证输出形状print(f"输入形状: {input_tensor.shape}")print(f"输出形状: {output_tensor.shape}") # 预期: torch.Size([2, 196, 768])(196=14x14)
49、倒置残差块(Inverted Residual Block)
论文《MobileNetV2: Inverted Residuals and Linear Bottlenecks》
1、作用: 倒置残差块是 MobileNetV2 中提出的核心模块,旨在解决移动端神经网络中特征表达能力与计算效率的平衡问题。传统的残差块(如 ResNet 中的残差块)采用 “降维 - 卷积 - 升维” 的结构,而倒置残差块创新性地使用 “升维 - 卷积 - 降维” 的结构,配合线性瓶颈(Linear Bottleneck),在减少计算量的同时,有效缓解了特征信息在低维空间的损失,显著提升了轻量级模型的性能。
2、机制 倒置残差块的机制主要包括以下步骤:
- 升维(Expansion):通过\(1 \times 1\)的逐点卷积将输入特征的通道数从\(C_{in}\)提升至\(t \times C_{in}\)(t为扩展因子,通常取 6),目的是在深度卷积前增加特征维度,提升特征表达能力。
- 深度卷积(Depthwise Convolution):对升维后的特征图应用\(3 \times 3\)的深度卷积(每个通道单独卷积),捕捉空间特征,此时计算量为\(t \times C_{in} \times H \times W \times 3 \times 3\),由于深度卷积的特性,计算量仍保持较低水平。
- 降维(Projection):通过\(1 \times 1\)的逐点卷积将特征通道数从\(t \times C_{in}\)降维至\(C_{out}\),并移除非线性激活函数(使用线性层),形成线性瓶颈,减少通道冗余。
- 残差连接(Residual Connection):当输入输出通道数和空间尺寸相同时,添加跳跃连接,将输入特征与输出特征相加,缓解梯度消失问题。
与传统残差块相比,倒置残差块在高维空间进行卷积操作,避免了低维空间的信息损失,同时通过深度卷积保持计算效率。
3、独特优势
- 高效特征提取:通过升维操作增强特征表达能力,配合深度卷积减少计算量,在相同 FLOPs 下比传统残差块具有更强的特征提取能力。
- 缓解信息损失:线性瓶颈设计避免了非线性激活函数在低维空间对特征信息的破坏,实验表明 MobileNetV2 在 ImageNet 上的准确率比 MobileNetV1 提升了 3.2%。
- 轻量化设计:适用于移动端和嵌入式设备,MobileNetV2 的模型大小仅为 14MB,却能在 COCO 目标检测任务上达到与 ResNet-50 相当的性能。
4、代码
import torch
import torch.nn as nnclass InvertedResidual(nn.Module):"""倒置残差块(Inverted Residual Block)采用"升维-深度卷积-降维"结构,配合残差连接"""def __init__(self, in_channels, out_channels, stride, expansion_factor=6):"""参数说明:in_channels: 输入特征通道数out_channels: 输出特征通道数stride: 深度卷积的步长(1或2,决定是否改变空间尺寸)expansion_factor: 升维的扩展因子"""super().__init__()self.stride = strideself.use_residual = (stride == 1) and (in_channels == out_channels)# 升维通道数expanded_channels = in_channels * expansion_factor# 升维:1x1卷积self.expand = nn.Conv2d(in_channels=in_channels,out_channels=expanded_channels,kernel_size=1,stride=1,padding=0,bias=False)self.bn1 = nn.BatchNorm2d(expanded_channels)self.relu6 = nn.ReLU6(inplace=True) # 限制ReLU的最大值为6,增强稳定性# 深度卷积:3x3卷积(每个通道单独卷积)self.depthwise = nn.Conv2d(in_channels=expanded_channels,out_channels=expanded_channels,kernel_size=3,stride=stride,padding=1,groups=expanded_channels, # 分组数等于输入通道数bias=False)self.bn2 = nn.BatchNorm2d(expanded_channels)# 降维:1x1卷积(线性瓶颈,无激活函数)self.project = nn.Conv2d(in_channels=expanded_channels,out_channels=out_channels,kernel_size=1,stride=1,padding=0,bias=False)self.bn3 = nn.BatchNorm2d(out_channels)def forward(self, x):residual = x# 升维x = self.expand(x)x = self.bn1(x)x = self.relu6(x)# 深度卷积x = self.depthwise(x)x = self.bn2(x)x = self.relu6(x)# 降维(线性操作)x = self.project(x)x = self.bn3(x)# 残差连接(若满足条件)if self.use_residual:x += residualreturn x# 测试代码
if __name__ == '__main__':# 实例化倒置残差块(输入32通道,输出16通道,步长1,扩展因子6)model = InvertedResidual(in_channels=32, out_channels=16, stride=1).cuda()# 创建随机输入张量 [batch_size=2, channels=32, height=64, width=64]input_tensor = torch.randn(2, 32, 64, 64).cuda()# 前向传播output_tensor = model(input_tensor)# 验证输出形状print(f"输入形状: {input_tensor.shape}")print(f"输出形状: {output_tensor.shape}") # 预期: torch.Size([2, 16, 64, 64])
45、MLP-Mixer 的 Mixer 块
论文《MLP-Mixer: An all-MLP Architecture for Vision》
1、作用: Mixer 块是 MLP-Mixer 模型的核心组件,该模型完全摒弃了卷积和自注意力机制,仅通过多层感知机(MLP)实现图像识别任务。Mixer 块的设计旨在证明纯 MLP 架构也能有效捕捉图像中的空间和通道特征,挑战了卷积神经网络和视觉 Transformer 在视觉领域的主导地位。其作用是通过两种类型的 MLP(token-mixing MLP 和 channel-mixing MLP)分别建模空间维度和通道维度的特征交互,从而实现对图像特征的有效提取。
2、机制 Mixer 块的机制围绕 “混合”(Mixing)操作展开,具体包括:
- 输入准备:输入为经过 Patch Embedding 后的补丁序列(形状为\(N \times D\),N为补丁数量,D为嵌入维度),通常将其转换为\(N \times D\)的矩阵。
- Token-Mixing MLP:用于建模不同补丁(token)之间的空间关系。首先对输入矩阵进行转置(\(D \times N\)),然后通过 MLP 处理:
- 第一个全连接层将维度从N扩展至h(隐藏维度,通常为4D);
- 应用 GELU 激活函数;
- 第二个全连接层将维度从h压缩回N;
- 转置回原形状(\(N \times D\)),与输入相加(残差连接)并进行层归一化(Layer Norm)。
- Channel-Mixing MLP:用于建模通道之间的关系。直接对 Token-Mixing 的输出进行处理:
- 第一个全连接层将维度从D扩展至h;
- 应用 GELU 激活函数;
- 第二个全连接层将维度从h压缩回D;
- 与输入相加(残差连接)并进行层归一化。
通过交替进行 token-mixing 和 channel-mixing,Mixer 块能够同时捕捉空间和通道特征的交互信息。
3、独特优势
- 结构简单:完全基于 MLP,无需卷积或自注意力机制,实现和训练难度低,易于并行化。
- 计算高效:token-mixing 和 channel-mixing 的计算复杂度分别为\(O(D \times N^2)\)和\(O(N \times D^2)\),通过合理设置N和D,可在大规模数据上达到与 ViT 相当的性能。
- 泛化能力强:在 ImageNet-1k 上,Mixer-B 达到 84.8% 的 top-1 准确率,与 ViT-B 相当,且在迁移学习任务上表现优异,证明了纯 MLP 架构在视觉任务中的潜力。
4、代码
import torch
import torch.nn as nnclass MixerBlock(nn.Module):"""MLP-Mixer的Mixer块包含Token-Mixing MLP和Channel-Mixing MLP,用于混合空间和通道特征"""def __init__(self, num_patches, hidden_dim, mlp_dim):"""参数说明:num_patches: 补丁(token)的数量(N)hidden_dim: 补丁嵌入的维度(D)mlp_dim: MLP的隐藏层维度"""super().__init__()# Token-Mixing MLP:混合不同补丁(空间维度)self.token_mixing = nn.Sequential(nn.LayerNorm(hidden_dim), # 层归一化nn.Linear(num_patches, mlp_dim), # 扩展维度nn.GELU(),nn.Linear(mlp_dim, num_patches) # 压缩回原维度)# Channel-Mixing MLP:混合不同通道self.channel_mixing = nn.Sequential(nn.LayerNorm(hidden_dim), # 层归一化nn.Linear(hidden_dim, mlp_dim), # 扩展维度nn.GELU(),nn.Linear(mlp_dim, hidden_dim) # 压缩回原维度)def forward(self, x):"""参数x: 输入张量,形状为 [batch_size, num_patches, hidden_dim]返回: 输出张量,形状与输入相同"""# Token-Mixing:先转置,处理后再转置回来,添加残差residual = xx = x.transpose(1, 2) # [batch_size, hidden_dim, num_patches]x = self.token_mixing(x)x = x.transpose(1, 2) # [batch_size, num_patches, hidden_dim]x += residual# Channel-Mixing:直接处理,添加残差residual = xx = self.channel_mixing(x)x += residualreturn x# 测试代码
if __name__ == '__main__':# 实例化Mixer块(196个补丁,768维嵌入,3072维MLP隐藏层)model = MixerBlock(num_patches=196, hidden_dim=768, mlp_dim=3072).cuda()# 创建随机输入张量 [batch_size=2, num_patches=196, hidden_dim=768]input_tensor = torch.randn(2, 196, 768).cuda()# 前向传播output_tensor = model(input_tensor)# 验证输出形状print(f"输入形状: {input_tensor.shape}")print(f"输出形状: {output_tensor.shape}") # 预期: torch.Size([2, 196, 768])