51×51超大卷积核,超越 ConvNeXt、RepLKNet的创新方式

以下文章来源于微信公众号:集智书童

作者:ChaucerG

原文链接:https://mp.weixin.qq.com/s/MYU82hGH47-2JUl13MmgmA

本文仅用于学术分享,如有侵权,请联系后台作删文处理

自从Vision Transformers (ViT) 出现以来,Transformers迅速在计算机视觉领域大放异彩。卷积神经网络 (CNN) 的主导作用似乎受到越来越有效的基于Transformer的模型的挑战。最近,一些先进的卷积模型使用受局部大注意力机制驱动设计了大Kernel的卷积模块进行反击,并显示出吸引人的性能和效率。其中之一,即 RepLKNet,以改进的性能成功地将Kernel-size扩展到 31×31,但与 Swin Transformer 等高级 ViT 的扩展趋势相比,随着Kernel-size的持续增长,性能开始饱和。

在本文中,作者探索了训练大于 31×31 的极端卷积的可能性,并测试是否可以通过策略性地扩大卷积来消除性能差距。这项研究最终得到了一个从稀疏性的角度应用超大kernel的方法,它可以平滑地将kernel扩展到 61×61,并具有更好的性能。基于这个方法,作者提出了Sparse Large Kernel Network(SLaK),这是一种配备 51×51 kernel-size的纯 CNN 架构,其性能可以与最先进的分层 Transformer 和现代 ConvNet 架构(如 ConvNeXt 和 RepLKNet,关于 ImageNet 分类以及典型的下游任务。

1应用超过 31×31 的超大卷积核

作者首先研究了大于 31×31 的极端Kernel-size的性能,并总结了3个主要观察结果。这里作者以 ImageNet-1K 上最近开发的 CNN 架构 ConvNeXt 作为进行这项研究的 benchmark

作者关注最近使用 MixupCutmixRandAugment 和 Random Erasing 作为数据增强的作品。随机深度标签平滑作为正则化应用,具有与 ConvNeXt 中相同的超参数。用 AdamW 训练模型。在本节中,所有模型都针对 120 个 epoch 的长度进行了训练,以仅观察大Kernel-size的缩放趋势。

观察1:现有的技术不能扩展卷积超过31×31

最近,RepLKNet 通过结构重新参数化成功地将卷积扩展到 31×31。作者进一步将Kernel-size增加到 51×51 和 61×61,看看更大的kernel是否能带来更多的收益。按照RepLKNet中的设计,依次将每个阶段的Kernel-size设置为[51,49,47,13]和[61,59,57,13]。

测试精度如表 1 所示。正如预期的那样,将Kernel-size从 7×7 增加到 31×31 会显着降低性能,而 RepLKNet 可以克服这个问题,将精度提高 0.5%。然而,这种趋势不适用于较大的kernel,因为将Kernel-size增加到 51×51 开始损害性能。

一种合理的解释是,虽然感受野可以通过使用非常大的kernel,如51×5161×61来扩大感受野,但它可能无法保持某些理想的特性,如局部性。由于标准ResNetConvNeXt中的stem cell导致输入图像的4×降采样,具有51×51的极端核已经大致等于典型的224×224 ImageNet的全局卷积。因此,这一观察结果是有意义的,因为在ViTs的类似机制中,局部注意力通常优于全局注意力。在此基础上,通过引入局部性来解决这个问题的机会,同时保留了捕获全局关系的能力。

观察2:将一个方形的大kernel分解为2个矩形的parallel kernels,可以将Kernel-size平滑地缩放到 61。

虽然使用中等大小的卷积(例如,31×31)似乎可以直接避免这个问题,但作者就是想看看是否可以通过使用(全局)极端卷积来进一步推动cnn的性能。作者这里使用的方法是用2个平行和矩形卷积的组合来近似大的 M×M kernel,它们的Kernel-size分别为M×NN×M(其中N<M),如图1所示。在RepLKNet之后,保持一个5×5层与大kernel并行,并在一个批处理范数层之后汇总它们的输出。

这种分解不仅继承了大kernel捕获远程依赖关系的能力,而且可以提取边缘较短的局部上下文特征。更重要的是,随着深度Kernel-size的增加,现有的大kernel训练技术会受到二次计算和内存开销的影响。

与之形成鲜明对比的是,这种方法的开销随着Kernel-size线性增加(图 4)。N = 5 的kernel分解的性能在表 2 中报告为“分解”组。由于分解减少了 FLOP,与具有中等kernel的结构重新参数化 (RepLKNet) 相比,预计网络会牺牲一些准确性,即 31×31。然而,随着卷积大小增加到全局卷积,它可以惊人地将Kernel-size扩展到 61 并提高性能。

观察3:“use sparse groups, expand more”显着提高了模型的容量

最近提出的 ConvNeXt 重新访问了ResNeXt中引入的原理,该原理将卷积滤波器分成小但更多的组。ConvNeXt没有使用标准的组卷积,而是简单地使用增加宽度的深度卷积来实现“use more groups, expand width”的目标。在本文中,作者试图从另一个替代的角度来扩展这一原则——“use sparse groups, expand more”。

具体来说,首先用稀疏卷积代替密集卷积,其中稀疏核是基于SNIP的分层稀疏比随机构造的。构建完成后,用动态稀疏度训练稀疏模型,其中稀疏权值在训练过程中通过修剪最小幅值的权值,随机增加相同数量的权值进行动态调整。这样做可以动态地适应稀疏权值,从而获得更好的局部特征。

由于kernel在整个训练过程中都是稀疏的,相应的参数计数和训练/推理流只与密集模型成正比。为了评估,以40%的稀疏度稀疏化分解后的kernel,并将其性能报告为“稀疏分解”组。可以在表2的中间一列中观察到,动态稀疏性显着降低了FLOPs超过2.0G,导致了暂时的性能下降。

接下来,作者证明了上述动态稀疏性的高效率可以有效地转移到模型的可扩展性中。动态稀疏性允许能够友好地扩大模型的规模。例如,使用相同的稀疏性(40%),可以将模型宽度扩展1.3×,同时保持参数计数和FLOPs与密集模型大致相同。这带来了显着的性能提高,在极端的51×51 kernel下,性能从81.3%提高到81.6%。令人印象深刻的是,本文方法配备了61×61内核,性能超过了之前的RepLKNet,同时节省了55%的FLOPs。

2 Sparse Large Kernel Network – SLaK

到目前为止,已经发现了本文的方法可以成功地将Kernel-size扩展到61,而不需要反向触发性能。它包括2个受稀疏性启发的设计。

在宏观层面上,构建了一个本质稀疏网络,并进一步扩展网络,以提高在保持相似模型规模的同时的网络容量。

在微观层面上,将一个密集的大kernel分解为2个具有动态稀疏性的互补kernel,以提高大kernel的可扩展性。

与传统的训练后剪枝不同,直接从头开始训练的网络,而不涉及任何预训练或微调。在此基础上提出了Sparse Large Kernel Network(SLaK),这是一种纯CNN架构,使用了极端的51×51 kernel

SLaK 是基于 ConvNeXt 的架构构建的。阶段计算比和干细胞的设计继承自ConvNeXt。每个阶段的块数对于 SLaK-T 为 [3, 3, 9, 3],对于 SLaK-S/B 为 [3, 3, 27, 3]。stem cell只是一个具有 kernel-size为4×4和stride=4的卷积层。作者将 ConvNeXt 阶段的Kernel-size分别增加到 [51, 49, 47, 13],并将每个 M×M kernel替换为 M×5 和 5×M kernel的组合,如图 1 所示。作者发现添加在对输出求和之前,直接在每个分解的kernel之后的 BatchNorm 层是至关重要的。

遵循 “use sparse groups, expand more”的指导方针,进一步稀疏整个网络,将阶段宽度扩大 1.3 倍,最终得到 SLaK-T/S/B。尽管知道通过调整模型宽度和稀疏度之间的权衡来提高 SLaK 的性能有很大的空间,但为了简单起见,将所有模型的宽度保持为 1.3 倍。所有模型的稀疏度设置为 40%。

虽然模型配置了极端的 51×51 kernel,但整体参数计数和 FLOP 并没有增加太多,并且由于RepLKNet提供的出色实现,它在实践中非常有效。

3实验

3.1 分类实验

3.2 语义分割

4参考

[1].More ConvNets in the 2020s : Scaling up Kernels Beyond 51 × 51 using Sparsity.

MAE–transformer模型预训练

假设我们想从图像中识别出不同种类的椅子,然后将购买链接推荐给用户。一种可能的方法是先找出100种常见的椅子,为每种椅子拍摄1,000张不同角度的图像,然后在收集到的图像数据集上训练一个分类模型。这个椅子数据集虽然可能比Fashion-MNIST数据集要庞大,但样本数仍然不及ImageNet数据集中样本数的十分之一。这可能会导致适用于ImageNet数据集的复杂模型在这个椅子数据集上过拟合。同时,因为数据量有限,最终训练得到的模型的精度也可能达不到实用的要求。

为了应对上述问题,一个显而易见的解决办法是收集更多的数据。然而,收集和标注数据会花费大量的时间和资金。例如,为了收集ImageNet数据集,研究人员花费了数百万美元的研究经费。虽然目前的数据采集成本已降低了不少,但其成本仍然不可忽略。

另外一种解决办法是应用迁移学习(transfer learning),将从源数据集学到的知识迁移到目标数据集上。例如,虽然ImageNet数据集的图像大多跟椅子无关,但在该数据集上训练的模型可以抽取较通用的图像特征,从而能够帮助识别边缘、纹理、形状和物体组成等。这些类似的特征对于识别椅子也可能同样有效。

本节我们介绍迁移学习中的一种常用技术:微调(fine tuning)。如图9.1所示,微调由以下4步构成。

  1. 在源数据集(如ImageNet数据集)上预训练一个神经网络模型,即源模型。
  2. 创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。我们假设这些模型参数包含了源数据集上学习到的知识,且这些知识同样适用于目标数据集。我们还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型中不予采用。
  3. 为目标模型添加一个输出大小为目标数据集类别个数的输出层,并随机初始化该层的模型参数。
  4. 在目标数据集(如椅子数据集)上训练目标模型。我们将从头训练输出层,而其余层的参数都是基于源模型的参数微调得到的。

当目标数据集远小于源数据集时,微调有助于提升模型的泛化能力。

代码实现微调:

pretrained_net = models.resnet18(pretrained=True)
pretrained_net.load_state_dict(torch.load('/home/kesci/input/resnet185352/resnet18-5c106cde.pth'))

下面打印源模型的成员变量fc。作为一个全连接层,它将ResNet最终的全局平均池化层输出变换成ImageNet数据集上1000类的输出。

print(pretrained_net.fc)

输出:Linear(in_features=512, out_features=1000, bias=True)

可见此时pretrained_net最后的输出个数等于目标数据集的类别数1000。所以我们应该将最后的fc成修改我们需要的输出类别数:

pretrained_net.fc = nn.Linear(512, 2)
print(pretrained_net.fc)

此时,pretrained_netfc层就被随机初始化了,但是其他层依然保存着预训练得到的参数。由于是在很大的ImageNet数据集上预训练的,所以参数已经足够好,因此一般只需使用较小的学习率来微调这些参数,而fc中的随机初始化参数一般需要更大的学习率从头训练。PyTorch可以方便的对模型的不同部分设置不同的学习参数,我们在下面代码中将fc的学习率设为已经预训练过的部分的10倍。

output_params = list(map(id, pretrained_net.fc.parameters()))
feature_params = filter(lambda p: id(p) not in output_params, pretrained_net.parameters())

lr = 0.01
optimizer = optim.SGD([{'params': feature_params},
                       {'params': pretrained_net.fc.parameters(), 'lr': lr * 10}],
                       lr=lr, weight_decay=0.001)

记录: 在MAE的微调训练中,提供了两种 微调

  1. Linear probing: 锁死transformer的参数,只训练CIFAR10的那个Linear层。
  2. Fine-tuning: 接着训练transformer的参数,同时也训练CIFAR10的那个Linear。

论文做了MAE各个部分的不同设置对比实验,这些实验能够揭示MAE更多的特性。首先是masking ratio,从下图可以看到,最优的设置是75%的masking ratio,此时linear probing和finetune效果最好,这比之前的研究要高很多,比如BEiT的masking ratio是40%。另外也可以看到linear probing和finetune的表现不一样,linear probing效果随着masking ratio的增加逐渐提高直至一个峰值后出现下降,而finetune效果在不同making ratio下差异小,masking ratio在40%~80%范围内均能表现较好。 ​

preview

ESPCN 图像超分辨率方法

论文地址: https://arxiv.org/abs/1609.05158

代码:https://github.com/leftthomas/ESPCN

Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network

ESPCN 是在2016年在CVPR上发表的一片论文,中提出的一种实时的基于卷积神经网络的图像超分辨率方法。

这篇论文主要就是提出了一种新的亚像素卷积层(sub-pixel convolutional layer),以往的方法,为了生成高分辨率的输出,一般是先对输入进行上采样扩大图像分辨率,得到与高分辨率图像同样的大小,再作为网络输入,意味着卷积操作在较高的分辨率上进行,相比于在低分辨率的图像上计算卷积,会降低效率。 ESPCN(Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network,CVPR 2016)提出一种在低分辨率图像上直接计算卷积得到高分辨率图像的高效率方法。

如果想最后的分辨率从 n 到 rn,ESPCN会生成r*r个通道,再进行sub-pixel convolutional,生成高分辨率的图片。假设是9通道 混合,这里的通道混合是将每个通道对应位置的元素重新排列成3*3的图像。这个变换虽然被称作sub-pixel convolution, 但实际上并没有卷积操作。

通过使用sub-pixel convolution, 图像从低分辨率到高分辨率放大的过程,插值函数被隐含地包含在前面的卷积层中,可以自动学习到。只在最后一层对图像大小做变换,前面的卷积运算由于在低分辨率图像上进行,因此效率会较高。

ESPCN激活函数采用tanh替代了ReLU。损失函数为均方误差。

pytorch中已经集成了 sub-pixel convolution :

nn.PixelShuffle(upscale_factor)

以四维输入(N,C,H,W)为例,Pixelshuffle会将为(∗,r 2 C r^2Cr2C,H,W)的Tensor给reshape成(∗,C,rH,rW)的Tensor

Upsample:

对给定多通道的1维(temporal)、2维(spatial)、3维(volumetric)数据进行上采样。

对volumetric输入(3维——点云数据),输入数据Tensor格式为5维:minibatch x channels x depth x height x width
对spatial输入(2维——jpg、png等数据),输入数据Tensor格式为4维:minibatch x channels x height x width
对temporal输入(1维——向量数据),输入数据Tensor格式为3维:minibatch x channels x width

此算法支持最近邻,线性插值,双线性插值,三次线性插值对3维、4维、5维的输入Tensor分别进行上采样(Upsample)。

RepVGG: Making VGG-style ConvNets Great Again

论文下载地址:https://arxiv.org/abs/2101.03697
官方源码(Pytorch实现):https://github.com/DingXiaoH/RepVGG

这篇论文对于我来说最大的用处是提出了结构的重重参数化:

在推理时将三个并行分支合并成单个分支,并保证输出输出不变。

结构重参数化主要分为两步,第一步主要是将Conv2d算子和BN算子融合以及将只有BN的分支转换成一个Conv2d算子,第二步将每个分支上的3x3卷积层融合成一个卷积层。

1、Conv2d和BN 这个已经是非常常见的,因为卷积核bn都是线性运算,所以可以进行合并。

这里假设输入的特征图(Input feature map)如下图所示,输入通道数为2,然后采用两个卷积核(图中只画了第一个卷积核对应参数)。

在这里插入图片描述

接着计算一下输出特征图(Output feature map)通道1上的第一个元素,即当卷积核1在输入特征图红色框区域卷积时得到的值(为了保证输入输出特征图高宽不变,所以对Input feature map进行了Padding)。其他位置的计算过程类似这里就不去演示了。

在这里插入图片描述

然后再将卷积层输出的特征图作为BN层的输入,这里同样计算一下输出特征图(Output feature map)通道1上的第一个元素,按照上述BN在推理时的计算公式即可得到如下图所示的计算结果。

在这里插入图片描述

代码

Conv2d+BN融合实验(Pytorch)
下面是参考作者提供的源码改的一个小实验,首先创建了一个module包含了卷积和BN模块,然后按照上述转换公式将卷积层的权重和BN的权重进行融合转换,接着载入到新建的卷积模块fused_conv中,最后随机创建一个Tensor(f1)将它分别输入到module以及fused_conv中,通过对比两者的输出可以发现它们的结果是一致的。

from collections import OrderedDict

import numpy as np
import torch
import torch.nn as nn


def main():
    torch.random.manual_seed(0)

    f1 = torch.randn(1, 2, 3, 3)

    module = nn.Sequential(OrderedDict(
        conv=nn.Conv2d(in_channels=2, out_channels=2, kernel_size=3, stride=1, padding=1, bias=False),
        bn=nn.BatchNorm2d(num_features=2)
    ))

    module.eval()

    with torch.no_grad():
        output1 = module(f1)
        print(output1)

    # fuse conv + bn
    kernel = module.conv.weight 
    running_mean = module.bn.running_mean
    running_var = module.bn.running_var
    gamma = module.bn.weight
    beta = module.bn.bias
    eps = module.bn.eps
    std = (running_var + eps).sqrt()
    t = (gamma / std).reshape(-1, 1, 1, 1)  # [ch] -> [ch, 1, 1, 1]
    kernel = kernel * t
    bias = beta - running_mean * gamma / std
    fused_conv = nn.Conv2d(in_channels=2, out_channels=2, kernel_size=3, stride=1, padding=1, bias=True)
    fused_conv.load_state_dict(OrderedDict(weight=kernel, bias=bias))

    with torch.no_grad():
        output2 = fused_conv(f1)
        print(output2)

    np.testing.assert_allclose(output1.numpy(), output2.numpy(), rtol=1e-03, atol=1e-05)
    print("convert module has been tested, and the result looks good!")


if __name__ == '__main__':
    main()

repVGG中大量运用conv+BN层,我们知道将层合并,减少层数能提升网络性能,下面的推理是conv带有bias的过程:

这其实就是一个卷积层,只不过权重考虑了BN的参数 我们令:

最终的融合结果即为:

相关融合代码如下图所示:

def _fuse_bn_tensor(self, branch):
        if branch is None:
            return 0, 0
        if isinstance(branch, nn.Sequential):
            kernel = branch.conv.weight
            running_mean = branch.bn.running_mean
            running_var = branch.bn.running_var
            gamma = branch.bn.weight
            beta = branch.bn.bias
            eps = branch.bn.eps
        else:
            ...
        std = (running_var + eps).sqrt()
        t = (gamma / std).reshape(-1, 1, 1, 1)
        return kernel * t, beta - running_mean * gamma / std

2、如何将不同分支合并:

作者这里首先将不同分支的卷积核都变成3*3:

2.1 将1×1卷积转换成3×3卷积
这个过程比较简单,如下图所示,以1×1卷积层中某一个卷积核为例,只需在原来权重周围补一圈零就行了,这样就变成了3×3的卷积层,注意为了保证输入输出特征图高宽不变,此时需要将padding设置成1(原来卷积核大小为1×1时padding为0)。最后按照上述2.1中讲的内容将卷积层和BN层进行融合即可。

在这里插入图片描述

2.2将BN转换成3×3卷积
对于只有BN的分支由于没有卷积层,所以我们可以先自己构建出一个卷积层来。如下图所示,构建了一个3×3的卷积层,该卷积层只做了恒等映射,即输入输出特征图不变。既然有了卷积层,那么又可以按照上述2.1中讲的内容将卷积层和BN层进行融合。

在这里插入图片描述

2.3 多分支融合
在上面的章节中,我们已经讲了怎么把每个分支融合转换成一个3×3的卷积层,接下来需要进一步将多分支转换成一个单路3×3卷积层。

在这里插入图片描述

合并的过程其实也很简单,直接将这三个卷积层的参数相加即可,具体推理过程就不讲了,如果不了解的可以自己动手算算。

总的来说,这篇论文的目标是Simple is Fast, Memory-economical, Flexible,提出了很多想法去实现上述目标,对于当前我的工作还是比较有启发的,尤其是最后对网络进行合并以及量化部分。下一步要好好学习下torch的量化QAT (torch.quantization.prepare_qat)

Flowformer: Linearizing Transformers with Conservation Flows (任务通用的主干网络-线性复杂度的transformers)

【导读】近年来,Transformer方兴未艾,但是其内在的二次复杂度阻碍了它在长序列和大模型上的进一步发展。清华大学软件学院机器学习实验室从网络流理论出发,提出任务通用的线性复杂度主干网络Flowformer,在长序列、视觉、自然语言、时间序列、强化学习五大任务上取得优秀效果。

任务通用是基础模型研究的核心目标之一,同时也是深度学习研究通向高级智能的必经之路。
近年来,得益于注意力机制的通用关键建模能力,Transformer在众多领域中表现优异,逐渐呈现出通用架构的趋势。但是随着序列长度的增长,标准注意力机制的计算呈现二次复杂度,严重阻碍了其在长序列建模与大模型中的应用。

为此,来自清华大学软件学院的团队深入探索了这一关键问题,提出了任务通用的线性复杂度主干网络Flowformer,在保持标准Transformer的通用性的同时,将其复杂度降至线性,论文被ICML 2022接受。

作者列表:吴海旭,吴佳龙,徐介晖,王建民,龙明盛

链接:https://arxiv.org/abs/2202.06258

代码:https://github.com/thuml/Flowformer相比于标准Transformer,本文提出的Flowformer模型,具有以下特点:

  • 线性复杂度,可以处理数千长度的输入序列;
  • 没有引入新的归纳偏好,保持了原有注意力机制的通用建模能力;
  • 任务通用,在长序列、视觉、自然语言、时间序列、强化学习五大任务上取得优秀效果。

本文深入研究了注意力机制存在的二次复杂度问题,通过将网络流中的守恒原理引入设计,自然地将竞争机制引入到注意力计算中,有效避免了平凡注意力问题。

我们提出的任务通用的骨干网络Flowformer,实现了线性复杂度,同时在长序列、视觉、自然语言、时间序列、强化学习五大任务上取得优秀效果。

在长序列建模应用上,如蛋白质结构预测、长文本理解等,Flowformer具有良好的应用潜力。此外,Flowformer中“无特殊归纳偏好”的设计理念也对通用基础架构的研究具有良好的启发意义。

Vision MLP系列–MLP-Mixer: An all-MLP Architecture for Vision

MLP-Mixer是ViT团队的另一个纯MLP架构的尝试。如果MLP-Mixer重新引领CV领域主流架构的话,那么CV领域主流架构的演变过程就是MLP->CNN->Transformer->MLP? 要回到最初的起点了吗???( Transformer移除了注意力以后就剩MLP了)

这篇论文提出了一种”纯“MLP结构的视觉架构。

先将输入图片拆分成patches,然后通过Per-patch Fully-connected将每个patch转换成feature embedding,然后送入N个Mixer Layer,最后通过Fully-connected进行分类。

Mixer分为channel-mixing MLP和token-mixing MLP两类。channel-mixing MLP允许不同通道之间进行交流;token-mixing MLP允许不同空间位置(tokens)进行交流。这两种类型的layer是交替堆叠的,方便支持两个输入维度的交流。每个MLP由两层fully-connected和一个GELU构成。

从上图我们可以看出,MLP -Mixer 首先使用图片分成很多个小正方形的patch,每个patch的大小定义为patch_size。论文中实现这一步骤使用的是前面提到的卷积,卷积核的大小和步长均patch_size。论文中给的参数,也是2的幂。
网络不再使用传统的RELU激活函数,而是使用了GELU激活函数。

将图片分成小块后,在将它转换为一维结构。如图:

在这里插入图片描述

然后将每一个patch进行转换,如下图所示:

在这里插入图片描述

通过这样一种方式呢,就将一张图片转换为了一个大矩阵,就可以输入到Mixer Layer 中进行计算。

MLP 是两个全连接层的感知机,W1,W2,对应token_mixer中两个全连接的权重,W3,W4则表示channel_mixer两个全连接的权重。σ表示GELU激活函数。那么公示就很简单了,输入X经过Layer Normalize,再乘以W1,再经过激活函数后乘以W2,再加上X。第二个公式也是相同的计算过程。
将前面通过编码得到的矩阵经过Layer Norm 在将矩阵进行旋转(T 表示旋转)连接MLP1,MLP1 就是文章token_mixer 用来寻找像素与像素之间的关系,其中,MLP1中的权值共享。计算完之后,再将矩阵旋转回来,通过Layer Norm 后再接一个channel_mixer 用于寻找通道与通道之间的关系。其中MixerLayer 还启用了ResNet中的跨连结构,跨连结构的作用可以参考[ResNet原理讲解和复现],看到这里,是不是感觉它跟卷积的原理很类似。
从上图可以看出Mixer Layer的输入维度和输出维度相同,并且通过MLP的方式来寻找图片像素与像素,通道与通道的关系。
这就是MLP-MIXER的网络结构了

实现的难点在于,矩阵旋转,我们使用einops中的Rearrange实现矩阵旋转

使用Rearrange 实现旋转

Rearrange(‘b n d -> b d n’) #这里是[batch_size, num_patch, dim] -> [batch_size, dim, num_patch]

#定义多层感知机
import torch
import numpy as np
from torch import nn
from einops.layers.torch import Rearrange
from torchsummary import summary
import torch.nn.functional as F

class FeedForward(nn.Module):
    def __init__(self,dim,hidden_dim,dropout=0.):
        super().__init__()
        self.net=nn.Sequential(
            #由此可以看出 FeedForward 的输入和输出维度是一致的
            nn.Linear(dim,hidden_dim),
            #激活函数
            nn.GELU(),
            #防止过拟合
            nn.Dropout(dropout),
            #重复上述过程
            nn.Linear(hidden_dim,dim),

            nn.Dropout(dropout)
        )
    def forward(self,x):
        x=self.net(x)
        return x


class MixerBlock(nn.Module):
    def __init__(self,dim,num_patch,token_dim,channel_dim,dropout=0.):
        super().__init__()
        self.token_mixer=nn.Sequential(
            nn.LayerNorm(dim),
            Rearrange('b n d -> b d n'),   #这里是[batch_size, num_patch, dim] -> [batch_size, dim, num_patch]
            FeedForward(num_patch,token_dim,dropout),
            Rearrange('b d n -> b n d')    #[batch_size, dim, num_patch] -> [batch_size, num_patch, dim]

         )
        self.channel_mixer=nn.Sequential(
            nn.LayerNorm(dim),
            FeedForward(dim,channel_dim,dropout)
        )
    def forward(self,x):

        x=x+self.token_mixer(x)

        x=x+self.channel_mixer(x)

        return x

class MLPMixer(nn.Module):
    def __init__(self,in_channels,dim,num_classes,patch_size,image_size,depth,token_dim,channel_dim,dropout=0.):
        super().__init__()
        assert image_size%patch_size==0
        self.num_patches=(image_size//patch_size)**2
        #embedding 操作,用卷积来分成一小块一小块的
        self.to_embedding=nn.Sequential(nn.Conv2d(in_channels=in_channels,out_channels=dim,kernel_size=patch_size,stride=patch_size),
            Rearrange('b c h w -> b (h w) c')
        )
        #经过Mixer Layer 的次数
        self.mixer_blocks=nn.ModuleList([])
        for _ in range(depth):
            self.mixer_blocks.append(MixerBlock(dim,self.num_patches,token_dim,channel_dim,dropout))
        self.layer_normal=nn.LayerNorm(dim)

        self.mlp_head=nn.Sequential(
            nn.Linear(dim,num_classes)
        )
    def forward(self,x):
        x=self.to_embedding(x)
        for mixer_block in self.mixer_blocks:
            x=mixer_block(x)
        x=self.layer_normal(x)
        x=x.mean(dim=1)

        x=self.mlp_head(x)

        return x

MLP-Mixer用Mixer的MLP来替代ViT的Transformer,减少了特征提取的自由度,并且巧妙的可以交替进行patch间信息交流和patch内信息交流,从结果上来看,纯MLP貌似也是可行的,而且省去了Transformer复杂的结构,变的更加简洁,有点期待后续ViT和MLP-Mixer如何针锋相对的,感觉大组就是东挖一个西挖一个的,又把尘封多年的MLP给挖出来了

Patches Are All You Need?

———– ConvMixer 网络

论文地址:https://openreview.net/pdf?id=TVHS5Y4dNvM
Github 地址:https://github.com/tmp-iclr/convmixer

ConvMixer is now integrated into the timm framework itself. You can see the PR here.

Conv Mixer 这篇文章提出的初衷是想去弄清楚,ViT系列模型表现优越,到底是图片分块的功劳 还是网络中Attention的功劳。于是作者就根据深度可分离卷积,在ViT 和 MLP Mixer 的启发中 设计了Conv Mixer。并且在表现上超越了一些ViT (某些ViT结构),MLP Mixer 和 ResNet。文章本身并没去追求模型的速度,和表现能力。

在这里插入图片描述
网络结构

网络结构详解:

1、 Patch embedding

这里的Patch embedding实际上是使用一个卷积层 实现的

nn.Conv2d(3,dim,kernel_size=patch_size,stride=patch_size)

其中 kernel_size 就是patch的大小

2、GELU激活函数(高斯误差线性单元)

这个是最近很多模型都在用的函数(dert、高斯误差线性单元激活函数在最近的 Transformer 模型)GELUs正是在激活中引入了随机正则的思想,是一种对神经元输入的概率描述,直观上更符合自然的认识,同时实验效果要比Relus与ELUs都要好。

GELUs其实是 dropout、zoneout、Relus的综合,GELUs对于输入乘以一个0,1组成的mask,而该mask的生成则是依概率随机的依赖于输入。假设输入为X, mask为m,则m服从一个伯努利分布(Φ ( x ) \Phi(x)Φ(x), Φ ( x ) = P ( X < = x ) , X 服 从 标 准 正 太 分 布 \Phi(x)=P(X<=x), X服从标准正太分布Φ(x)=P(X<=x),X服从标准正太分布),这么选择是因为神经元的输入趋向于正太分布,这么设定使得当输入x减小的时候,输入会有一个更高的概率被dropout掉,这样的激活变换就会随机依赖于输入了。

看得出来,这就是某些函数(比如双曲正切函数 tanh)与近似数值的组合。没什么过多可说的。有意思的是这个函数的图形:

GELU 激活函数。

可以看出,当 x 大于 0 时,输出为 x;但 x=0 到 x=1 的区间除外,这时曲线更偏向于 y 轴。

优点:

  • 似乎是 NLP 领域的当前最佳;尤其在 Transformer 模型中表现最好;
  • 能避免梯度消失问题。

3、ConvMixerLayer

class ConvMixerLayer(nn.Module):
    def __init__(self,dim,kernel_size = 9):
        super().__init__()
        #残差结构
        self.Resnet =  nn.Sequential(
            nn.Conv2d(dim,dim,kernel_size=kernel_size,groups=dim,padding='same'),
            nn.GELU(),
            nn.BatchNorm2d(dim)
        )
        #逐点卷积
        self.Conv_1x1 = nn.Sequential(
            nn.Conv2d(dim,dim,kernel_size=1),
            nn.GELU(),
            nn.BatchNorm2d(dim)
        )
    def forward(self,x):
        x = x +self.Resnet(x)
        x = self.Conv_1x1(x)
        return 

在ConvMixer Layer 中, 使用了深度可分离卷积,GELU 激活函数,逐点卷积。
论文中将图中红色部 称为 “channel wise mixing” 蓝色部分称为 “spatial mixing”
论文得到的结论是当深度可分离卷积部分的卷积核越大,模型的性能越好。文章中的使用的是9×9的卷积核,因为卷积核越大表现越好

文章最后也认为,ViT 表现如此优越 是因为patch embedding (图片分块)的原因。
作者认为 patch embedding 操作就能完成神经网络的所有下采样过程,降低了图片的分辨率,增加了感受野,更容易找到远处的空间信息。从而模型表现良好

论文阅读——合集(持续更新)

文献链接

图像分类(Classification)

目标检测(Object Detection)

语义分割(Semantic Segmentation)

实例分割(Instance Segmentation)

关键点检测(Keypoint Detection)

自然语言处理

Others

CVPR 2022 | 重新审视池化:你的感受野不是最理想的

作者丨简单来源丨CV技术指南

本文提出了一种简单而有效的动态优化池操作( Dynamically Optimized Pooling operation),称为DynOPool,它通过学习每一层感受野的最佳大小和形状来优化特征映射的端到端比例因子。

前言

本文提出了一种简单而有效的动态优化池操作( Dynamically Optimized Pooling operation),称为DynOPool,它通过学习每一层感受野的最佳大小和形状来优化特征映射的端到端比例因子。深度神经网络中任何类型的调整大小模块都可以用DynOPool操作以最小的成本替换。此外,DynOPool通过引入一个限制计算成本的附加损失项来控制模型的复杂性。

Pooling Revisited: Your Receptive Field is Suboptima

论文:https://arxiv.org/abs/2205.15254

公众号后台回复“DynOPool”获取论文PDF

背景

尽管深度神经网络在计算机视觉、自然语言处理、机器人、生物信息学等各种应用中取得了前所未有的成功,但最优网络结构的设计仍然是一个具有挑战性的问题。而感受野的大小和形状决定了网络如何聚集本地信息,并对模型的整体性能产生显著影响。神经网络中的许多组成部分,例如用于卷积和池化运算的内核大小和步长,都会影响感受野的配置。然而,它们仍然依赖于超参数,现有模型的感受野会导致形状和大小不理想

本文通过介绍固定大小和形状的传统感受野是次优的问题,讨论了DynOPool如何通过CIFAR-100上的VGG-16玩具实验解决这个问题。

固定大小和形状的传统感受野存在的问题:

1.不对称分布的信息

最佳感受野形状会根据数据集中固有的空间信息不对称性而改变。而大多数情况下固有的不对称性是不可测量的。此外,通常用于预处理的输入大小调整有时也会导致信息不对称。在人工设计的网络中,图像的长宽比经常被调整以满足模型的输入规格。然而,这种网络中的感受野不是用来处理操作的。

为了验证所提出的方法,作者在CIFAR-stretch-V上进行实验,如图1(a)所示,相较于人工设计模型,形状通过DynOPool动态优化的特征映射通过在水平方向上提取更具有价值的信息提高性能。

图片

图1  用来自CIFAR-100的三个不同的合成数据集进行玩具实验:(a)随机裁剪垂直拉伸的图像 (b)在4×4网格中平铺缩小的图像 (c)放大缩小的图像。

2.密集分布或稀疏分布信息

局部性是设计最优模型的组成部分。CNN通过级联的方式聚合局部信息来学习图像的复杂表示。而局部信息的重要性很大程度上取决于每个图像的属性。例如,当一个图像被模糊化时,大多数有意义的微观模式,如物体的纹理,都会被抹去。在这种情况下,最好在早期层中扩展感受野,集中于全局信息。另一方面,如果一幅图像在局部细节中包含大量类特定的信息,例如纹理,则识别局部信息将会更加重要。

为了验证假设,作者构建了CIFAR-100数据集的两个变体,CIFAR-tile和CIFAR-large,如图1(b)和(c)所示。作者模型在很大程度上优于人工设计的模型。

贡献

为了缓解人工构建的体系结构和操作的次优性,作者提出了动态优化池操作(DynOPool),这是一个可学习的调整大小模块,可以替代标准的调整大小操作。该模块为在数据集上学习的操作找到感受野的最佳比例因子,从而将网络中的中间特征图调整为适当的大小和形状。

论文的主要贡献:

1、解决了深度神经网络中现有尺度算子依赖于预定超参数的局限性。指出了在中间特征图中寻找最佳空间分辨率和感受野的重要性。

2、提出了一个可学习的调整尺寸大小的模块DynOPool,它可以找到中间特征图的最佳比例因子和感受域。DynOPool使用学习到的比例因子识别某一层的最佳分辨率和感受野,并将信息传播到后续层,从而在整个网络中实现规模优化。

3、证明了在图像分类和语义分割任务中,使用DynOPool的模型在多个数据集和网络架构上优于基线算法。它还显示了精度和计算成本之间的理想权衡。

方法

1.动态优化池(DynOPool)

图片

图2 DynOPool中的调整大小模块

模块通过优化一对输入和输出特征映射之间的比例因子r来优化查询点q的位置以及获得中间特征映射的最佳分辨率。DynOPool在不影响其他算子的情况下,自适应控制较深层接收域的大小和形状。

图片

图3 DynOPool整个的优化过程

针对比例因子r梯度不稳定,会产生梯度爆炸导致训练过程中分辨率发生显著变化的问题,使用a重新参数化r如下:

图片

2.模型复杂性约束

为了最大化模型的精度,DynOPool有时会有较大的比例因子,增加了中间特征图的分辨率。因此,为了约束计算代价,减少模型规模,引入了一个额外的损失项LGMACs,它由每次训练迭代t的分层GMACs计数的简单加权和给出,如下所示:

图片

实验

图片

表1 人工设计模型与使用DynOPool模型的精度(%)和GMACs比较

图片

图4 在VGG-16上使用人工设计的Shape Adaptor与使用DynOPool的训练模型可视化

图片

表2 在CIFAR-100数据集上DynOPool和Shape Adaptor的比较

图片

表3 在ImageNet数据集上EfficientNet-B0+DynOPool的性能

图片

表4 基于PascalVOC的HRNet-W48语义分割结果

结论

作者提出了一种简单而有效的动态优化池操作(DynOPool),它通过学习每个层中感受野的理想大小和形状来优化端到端的特征映射的比例因子,调整中间特征图的大小和形状,有效提取局部细节信息,从而优化模型的整体性能;

DynOPool还通过引入一个额外的损失项来限制计算成本,从而控制模型的复杂性。实验表明,在多个数据集上,该模型在图像分类和语义分割方面均优于基线网络。

IEEE论文投稿流程

摘自 BeyondSelf

这是一篇节省你翻论坛、查资料的文章。

我将把每一步尽量写的清楚,能够让新手按照这篇文章进行完整的投稿

即使你从未投过稿,完全不了解IEEE的投稿流程,希望按照这篇文章,能够帮助你流畅无误的投稿

这是针对本科生和低年级研究生,对投稿流程还不清楚,或者在初次投稿时可以参考的文章。

(零)预备:账号注册

这一步是为了注册ORCID和IEEE的账号,已经有账号的可以忽略不看

对于IEEE的不同杂志,是由不同的投稿账号的,这一点需要注意

1.注册ORCID,ORCID类似于你的身份证号码,即使你的名字改变,但是号码是不变的。

(1)进入orcid官网ORCID​orcid.org/

(2)选择右上方的SIGN IN/REGISTER

(3)依次填入所需的信息

2.注册IEEE账号

(1)进入IEEE查文献的主页https://ieeexplore.ieee.org/Xplore/home.jsp​ieeexplore.ieee.org/Xplore/home.jsp

(2)搜索期刊名字,会展示如下画面,这里以IEEE Transactions on communications 举例:

点击你想要投稿的期刊

(3)找到Submit Manusript,这个选项几乎所有的期刊都有。

(4)点击创建账户

(5)点击Associate your existing ORCID id

(6)在弹出页面中点击授权

(7)依次填入姓名和邮箱,只填必须项即可

之后就是输入地址和密码,按实际情况填写即可

(一)论文写作

1.下载模板

IEEE的期刊都会给一个模板,但是不同期刊会共用同一套模板。下载模板的网址为:https://journals.ieeeauthorcenter.ieee.org/create-your-ieee-journal-article/authoring-tools-and-templates/tools-for-ieee-authors/ieee-article-templates/​journals.ieeeauthorcenter.ieee.org/create-your-ieee-journal-article/authoring-tools-and-templates/tools-for-ieee-authors/ieee-article-templates/

IEEE Article Templates – IEEE Author Center JournalsIEEE Article Templates – IEEE Author Center Journals​journals.ieeeauthorcenter.ieee.org/create-your-ieee-journal-article/authoring-tools-and-templates/tools-for-ieee-authors/ieee-article-templates/

(0)点击 IEEE Template Selector 进入模板下载页面

(1)首先会让选择要写的文章类型,给了三个选项:

如果写常规的文章的话,就选第一个,包含trans和letter。会议的话,就选第三个。Magazine一般是指总结或者科普类的文章,影响因子高,但是一般你的第一篇文章,导师不会让你写这个。

(2)其次选择期刊的名字

这里直接输入即可,如“IEEE Transactions on Communications”.

(3)之后会依次选择文章的类型和格式,这里可供选择的格式有Latex和Word,首选Latex,一方面是因为格式好调,另一方面是因为敲公式快一些

(4)下载好模板之后,就可以开始写作了

2.论文写作准备

有了模板之后,还需要知道该期刊对于文章的要求,比如说页数,单栏还是双栏。这里不同期刊的要求不一样,对于新手来说,由于网页的设置,找到它并不容易。我这里来详细介绍下。

(1)进入IEEE查文献的主页https://ieeexplore.ieee.org/Xplore/home.jsp​ieeexplore.ieee.org/Xplore/home.jsp

(2)搜索期刊名字,会展示如下画面,这里以IEEE Transactions on communications 举例:

点击你想要投稿的期刊

(3)找到Submit Manusript,这个选项几乎所有的期刊都有。

(4)点进去之后,发现左下角有一个Journal Home,点击

(5)在上述网页中,找到你想要投稿的期刊(当然,你也可以跳过上述步骤,直接进入到这里~之所以要经过这些步骤,是因为接下来会用到其中一些步骤)

在出现的页面中,会有submission guidelines:

这里就说明,此期刊提交的时候要求单栏,12号字,不超过30页

这里教大家一个小技巧,如果整个网页页面太长的话,直接按Ctrl+F 搜索column关键词,因为所有的期刊都会要求单栏还是双栏,所以主要要求就在这个附近

(6)在第(4)步中,如果页面的右边已经有guidelines,那么就不需要再进入journal home了

对TPAMI杂志来说,直接点击红线所示,就可以得到文章要求,双栏,不超过12页

对于IEEE trans系列的杂志,基本上模板都是差不多的,如果在官网上找不到,也可以直接百度搜索一个模板

(二)论文投稿

在得到模板以及文章的要求之后,就可以开始论文写作了。当然,如果在上述步骤中找不到期刊的具体要求也没事,可以先写,一些格式问题可以之后再说,在文章正式发表前,编辑还会进行修改。这里假设你的论文已经写完,开始进入投稿环节

1.进入想要投稿期刊的主页,点击submit mannusript

2. 这里会要求你登入账号,一般是用导师的账号或者自己注册。投稿的账号一般就是通信作者

3.登录成功后,点击Author

4.点击Start New Submission中的Begin Submission,会出现如下信息

依次填入文章的类型、上传文件、作者等相关信息,就可以完成整篇文章的上传。这里以IEEE transactions on communications 举例,展示提交的流程

Step 1:

(1)首先选择类型,一般的话,就选常规的

(2)然后输入题目和摘要

Step 2:上传文件,一般是上传PDF

Step 3:输入关键字,下拉列表找到最贴合论文的,点击添加

Step 4:输入作者信息,与论文保持一致

Step 5 :选择审稿人,如果没有想选的话,可以跳过

Step 6 :是一些与文章相关的细节,可以按文章情况填写。

Step 7 :检查环节,确认无误后就可以正式提交了

注:邮箱是最重要的联系方式,之后会通过邮件联系你,请及时查看,而且对于修改等是有截止日期的。

(三)等待

论文会首先分配编辑,然后分配审稿人。可能会给你返回的状态有:Reject,Reject(Resubmission Allowed),Major,Minor,Accept

1.Reject并且不允许重投的话,说明就凉了

2.Reject且允许重投的话,也还凑合,重投的话可能被接受

3.Major大修,一般Major的话,如果修改没问题的话,大概率被接收

4.Minor小修,这样子的话基本也没问题

5.Accept是最好的,但不会刚投稿就这样,起码得有个minor,都得为难你一下

(四)接收后

当文章被接收后,编辑会给你发一个邮件,让你填一些内容,大概是著作权之类的。这些就按照要求来即可

之后会有一个校对环节,你需要在编辑给你发送的PDF上修改,而不能再重新提交PDF。此时的文章状态一般是Early Access,如果没有挂arxiv的话,在这个阶段,你就可以搜到自己的文章,并且也可以被引用了。再过一段时间,状态就会更新为Publish,就说明正式发表了。