深度學習網絡模型的輕量化方法


深度學習網絡的輕量化

由於大部分的深度神經網絡模型的參數量很大,無法滿足直接部署到移動端的條件,因此在不嚴重影響模型性能的前提下對模型進行壓縮加速,來減少網絡參數量和計算復雜度,提升運算能力。

一、深度可分離卷積

了解深度可分離卷積之前,我們先看一下常規的卷積操作:對於一張 \(3 \times 16 \times 16\) 的圖像,如果采用 \(3\times3\) 的卷積核,輸出 \(32 \times 16 \times 16\) 的feature map,則所需要的參數量為:

\[3 \times 3 \times3 \times 32 = 864 \]

常規卷積中每一個卷積核對輸入的所有通道進行卷積,如下圖所示:

與常規卷積不同,深度可分離卷積 (depthwise separable convolution) 分為兩個部分,分為逐通道卷積 (depthwise) 和逐點卷積 (pointwise) 。

1.1 逐通道卷積

depthwise中,每一個卷積核只對一個通道進行卷積,如下圖所示:

於是,還是對於一個 \(3 \times 16 \times 16\) 的圖像來說,通過一個 \(3 \times 3\) 的卷積,其輸出feature map 的維度為 \(3 \times 16 \times 16\),所用到的卷積核的參數為:

\[3 \times 3 \times 3 = 27 \]

Depthwise Convolution完成后的Feature map數量與輸入層的通道數相同,無法擴展Feature map。而且這種運算對輸入層的每個通道獨立進行卷積運算,沒有有效的利用不同通道在相同空間位置上的feature信息。因此需要Pointwise Convolution來將這些Feature map進行組合生成新的Feature map。

1.2 逐點卷積

pointconvolution的運算類似於 \(1\times1\) 卷積,對DW得到的feature map升維,在考慮到空間特征的同時,將維度變換到我們所期望的大小。

此時,如果需要輸出 \(32 \times 16 \times 16\) 的feature map,那么需要的 \(1 \times 1\) 的卷積核的個數為32個,此時的參數量為:

\[1 \times 1 \times 3 \times 32 = 96 \]

所以,綜合兩個過程考慮,采用深度可分離卷積后的參數量為: \(96+27=123\)

而采用常規卷積,完成此過程所需要的參數量為:\(3 \times 3 \times3 \times 32 = 864\)

1.3 深度可分離卷積實現代碼

其實,深度可分離卷積的實現也是依靠常規的卷積函數:torch.nn.Conv2d(),首先我們先來看一下官方教程:

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
  • in_channels–輸入 feature map 的通道數
  • out_channels– 輸出 feature map 的通道數
  • kernel_size– 卷積核的尺寸
  • stride – 卷積的步長,默認為1
  • padding –填充尺寸,默認為1
  • padding_mode – 填充的方式,默認為0填充
  • dilation – 卷積核元素之間的間隔,即空洞卷積. 默認為 1 時,為普通卷積
  • groups – 控制輸入和輸出之間的連接,默認為1

此外,官網上還給出了另外一段話:

When groups == in_channels and out_channels == K * in_channels, where K is a positive integer, this operation is also known as a “depthwise convolution”.

首先定義一個卷積類:

class CSDN_Tem(nn.Module):
    def __init__(self, in_ch, out_ch, kernel_size, padding, groups):
        super(CSDN_Tem, self).__init__()
        self.conv = nn.Conv2d(
            in_channels=in_ch,
            out_channels=out_ch,
            kernel_size=kernel_size,
            stride=1,
            padding=padding,
            groups=groups,
            bias=False
        )

    def forward(self, input):
        out = self.conv(input)
        return out
   
g_input = torch.FloatTensor(3, 16, 16)		# 定義隨機輸入
conv = CSDN_Tem(3, 32, 3, 1, 1)		# 實例化卷積
print(summary(conv, g_input.size()))		# 輸出卷積的參數信息
# [1, 3, 16, 16] => [1, 32, 16, 16]
conv_result = conv(g_input.unsqueeze(0))	# 計算普通卷積的結果,要把輸入變成4維

conv_dw = CSDN_Tem(3, 3, 3, padding=1, groups=3)
print(summary(conv_dw,  g_input.size()))	# 輸出分組卷積的參數信息
# [1, 3, 16, 16] => [1, 3, 16, 16]
dw_result = conv_dw(g_input.unsqueeze(0))	# 計算逐通道卷積的結果,要把輸入變成4維

conv_pw = CSDN_Tem(3, 32, 1, padding=0, groups = 1)
print(summary(conv_pw,  g_input.size()))	# 輸出逐點卷積的參數信息
# [1, 3, 16, 16] => [1, 32, 16, 16]
pw_result = conv_pw(dw_result)			    # 在逐通道卷積結果的基礎上,計算逐點卷積的結果

輸出結果如下:

# 普通卷積的參數量
Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 32, 16, 16]             864
================================================================
Total params: 864
Trainable params: 864
Non-trainable params: 0

# DW 的 參數量
 Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1            [-1, 3, 16, 16]              27
================================================================
Total params: 27
Trainable params: 27
Non-trainable params: 0

# PW 的 參數量
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 32, 16, 16]              96
================================================================
Total params: 96
Trainable params: 96
Non-trainable params: 0

1.4 深度可分離卷積的缺點

普通的卷積,每輸出一個 feature map,都考慮到了所有通道維度和通道之間的關系。從深度可分離卷積的原理可以看出,其先在通道域上提取特征,然后通過 \(1 \times 1\) 的卷積修改維度,這樣做雖然也考慮到了通道維度和通道之間的信息,然而其通道維度上的特征只在 DW 時提取了一次,相當於無論最后輸出的feature map是多少維度的,DW 輸出的 feature map 永遠都是同一個模板。這樣的操作弱化了在通道維度上的特征提取過程,因此效果會打折扣。並且用簡單的 \(1 \times 1\) 卷積來考慮通道之間的信息相關性,也過於簡單。

二、其他結構上改進的方法

  1. 采用全局池化代替全連接層
  2. 使用多個小卷積核來代替一個大卷積核
  3. 使用並聯的非對稱卷積核來代替一個正常的卷積核。比如 Inception V3 中將一個 \(7 \times 7\) 的卷積拆分成了 \(1 \times 7\)\(7 \times 1\) 的兩個卷積核。在提高了卷積多樣性的同時減少了參數量

三、剪枝

剪枝歸納起來就是取其精華去其糟粕。按照剪枝粒度可分為突觸剪枝神經元剪枝權重矩陣剪枝等。總體思想是,將權重矩陣中不重要的參數設置為0,結合稀疏矩陣來進行存儲和計算。通常為了保證performance,需要一小步一小步地進行迭代剪枝。剪枝的流程如下:

  1. 訓練一個performance較好的大模型。
  2. 評估模型中參數的重要性。常用的評估方法是,越接近0的參數越不重要。當然還有其他一些評估方法,這一塊也是目前剪枝研究的熱點。
  3. 將不重要的參數去掉,或者說是設置為0。之后可以通過稀疏矩陣進行存儲。比如只存儲非零元素的index和value。
  4. 訓練集上微調,從而使得由於去掉了部分參數導致的performance下降能夠盡量調整回來。
  5. 驗證模型大小和performance是否達到了預期,如果沒有,則繼續迭代進行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM