Split to Be Slim: 论文复现

摘要:在本论文中揭示了这样一种现象:一层内的许多特征图共享相似但不相同的模式。

本文分享自华为云社区《Split to Be Slim: 论文复现》,作者: 李长安 。

Split to Be Slim: An Overlooked Redundancy in Vanilla Convolution 论文复现

1、问题切入

已经提出了许多有效的解决方案来减少推理加速模型的冗余。然而,常见的方法主要集中在消除不太重要的过滤器或构建有效的操作,同时忽略特征图中的模式冗余。

在本论文中揭示了这样一种现象:一层内的许多特征图共享相似但不相同的模式。但是,很难确定具有相似模式的特征是否是冗余的或包含基本细节。因此,论文作者不是直接去除不确定的冗余特征,而是提出了一种基于分割的卷积操作,即 SPConv,以容忍具有相似模式但需要较少计算的特征。

具体来说,论文将输入特征图分为Representative部分和不Uncertain冗余部分,其中通过相对繁重的计算从代表性部分中提取内在信息,而对不确定冗余部分中的微小隐藏细节进行一些轻量级处理手术。为了重新校准和融合这两组处理过的特征,我们提出了一个无参数特征融合模块。此外,我们的 SPConv 被制定为以即插即用的方式替换 vanilla 卷积。在没有任何花里胡哨的情况下,基准测试结果表明,配备 SPConv 的网络在 GPU 上的准确性和推理时间上始终优于最先进的基线,FLOPs 和参数急剧下降。

2、特征冗余问题

Split to Be Slim: 论文复现

然而,如上图所示,同一层的特征中存在相似模式,也就是说存在特征冗余问题。但同时,并未存在完全相同的两个通道特征,进而导致无法直接剔除冗余通道特征。 因此,可以选择一些有代表性的特征图来补充内在信息,而剩余的冗余只需要补充微小的不同细节。

3、SPConv详解

Split to Be Slim: 论文复现

在现有的滤波器中,比如常规卷积、GhostConv、OctConv、HetConv均在所有输入通道上执行k*k卷积。然而,如上图所示,同一层的特征中存在相似模式,也就是说存在特征冗余问题。但同时,并未存在完全相同的两个通道特征,进而导致无法直接剔除冗余通道特征。

受此现象启发,作者提出将所有输入特征按比例拆分为两部分:

  1. Representative部分执行k*k卷积提取重要信息;
  2. Uncertain部分执行1*1卷积补充隐含细节信息。

因此该过程可以描述为(见SPConv的左侧部分),公式如下图所示:

Split to Be Slim: 论文复现

3.1 Further Reduction for Reprentative

在将所有输入通道分成两个主要部分后,代表部分之间可能存在冗余。换句话说,代表通道可以分为几个部分,每个部分代表一个主要类别的特征,例如颜色和纹理。因此,我们在代表性通道上采用组卷积以进一步减少冗余,如图 2 的中间部分所示。我们可以将组卷积视为具有稀疏块对角卷积核的普通卷积,其中每个块对应于通道,并且分区之间没有连接。这意味着,在组卷积之后,我们进一步减少了代表性部分之间的冗余,同时我们还切断了可能不可避免地有用的跨通道连接。我们通过在所有代表性通道上添加逐点卷积来弥补这种信息丢失。与常用的组卷积后点卷积不同,我们在相同的代表性通道上进行 GWC 和 PWC。然后我们通过直接求和来融合这两个结果特征,因为它们具有相同的通道来源,从而获得了额外的分数(这里我们将组大小设置为 2)。所以方程2的代表部分可以表述为方程3:

Split to Be Slim: 论文复现

3.2 Parameter Free Feature Fusion Module

到目前为止,我们已经将 vanilla 3×3 卷积拆分为两个操作:对于代表部分,我们进行 3×3 组卷积和 1×1 逐点卷积的直接求和融合,以抵消分组信息丢失;对于冗余部分,我们应用 1 × 1 内核来补充一些微小的有用细节。结果,我们得到了两类特征。因为这两个特征来自不同的输入通道,所以需要一种融合方法来控制信息流。与等式 2 的直接求和融合不同,我们为我们的 SP-Conv 设计了一个新颖的特征融合模块,无需导入额外的参数,有助于实现更好的性能。如图 2 右侧所示,

Split to Be Slim: 论文复现

3.3 代码复现

import paddle
import paddle.nn as nn
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
 """3x3 convolution with padding"""
 return nn.Conv2D(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=dilation, groups=groups, dilation=dilation)
class SPConv_3x3(nn.Layer):
 def __init__(self, inplanes=32, outplanes=32, stride=1, ratio=0.5):
 super(SPConv_3x3, self).__init__()
 self.inplanes_3x3 = int(inplanes*ratio)
 self.inplanes_1x1 = inplanes - self.inplanes_3x3
 self.outplanes_3x3 = int(outplanes*ratio)
 self.outplanes_1x1 = outplanes - self.outplanes_3x3
 self.outplanes = outplanes
 self.stride = stride
 self.gwc = nn.Conv2D(self.inplanes_3x3, self.outplanes, kernel_size=3, stride=self.stride,
                             padding=1, groups=2)
 self.pwc = nn.Conv2D(self.inplanes_3x3, self.outplanes, kernel_size=1)
 self.conv1x1 = nn.Conv2D(self.inplanes_1x1, self.outplanes,kernel_size=1)
 self.avgpool_s2_1 = nn.AvgPool2D(kernel_size=2,stride=2)
 self.avgpool_s2_3 = nn.AvgPool2D(kernel_size=2, stride=2)
 self.avgpool_add_1 = nn.AdaptiveAvgPool2D(1)
 self.avgpool_add_3 = nn.AdaptiveAvgPool2D(1)
        self.bn1 = nn.BatchNorm2D(self.outplanes)
        self.bn2 = nn.BatchNorm2D(self.outplanes)
 self.ratio = ratio
 self.groups = int(1/self.ratio)
 def forward(self, x):
 # print(x.shape)
        b, c, _, _ = x.shape
        x_3x3 = x[:,:int(c*self.ratio),:,:]
        x_1x1 = x[:,int(c*self.ratio):,:,:]
        out_3x3_gwc = self.gwc(x_3x3)
 if self.stride ==2:
            x_3x3 = self.avgpool_s2_3(x_3x3)
        out_3x3_pwc = self.pwc(x_3x3)
        out_3x3 = out_3x3_gwc + out_3x3_pwc
        out_3x3 = self.bn1(out_3x3)
        out_3x3_ratio = self.avgpool_add_3(out_3x3).squeeze(axis=3).squeeze(axis=2)
 # use avgpool first to reduce information lost
 if self.stride == 2:
            x_1x1 = self.avgpool_s2_1(x_1x1)
        out_1x1 = self.conv1x1(x_1x1)
        out_1x1 = self.bn2(out_1x1)
        out_1x1_ratio = self.avgpool_add_1(out_1x1).squeeze(axis=3).squeeze(axis=2)
        out_31_ratio = paddle.stack((out_3x3_ratio, out_1x1_ratio), 2)
        out_31_ratio = nn.Softmax(axis=2)(out_31_ratio)
        out = out_1x1 * (out_31_ratio[:,:,1].reshape([b, self.outplanes, 1, 1]).expand_as(out_1x1))\
 + out_3x3 * (out_31_ratio[:,:,0].reshape([b, self.outplanes, 1, 1]).expand_as(out_3x3))
 return out
# paddle.summary(SPConv_3x3(), (1,32,224,224))
spconv = SPConv_3x3()
tmp = paddle.randn([1, 32, 224, 224])
conv_out1 = spconv(tmp)
print(conv_out1.shape) 
W0724 22:30:03.841145 13041 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0724 22:30:03.845882 13041 gpu_resources.cc:91] device: 0, cuDNN Version: 7.6.
[1, 32, 224, 224]
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:654: UserWarning: When training, we now always track global mean and variance.
 "When training, we now always track global mean and variance.")

4、消融实验

为验证所提方法的有效性,设置SPConv中的卷积核k=3,g=2,同时整个网络设置统一的全局超参数(不同阶段设置不同的会更优,但会过于精细)。

在小尺度数据集Cifar10、resnet18网络进行对比分析,为公平对比,所有实验均在含1个NVIDIA Tesla V100GPU的服务器上从头开始训练,且采用默认的数据增广与训练策略,不包含其他额外Tricks。

import paddle
from paddle.metric import Accuracy
from paddle.vision.transforms import Compose, Normalize, Resize, Transpose, ToTensor
from sp_resnet import resnet18_sp
callback = paddle.callbacks.VisualDL(log_dir='visualdl_log_res_sp')
normalize = Normalize(mean=[0.5, 0.5, 0.5],
                    std=[0.5, 0.5, 0.5],
 data_format='HWC')
transform = Compose([ToTensor(), Normalize(), Resize(size=(224,224))])
cifar10_train = paddle.vision.datasets.Cifar10(mode='train',
                                               transform=transform)
cifar10_test = paddle.vision.datasets.Cifar10(mode='test',
                                              transform=transform)
# 构建训练集数据加载器
train_loader = paddle.io.DataLoader(cifar10_train, batch_size=128, shuffle=True, drop_last=True)
# 构建测试集数据加载器
test_loader = paddle.io.DataLoader(cifar10_test, batch_size=128, shuffle=True, drop_last=True)
res_sp = paddle.Model(resnet18_sp(num_classes=10))
optim = paddle.optimizer.Adam(learning_rate=3e-4, parameters=res_sp.parameters())
res_sp.prepare(
 optim,
 paddle.nn.CrossEntropyLoss(),
 Accuracy()
 )
res_sp.fit(train_data=train_loader,
 eval_data=test_loader,
        epochs=10,
        callbacks=callback,
        verbose=1
 )
import paddle
from paddle.metric import Accuracy
from paddle.vision.transforms import Compose, Normalize, Resize, Transpose, ToTensor
from paddle.vision.models import resnet18
callback = paddle.callbacks.VisualDL(log_dir='visualdl_log_res_18')
normalize = Normalize(mean=[0.5, 0.5, 0.5],
                    std=[0.5, 0.5, 0.5],
 data_format='HWC')
transform = Compose([ToTensor(), Normalize(), Resize(size=(224,224))])
cifar10_train = paddle.vision.datasets.Cifar10(mode='train',
                                               transform=transform)
cifar10_test = paddle.vision.datasets.Cifar10(mode='test',
                                              transform=transform)
# 构建训练集数据加载器
train_loader = paddle.io.DataLoader(cifar10_train, batch_size=128, shuffle=True, drop_last=True)
# 构建测试集数据加载器
test_loader = paddle.io.DataLoader(cifar10_test, batch_size=128, shuffle=True, drop_last=True)
res_18 = paddle.Model(resnet18(num_classes=10))
optim = paddle.optimizer.Adam(learning_rate=3e-4, parameters=res_18.parameters())
res_18.prepare(
 optim,
 paddle.nn.CrossEntropyLoss(),
 Accuracy()
 )
res_18.fit(train_data=train_loader,
 eval_data=test_loader,
        epochs=10,
        callbacks=callback,
        verbose=1
 )

5、实验结果分析

最后,我们再来看一下消融实验结果,见下图。可以看到:

  • 添加了SPConV模块的ResNet18效果反而不如原始的ResNet18

在原作中,作者给出了ResNet20、VGG16在数据集Cifar10上的对比结果,原因也可能在于本实验中模型迭代次数不够,但是相比来看,特征图在进行了去冗余操作之后(类似于剪枝),精度下降似乎是正确的。

Split to Be Slim: 论文复现

6、总结

在该文中,作者重新对常规卷积中的信息冗余问题进行了重思考,为缓解该问题,作者提出了一种新颖的SPConv,它将输入特征拆分为两组不同特征并进行不同的处理,最后采用简化版SK进行融合。最后作者通过充分的实验分析说明了所提方法的有效性,在具有更高精度的时候具有更快的推理速度、更少的FLOPs与参数量。

所提SPConv是一种“即插即用”型单元,它可以轻易与其他网络架构相结合,同时与当前主流模型压缩方法互补,如能精心组合设计,有可能得到更轻量型的模型。

7、参考资料

即插即用!北邮&南开大学开源SPConv:精度更高、速度更快的卷积

Split to Be Slim: An Overlooked Redundancy in Vanilla Convolution

 

点击关注,第一时间了解华为云新鲜技术~

原文链接:https://www.cnblogs.com/huaweiyun/p/17349605.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Split to Be Slim: 论文复现 - Python技术站

(0)
上一篇 2023年4月25日
下一篇 2023年4月25日

相关文章

  • 深度学习2.0-37.循环神经网络层

    文章目录 weight sharing-用一个线性层处理单词(共享)解决参数量过大的问题 Consistent memory-解决语义相关性问题 Gradient-可导 weight sharing-用一个线性层处理单词(共享)解决参数量过大的问题 Consistent memory-解决语义相关性问题 需要用全局的视角来处理语义信息,来解决语义相关性问题 …

    2023年4月6日
    00
  • Pytorch_模型转Caffe(三)pytorch转caffemodel

    目录 Pytorch_模型转Caffe(三)pytorch转caffemodel 1. Pytorch下生成模型 2. pth转换成caffemodel和prototxt 3. pytorch_to_caffe_alexNet.py剖析 4. 用转换后的模型进行推理 5. prototxt注意问题 模型转换基于GitHub上xxradon的代码进行优化,在此…

    2023年4月8日
    00
  • 机器学习(ML)九之GRU、LSTM、深度神经网络、双向循环神经网络

    门控循环单元(GRU) 循环神经网络中的梯度计算方法。当时间步数较大或者时间步较小时,循环神经网络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题。通常由于这个原因,循环神经网络在实际中较难捕捉时间序列中时间步距离较大的依赖关系。 门控循环神经网络(gated recurrent neural network)的提出,正是为…

    2023年4月8日
    00
  • ubuntu 14.04 LTS(64bit) Anaconda2 + Theano(1.0.2) + Keras安装

    Anaconda安装前面已经写过 主要介绍theano与keras的安装 theano安装: 使用conda安装,命令行下直接执行:conda install theano安装过程与验证:Python命令行导入成功 keras安装:直接使用pip install keras安装提示安装成功,但会有两条标红的记录如下:distributed 1.21.8 re…

    2023年4月8日
    00
  • 转:25个Java机器学习工具和库

    转自:http://www.cnblogs.com/data2value/p/5419864.html 本列表总结了25个Java机器学习工具&库: 1. Weka集成了数据挖掘工作的机器学习算法。这些算法可以直接应用于一个数据集上或者你可以自己编写代码来调用。Weka包括一系列的工具,如数据预处理、分类、回归、聚类、关联规则以及可视化。 2.Mas…

    机器学习 2023年4月13日
    00
  • 生成对抗网络GAN(Generative Adversarial Network)

    生成对抗网络GAN(Generative Adversarial Network)         2014年Szegedy在研究神经网络的性质时,发现针对一个已经训练好的分类模型,将训练集中样本做一些细微的改变会导致模型给出一个错误的分类结果,这种虽然发生扰动但是人眼可能识别不出来,并且会导致误分类的样本被称为对抗样本,他们利用这样的样本发明了对抗训练(a…

    2023年4月5日
    00
  • 机器学习之杂乱笔记

    https://tensorflow.google.cn/tutorials/representation/word2vec   暂时我们使用 vanilla 定义,将“上下文”定义为目标字词左侧和右侧的字词窗口 通过噪声对比训练进行扩展 神经概率语言模型一直以来都使用最大似然率 (ML) 原则进行训练,以最大限度地提高使用 softmax 函数根据之前的字…

    机器学习 2023年4月13日
    00
合作推广
合作推广
分享本页
返回顶部