# 對深度學習模型整形的能力-修剪 (Pruning)
## Goals
```
假設手上有一個神經網路模型,目標是將該模型修剪到只有原本大小的25%,
且經過微調後的 validation accuracy 大於 92.5
要用怎樣的剪枝法? 要在手機或 NPU 上加速,該使用哪種方法?
```
修剪神經網路模型來減小模型大小跟延遲。
- 可以理解基本概念修剪(pruning)
- 實作並應用細粒度修剪(fine-grained pruning)
- 實作並應用通道修剪 (channel pruning)
- 了解基本剪枝造成的加速
- 了解這兩種剪枝之間的差異與權衡
```
先用 VGG model 來看如何修剪模型 (Dense Model)
1. 模型準確度及大小 (Accuracy and size)
2. 來看權重的分布 (the distribution of weight values)
3. Fine-grained Pruning
4. Channel Pruning
5. 比較兩種模型修剪的方式,如果要使模型在手機上加速,要用哪一種方法呢?
```
---
1. 模型準確度及大小 (Accuracy and size)
```
VGG 是一個經典的深度卷積神經網路,簡單而均一。
主要由3x3卷積層,ReLU激活函數和2x2最大池化層組成。
隨著網絡深度增加,卷積層的通度數也逐漸增加,
通常是 64,128,256和512。並在最後幾層使用全連接層進行分類,
在多個視覺識別任務中展現了出色的性能。
Pretrained VGG model 在 CIFAR10 dataset 的準確度及模型大小為:
dense model has accuracy=92.95%
dense model has size=35.20 MiB
```
2. 來看權重的分布 (the distribution of weight values)
```
不同層的權重分布是否有共同的特徵呢?
答案是有的,不同層的權重呈現高斯分布,而且很多權重都集中在分布的平均值附近,而均值通常為零。
這啟發了我們,可以拿掉部分權重而不會大幅影響模型的性能!
```

3. 細粒度修剪
```
threshold = 0.38
target sparsity: 0.75
sparsity before pruning: 0.04
sparsity after pruning: 0.76
sparsity of pruning mask: 0.76
算法很單純,一張圖即可解釋:
左圖是模擬未修剪前的模型權重,右圖是經算法修剪後的模型權重。
target sparsity 為 0.75 表示希望 3/4 的權重都是 0。
根據權重及 target sparsity,算法會計算出 threshold (此為 0.38),
權重絕對值小於 0.38 者,會被設為 0 (就是被修剪掉了)。
```

```
不同層會對模型性能有不同的貢獻。要如何確定每層的稀疏性呢?
答案是敏感度掃描 (sensitivity scanning)。
想法是這樣的: 對模型的每一層(其他層不變)做修剪,並量測此時模型性能的下降,不斷重複此過程,直到每一層都被修剪過。
這種分析可以幫助我們排序不同層對性能的重要性,能幫助我們更好地修剪模型而不過分地降低性能。
```

```
如果再加上每層參數的量,就能更好的判斷模型該如何修剪
也就是 "Select Sparsity Based on Sensitivity Curves and #Parameters Distribution"
有越多參數的那一層,通常需要更高的 sparsity。
對修剪越敏感的那一層,通常需要更低的 sparsity。
```

```
開始修剪模型!
```
```
#這個設定必須從以上分析提供的資訊人為設置,需調參幾次。
#設定每一層的sparsity:
'backbone.conv0.weight': 0.5,
'backbone.conv1.weight': 0.5,
'backbone.conv2.weight': 0.5,
'backbone.conv3.weight': 0.5,
'backbone.conv4.weight': 0.8,
'backbone.conv5.weight': 0.8,
'backbone.conv6.weight': 0.8,
'backbone.conv7.weight': 0.8,
'classifier.weight': 0.9
```
```
結果 :
Sparse model has size=8.15 MiB = 23.16% of dense model size
Sparse model has accuracy=83.30% before fintuning
而不要忘了我們的目標是:
the sparse model is 25% of the size of the dense model,
and validation accuracy is higher than 92.5 after finetuning
所以修剪後的模型還需要經過微調,使 accuracy 超過 92.5%...
```

```
模型微調後,
Finetuning Fine-grained Pruned Sparse Model
Epoch 1 Accuracy 92.37% / Best Accuracy: 92.37%
Epoch 2 Accuracy 92.71% / Best Accuracy: 92.71%
Epoch 3 Accuracy 92.63% / Best Accuracy: 92.71%
Epoch 4 Accuracy 92.80% / Best Accuracy: 92.80%
Epoch 5 Accuracy 92.57% / Best Accuracy: 92.80%
達標!
Sparse model has size=8.15 MiB = 23.16% of dense model size
Sparse model has accuracy=92.8% after fintuning
```
4. Channel Pruning (通道修剪)
關鍵在於具體理解 "通道" 的含意
從最簡單的問題開始,通道(channel)是指模型的卷積核的通道。
這個跟面試陷阱題有點關係:"當影像尺寸變為 2 倍,CNN 的參數數量變為幾倍?"
答案是 CNN 的參數數量維持不變,因為模型參數數量主要由卷積核大小跟數量來決定,
而非影像尺寸。
卷積層的參數數量由卷積核的大小(通常是 3x3 或 5x5),卷積核數量(輸出通道的數量)以及輸入通道的數量來決定
例如某個卷積層的輸入通道數為 C_in,輸出通道數為 C_out,卷積核的大小為 k x k,
則卷積層的參數數量為 k x k x C_in x C_out
(給面試者的跟進題:那輸入影像尺寸持續增加,總會對系統造成負擔吧? 是在那裡造成增加呢?)
回到修剪,先處理最簡單的狀況:
1. 移除整層的通道 (可加速GPU上的推論速度)
$\#\mathrm{out\_channels}_{\mathrm{new}} = \#\mathrm{out\_channels}_{\mathrm{origin}} \cdot (1 - \mathrm{sparsity})$
這步修剪,表示 C_out 減小。
另外每個不同卷積層,可以有不同修剪程度(pruning rate),
若每層卷積層都用固定的修剪率 30%,可達成 2x 的計算量減少。
```
快速答案是:(0.7)^2 = 0.49
詳解如下:
# 定義第一個卷積層,輸入通道數為3(假設處理RGB圖像),輸出通道數為16
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
# 定義第二個卷積層,輸入通道數為16(與上一層的輸出通道數相同),輸出通道數為32
self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)
# 定義一個池化層
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
# 定義一個全連接層
self.fc = nn.Linear(32 * 8 * 8, 10) # 假設輸入圖像尺寸為32x32
原本的計算量為
3x3x3x16+16x3x3x32+32x8x8x10
修剪後
3x3x3x16x0.7+(16x0.7x3x3x32x0.7)+32x0.7x8x8x10
^^^^^^^^^^^^^^^^^^^
扣掉頭尾項,中間層計算量都是乘以 (0.7)^2
假設我們對所有層使用相同的剪枝率,那麼這個剪枝率大約需要是30%。
這是因為,如果我們移除30%的權重,則剩餘70%的權重將會產生大約70%的計算量。
由於剪枝後的計算量與剩餘權重的平方成正比(因為計算量與參數數量的平方成正比),
所以剩餘70%的權重大約對應50%的計算量(因為0.7的平方大約等於0.5)。
因此,大約30%的統一剪枝率可以達到我們目標的2倍計算量減少。
```
2. 移除權重較小的通道 (如何衡量較小? 用 L2 norm 來衡量)
```
移除卷積核整層的通道確實可以加速GPU上的推論速度,
因為這樣做會減少模型的參數數量和計算量。
然而,這也會影響模型的表達能力,因為每個通道通常負責學習特定的特徵或模式。
準確度是否下降取決於多種因素,包括移除的通道數量、
模型的整體架構、訓練數據的多樣性等。
在一些情況下,輕微地剪枝(即移除一些不重要的通道)
可能對準確度影響不大,甚至有可能通過重新訓練來恢復或改善準確度。然而,如果移除過多的通道,特別是一些重要的通道,那麼準確度可能會顯著下降。
總之,移除卷積核整層的通道可以作為模型壓縮和加速的一種手段,
但需要仔細選擇哪些通道被移除,
並且可能需要對模型進行重新訓練以維持或優化性能。
這是一個權衡速度和準確度的過程。
```
```
實作:
1. 實際的通道剪枝操作。它遍歷每一對相鄰的卷積層(prev_conv和next_conv),
並根據每層的剪枝比例(prune_ratio)來調整它們的參數
2. 四個維度:輸出通道數(即卷積核的數量)、輸入通道數(即上一層輸出的特征圖數量)、
卷積核的高度、卷積核的寬度
3. 修改next_conv.weight以匹配前一層的輸出通道剪枝。
由於next_conv的輸入通道與prev_conv的輸出通道是對應的,
因此需要確保next_conv.weight的輸入通道數與prev_conv的輸 出通道數相匹配。
```
5. 比較兩種模型修剪的方式,如果要使模型在手機上加速,要用哪一種方法呢?
```
Fine-grained pruning 的壓縮率較高,微調後的模型精度較高,延遲也較低,hardware support 則較差
Channel pruing 則是對專用硬體加速器比較友善。(比較不客製化)
所以適合實作在手機或NPU加速上
```