乐趣区

关于人工智能:Visoin-MLP之CycleMLP-A-MLPlike-Architecture-for-Dense-Prediction

Visoin MLP 之 CycleMLP A MLP-like Architecture for Dense Prediction

原始文档:https://www.yuque.com/lart/pa…

从摘要读文章

This paper presents a simple MLP-like architecture, CycleMLP, which is a versatile backbone for visual recognition and dense predictions, unlike modern MLP architectures, e.g., MLP-Mixer, ResMLP, and gMLP, whose architectures are correlated to image size and thus are infeasible in object detection and segmentation.

看来又是一个金字塔形态的 MLP 架构,并且能够看到次要的工作必然又是围绕空间 MLP 而开展的。因为这里解除了架构对于输出尺寸的依赖。
实际上在 MLP-Mixer 原论文中,作者们其实也尝试了金字塔构造,相较与固定分辨率的模式,的确收敛更快。

We tried using the token-mixing MLP to reduce the number of tokens by mapping from S input tokens to S'<S output tokens. While first experiments showed that on JFT-300M such models significantly reduced training time without losing much performance, we were unable to transfer these findings to ImageNet or ImageNet-21k.

CycleMLP has two advantages compared to modern approaches.

  • (1) It can cope with various image sizes.
  • (2) It achieves linear computational complexity to image size by using local windows.

    这里应用部分窗口的操作实现了线性计算复杂度,这中固定部分窗口也使得能够解决不同大小的图像。

In contrast, previous MLPs have quadratic computations because of their fully spatial connections.

指出了改良空间 MLP 的必要性。其计算复杂度太高。

We build a family of models that surpass existing MLPs and achieve a comparable accuracy (83.2%) on ImageNet-1K classification compared to the state-of-the-art Transformer such as Swin Transformer (83.3%) but using fewer parameters and FLOPs.

能够看到,本文的办法的性能是十分高的。不晓得除了构造之外,是否应用了其余的策略。

We expand the MLP-like models’ applicability, making them a versatile backbone for dense prediction tasks. CycleMLP aims to provide a competitive baseline on object detection, instance segmentation, and semantic segmentation for MLP models. In particular, CycleMLP achieves 45.1 mIoU on ADE20K val, comparable to Swin (45.2 mIOU). Code is available at this https URL.

次要内容

整体构造


能够看到,外围是在改良空间 MLP。
现有 MLP 构造的三点有余:

  1. 大多属于单尺度构造,不便于迁徙到其余的须要特色金字塔的工作上,例如检测、宰割等。
  2. 空间 MLP 会连贯输出特色空间上的所有点,这也限度了模型对于输出尺寸的依赖。不利于多尺度训练、多尺度测试、甚至训练和测试分辨率不一样的状况。
  3. 空间 MLP 具备二次方量级的计算复杂度,使其不便于解决高分辨率图像。

对此,作者们从两个方面进行应答:

  1. 针对问题 1,设计了层级构造来生成特色金字塔。
  2. 针对问题 2 和 3,实际上是针对空间 MLP 的问题,作者们设计了一种非凡的通道 MLP 来实现对于部分空间的解决。因为是针对部分空间的,所以说不再对于输出尺寸有过强的依赖。并且依然是通道 MLP(空间上共享的点操作),所以计算复杂度升高到了线性。

与 S2-MLP 的不同

尽管都是应用通道 MLP 替换了空间 MLP,然而具体形式和模型整体模式有所不同:

  1. S2-MLP 对特色进行通道分组,不同组进行空间上不同方向的绝对偏移。这在特色图上引入了额定的分组和偏移操作。而本文则是不须要扭转特色,仅仅是调整了通道 MLP 的运算模式。具备更良好的通用性和可拔插性。
  2. S2-MLP 仍然是单尺度构造,本文引入了金字塔构造来更好的适应检测宰割等工作。

实际上,对于第 1 点,其实 S2-MLP 中给出了应用深度拆散卷积实现的策略,即偏移能够通过特定模式的深度拆散卷积核实现,对于输出数据的分组和偏移都是能够间接通过对卷积核的操作来实现的。这里的第 1 点并不成立。只能说,Cycle FC 的实现可能更加间接一些,不同于 S2-MLP 须要借助一些与计算无关的解决操作。
更进一步,从实现上来讲,Cycle FC 是否也是能够通过应用深度拆散卷积实现便宜操作呢?答复是能够的,我会在后文的代码剖析中提供一些简略的尝试。

模型细节

  • patch embedding module: window size 7, stride 4, 最终特色下采样四倍。
  • 两头通过跨步卷积实现 2 倍下采样。
  • 最终输入为下采样 32 倍的特色。
  • 最终应用一个全连贯层整合所有 token 的特色。

外围操作——Cycle FC

论文提出 Cycle FC 的外围想法在于利用通道 MLP 的与特色尺寸的无关性(缩小了对输出形态的限度并且能够将计算复杂度降到线性),同时想方法增大其感触野来更好的集成上下文特色。

从图中给出的模式能够看到,Cycle FC 实际上是一种在通道上进行特定地位的偏移(阶梯状采样,stair-like style)的通道 MLP。所以对于输出的形态要求不会太严苛。当然,至多偏移地位不能超出 HW 上限定的核尺寸。
从代码中能够看到,这里是限定了一个范畴,通过让通道索引对其取模从而实现限定范畴内的循环偏移,这里的实现很有意思,用到了可变形卷积来对核参数利用偏移。
具体而言,原始的通道 MLP 的计算形式为:$Y_{i,j} = \sum_{c=0}^{C_i – 1} \mathcal{F}^{\top}_{j,c} \cdot X_{i,c}$,其中的 $\mathcal{F} \in \mathbb{R}^{C_i \times C_o}$ 示意通道 MLP 的可学习权重。其中的 $i\&j$ 别离示意空间和通道的索引。
而对于本文提出的 Cycle FC,计算形式扩大成为:$Y_{i, j} = \sum_{c=0}^{C_i-1} \mathcal{F}^{\top}_{j,c} \cdot X_{i+c\%S_{\mathcal{P}},c}$,这引入了一个偏移范畴参数 $S_{\mathcal{P}}$,即 pseudo-kernel size,示意通道偏移后所有波及到的计算地位在 HW 空间上的投影的面积,而另一个参数 $i$ 示意偏移的起始地位。在代码中取值为 pseudo-kernel 矩形区域外部的核心绝对坐标(以区域外部左上角为终点,索引为 0,对矩形区域依照行主序进行排序索引),即start_idx=(self.kernel_size[0]*self.kernel_size[1])//2

代码解析

这里通过正文的模式对局部外围代码剖析。

from torchvision.ops.deform_conv import deform_conv2d as deform_conv2d_tv

class CycleFC(nn.Module):
    def __init__(
        self,
        in_channels: int,
        out_channels: int,
        kernel_size,  # re-defined kernel_size, represent the spatial area of staircase FC
        stride: int = 1,
        padding: int = 0,
        dilation: int = 1,
        groups: int = 1,
        bias: bool = True,
    ):
        """这里的 kernel_size 理论应用的时候时 3x1 或者 1x3"""
        super(CycleFC, self).__init__()

        if in_channels % groups != 0:
            raise ValueError('in_channels must be divisible by groups')
        if out_channels % groups != 0:
            raise ValueError('out_channels must be divisible by groups')
        if stride != 1:
            raise ValueError('stride must be 1')
        if padding != 0:
            raise ValueError('padding must be 0')

        self.in_channels = in_channels
        self.out_channels = out_channels
        self.kernel_size = kernel_size
        self.stride = _pair(stride)
        self.padding = _pair(padding)
        self.dilation = _pair(dilation)
        self.groups = groups

        # 被偏移调整的 1x1 卷积的权重,因为前面应用 torchvision 提供的可变形卷积的函数,所以权重须要本人结构
        self.weight = nn.Parameter(torch.empty(out_channels, in_channels // groups, 1, 1))
        # kernel size == 1

        if bias:
            self.bias = nn.Parameter(torch.empty(out_channels))
        else:
            self.register_parameter('bias', None)
        # 要留神,这里是在注册一个 buffer,是一个常量,不可学习,然而能够保留到模型权重中。self.register_buffer('offset', self.gen_offset())

    def gen_offset(self):
        """
        生成卷积核偏移量的外围操作。要想了解这一函数的操作,须要首先了解前面应用的 deform_conv2d_tv 的具体用法。具体可见:https://pytorch.org/vision/0.10/ops.html#torchvision.ops.deform_conv2d
        这里对于 offset 参数的要求是:offset (Tensor[batch_size,
                       2 * offset_groups * kernel_height * kernel_width,
                       out_height,
                       out_width])
                       – offsets to be applied for each position in the convolution kernel.
        也就是说,对于样本 s 的输入特色图的通道 c 中的地位(x,y),这个函数会从 offset 中取出,形态为
        kernel_height*kernel_width 的卷积核所对应的偏移参数为
        offset[s, 0:2*offset_groups*kernel_height*kernel_width, x, y]
        也就是这一系列参数都是对应样本 s 的单个地位 (x,y) 的。针对不同的地位能够有不同的 offset,也能够有雷同的(上面的实现就是后者)。对于这 2 *offset_groups*kernel_height*kernel_width 个数,波及到对于输出特色通道的分组。将其分成 offset_groups 组,每份独自领有一组对应于卷积核核心地位的绝对偏移量,共 2 *kernel_height*kernel_width 个数。对于每个核参数,应用两个量来形容偏移,即 h 方向和 w 方向绝对核心地位的偏移,即上面代码中的减去 kernel_height// 2 或者 kernel_width//2。须要留神的是,当偏移地位位于 padding 后的 tensor 边界外,则是将网格应用 0 补齐。如果网格上有边界值,则应用边界值和用 0 补齐的网格顶点来计算双线性插值的后果。"""
        offset = torch.empty(1, self.in_channels*2, 1, 1)
        start_idx = (self.kernel_size[0] * self.kernel_size[1]) // 2
        assert self.kernel_size[0] == 1 or self.kernel_size[1] == 1, self.kernel_size
        for i in range(self.in_channels):
            if self.kernel_size[0] == 1:
                offset[0, 2 * i + 0, 0, 0] = 0
                # 这里计算了一个绝对偏移地位。# deform_conv2d 应用的以对应输入地位为核心的偏移坐标索引形式
                offset[0, 2 * i + 1, 0, 0] = ((i + start_idx) % self.kernel_size[1] - (self.kernel_size[1] // 2)
                )
            else:
                offset[0, 2 * i + 0, 0, 0] = ((i + start_idx) % self.kernel_size[0] - (self.kernel_size[0] // 2)
                )
                offset[0, 2 * i + 1, 0, 0] = 0
        return offset

    def forward(self, input: Tensor) -> Tensor:
        """
        Args:
            input (Tensor[batch_size, in_channels, in_height, in_width]): input tensor
        """
        B, C, H, W = input.size()
        return deform_conv2d_tv(input,
                                self.offset.expand(B, -1, H, W),
                                self.weight,
                                self.bias,
                                stride=self.stride,
                                padding=self.padding,
                                dilation=self.dilation)

因为这里对于 offset 的生成有些让人纳闷的中央,我也给作者提了 issue(https://github.com/ShoufaChen/CycleMLP/issues/11),同时附了一个对于 deform_conv2d 的小例子。
这里作者给提供了一个与代码更贴合的图示:

class CycleMLP(nn.Module):
    def __init__(self, dim, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()
        self.mlp_c = nn.Linear(dim, dim, bias=qkv_bias)

        self.sfc_h = CycleFC(dim, dim, (1, 3), 1, 0)
        self.sfc_w = CycleFC(dim, dim, (3, 1), 1, 0)

        self.reweight = Mlp(dim, dim // 4, dim * 3)

        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(proj_drop)

    def forward(self, x):
        B, H, W, C = x.shape
        h = self.sfc_h(x.permute(0, 3, 1, 2)).permute(0, 2, 3, 1)
        w = self.sfc_w(x.permute(0, 3, 1, 2)).permute(0, 2, 3, 1)
        c = self.mlp_c(x)

        a = (h + w + c).permute(0, 3, 1, 2).flatten(2).mean(2)
        a = self.reweight(a).reshape(B, C, 3).permute(2, 0, 1).softmax(dim=0).unsqueeze(2).unsqueeze(2)

        x = h * a[0] + w * a[1] + c * a[2]

        x = self.proj(x)
        x = self.proj_drop(x)

        return x

从代码中能够看到,在理论应用的时候,是基于相似于 Inception V3 中分形卷积的模式,构建了 1×3 和 3×1 的两组并行操作。另外也有一个一般的通道 MLP 来进行单个地位的解决。从而构建了一个三分支构造。

which is inspired by the factorization of convolution [47] and criss-cross attention [26].

试验成果

和同期 MLP 办法的比拟

这里作者提到了 GFNet,它应用了 FFT 来学习空间特色,并且计算量也更少,与 CycleMLP 也有着相近的性能。然而其受输出分辨率的制约,如果想要更改输出分辨率则须要应用参数插值。这可能会侵害密集预测工作的性能。(这真的会有很大影响么?)

另外,作者们也补充了对于不同测试分辨率和不同分支的影响的融化试验。

这两个试验展现出了一些乏味的景象。

  • 首先看分辨率的影响,能够看到,最优的测试粉笔那缕可能并不是与原始训练统一。后果中反映出了 CycleFC 对于测试尺寸的稳定性。

    • 然而这里要留神的是,这里的试验从代码中能够看到,测试中应用的是先放大到指定尺寸的 $$\frac{256}{224}$$ 倍后再从核心 crop 出对应的尺寸的操作形式。而其余模式的数据处理的对应的景象仍需进行更多的试验验证。
  • 表 4 中对于多分支构造中的三个分支进行了融化试验。能够看到,操作多样性对于多分支构造是具备正向增益的。

也通过在检测和宰割工作上的体现展现出了提出构造(更灵便的输出尺寸、更高效的空间计算)的有效性。

链接

  • 论文:https://arxiv.org/pdf/2107.10224.pdf
  • 代码:https://github.com/ShoufaChen/CycleMLP
退出移动版