深度学习模块实践手册(第十一期)

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 F

class 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 nn

class 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 nn

class 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_size
        self.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_embed
        
        return 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 nn

class 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 = stride
        self.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 += residual
        
        return 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 处理:
    1. 第一个全连接层将维度从N扩展至h(隐藏维度,通常为4D);
    2. 应用 GELU 激活函数;
    3. 第二个全连接层将维度从h压缩回N;
    4. 转置回原形状(\(N \times D\)),与输入相加(残差连接)并进行层归一化(Layer Norm)。
  • Channel-Mixing MLP:用于建模通道之间的关系。直接对 Token-Mixing 的输出进行处理:
    1. 第一个全连接层将维度从D扩展至h;
    2. 应用 GELU 激活函数;
    3. 第二个全连接层将维度从h压缩回D;
    4. 与输入相加(残差连接)并进行层归一化。

通过交替进行 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 nn

class 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 = x
        x = 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 = x
        x = self.channel_mixing(x)
        x += residual
        
        return 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])

你可能感兴趣的:(目标检测,目标检测模块解析与实践,深度学习,人工智能,计算机视觉,目标检测,python)