1. 程式人生 > >【神經網路】神經網路加速之量化模型

【神經網路】神經網路加速之量化模型

1.簡介

量化模型(Quantized Model)是一種模型加速(Model Acceleration)的方法的總稱,主要包括二值化網路(Binary Network)、三值化網路(Ternary Network)、深度壓縮(Deep Compression)。本文通過TensorLayer講解各類量化模型。

對於TensorLayer是基於TensorFlow的高階開發工具,TensorLayer提供了一套搭建量化網路的API。這套API不能加速,主要是進行產品部署,可以使用TensorLayer進行魔性訓練,然後可以使用C++實現二值化的計算。

2.模型介紹

比較常見的量化模型主要有DeepCompression、Binary-Net、Tenary-Net、Dorefa-Net。

2.1 DeepCompression

DeepCompression 主要分為三個部分:剪枝、量化、哈夫曼編碼

剪枝:在訓練的過程中,當網路收斂到一定的程度的時候,認為網路結構中小於閾值的網路權重對網路的作用很小,因此對這種權重引數進行剪枝,被剪掉的權重不會再接受任何梯度。

具體過程:首先載入網路,然後重新訓練到收斂,進行剪枝,重複這個過程。直到網路引數變為一個高度稀疏的矩陣。由於小的引數會不斷地進行剪枝,因此為了不斷地增大壓縮率,就要不斷地加大閾值。並針對該問題設計了這一個基於準確率損失和壓縮率上升的公式,當最後引數變為一個稀疏矩陣的時候,然後繼續編碼。不過當壓縮率低於一定的值的時候,採用編碼解碼的方式會增大演算法的開銷。

量化:將接近的值變成一個數。其實是一種權值共享的策略。量化後的權值張量是一個高度稀疏的有很多共享權值的矩陣,對於非零引數,還可以進行定點壓縮,從而獲得更高的壓縮率。

哈弗曼編碼:使用哈弗曼編碼方式對權值進行壓縮,但是這個編碼解碼過程是會增加時間開銷代價的。

2.2 Binary-Net

通常我們在構建神經網路的時候,使用的精度都是32位單精度浮點數。當網路模型規模較大的時候,就會消耗更多的記憶體資源。浮點數由一位符號位、八位指數位和尾數位三部分組成。完成浮點數加減主要一下四步:
(1)0運算元的檢查,如果在兩個運算元中有零值,則可以直接得到結果。
(2)比較階碼大小完成對接。
(3)尾數進行加減操作。
(4)結果規格化並進行舍入處理。

因此對於該問題,通過降低權重和輸出精度的方案進行模型加速,並使用位運算(bit-wise operator)代表普通的運算方式(arithmatic operator)。

對於網路模型中的權重,可以採用二值化的方式進行壓縮。
(1)直接將大於0的引數設定為1,小於0的引數設定為-1 。
(2)將絕對值大於的引數設定為1, 將絕對值小於的引數根據距離±1的遠近按概率隨機設定為±1。

由於引數和各層的輸出都是二值化的,但是梯度不得不使用較高精度的實數而不是二值進行儲存。梯度很小,那麼無法使用低精度來正確的表示梯度,同時梯度是具有高斯白噪聲的,只有累加梯度才能抵消噪音。另一方面,二值化相當於正則化的功能,可以防止模型過擬合。

舉例,如果是一個3*3的卷積核進行二值化,只有 2 的 9 次方個的卷積核。原本每個引數 32bit,壓縮後每個引數 1bit。在 GPU 上還可以進行 SWAR(single instruction,multiple data within register)的處理,對 xnor 進行優化,SWAR 的基本思想是將 32 個二進位制變數組連線成 32 位暫存器,從而在按位操作(例如 XNOR)上獲得 32 倍的加速。

2.3 Ternary-Net

多權值相對比於二值化具有更好的網路泛化能力。權值的分佈接近於一個正態分佈和一個均勻分佈的組合。使用一個 scale 引數去最小化三值化前的權值和三值化之後的權值的 L2 距離。

3.實驗分析

3.1 Binary-Net in MNIST

net = tl.layers.InputLayer(x, name='input')
# n_filter=32  filter_size=(5, 5)  strides=(1, 1)  bias don’t init
net = tl.layers.BinaryConv2d(net, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')

net = tl.layers.SignLayer(net)
net = tl.layers.BinaryConv2d(net, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
# net = tl.layers.DropoutLayer(net, 0.8, True, is_train, name='drop1')
net = tl.layers.SignLayer(net)
net = tl.layers.BinaryDenseLayer(net, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')

# net = tl.layers.DropoutLayer(net, 0.8, True, is_train, name='drop2')
net = tl.layers.SignLayer(net)
net = tl.layers.BinaryDenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

3.2 Ternary-Net in MNIST

net = tl.layers.InputLayer(x, name='input')
net = tl.layers.TernaryConv2d(net, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')
net = tl.layers.TernaryConv2d(net, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
net = tl.layers.TernaryDenseLayer(net, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')
net = tl.layers.TernaryDenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

3.3 DoReFa-Net in MNIST

net = tl.layers.InputLayer(x, name='input')
net = tl.layers.DorefaConv2d(net, 1, 3, 32, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn1')  #pylint: disable=bare-except
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool1')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn1')
net = tl.layers.DorefaConv2d(net, 1, 3, 64, (5, 5), (1, 1), padding='SAME', b_init=None, name='bcnn2')  #pylint: disable=bare-except
net = tl.layers.MaxPool2d(net, (2, 2), (2, 2), padding='SAME', name='pool2')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn2')

net = tl.layers.FlattenLayer(net)
net = tl.layers.DorefaDenseLayer(net, 1, 3, 256, b_init=None, name='dense')
net = tl.layers.BatchNormLayer(net, act=tl.act.htanh, is_train=is_train, name='bn3')
net = tl.layers.DenseLayer(net, 10, b_init=None, name='bout')
net = tl.layers.BatchNormLayer(net, is_train=is_train, name='bno')

4.卷積優化

4.1 記憶體換時間

如果深度學習中每一層的卷積都是針對同一張圖片,那麼所有的卷積核可以一起對這張圖片進行卷積運算,然後再分別儲存到不同的位置,這就可以增加記憶體的使用率,一次載入圖片,產生多次的資料,而不需要多次訪問圖片,這就是用記憶體來換時間。

4.2 乘法優化

將卷積核展開為矩陣,將影象也展開為矩陣,那麼原本的多次卷積核運算就轉換為了一個大矩陣的乘法。

4.3 GPU優化

對於GPU優化的主要問題在於IO的時間與計算時間之間的協調過程。
(1)瞭解IO的訪問情況以及IO的效能
(2)多執行緒的平行計算特性
(3)IO和平行計算間的計算時間重疊
NVIDIA的GPU通過增加計算流處理器中間的快取來提高效能,加快IO速度。

4.4 Strassen演算法

根據CNN的線性代數特性,增加演算法減少乘法,降低卷積運算的計算複雜度。
這裡寫圖片描述

4.5 卷積中的資料重用

權重固定:可以最小化權重讀取的消耗,最大化卷積和權重的重複利用。
輸出固定:最小化部分和R/W能量消耗,最大化本地積累。
NLR(No Local Reuse):使用大型全域性緩衝區共享儲存,減少DRAM的訪問消耗。
RS:在內部的暫存器中最大化重用和累加,針對整體能源效率進行優化,而不是隻針對某種資料型別。