CNTK(Cognitive Tool Kit)によるBinary Convolution Neural Network

CNNを二値化する取り組みは制限のきついハードウエアでのCNNの実行が主な目的です
二値化することにより、下記のようなメリットが得られます。
-ハードウエア化を簡単にする
-コンパクトかつ高速化を行う

CNTKでBNNをトレーニングして実行する部分については、下記の記事を参照ください。BNNはMicrosoft Embedded Learning Libraryに付属しているモジュールを使用しています。
https://qiita.com/yasukit/items/a5507e131583d2fca490

コードやモデルは以下のサイトにまとめています。
https://github.com/yasukit1414/binaryconv

実際に、CNTKを使って、CNNとBNNを比較してみました。


#BNNの作成と学習

```
    scaled_input = C.element_times(C.constant(0.00390625), feature_var)


    z = C.layers.Convolution((3, 3), 32, pad=True, activation=C.relu)(scaled_input)
    z = C.layers.MaxPooling((3,3), strides=(2,2))(z)

    z = C.layers.BatchNormalization(map_rank=1)(z)
    z = BinaryConvolution((3,3), 128, channels=32, pad=True)(z)
   
    z = C.layers.MaxPooling((3,3), strides=(2,2))(z)

    z = C.layers.BatchNormalization(map_rank=1)(z)
    z = BinaryConvolution((3,3), 128, channels=128, pad=True)(z)
    z = C.layers.MaxPooling((3,3), strides=(2,2))(z)

    z = C.layers.BatchNormalization(map_rank=1)(z)
    z = BinaryConvolution((1,1), num_classes, channels=128, pad=True)(z)
    z = C.layers.AveragePooling((z.shape[1], z.shape[2]))(z)
    z = C.reshape(z, (num_classes,))


    SP = C.parameter(shape=z.shape, init=0.001)
    z = C.element_times(z, SP)
```

下記がBNNの各オペレーションになります。

```
ElementTimes : 32x32x3 -> 34x34x3 | input padding 0 output padding 1
Convolution (ReLU) : 34x34x3 -> 32x32x32 | input padding 1 output padding 0
MaxPooling : 32x32x32 -> 15x15x32 | input padding 0 output padding 0
BatchNormalization : 15x15x32 -> 17x17x32 | input padding 0 output padding 1
BinaryConvolution : 17x17x32 -> 15x15x128 | input padding 1 output padding 0
Plus : 15x15x128 -> 15x15x128 | input padding 0 output padding 0
PReLU : 15x15x128 -> 15x15x128 | input padding 0 output padding 0
MaxPooling : 15x15x128 -> 7x7x128 | input padding 0 output padding 0
BatchNormalization : 7x7x128 -> 9x9x128 | input padding 0 output padding 1
BinaryConvolution : 9x9x128 -> 7x7x128 | input padding 1 output padding 0
Plus : 7x7x128 -> 7x7x128 | input padding 0 output padding 0
PReLU : 7x7x128 -> 7x7x128 | input padding 0 output padding 0
MaxPooling : 7x7x128 -> 3x3x128 | input padding 0 output padding 0
BatchNormalization : 3x3x128 -> 3x3x128 | input padding 0 output padding 0
BinaryConvolution : 3x3x128 -> 3x3x10 | input padding 0 output padding 0
Plus : 3x3x10 -> 3x3x10 | input padding 0 output padding 0
PReLU : 3x3x10 -> 3x3x10 | input padding 0 output padding 0
AveragePooling : 3x3x10 -> 1x1x10 | input padding 0 output padding 0
ElementTimes : 1x1x10 -> 1x1x10 | input padding 0 output padding 0
Softmax : 1x1x10 -> 1x1x10 | input padding 0 output padding 0
```

#CNNの作成と学習
基本のCNNにBatch Normalizationを入れた形になっています。BNNと構成を合わせるように作成しています。

```
    scaled_input = C.element_times(C.constant(0.00390625), feature_var)

    # first layer is ok to be full precision
    z = C.layers.Convolution((3, 3), 32, pad=True, activation=C.relu)(scaled_input)
    z = C.layers.MaxPooling((3,3), strides=(2,2))(z)

    z = C.layers.BatchNormalization(map_rank=1)(z)
    z = C.layers.Convolution((3,3), 128, pad=True)(z)
    z = C.layers.MaxPooling((3,3), strides=(2,2))(z)

    z = C.layers.BatchNormalization(map_rank=1)(z)
    z = C.layers.Convolution((3,3), 128,  pad=True)(z)
    z = C.layers.MaxPooling((3,3), strides=(2,2))(z)

    z = C.layers.BatchNormalization(map_rank=1)(z)
    z = C.layers.Convolution((1,1), num_classes,  pad=True)(z)
    z = C.layers.AveragePooling((z.shape[1], z.shape[2]))(z)
    z = C.reshape(z, (num_classes,))



    SP = C.parameter(shape=z.shape, init=0.001)
    z = C.element_times(z, SP)
```
実際のオペレーションの中身になります。

```
ElementTimes : 32x32x3 -> 34x34x3 | input padding 0 output padding 1
Convolution (ReLU) : 34x34x3 -> 32x32x32 | input padding 1 output padding 0
MaxPooling : 32x32x32 -> 15x15x32 | input padding 0 output padding 0
BatchNormalization : 15x15x32 -> 17x17x32 | input padding 0 output padding 1
Convolution : 17x17x32 -> 15x15x128 | input padding 1 output padding 0
MaxPooling : 15x15x128 -> 7x7x128 | input padding 0 output padding 0
BatchNormalization : 7x7x128 -> 9x9x128 | input padding 0 output padding 1
Convolution : 9x9x128 -> 7x7x128 | input padding 1 output padding 0
MaxPooling : 7x7x128 -> 3x3x128 | input padding 0 output padding 0
BatchNormalization : 3x3x128 -> 3x3x128 | input padding 0 output padding 0
Convolution : 3x3x128 -> 3x3x10 | input padding 0 output padding 0
AveragePooling : 3x3x10 -> 1x1x10 | input padding 0 output padding 0
ElementTimes : 1x1x10 -> 1x1x10 | input padding 0 output padding 0
Softmax : 1x1x10 -> 1x1x10 | input padding 0 output padding 0
```
#結果

GPUが搭載されていれば、通常のConvlutionの方がGPUの実行効率が高く、計算速度も高い結果になります。またmetrixも５％程度通常のConvotluionの方が有利になります。

Binary Convolutionはやはりハードウエアが制限された環境で、うまくチューニングして使用するか、FPGA等で直接ハードウエア化を行なわなければ、通常のGPU付きの環境ではあまりメリットがないです。

CPUの環境であれば、2－3割程度実行速度が上がります。Batch Normalizationを減らすと認識率は下がりますが実行速度は上がります。最終はそのトレードオフではないかと思います。