深度學習模型壓縮與加速理論與實戰(一):模型剪枝
2021-06-23 15:42:47
Source: https://blog.csdn.net/wlx19970505/article/details/111826742
Code: https://github.com/lixiangwang/model_prune_yolov3-
其他文獻:
- SlimYOLOv3: Narrower, Faster and Better for Real-Time UAV Applications, Pengyi Zhang, Yunxin Zhong, Xiaoqiong Li [Paper]
- Learning Efficient Convolutional Networks through Network Slimming, Zhuang Liu, Jianguo Li, Zhiqiang Shen, Gao Huang, Shoumeng Yan, Changshui Zhang [Paper] [Code]
文章目錄
通道剪枝
稀疏訓練策略
層剪枝
微調精度恢復訓練
剪枝顧名思義,就是通過一些算法或規則刪去一些不重要的部分,來使模型變得更加緊湊,減小計算或者搜索的復雜度,一種典型的模型剪枝方法如下圖:

它包括四個迭代步驟:
1. 評估一個預先訓練的深度模型中每個組件的重要性;
2. 剔除對模型推理不重要的成分;
3. 微調修剪模型,以彌補潛在的暫時性能下降;
4. 對微調后的模型進行評估,確定修剪后的模型是否適合部署。為了防止過度剪枝,最好采用增量剪枝策略。
對一個深度卷積系的模型而言,剪枝操作之前需要對模型進行稀疏化訓練,從而根據稀疏情況進行模型組件的選擇,通常來說,稀疏化有四個粒度的稀疏方式,分別是weight-level,kernel-level,channel-level,layer-level。weight-level具有最高的靈活性和泛化性能,也能獲得更高的壓縮比率,但是它通常需要特殊的軟硬件加速器才能在稀疏模型上快速推理。相反,layer-level稀疏化不需要特殊的包做推理加速,而channel-level稀疏化在靈活性和實現上做了一個平衡,它可以被應用到任何典型的CNN或者全連接層(將每個神經元看作一個通道)。
在我們的方案中,結合layer-level和channel-level,相當於是考慮兩個角度去剪枝模型:模型變短(layer-level)和模型變窄(channel-level),它的具體思路如下:
通道剪枝:
和模型剪枝的通用算法類似,我們的通道剪枝流程如下:

根據模型的主體結構,我們會篩選出參與剪枝的CBL結構(即Conv Layer + BN Layer + LeakyReLU Layer)的集合, 大部分分布於Darknet骨干網絡,對於上采樣 upsample 之前的一個CBL不參與剪枝(實驗表明這個CBL對精度影響較大)。
如何去評估CBL集合中,每個channel的重要性呢?具體的做法是利用BN層中的縮放因子 γ,在訓練過程當中來衡量 channel 的重要性,將低於一個閾值的CBL層的channel進行刪減,達到壓縮模型大小,提升運算速度的效果。其中,閾值可以通過我們設定的裁剪比例(超參數,實驗可調)進行排序截斷得到。如下圖:

左邊為訓練后的稀疏模型,中間一列是scaling factors,也就是BN層當中的縮放因子γ,當γ較小時(如圖中0.001, 0.003),所對應的channel就會被刪減,得到右邊所示的模型。在我們的算法在,先以全局閾值找出各卷積層的mask,然后對於每組 shortcut,它將相連的各卷積層的剪枝 mask 取並集,用 merge 后的 mask 進行剪枝,這樣對每一個相關層都做了考慮,同時它還對每一個層的保留通道做了限制。如何去訓練出這樣的稀疏模型呢?一個簡單的做法是對BN層在梯度回傳的時候附加一個梯度,是只訓練趨於稀疏化,具體做法可以反映在損失函數中,附加一個γ參數的L1正則項:

第一項是模型預測所產生的損失,第二項就是用來約束γ的,λ是權衡兩項的超參。
稀疏訓練策略:
在我們的方案中,對BN參數進行稀疏訓練我們提供三種策略:
1. 恆定λ:
在整個稀疏過程中,始終以恆定的λ給模型添加額外的梯度,因為力度比較均勻,往往壓縮度較高。但稀疏過程是個博弈過程,我們不僅想要較高的壓縮度,也想要在學習率下降后恢復足夠的精度,不同的λ最后稀疏結果也不同,想要找到合適的往往需要較高的時間成本。
2. 全局λ衰減:
設定在epochs的0.5階段λ衰減100倍。前提是0.5之前權重已經完成大幅壓縮,這時對λ衰減有助於精度快速回升,但是相應的bn會出現一定膨脹,降低壓縮度,有利有弊,可以說是犧牲較大的壓縮度換取較高的精度,同時減少尋找 λ 的時間成本。當然這個0.5和100可以自己調整。注意也不能為了在前半部分加快壓縮 bn 而大大提高 λ,過大的 λ 會導致模型精度下降厲害,且 λ 衰減后也無法恢復。
3. 局部λ衰減:
在 epochs 的0.5階段開始對85%的通道保持原力度壓縮,15%的通道進行λ衰減100倍。這個85%是個先驗知識,是由策略一稀疏后嘗試剪通道幾乎不掉點的最大比例,幾乎不掉點指的是相對稀疏后精度;如果微調后還是不及baseline,或者說達不到精度要求,就可以使用策略三進行局部λ衰減,從中間開始重新稀疏,這可以在犧牲較小壓縮度情況下提高較大精度。
在代碼中需要在訓練的時候,獲取需要考量prune層的index,在進行梯度的附加項:
CBL_idx, _, prune_idx=parse_module_defs2(model.module_defs)
BNOptimizer.updateBN(model.module_list, opt.s, prune_idx, epoch,opt, opt.sr_mode)
其中,parse_module_defs2 的定義如下:
def parse_module_defs2(module_defs): CBL_idx = [] Conv_idx = [] shortcut_idx = dict() shortcut_all = set() ignore_idx = set() for i, module_def in enumerate(module_defs): if module_def['type'] == 'convolutional': if module_def['batch_normalize'] == 1: CBL_idx.append(i) else: Conv_idx.append(i) if module_defs[i + 1]['type'] == 'route' and 'groups' in module_defs[i + 1]: ignore_idx.add(i) elif module_def['type'] == 'upsample': # 上采樣層前的卷積層不裁剪 ignore_idx.add(i - 1) prune_idx = [idx for idx in CBL_idx if idx not in ignore_idx] return CBL_idx, Conv_idx, prune_idx class BNOptimizer(): ''' 稀疏訓練 mode==1: 恆定s給bn回傳添加額外梯度 mode==2: 全局s衰減,前50%epoch恆定s,后50%恆定s*alpha mode==3: 局部s衰減:前50%epoch恆定s,后50%中,對percent比例的通道保持s,1-percent比例的通道衰減s*alpha ''' @staticmethod def updateBN(module_list, s, prune_idx, epoch,opt=None,mode = None,percent = 0.85, alpha = 0.01): if mode == 1: for idx in prune_idx: bn_module = module_list[idx][1] bn_module.weight.grad.data.add_(s * torch.sign(bn_module.weight.data)) # L1 elif mode ==2: s = s if epoch <= opt.epochs * 0.5 else s * alpha for idx in prune_idx: bn_module = module_list[idx][1] bn_module.weight.grad.data.add_(s * torch.sign(bn_module.weight.data)) # L1 elif mode == 3: if opt.sr and opt.prune==1 and epoch > opt.epochs * 0.5: idx2mask = get_mask2(module_list, prune_idx, percent) if idx2mask: for idx in idx2mask: bn_module = module_list[idx][1] # bn_module.weight.grad.data.add_(0.5 * s * torch.sign(bn_module.weight.data) * (1 - idx2mask[idx].cuda())) bn_module.weight.grad.data.sub_((1-alpha) * s * torch.sign(bn_module.weight.data) * idx2mask[idx].cu
層剪枝:
層剪枝是在之前的通道剪枝策略基礎上衍生出來的,在通道剪枝后進行層剪枝操作。針對每一個Darknet的shortcut層前一個CBL進行評價,對各層的γ均值進行排序,取最小的N層(N是實驗中可調的超參數)進行層剪枝。為保證Darknet結構完整,這里每剪一個shortcut結構,會同時剪掉一個shortcut層和它前面的兩個卷積層。Darknet中有23處shortcut,剪掉8個shortcut就是剪掉了24個層,剪掉16個shortcut就是剪掉了48個層,總共有69個層的剪層空間。
微調精度恢復訓練:
在進行通道剪枝和層剪枝之后,一般精度都會有不同程度的下降,這取決於剪枝的力度,但一般都可以通過微調剪枝之后的模型后再訓練進行精度恢復,由於剪枝后的模型更加簡單,所以往往在任務目標不那么復雜的情況下,具有更好的泛化性能(即剪枝前的模型可能過擬合於任務的數據集,而剪枝並且微調訓練后的模型精度可能表現的更好,這在我們的實驗中是有體現的)。即使是微調訓練精度恢復不上去,依然可以采用知識蒸餾技術,將剪枝前的模型作為教師模型,引導剪枝后的學生模型進行蒸餾訓練。需要強調的是,蒸餾在這里只是輔助微調,如果注重精度優先,剪枝時盡量剪不掉點的比例,這時蒸餾的作用也不大;如果注重速度,剪枝比例較大,導致模型精度下降較多,可以結合蒸餾提升精度。或者如果微調后精度能很好的恢復上去,也不要采用蒸餾策略,因為蒸餾策略會限制學生網絡的精度上限,且蒸餾訓練會需要更大的GPU內存來實現,增加了實驗的成本。
————————————————
版權聲明:本文為CSDN博主「貝殼er」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wlx19970505/article/details/111826742
