摘要:在本論文中揭示了這樣一種現(xiàn)象:一層內(nèi)的許多特征圖共享相似但不相同的模式。
本文分享自華為云社區(qū)《Split to Be Slim: 論文復(fù)現(xiàn)》,作者: 李長安 。
Split to Be Slim: An Overlooked Redundancy in Vanilla Convolution 論文復(fù)現(xiàn)
1、問題切入
已經(jīng)提出了許多有效的解決方案來減少推理加速模型的冗余。然而,常見的方法主要集中在消除不太重要的過濾器或構(gòu)建有效的操作,同時忽略特征圖中的模式冗余。
在本論文中揭示了這樣一種現(xiàn)象:一層內(nèi)的許多特征圖共享相似但不相同的模式。但是,很難確定具有相似模式的特征是否是冗余的或包含基本細(xì)節(jié)。因此,論文作者不是直接去除不確定的冗余特征,而是提出了一種基于分割的卷積操作,即 SPConv,以容忍具有相似模式但需要較少計算的特征。
具體來說,論文將輸入特征圖分為Representative部分和不Uncertain冗余部分,其中通過相對繁重的計算從代表性部分中提取內(nèi)在信息,而對不確定冗余部分中的微小隱藏細(xì)節(jié)進(jìn)行一些輕量級處理手術(shù)。為了重新校準(zhǔn)和融合這兩組處理過的特征,我們提出了一個無參數(shù)特征融合模塊。此外,我們的 SPConv 被制定為以即插即用的方式替換 vanilla 卷積。在沒有任何花里胡哨的情況下,基準(zhǔn)測試結(jié)果表明,配備 SPConv 的網(wǎng)絡(luò)在 GPU 上的準(zhǔn)確性和推理時間上始終優(yōu)于最先進(jìn)的基線,F(xiàn)LOPs 和參數(shù)急劇下降。
2、特征冗余問題
然而,如上圖所示,同一層的特征中存在相似模式,也就是說存在特征冗余問題。但同時,并未存在完全相同的兩個通道特征,進(jìn)而導(dǎo)致無法直接剔除冗余通道特征。 因此,可以選擇一些有代表性的特征圖來補充內(nèi)在信息,而剩余的冗余只需要補充微小的不同細(xì)節(jié)。
3、SPConv詳解
在現(xiàn)有的濾波器中,比如常規(guī)卷積、GhostConv、OctConv、HetConv均在所有輸入通道上執(zhí)行k*k卷積。然而,如上圖所示,同一層的特征中存在相似模式,也就是說存在特征冗余問題。但同時,并未存在完全相同的兩個通道特征,進(jìn)而導(dǎo)致無法直接剔除冗余通道特征。
受此現(xiàn)象啟發(fā),作者提出將所有輸入特征按比例拆分為兩部分:
- Representative部分執(zhí)行k*k卷積提取重要信息;
- Uncertain部分執(zhí)行1*1卷積補充隱含細(xì)節(jié)信息。
因此該過程可以描述為(見SPConv的左側(cè)部分),公式如下圖所示:
3.1 Further Reduction for Reprentative
在將所有輸入通道分成兩個主要部分后,代表部分之間可能存在冗余。換句話說,代表通道可以分為幾個部分,每個部分代表一個主要類別的特征,例如顏色和紋理。因此,我們在代表性通道上采用組卷積以進(jìn)一步減少冗余,如圖 2 的中間部分所示。我們可以將組卷積視為具有稀疏塊對角卷積核的普通卷積,其中每個塊對應(yīng)于通道,并且分區(qū)之間沒有連接。這意味著,在組卷積之后,我們進(jìn)一步減少了代表性部分之間的冗余,同時我們還切斷了可能不可避免地有用的跨通道連接。我們通過在所有代表性通道上添加逐點卷積來彌補這種信息丟失。與常用的組卷積后點卷積不同,我們在相同的代表性通道上進(jìn)行 GWC 和 PWC。然后我們通過直接求和來融合這兩個結(jié)果特征,因為它們具有相同的通道來源,從而獲得了額外的分?jǐn)?shù)(這里我們將組大小設(shè)置為 2)。所以方程2的代表部分可以表述為方程3:
3.2 Parameter Free Feature Fusion Module
到目前為止,我們已經(jīng)將 vanilla 3×3 卷積拆分為兩個操作:對于代表部分,我們進(jìn)行 3×3 組卷積和 1×1 逐點卷積的直接求和融合,以抵消分組信息丟失;對于冗余部分,我們應(yīng)用 1 × 1 內(nèi)核來補充一些微小的有用細(xì)節(jié)。結(jié)果,我們得到了兩類特征。因為這兩個特征來自不同的輸入通道,所以需要一種融合方法來控制信息流。與等式 2 的直接求和融合不同,我們?yōu)槲覀兊?SP-Conv 設(shè)計了一個新穎的特征融合模塊,無需導(dǎo)入額外的參數(shù),有助于實現(xiàn)更好的性能。如圖 2 右側(cè)所示,
3.3 代碼復(fù)現(xiàn)
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、消融實驗
為驗證所提方法的有效性,設(shè)置SPConv中的卷積核k=3,g=2,同時整個網(wǎng)絡(luò)設(shè)置統(tǒng)一的全局超參數(shù)(不同階段設(shè)置不同的會更優(yōu),但會過于精細(xì))。
在小尺度數(shù)據(jù)集Cifar10、resnet18網(wǎng)絡(luò)進(jìn)行對比分析,為公平對比,所有實驗均在含1個NVIDIA Tesla V100GPU的服務(wù)器上從頭開始訓(xùn)練,且采用默認(rèn)的數(shù)據(jù)增廣與訓(xùn)練策略,不包含其他額外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) # 構(gòu)建訓(xùn)練集數(shù)據(jù)加載器 train_loader = paddle.io.DataLoader(cifar10_train, batch_size=128, shuffle=True, drop_last=True) # 構(gòu)建測試集數(shù)據(jù)加載器 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) # 構(gòu)建訓(xùn)練集數(shù)據(jù)加載器 train_loader = paddle.io.DataLoader(cifar10_train, batch_size=128, shuffle=True, drop_last=True) # 構(gòu)建測試集數(shù)據(jù)加載器 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、實驗結(jié)果分析
最后,我們再來看一下消融實驗結(jié)果,見下圖??梢钥吹剑?/p>
- 添加了SPConV模塊的ResNet18效果反而不如原始的ResNet18
在原作中,作者給出了ResNet20、VGG16在數(shù)據(jù)集Cifar10上的對比結(jié)果,原因也可能在于本實驗中模型迭代次數(shù)不夠,但是相比來看,特征圖在進(jìn)行了去冗余操作之后(類似于剪枝),精度下降似乎是正確的。
6、總結(jié)
在該文中,作者重新對常規(guī)卷積中的信息冗余問題進(jìn)行了重思考,為緩解該問題,作者提出了一種新穎的SPConv,它將輸入特征拆分為兩組不同特征并進(jìn)行不同的處理,最后采用簡化版SK進(jìn)行融合。最后作者通過充分的實驗分析說明了所提方法的有效性,在具有更高精度的時候具有更快的推理速度、更少的FLOPs與參數(shù)量。
所提SPConv是一種“即插即用”型單元,它可以輕易與其他網(wǎng)絡(luò)架構(gòu)相結(jié)合,同時與當(dāng)前主流模型壓縮方法互補,如能精心組合設(shè)計,有可能得到更輕量型的模型。
7、參考資料
即插即用!北郵&南開大學(xué)開源SPConv:精度更高、速度更快的卷積
Split to Be Slim: An Overlooked Redundancy in Vanilla Convolution
?文章來源:http://www.zghlxwxcb.cn/news/detail-423767.html
點擊關(guān)注,第一時間了解華為云新鮮技術(shù)~文章來源地址http://www.zghlxwxcb.cn/news/detail-423767.html
到了這里,關(guān)于Split to Be Slim: 論文復(fù)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!