YOLO26改进 – 采样 ICCV 顶会技术:WaveletPool 小波池化强化采样,保留小目标细节

# 前言 本文介绍了基于小波变换的池化方法——Wavelet Pooling,作为传统最大池化与平均池化的有效替代方案。该方法通过两级小波分解丢弃高频子带,保留更具代表性的低频特征,从而在减少信息丢失的同时提升模型的正则化能力。我们将 Wavelet Pool 和 UnPool 成功集成进 YOLO26,替代原有的下采样与上采样模块,实现更高效的特征提取与恢复。实验证明,YOLO26-WaveletPool 在多个分类与检测任务中均取得优异表现,展现了小波池化在深度学习中的广泛应用前景。

文章目录: YOLO26改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总

专栏链接: YOLO26改进专栏

介绍

image-20241124212318344

摘要

卷积神经网络(Convolutional Neural Networks, CNNs)持续推动着二维和三维图像分类及目标识别技术的发展。然而,为了维持这一快速进展,有必要对神经网络中的基础构件进行持续的评估与改进。当前主流的网络正则化方法大多侧重于卷积操作本身,而对池化层的设计选择关注不足。

为此,我们提出了一种新的池化策略——小波池化(Wavelet Pooling),作为传统邻域池化方法(如最大池化和平均池化)的有效替代方案。该方法通过将特征分解为多层小波子带,并舍弃第一层级的高频子带来实现下采样,从而有效降低特征维度。与最大池化中常见的过拟合问题不同,小波池化在降维过程中保留了更多结构信息,具备更强的泛化能力。此外,相比于基于固定邻域的池化方式,小波池化在结构上实现了更紧凑、高效的特征压缩。

我们在四个标准图像分类数据集上进行了系统实验,结果表明:所提出的小波池化方法在性能上显著优于或与最大池化、平均池化、混合池化以及随机池化等主流方法相当,验证了其作为通用池化策略的潜力。

文章链接

论文地址:论文地址

代码地址:代码地址

论文地址:论文地址

基本原理:

首先,池化是一种通过舍弃信息实现正则化效果的操作。然而,传统的池化方法存在一些不足:

  • Max pooling:当重要特征的幅度值低于不重要特征时,重要特征会被忽略。
  • Average pooling:同时接纳幅值大和幅值小的特征,容易稀释关键特征。

为了解决这些问题,该论文提出基于小波变换的池化操作,具体思路如下:


小波变换的基本原理

小波变换可将输入特征图划分为低频子带(LL)和高频子带(LH、HL、HH)。其数学公式为:

  • 一级小波变换:
    $$ LL1, LH1, HL1, HH1 = DWT(I) $$ 逆变换:
    $$ I = IDWT(LL1, LH1, HL1, HH1) $$

  • 二级小波变换:
    $$ LL2, LH2, HL2, HH2 = DWT(LL1) $$ 逆变换:
    $$ LL1 = IDWT(LL2, LH2, HL2, HH2) $$

小波变换通过下采样将特征图尺寸缩小一半,逆变换可完美重建原始图像。


论文的方法

该论文方法流程如下:

  1. 对输入图像 $I$ 进行两次小波变换,得到:
    $$ LL2, (LH2, HL2, HH2), (LH1, HL1, HH1) = DWT(DWT(I)) $$
  2. 舍弃最高频子带 $(LH1, HL1, HH1)$,保留低频子带 $(LL2, LH2, HL2, HH2)$。
  3. 对保留的二级小波系数进行逆变换,重建池化后的图像:
    $$ I' = IDWT(LL2, LH2, HL2, HH2) $$

核心代码

class WaveletPool(nn.Module):
    def __init__(self):
        super(WaveletPool, self).__init__()
        ll = np.array([[0.5, 0.5], [0.5, 0.5]])
        lh = np.array([[-0.5, -0.5], [0.5, 0.5]])
        hl = np.array([[-0.5, 0.5], [-0.5, 0.5]])
        hh = np.array([[0.5, -0.5], [-0.5, 0.5]])
        filts = np.stack([ll[None,::-1,::-1], lh[None,::-1,::-1],
                            hl[None,::-1,::-1], hh[None,::-1,::-1]],
                            axis=0)
        self.weight = nn.Parameter(
            torch.tensor(filts).to(torch.get_default_dtype()),
            requires_grad=False)
    def forward(self, x):
        C = x.shape[1]
        filters = torch.cat([self.weight,] * C, dim=0)
        y = F.conv2d(x, filters, groups=C, stride=2)
        return y

class WaveletUnPool(nn.Module):
    def __init__(self):
        super(WaveletUnPool, self).__init__()
        ll = np.array([[0.5, 0.5], [0.5, 0.5]])
        lh = np.array([[-0.5, -0.5], [0.5, 0.5]])
        hl = np.array([[-0.5, 0.5], [-0.5, 0.5]])
        hh = np.array([[0.5, -0.5], [-0.5, 0.5]])
        filts = np.stack([ll[None, ::-1, ::-1], lh[None, ::-1, ::-1],
                            hl[None, ::-1, ::-1], hh[None, ::-1, ::-1]],
                            axis=0)
        self.weight = nn.Parameter(
            torch.tensor(filts).to(torch.get_default_dtype()),
            requires_grad=False)

    def forward(self, x):
        C = torch.floor_divide(x.shape[1], 4)
        filters = torch.cat([self.weight, ] * C, dim=0)
        y = F.conv_transpose2d(x, filters, groups=C, stride=2)
        return y

实验

脚本

import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO

if __name__ == '__main__':
#     修改为自己的配置文件地址
    model = YOLO('./ultralytics/cfg/models/26/yolo26-WaveletPool.yaml')
#     修改为自己的数据集地址
    model.train(data='./ultralytics/cfg/datasets/coco8.yaml',
                cache=False,
                imgsz=640,
                epochs=10,
                single_cls=False,  # 是否是单类别检测
                batch=8,
                close_mosaic=10,
                workers=0,
                optimizer='MuSGD',  
                # optimizer='SGD',
                amp=False,
                project='runs/train',
                name='yolo26-WaveletPool',
                )

结果

image-20260125212930968

THE END