Reference:
https://github.com/NervanaSystems/distiller
https://nervanasystems.github.io/distiller/index.html
PART I: 介紹
Distiller模型壓縮包含的算法: 稀疏算法(剪枝+正則化)+低精度算法(量化)
Distiller特點:
(1) 該框架融合了剪枝,正則化及量化算法
(2) 一系列分析及評估壓縮性能的工具
(3) 較流行壓縮算法的應用
稀疏神經網絡可以基於 (weight and biases) activations的稀疏tensor進行計算。
關注稀疏度的原因:對於像GoogleNet 之類的大型網絡,對計算十分敏感,在經過加速處理過的設備上進行inference仍需花費大量時間。較大的模型占用內存資源
在移動端模型的存儲及轉移主要受到模型的大小及下載時間限制
Smaller
具有稀疏表示的數據並不需要進行解壓縮
三種表示方法:1.稀疏表示,2.密集表示,3.針對硬件特點進行特殊的表示
通過剪枝及正則化處理,對於向量計算的compute engine的權重刪除。
Faster
神經網絡中的許多層都是bandwidth-bound,這意味着執行時間主要是被可利用的bandwidth占用,其中,占用時間比較長的為全連接層,同時RNN及LSTM層存在大量的bandwidth-dominated操作。
Pruning:對網絡中的權重及激活值進行稀疏化處理,基於二元標准來進行決定那個權重可以修剪。將滿足修剪標准的權重進行賦值為0並不讓其參見反向傳播運算。
可修剪的對象包括:權重,偏差及激活值。
稀疏性:張量中存在0元素的個數占整個tensor向量中的比例,L0正則化,統計非0元素的個數

Density = 1 – sparsity
Distiller工具:distiller.sparsity distiller.density
權重/模型修剪:用於提高網絡權重稀疏性的一系列方法,通過一個修剪標准來決定哪個元素需要進行修剪,標准即pruning criteria,最常用的標准是每個元素的絕對值,其值與某個閾值進行比較,如果低於這個閾值,就將該元素的值置為0.
Distiller工具:distiller.MagnitudeParameterPruner 基於L1正則化的對於最終結果貢獻量較小的權重重要性較低,而且可以被移除。
對於prune的理解 :參數量較大的模型中,存在大量的邏輯及特征冗余。一種想法是搜索盡可能多的權值為0的權重,同時在inference時可以達到與密集模型相似的准確率。另一種想法是,在高維度的參數空間中,密集模型的解空間中可能會存在一些稀疏解,修剪算法就是為了尋找這個稀疏解。
One-shot pruning :對一個訓練好的模型進行一次修剪
Iterative training :pruning-followed-by-retraining(fine-tuning),在訓練迭代過程中,需要考慮的問題包括:pruning的標准是如何改變,迭代多少次,多久變化一次,那些張量需要被prune,這些被稱為pruning schedule
對Iterative pruning 的理解:基於一個評價標准將權重進行重復訓練學習,判斷哪個權重是重要的,並將不重要的權重進行刪除。 調整后的權重將模型進行性能上的恢復,每次迭代修剪更多的權重。
修剪的停止條件也在schedule中,其取決於修剪算法。停止條件兩個參考: specific sparsity level,required compute budget
修剪粒度(pruning granularity):對單個權重元素進行修剪,稱為element-wise修剪,有時被稱為find-grained pruning
Coarse-grained pruning: structure pruning , group pruning(filter pruning整個filters被移除),block pruning
敏感度分析:
將稀疏性引入修剪過程中的難點在於:對於每層網絡張量中,閾值的選擇及sparsity level,根據張量對修剪的敏感度進行排序。
其思想是,針對特定的網絡層設置pruning level(percentage),只對模型修剪一次,然后在測試數據集上進行評估並記錄得到的分數值。若在每個參數化的網絡層上應用此方法,在每個層上設置多個sparsity levels,可以得到每個層對於修剪的敏感性。
在進行敏感度分析前,預訓練模型應達到最大的准確率,這是因為需要了解對於特定層權重的修剪對模型的性能造成的影響。
Distiler可以對結構進行敏感性評估,基於L1 –normal的獨立元素進行element-wise pruning 敏感度分析以及基於filters間的mean L1-Norm 的filter-wise pruning 。
相關函數:perform_sensitivity_analysis
正則化: 對模型算法進行修改的主要目的是減少generalization loss而不是訓練損失。
正則化與網絡稀疏表達的關系:
通過稀疏修剪對模型進行正則化處理:在給定sparsity constraint的限制條件下,網絡一旦達到了局部最小值,則放寬限制條件,讓網絡可以以更高准確率逃離局部極小值的鞍點。
正則化也可以應用到稀疏表達中:L2正則化可以減少過擬合,同時壓縮較大值的參數,但無法使這些參數的值完全變為0。而L1正則化則可以將某些參數置為0,在限制模型的同時對模型進行了簡化處理。
Group Regularization:
對整個groups of parameter elements進行正則化處理。無論這個group是否被稀疏化處理過,該group結構都要預先定義。

Distiller中的相關函數: distiller.GroupLassoRegularizer
量化
量化是減少表示一個數的bit位數的過程,deep learning中的主流精度為32位浮點精度。為了降低網絡模型的帶寬及計算需求,開始轉向較低精度的研究。
如果權重為二值的,如(-1,1)或者為(-1,0,1)則卷積層及全連接層中的計算可以由加減操作執行,將乘法操作完全移除。如果激活值也為二值類型的,則加法操作也可以移除,可以用位運算代替。
Integer與FP32的對比
數字精度的兩個屬性,I:動態范圍,即可以表示數字的范圍。II:根據需要表示的數值來決定精度。對於n位整形數字,其動態范圍為[-2^(n-1)..2^(n-1)-1],INT8:[-128,127],可以表示2^(N)個數字,網絡模型中32位精度能夠表示的分布范圍更廣,同時,模型中不同層動態范圍是不同的,權重及activation的分布也是不同的。
為了將tensor的浮點動態范圍映射到整數精度的范圍內增加了一個scale factor,scale factor大部分情況為浮點型。
避免溢出
累加器中存放卷積層及全連接層的中間結果。如果,將權重以及激活值的設置相同的比特位,很快就會溢出。因此,累加器中的精度要更高一些。兩個n-bit的數字相乘得到2n-bit的數字,卷積層中存在c*k^2次乘法操作,為了避免溢出,累加器的位數應為(2n+M),M至少為log2(c*k^2)
保守量化:INT8
如果將一個FP32的預訓練模型不進行再訓練直接將其量化為INT8,則會造成一定准確率的損失。
Simple factor的設置:根據每個tensor中的每一層進行確定,最簡單的方式就是將float tensor中的min/max映射為integer中的min/max,對於權重及偏差,這種操作比較簡單,是因為其訓練完成后,其值是固定的,而對於activation,其值可以從兩個方面進行獲得。
(1)Offline:即在部署模型之前進行激活值統計操作,基於統計的信息,計算得到scale factor,當模型部署完后將其值固定。這種方式存在一定的風險,即運行時,遇到的值超出了原來觀察到的范圍,而這個超出的值會被clip,進而造成准確率上的損失。
(2)Online:即在運行時動態的計算tensor中的min/max值。這種方式不會發生clip情況。而增加的計算量卻很大。
基於全精度的激活層存在異常值,可以通過縮小min/max的范圍將這個異常值進行丟棄,同時,也可以通過clip操作來提高包含主要信息部分的精度。一種簡單有效的方式是用min/max的均值來替換實際的值
另一個可以優化的點是scale factor:最常用的方法是每一層使用scale-factor,也可以每個通道進行設置。
貪婪型量化:INT4
將FP32模型直接量化為INT4或者更低的精度,使准確率大幅度降低
解決方法:
(1) training/retraining:將量化考慮到訓練中
(2) 替換激活函數:將無邊界的ReLU替換為有邊界的激活函數。
(3) 調整網絡的結構:通過增加層的寬度來彌補由於量化產生的損失信息。或者將卷積用多個二值卷積進行替換,通過scale可以調整覆蓋更大的范圍
(4) 模型的第一層和最后一層不進行量化操作
(5) 迭代量化:首先對預訓練的FP32的模型進行部分量化,然后進行再訓練恢復由於量化產生的准確率的損失
(6) 將權重及激活值的精度進行混合:激活對量化的敏感度相比權重要更敏感一些
包含量化的訓練

訓練過程中全精度的權重也被保留,用於記錄精度損失造成 的梯度上的變化,inference時只有量化后的權重起作用。
PART II: 基於Distiller 的分類模型
Step I : git clone https://github.com/NervanaSystems/distiller.git
Step II: 創建環境 python3 –m virtualenv env (安裝參考: https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/)
StepIII: cd distiller && pip3 install -e .
StepIV: cd distiller/examples/classifier_compression
StepV: 只對模型進行訓練,不進行壓縮處理
python3 compress_classifier.py --arch simplenet_cifar ../../../data.cifar10 -p 30 -j=1 --lr=0.01
--arch:模型名稱
-p:打印頻率
-j: 線程
--lr:學習率
StepVI: 統計稀疏模型的參數
python3 compress_classifier.py --resume=../ssl/checkpoints/checkpoint_trained_ch_regularized_dense.pth.tar -a=resnet20_cifar ../../../data.cifar10 --summary=sparsity
--resume:稀疏模型文件路徑
--summary: {sparsity,compute,model,modules,png,png_w_params,onnx}
Sparsity:統計稀疏信息
Compute:統計計算量信息
Model:詳細的模型描述
Modules:每層對應的類型
PNG:將網絡結構保存為圖片
png_w_params:將網絡中的權重信息保存為圖片
打印信息

Name:處理層名稱
Shape:該層尺寸
NNZ(dense):稀疏前該層的參數量,其值為shape[0]*shape[1]*shape[2]*shape[3]
NNZ(sparse):處理后該層的參數量。
Cols,Rows,Ch,2D,3D,Fine分別代表,在縱向,橫向,Channel-wise,Kernel-wise,Filter-wise,Element-wise(屬於Fine-grained pruning)上參數稀疏的程度,其值為(NNZ(dense)-NNZ(sparse)) / NNZ(dense)
Std:該層的標准差
Mean: 該層的均值
Abs-Mean:該層絕對值的均值
Total sparsity = (total_NNZ(dense) – total_NNZ(sparse)) / total_NNZ(dense)
StepVII: 統計稀疏模型的計算量
python3 compress_classifier.py --resume=../ssl/checkpoints/checkpoint_trained_ch_regularized_dense.pth.tar -a=resnet20_cifar ../../../data.cifar10 --summary=sparsity

Name:該層名稱
Type:該層的類型
Attrs: 卷積核大小
IFM:輸入尺寸
IFM volume:輸入維度 = IFM[0] * IFM[1] * IFM[2] * IFM[3]
OFM:輸出尺寸
OFM volume:輸出維度 = OFM[0] * OFM[1] * OFM[2] * OFM[3]
Weights volume:權重維度 = Attrs[0] * Attrs[1] * IFM[1] * OFM[1]
MACs = OFM[2] * OFM[3] * Weights volume
Step VIII: post-training quantization
python3 compress_classifier.py -a resnet20_cifar ../../../data.cifar10 --resume ../ssl/checkpoints/checkpoint_trained_dense.pth.tar --quantize-eval --evaluate
--quantize-eval 在進行evaluate前進行線性量化操作
PART III: Distiller 代碼部署步驟
Step I: 在 main.py中增加代碼
1. Preparing the code: 調用Distiller 通過sys.path將distiller的根目錄引入到main.py文件中。

2. 置定義輸入參數

--summary:有關壓縮說明
--compress:指明 compression schedule文件路徑
增加對上面兩個參數的處理代碼


3. Optimizer及learning-rate decay policy scheduler

4. setup the logging backends:一個是Python logger backend用於從文件及命令行中讀取參數;另一個是TensorBoard backend logger將logs信息變為tensorboard 數據文件(tflogger)

5. Training loop:

6. 下列代碼位於train中用於記錄信息

7. 在train外進行迭代訓練優化

