摘要:在本論文中揭示了這樣一種現象:一層內的許多特征圖共享相似但不相同的模式,
本文分享自華為云社區《Split to Be Slim: 論文復現》,作者: 李長安 ,
Split to Be Slim: An Overlooked Redundancy in Vanilla Convolution 論文復現
1、問題切入
已經提出了許多有效的解決方案來減少推理加速模型的冗余,然而,常見的方法主要集中在消除不太重要的過濾器或構建有效的操作,同時忽略特征圖中的模式冗余,
在本論文中揭示了這樣一種現象:一層內的許多特征圖共享相似但不相同的模式,但是,很難確定具有相似模式的特征是否是冗余的或包含基本細節,因此,論文作者不是直接去除不確定的冗余特征,而是提出了一種基于分割的卷積操作,即 SPConv,以容忍具有相似模式但需要較少計算的特征,
具體來說,論文將輸入特征圖分為Representative部分和不Uncertain冗余部分,其中通過相對繁重的計算從代表性部分中提取內在資訊,而對不確定冗余部分中的微小隱藏細節進行一些輕量級處理手術,為了重新校準和融合這兩組處理過的特征,我們提出了一個無引數特征融合模塊,此外,我們的 SPConv 被制定為以即插即用的方式替換 vanilla 卷積,在沒有任何花里胡哨的情況下,基準測驗結果表明,配備 SPConv 的網路在 GPU 上的準確性和推理時間上始終優于最先進的基線,FLOPs 和引數急劇下降,
2、特征冗余問題
然而,如上圖所示,同一層的特征中存在相似模式,也就是說存在特征冗余問題,但同時,并未存在完全相同的兩個通道特征,進而導致無法直接剔除冗余通道特征, 因此,可以選擇一些有代表性的特征圖來補充內在資訊,而剩余的冗余只需要補充微小的不同細節,
3、SPConv詳解
在現有的濾波器中,比如常規卷積、GhostConv、OctConv、HetConv均在所有輸入通道上執行k*k卷積,然而,如上圖所示,同一層的特征中存在相似模式,也就是說存在特征冗余問題,但同時,并未存在完全相同的兩個通道特征,進而導致無法直接剔除冗余通道特征,
受此現象啟發,作者提出將所有輸入特征按比例拆分為兩部分:
- Representative部分執行k*k卷積提取重要資訊;
- Uncertain部分執行1*1卷積補充隱含細節資訊,
因此該程序可以描述為(見SPConv的左側部分),公式如下圖所示:
3.1 Further Reduction for Reprentative
在將所有輸入通道分成兩個主要部分后,代表部分之間可能存在冗余,換句話說,代表通道可以分為幾個部分,每個部分代表一個主要類別的特征,例如顏色和紋理,因此,我們在代表性通道上采用組卷積以進一步減少冗余,如圖 2 的中間部分所示,我們可以將組卷積視為具有稀疏塊對角卷積核的普通卷積,其中每個塊對應于通道,并且磁區之間沒有連接,這意味著,在組卷積之后,我們進一步減少了代表性部分之間的冗余,同時我們還切斷了可能不可避免地有用的跨通道連接,我們通過在所有代表性通道上添加逐點卷積來彌補這種資訊丟失,與常用的組卷積后點卷積不同,我們在相同的代表性通道上進行 GWC 和 PWC,然后我們通過直接求和來融合這兩個結果特征,因為它們具有相同的通道來源,從而獲得了額外的分數(這里我們將組大小設定為 2),所以方程2的代表部分可以表述為方程3:
3.2 Parameter Free Feature Fusion Module
到目前為止,我們已經將 vanilla 3×3 卷積拆分為兩個操作:對于代表部分,我們進行 3×3 組卷積和 1×1 逐點卷積的直接求和融合,以抵消分組資訊丟失;對于冗余部分,我們應用 1 × 1 內核來補充一些微小的有用細節,結果,我們得到了兩類特征,因為這兩個特征來自不同的輸入通道,所以需要一種融合方法來控制資訊流,與等式 2 的直接求和融合不同,我們為我們的 SP-Conv 設計了一個新穎的特征融合模塊,無需匯入額外的引數,有助于實作更好的性能,如圖 2 右側所示,
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上的對比結果,原因也可能在于本實驗中模型迭代次數不夠,但是相比來看,特征圖在進行了去冗余操作之后(類似于剪枝),精度下降似乎是正確的,
6、總結
在該文中,作者重新對常規卷積中的資訊冗余問題進行了重思考,為緩解該問題,作者提出了一種新穎的SPConv,它將輸入特征拆分為兩組不同特征并進行不同的處理,最后采用簡化版SK進行融合,最后作者通過充分的實驗分析說明了所提方法的有效性,在具有更高精度的時候具有更快的推理速度、更少的FLOPs與引數量,
所提SPConv是一種“即插即用”型單元,它可以輕易與其他網路架構相結合,同時與當前主流模型壓縮方法互補,如能精心組合設計,有可能得到更輕量型的模型,
7、參考資料
即插即用!北郵&南開大學開源SPConv:精度更高、速度更快的卷積
Split to Be Slim: An Overlooked Redundancy in Vanilla Convolution
點擊關注,第一時間了解華為云新鮮技術~
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/551085.html
標籤:其他
上一篇:chatgpt~插件介紹
下一篇:返回列表
