# 對深度學習模型整形的能力-修剪 (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) ``` 不同層的權重分布是否有共同的特徵呢? 答案是有的,不同層的權重呈現高斯分布,而且很多權重都集中在分布的平均值附近,而均值通常為零。 這啟發了我們,可以拿掉部分權重而不會大幅影響模型的性能! ``` ![image](https://hackmd.io/_uploads/H1gtKFp66.png) 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 (就是被修剪掉了)。 ``` ![image](https://hackmd.io/_uploads/HJdUvaTpp.png) ``` 不同層會對模型性能有不同的貢獻。要如何確定每層的稀疏性呢? 答案是敏感度掃描 (sensitivity scanning)。 想法是這樣的: 對模型的每一層(其他層不變)做修剪,並量測此時模型性能的下降,不斷重複此過程,直到每一層都被修剪過。 這種分析可以幫助我們排序不同層對性能的重要性,能幫助我們更好地修剪模型而不過分地降低性能。 ``` ![image](https://hackmd.io/_uploads/rkDKMy0aT.png) ``` 如果再加上每層參數的量,就能更好的判斷模型該如何修剪 也就是 "Select Sparsity Based on Sensitivity Curves and #Parameters Distribution" 有越多參數的那一層,通常需要更高的 sparsity。 對修剪越敏感的那一層,通常需要更低的 sparsity。 ``` ![image](https://hackmd.io/_uploads/S1MEQJ06T.png) ``` 開始修剪模型! ``` ``` #這個設定必須從以上分析提供的資訊人為設置,需調參幾次。 #設定每一層的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%... ``` ![image](https://hackmd.io/_uploads/BJJPNkRpT.png) ``` 模型微調後, 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加速上 ```