#概要
tf.kerasを使って構築されたネットワークのパラメータを量子化してみました。
パラメータを量子化するメリットは、大雑把に次の2つです。
- 保存するパラメータのサイズが小さくなる
32bit float を例えば 8bit float に量子化できればメモリ使用量が1/4になります - 演算を高速化可能
専用のハードウェアを用意すれば、演算が高速化される(はず)
入力及びパラメータを1bitまで量子化できれば、掛け算がビット演算になるのですんごい速い(AlexNetで58倍高速化できたそうです ※1)
デメリットはもちろん、本来のパラメータが量子化されることによって誤差が大きくなり、出力の精度が落ちることです。
後々専用のハードウェアを作成して速度も検証してみたいですが、今回は精度だけ比較してみます。
#環境
OS: ubuntu 16.04 LTS
Python: 3.5.2
TensorFlow: 1.13.1
#量子化とは
量子化とは、アナログ信号などの連続量を整数などの離散値で近似的に表現すること。 自然界の信号などをコンピュータで処理・保存できるようデジタルデータに置き換える際などによく行われる。(Wikipedia, 2019/12/17)
普通、tensorflowを用いてネットワークを構築した場合、重みやしきい値などのパラメータは float32、つまり4Byte で宣言されるのではないかと思います。それらの各ビットは符号部、指数部、仮数部にわけられ、それぞれの役目を果たします。
それらのビットより、float型の数値は、
float の表す値 = (-1)^符号部 × 2^(指数部-127) × 1.仮数部
として計算されます。
(参考:http://www.cc.kyoto-su.ac.jp/~yamada/programming/float.html)
これで大きな数から小さな数、整数から小数点以下が細かいものまでfloat型で表現できますね!しかし、実際そんなに細かい数値が必要なのでしょうか。
例えばある重みの値が0.123456789とすれば、
00111101111111001101011011101010
と表現されます。
では、23bit存在した仮数部を10bitに減らしてみるとどうでしょうか。(わかりやすくするために減らした分を0で埋めています)
00111101111111001100000000000000
となりますね。これは0.12182617187という値を指します。元々の数との相対誤差は98.68%です。これはほとんど変わらないと言っていいのではないでしょうか。
この場合だと、仮数部の下13bitが省略されたので、実質19bitで表現されていることになります。
これを、32bitだった数値が19bitに量子化された といいます。
なお、今の例では仮数部のみを減らしましたが、指数部なども減らすことでより小さく量子化することが可能です。
#計測
##量子化なし
前回作成したネットワークをそのまま使ってみました。
学習のエポックは10で、このモデルのパラメータを保存しておきます。
> model.summary()
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 480, 640, 3) 0
__________________________________________________________________________________________________
conv2d (Conv2D) (None, 240, 320, 32) 2432 input_1[0][0]
__________________________________________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 119, 159, 32) 0 conv2d[0][0]
__________________________________________________________________________________________________
batch_normalization_v1 (BatchNo (None, 119, 159, 32) 128 max_pooling2d[0][0]
__________________________________________________________________________________________________
activation (Activation) (None, 119, 159, 32) 0 batch_normalization_v1[0][0]
__________________________________________________________________________________________________
conv2d_1 (Conv2D) (None, 60, 80, 32) 9248 activation[0][0]
__________________________________________________________________________________________________
batch_normalization_v1_1 (Batch (None, 60, 80, 32) 128 conv2d_1[0][0]
__________________________________________________________________________________________________
activation_1 (Activation) (None, 60, 80, 32) 0 batch_normalization_v1_1[0][0]
__________________________________________________________________________________________________
conv2d_3 (Conv2D) (None, 60, 80, 32) 1056 max_pooling2d[0][0]
__________________________________________________________________________________________________
conv2d_2 (Conv2D) (None, 60, 80, 32) 9248 activation_1[0][0]
__________________________________________________________________________________________________
add (Add) (None, 60, 80, 32) 0 conv2d_3[0][0]
conv2d_2[0][0]
__________________________________________________________________________________________________
batch_normalization_v1_2 (Batch (None, 60, 80, 32) 128 add[0][0]
__________________________________________________________________________________________________
activation_2 (Activation) (None, 60, 80, 32) 0 batch_normalization_v1_2[0][0]
__________________________________________________________________________________________________
conv2d_4 (Conv2D) (None, 30, 40, 64) 18496 activation_2[0][0]
__________________________________________________________________________________________________
batch_normalization_v1_3 (Batch (None, 30, 40, 64) 256 conv2d_4[0][0]
__________________________________________________________________________________________________
activation_3 (Activation) (None, 30, 40, 64) 0 batch_normalization_v1_3[0][0]
__________________________________________________________________________________________________
conv2d_6 (Conv2D) (None, 30, 40, 64) 2112 add[0][0]
__________________________________________________________________________________________________
conv2d_5 (Conv2D) (None, 30, 40, 64) 36928 activation_3[0][0]
__________________________________________________________________________________________________
add_1 (Add) (None, 30, 40, 64) 0 conv2d_6[0][0]
conv2d_5[0][0]
__________________________________________________________________________________________________
batch_normalization_v1_4 (Batch (None, 30, 40, 64) 256 add_1[0][0]
__________________________________________________________________________________________________
activation_4 (Activation) (None, 30, 40, 64) 0 batch_normalization_v1_4[0][0]
__________________________________________________________________________________________________
conv2d_7 (Conv2D) (None, 15, 20, 128) 73856 activation_4[0][0]
__________________________________________________________________________________________________
batch_normalization_v1_5 (Batch (None, 15, 20, 128) 512 conv2d_7[0][0]
__________________________________________________________________________________________________
activation_5 (Activation) (None, 15, 20, 128) 0 batch_normalization_v1_5[0][0]
__________________________________________________________________________________________________
conv2d_9 (Conv2D) (None, 15, 20, 128) 8320 add_1[0][0]
__________________________________________________________________________________________________
conv2d_8 (Conv2D) (None, 15, 20, 128) 147584 activation_5[0][0]
__________________________________________________________________________________________________
add_2 (Add) (None, 15, 20, 128) 0 conv2d_9[0][0]
conv2d_8[0][0]
__________________________________________________________________________________________________
flatten (Flatten) (None, 38400) 0 add_2[0][0]
__________________________________________________________________________________________________
activation_6 (Activation) (None, 38400) 0 flatten[0][0]
__________________________________________________________________________________________________
dropout (Dropout) (None, 38400) 0 activation_6[0][0]
__________________________________________________________________________________________________
dense (Dense) (None, 3) 115203 dropout[0][0]
__________________________________________________________________________________________________
activation_7 (Activation) (None, 3) 0 dense[0][0]
==================================================================================================
Total params: 425,891
Trainable params: 425,187
Non-trainable params: 704
__________________________________________________________________________________________________
10エポック学習させて、1000枚のテストデータでの精度が、こんな感じ
Accuracy: 0.815
Calc :
[[244. 0. 21.]
[ 55. 0. 56.]
[ 53. 0. 571.]]
Labels :
[[265. 0. 0.]
[ 0. 111. 0.]
[ 0. 0. 624.]]
Average_accuracy :
[0.92075472 0. 0.9150641 ]
やっぱり2つ目のラベルの精度が低いのはおいておきます。
それぞれのレイヤーの学習可能なパラメータを確認してみましょう。
> [x.name for x in model.trainable_variables]
['conv2d/kernel:0', 'conv2d/bias:0', 'batch_normalization_v1/gamma:0', 'batch_normalization_v1/beta:0', 'conv2d_1/kernel:0', 'conv2d_1/bias:0', 'batch_normalization_v1_1/gamma:0', 'batch_normalization_v1_1/beta:0', 'conv2d_3/kernel:0', 'conv2d_3/bias:0', 'conv2d_2/kernel:0', 'conv2d_2/bias:0', 'batch_normalization_v1_2/gamma:0', 'batch_normalization_v1_2/beta:0', 'conv2d_4/kernel:0', 'conv2d_4/bias:0', 'batch_normalization_v1_3/gamma:0', 'batch_normalization_v1_3/beta:0', 'conv2d_6/kernel:0', 'conv2d_6/bias:0', 'conv2d_5/kernel:0', 'conv2d_5/bias:0', 'batch_normalization_v1_4/gamma:0', 'batch_normalization_v1_4/beta:0', 'conv2d_7/kernel:0', 'conv2d_7/bias:0', 'batch_normalization_v1_5/gamma:0', 'batch_normalization_v1_5/beta:0', 'conv2d_9/kernel:0', 'conv2d_9/bias:0', 'conv2d_8/kernel:0', 'conv2d_8/bias:0', 'dense/kernel:0', 'dense/bias:0']
どうやら、畳み込み層と全結合層は kernel bias の2種類のパラメータをもち、バッチ正規化層は gamma beta の2種類のパラメータを持っているようです。一応中身を確認しておきましょう。どちらも一番後ろのレイヤーのパラメータのみのせておきます。
<tensorflow.python.keras.layers.core.Dense object at 0x7ffa40497128> [<tf.Variable 'dense/kernel:0' shape=(38400, 3) dtype=float32, numpy=
array([[-0.01491976, 0.01396543, 0.00093346],
[ 0.01540731, 0.00241674, 0.01131605],
[ 0.0119241 , -0.01668087, -0.00719858],
...,
[-0.00296943, 0.01103336, 0.00349635],
[ 0.00204816, -0.00147773, 0.00720838],
[-0.00207304, 0.00621508, -0.01145099]], dtype=float32)>, <tf.Variable 'dense/bias:0' shape=(3,) dtype=float32, numpy=array([ 4.8551615e-03, -5.9992936e-03, -4.9170827e-05], dtype=float32)>]
<tensorflow.python.keras.layers.normalization.BatchNormalizationV1 object at 0x7f0dc86d2898> [<tf.Variable 'batch_normalization_v1_5/gamma:0' shape=(128,) dtype=float32, numpy=
array([1.0053827 , 1.0023135 , 1.0041403 , 0.9895478 , 1.0039909 ,
0.98955464, 1.0088704 , 0.9910637 , 0.9913659 , 0.99780196,
1.000202 , 1.0014107 , 1.0005019 , 0.9903944 , 0.9944405 ,
0.9973047 , 1.0017028 , 0.9907751 , 0.99735796, 1.0081666 ,
0.99636775, 1.0030036 , 0.99932915, 1.004627 , 0.99306023,
1.0017363 , 0.98416674, 0.99064416, 1.0082388 , 1.0035444 ,
0.99265665, 0.99718237, 1.0001222 , 0.99854386, 1.0034426 ,
0.9999184 , 0.9963349 , 0.9844031 , 1.0056257 , 0.9994289 ,
0.99500334, 0.9923768 , 0.99127066, 0.9832162 , 1.0021056 ,
1.0047147 , 0.99886245, 1.0052888 , 0.99249786, 1.0065212 ,
0.99161077, 0.9932604 , 1.0078368 , 1.003879 , 0.9882739 ,
1.0010694 , 0.99350995, 0.99350333, 0.99219364, 0.99605584,
0.9823839 , 0.99674433, 1.0070174 , 1.00066 , 0.99863935,
0.99942183, 1.000792 , 1.0028559 , 0.9946366 , 0.99531853,
1.0003756 , 0.99510074, 0.9922438 , 1.005276 , 1.0053014 ,
0.9929603 , 0.99317366, 1.0056378 , 1.003331 , 1.0047318 ,
0.99011916, 1.0086612 , 0.99564356, 0.97692627, 0.9968611 ,
1.0015146 , 1.0121565 , 0.9942222 , 0.9938692 , 0.98945427,
1.0070571 , 0.99605334, 1.00008 , 1.0103743 , 0.991692 ,
0.98665 , 0.9992022 , 0.98171806, 0.99035376, 0.99664146,
1.0095577 , 1.0159916 , 1.0002235 , 0.9792612 , 1.0048499 ,
1.0002121 , 1.0031438 , 0.9905956 , 1.0046396 , 0.99771607,
0.99895054, 1.0037303 , 1.0038016 , 1.0067954 , 0.9906669 ,
0.9937981 , 0.9853775 , 0.99674666, 0.9853735 , 0.9933321 ,
0.9874309 , 1.0152614 , 1.0054263 , 0.99592394, 1.005618 ,
0.9954677 , 0.98061544, 1.0030056 ], dtype=float32)>,
<tf.Variable 'batch_normalization_v1_5/beta:0' shape=(128,) dtype=float32, numpy=
array([ 0.00262505, -0.01182571, 0.00543317, 0.00769176, -0.00662231,
-0.01436215, -0.01028995, -0.00620002, -0.00588665, -0.00019779,
0.004008 , -0.00465797, -0.0097509 , -0.00081195, -0.0140775 ,
-0.0078128 , 0.00924537, -0.0057598 , -0.0017888 , 0.00137905,
-0.01389314, 0.00725058, -0.01442415, -0.00770982, -0.01115939,
0.00144741, -0.00496186, -0.00233451, -0.00604108, -0.00561837,
0.00053209, 0.00626049, -0.00196816, 0.00111859, 0.00413055,
-0.00590881, -0.00492901, -0.01062248, -0.00155396, -0.00236207,
0.00136854, -0.00125252, -0.01765665, -0.01969386, -0.01379174,
-0.00236314, -0.00634788, -0.00099529, -0.00986335, 0.00195062,
-0.00986179, -0.00456762, 0.00789357, 0.00510846, -0.00891418,
-0.00093365, -0.00342024, -0.01001325, 0.00412015, -0.01361436,
-0.01547966, -0.00597356, -0.00561096, 0.00430186, 0.00629607,
-0.0032707 , -0.01115292, -0.01054228, -0.00661343, -0.0068517 ,
0.00250201, -0.0119157 , 0.00174254, -0.00736857, 0.00990474,
-0.01210231, -0.00133941, 0.00786918, -0.01437676, 0.00021543,
-0.01157008, 0.00787573, -0.01417518, -0.01176108, 0.01217426,
0.00777597, 0.00527618, 0.00068975, -0.01436776, -0.00979709,
0.00090197, -0.00535901, -0.00172908, 0.00525916, -0.00055489,
-0.01297212, -0.00279675, -0.0019424 , -0.01119416, -0.00220624,
0.01222709, 0.01195927, -0.00557467, -0.01582625, -0.00113147,
0.00383603, -0.0025774 , -0.00764492, -0.0054293 , -0.00483815,
0.00011727, 0.00274837, 0.0107253 , 0.00365951, 0.0037286 ,
-0.0052775 , -0.01400672, -0.01110312, -0.01492126, -0.00020007,
-0.02051772, 0.00057484, 0.00417511, -0.01659194, 0.0063057 ,
-0.00042243, -0.01960172, 0.00553354], dtype=float32)>]
kernel, biasは平均値が0に近く見えますね。
gamma, betaはそれぞれ1, 0が平均になっているように見えます。
これってつまり平均が0になるならば、正の値ならば1に、負の値なら-1へ
平均が1になるのであれば1以上の値であれば1に、それ未満であれば-1へ置き換えてやってもうまくいくのではないだろうか。やってみましょう。
なお、これらのパラメータの数値が見えない場合は、コードの最初のあたりでeager executionを有効にすると、print()で中身が見えるようになります。その結果学習時間が3倍程度に長くなりますが。これはtensorFlow 2.x からはデフォルトで有効になるみたいですね。実行速度が落ちないといいのですが。
tf.enable_eager_execution()
##量子化あり(1bit ※2)
それでは、先ほど保存したモデルを読み込んで、パラメータを量子化してみましょう。今回は最も単純な、1bit量子化(バイナリ化)します。
###畳み込み層、全結合層のパラメーターの量子化
まずは、畳み込み層及び全結合層のパラメーター、kernelとbiasをバイナリ化してみましょう。ここでのバイナリ化の式は単純で、こんな感じです
if parameter >= 0, then parameter ← 1
otherwise, parameter ← -1
layers = list()
i = 0
while True:
try:
l = model.get_layer(index = i)
layers.append(l)
i += 1
except Exception as e:
print(e, "No more layers.\n")
break
for num in range(i):
try:
bias = layers[num].bias
kernel = layers[num].kernel
except Exception as e:
#print(e)
continue
print(bias)
print(kernel)
#print(np.shape(bias), np.shape(kernel))
k_shape = np.shape(kernel)
b_shape = np.shape(bias)
k = tf.reshape(kernel, [-1])
b = tf.reshape( bias, [-1])
new_kernel = np.zeros(len(k))
new_bias = np.zeros(len(b))
for l in range(len(k)):
if k[l] >= 0:
new_kernel[l] = 1
else:
new_kernel[l] = -1
for l in range(len(b)):
if b[l] >= 0:
new_bias[l] = 1
else:
new_bias[l] = -1
new_kernel = np.reshape(new_kernel, k_shape)
new_bias = np.reshape(new_bias , b_shape)
tf.assign(layers[num].bias , new_bias)
tf.assign(layers[num].kernel, new_kernel)
print(model.get_layer(index=num).bias)
print(model.get_layer(index=num).kernel)
print()
さて出力は、、
<tf.Variable 'dense/bias:0' shape=(3,) dtype=float32, numpy=array([ 4.8551615e-03, -5.9992936e-03, -4.9170827e-05], dtype=float32)>
<tf.Variable 'dense/kernel:0' shape=(38400, 3) dtype=float32, numpy=
array([[-0.01491976, 0.01396543, 0.00093346],
[ 0.01540731, 0.00241674, 0.01131605],
[ 0.0119241 , -0.01668087, -0.00719858],
...,
[-0.00296943, 0.01103336, 0.00349635],
[ 0.00204816, -0.00147773, 0.00720838],
[-0.00207304, 0.00621508, -0.01145099]], dtype=float32)>
<tf.Variable 'dense/bias:0' shape=(3,) dtype=float32, numpy=array([ 1., -1., -1.], dtype=float32)>
<tf.Variable 'dense/kernel:0' shape=(38400, 3) dtype=float32, numpy=
array([[-1., 1., 1.],
[ 1., 1., 1.],
[ 1., -1., -1.],
...,
[-1., 1., 1.],
[ 1., -1., 1.],
[-1., 1., -1.]], dtype=float32)>
上側が元々のkernel及びbiasの値、下側がバイナリ化した結果の値です。無事パラメータを置き換えることができていますね!ただ代入しただけなので型はもちろんfloat32のままです。なので実際はバイナリ化できていませんが、今回は精度だけに着目しているので、とりあえず今はこれでいいでしょう。さて気になる精度は、、
Accuracy: 0.823
Calc :
[[234. 0. 31.]
[ 52. 0. 59.]
[ 35. 0. 589.]]
Labels :
[[265. 0. 0.]
[ 0. 111. 0.]
[ 0. 0. 624.]]
Average_accuracy :
[0.88301887 0. 0.94391026]
あれ、、バイナリ化前より微妙に良くなってる、、もう一度バイナリ化前の結果を見てみよう
Accuracy: 0.815
Calc :
[[244. 0. 21.]
[ 55. 0. 56.]
[ 53. 0. 571.]]
Labels :
[[265. 0. 0.]
[ 0. 111. 0.]
[ 0. 0. 624.]]
Average_accuracy :
[0.92075472 0. 0.9150641 ]
うーん、、誤差レベルでしょうが、わずかに精度が上がっていますね。相変わらず2つ目のラベルの精度が0ですが。
この程度の小さいモデルであれば、kernelとbiasを2値化してもほとんど精度は変わらないようです。
###バッチ正規化層のパラメーターの量子化
では次は、バッチ正規化層のパラメーター gammaとbeta を量子化してみましょう。
量子化方法は先程の同じですが、gammaは平均値が1になっているっぽいので、1以上なら2、それ未満なら0でやってみましょうか。1bitじゃなくなりますけど笑
<tf.Variable 'batch_normalization_v1_5/gamma:0' shape=(128,) dtype=float32, numpy=
array([1.0053827 , 1.0023135 , 1.0041403 , 0.9895478 , 1.0039909 ,
0.98955464, 1.0088704 , 0.9910637 , 0.9913659 , 0.99780196,
1.000202 , 1.0014107 , 1.0005019 , 0.9903944 , 0.9944405 ,
0.9973047 , 1.0017028 , 0.9907751 , 0.99735796, 1.0081666 ,
0.99636775, 1.0030036 , 0.99932915, 1.004627 , 0.99306023,
1.0017363 , 0.98416674, 0.99064416, 1.0082388 , 1.0035444 ,
0.99265665, 0.99718237, 1.0001222 , 0.99854386, 1.0034426 ,
0.9999184 , 0.9963349 , 0.9844031 , 1.0056257 , 0.9994289 ,
0.99500334, 0.9923768 , 0.99127066, 0.9832162 , 1.0021056 ,
1.0047147 , 0.99886245, 1.0052888 , 0.99249786, 1.0065212 ,
0.99161077, 0.9932604 , 1.0078368 , 1.003879 , 0.9882739 ,
1.0010694 , 0.99350995, 0.99350333, 0.99219364, 0.99605584,
0.9823839 , 0.99674433, 1.0070174 , 1.00066 , 0.99863935,
0.99942183, 1.000792 , 1.0028559 , 0.9946366 , 0.99531853,
1.0003756 , 0.99510074, 0.9922438 , 1.005276 , 1.0053014 ,
0.9929603 , 0.99317366, 1.0056378 , 1.003331 , 1.0047318 ,
0.99011916, 1.0086612 , 0.99564356, 0.97692627, 0.9968611 ,
1.0015146 , 1.0121565 , 0.9942222 , 0.9938692 , 0.98945427,
1.0070571 , 0.99605334, 1.00008 , 1.0103743 , 0.991692 ,
0.98665 , 0.9992022 , 0.98171806, 0.99035376, 0.99664146,
1.0095577 , 1.0159916 , 1.0002235 , 0.9792612 , 1.0048499 ,
1.0002121 , 1.0031438 , 0.9905956 , 1.0046396 , 0.99771607,
0.99895054, 1.0037303 , 1.0038016 , 1.0067954 , 0.9906669 ,
0.9937981 , 0.9853775 , 0.99674666, 0.9853735 , 0.9933321 ,
0.9874309 , 1.0152614 , 1.0054263 , 0.99592394, 1.005618 ,
0.9954677 , 0.98061544, 1.0030056 ], dtype=float32)>
<tf.Variable 'batch_normalization_v1_5/beta:0' shape=(128,) dtype=float32, numpy=
array([ 0.00262505, -0.01182571, 0.00543317, 0.00769176, -0.00662231,
-0.01436215, -0.01028995, -0.00620002, -0.00588665, -0.00019779,
0.004008 , -0.00465797, -0.0097509 , -0.00081195, -0.0140775 ,
-0.0078128 , 0.00924537, -0.0057598 , -0.0017888 , 0.00137905,
-0.01389314, 0.00725058, -0.01442415, -0.00770982, -0.01115939,
0.00144741, -0.00496186, -0.00233451, -0.00604108, -0.00561837,
0.00053209, 0.00626049, -0.00196816, 0.00111859, 0.00413055,
-0.00590881, -0.00492901, -0.01062248, -0.00155396, -0.00236207,
0.00136854, -0.00125252, -0.01765665, -0.01969386, -0.01379174,
-0.00236314, -0.00634788, -0.00099529, -0.00986335, 0.00195062,
-0.00986179, -0.00456762, 0.00789357, 0.00510846, -0.00891418,
-0.00093365, -0.00342024, -0.01001325, 0.00412015, -0.01361436,
-0.01547966, -0.00597356, -0.00561096, 0.00430186, 0.00629607,
-0.0032707 , -0.01115292, -0.01054228, -0.00661343, -0.0068517 ,
0.00250201, -0.0119157 , 0.00174254, -0.00736857, 0.00990474,
-0.01210231, -0.00133941, 0.00786918, -0.01437676, 0.00021543,
-0.01157008, 0.00787573, -0.01417518, -0.01176108, 0.01217426,
0.00777597, 0.00527618, 0.00068975, -0.01436776, -0.00979709,
0.00090197, -0.00535901, -0.00172908, 0.00525916, -0.00055489,
-0.01297212, -0.00279675, -0.0019424 , -0.01119416, -0.00220624,
0.01222709, 0.01195927, -0.00557467, -0.01582625, -0.00113147,
0.00383603, -0.0025774 , -0.00764492, -0.0054293 , -0.00483815,
0.00011727, 0.00274837, 0.0107253 , 0.00365951, 0.0037286 ,
-0.0052775 , -0.01400672, -0.01110312, -0.01492126, -0.00020007,
-0.02051772, 0.00057484, 0.00417511, -0.01659194, 0.0063057 ,
-0.00042243, -0.01960172, 0.00553354], dtype=float32)>
<tf.Variable 'batch_normalization_v1_5/beta:0' shape=(128,) dtype=float32, numpy=
array([ 1., -1., 1., 1., -1., -1., -1., -1., -1., -1., 1., -1., -1.,
-1., -1., -1., 1., -1., -1., 1., -1., 1., -1., -1., -1., 1.,
-1., -1., -1., -1., 1., 1., -1., 1., 1., -1., -1., -1., -1.,
-1., 1., -1., -1., -1., -1., -1., -1., -1., -1., 1., -1., -1.,
1., 1., -1., -1., -1., -1., 1., -1., -1., -1., -1., 1., 1.,
-1., -1., -1., -1., -1., 1., -1., 1., -1., 1., -1., -1., 1.,
-1., 1., -1., 1., -1., -1., 1., 1., 1., 1., -1., -1., 1.,
-1., -1., 1., -1., -1., -1., -1., -1., -1., 1., 1., -1., -1.,
-1., 1., -1., -1., -1., -1., 1., 1., 1., 1., 1., -1., -1.,
-1., -1., -1., -1., 1., 1., -1., 1., -1., -1., 1.],
dtype=float32)>
<tf.Variable 'batch_normalization_v1_5/gamma:0' shape=(128,) dtype=float32, numpy=
array([2., 2., 2., 0., 2., 0., 2., 0., 0., 0., 2., 2., 2., 0., 0., 0., 2.,
0., 0., 2., 0., 2., 0., 2., 0., 2., 0., 0., 2., 2., 0., 0., 2., 0.,
2., 0., 0., 0., 2., 0., 0., 0., 0., 0., 2., 2., 0., 2., 0., 2., 0.,
0., 2., 2., 0., 2., 0., 0., 0., 0., 0., 0., 2., 2., 0., 0., 2., 2.,
0., 0., 2., 0., 0., 2., 2., 0., 0., 2., 2., 2., 0., 2., 0., 0., 0.,
2., 2., 0., 0., 0., 2., 0., 2., 2., 0., 0., 0., 0., 0., 0., 2., 2.,
2., 0., 2., 2., 2., 0., 2., 0., 0., 2., 2., 2., 0., 0., 0., 0., 0.,
0., 0., 2., 2., 0., 2., 0., 0., 2.], dtype=float32)>
Accuracy: 0.818
Calc :
[[243. 0. 22.]
[ 55. 0. 56.]
[ 49. 0. 575.]]
Labels :
[[265. 0. 0.]
[ 0. 111. 0.]
[ 0. 0. 624.]]
Average_accuracy :
[0.91698113 0. 0.92147436]
こちらも結果は誤差レベルですかね。精度が著しく落ちると思っていたのでこれは予想外の結果です。
なお、gammaは平均が0になるようにしたり、全て1で揃えてしまっても精度は80%程度とそこまで結果は悪くなりませんでした。
全てのパラメーターを量子化した結果を最後にのせておきます。
Accuracy: 0.822
Calc :
[[230. 0. 35.]
[ 51. 0. 60.]
[ 32. 0. 592.]]
Labels :
[[265. 0. 0.]
[ 0. 111. 0.]
[ 0. 0. 624.]]
Average_accuracy :
[0.86792453 0. 0.94871795]
#まとめ
今回は学習済みのモデルのパラメーターを量子化しました。結果は、これくらいの小さなモデルであれば最終的な精度の差はあまりないように見えました。ですが、混合行列を観察してみると、案外結果に変動があるので、ラベルの数が増えたり(今回は実質ラベル2つですが笑)すると大きく精度に差が出る可能性もありますね。
今回はPart1ということで、次回の候補は、
・学習用のデータを増やすことで、元々のモデルでの精度を上げた後に再び試してみる
・今回は学習済みモデルのパラメーターを量子化したが、学習中に少しずつ量子化していく
・入力データ(活性)も量子化してみる
のどれかになりそうです。
#参考、注釈
※1 Rastegari, M., et al. "Xnor-net: Imagenet classification using binary convolutional neural networks." European conference on computer vision. Springer, Cham, 2016.
※2 本来1bitに量子化すると 0, 1のどちらかの値になる必要があるが、2通りの値しかとらないので、ここでは1bit量子化と書いています
#追記
量子化の際に結構な量のパラメータをループで、さらにif文で正負を判定しているので、上に書いたコードだとどうしてもそれなりに時間がかかってしまいます。
for l in range(len(k)):
if k[l] >= 0:
new_kernel[l] = 1
else:
new_kernel[l] = -1
これを高速化できそうな式を発見したのでそれに置き換えてみます。
for l in range(len(k)):
new_kernel[l] = np.float32(2.*np.greater_equal(k[l],0)-1.)
これはスマートでかっこいいですね。こんなコードをさっとかけるようになりたいものです、、
##追追記
上の追記では、if文を減らすことで計算時間が短縮できました。しかし、実はどうやらforループのほうが時間がかかっているようです。tensorをnumpy配列に変換することで、容易に値を操作できるので、ループを使わずパラメータの値を操作してみましょう。
new_kernel = kernel.numpy()
new_kernel = np.greater_equal(new_kernel, 0)
new_kernel = new_kernel * 2. - 1.
旧コード、if文を無くした新1コード、ループも消した新2コードで実行時間を比較してみましょう。
旧コード | 新1コード | 新2コード | |
---|---|---|---|
実行時間 (sec) | 116.17 | 92.05 | 0.160 |
いやぁ、これはすごい違いですね。pythonはやはりループが遅いので、行列やベクトルはなるべくnp.whereやtf.whereなどで一般化したほうがいいですね。なお、実行中にパラメータを標準出力したりしているので、計算だけならもっと高速にできそうですね。
学習後のパラメータを量子化しているのでそこまで重要じゃないかもしれませんが、学習中に更新する場合はこの差が積み重なってとんでもないほど遅くなりそうですね、、、
ループ、if文を避け、numpyやtensorFlow等のライブラリで計算するということは覚えておいたほうが良さそうです!
計測マシン:
Tesla V100-PCIE-32GB