Binary Convolution Neural Networkについて
この写真ですが、32×32×3の画像データであるCIFAR10で学習したモデルでも、『飛行機』と認識することができます。画像認識は認識精度において、ほぼ実用レベルになってまいりましたが、まだ課題が多くあります。
-モデルが大きくなり、パラメータのサイズの巨大化
-実行の負荷が高い
画像認識のモデルファイルだけで19層のVGGで500MB以上になります。当然実行にも負担がかかり、そこそこのPCレベルのスペックがなければ、学習どころか、推定すら実行できない状況となります。
そこで、この状態に対する一つの工夫として、Neural networkの二値化(binarization)があります。Weightを二値化する場合と、Neural NetowrkをXNOR演算に置き換える場合等の研究が進んでいます。CNTKの環境でもこの二値化ネットワークを作成し学習させることができ、CNTKで推論させることはもちろん、Microsoftの研究所から公開されている、組み込み用の環境であるELL(Microsoft Embedded Learning Library)上でも動作させることができます。
Binary化によりハードウエア化がやりやすくなりますし、直接FPGAで実行させるようなことも可能になります。東京工業大学の中原先生の下記の資料がわかりやすいと思います。
http://sssslide.com/www.slideshare.net/HirokiNakahara1/2-76413103
クラウド上のGPUインスタンスや高速のサーバー上で学習を行い、実行は目的や環境にあったところで実行するというのが最近の流れになってきているように思います。
図の①は、Deep Learningで、②はスパースコーディングを使った機械学習という使い分けになります。②のELLで提供されている、スパースコーティングを機械学習に適用した、Prototype-based Nearest NeighborやBonsaiという手法はとても興味深いのですが、ご紹介は別の機会にしたいと思います。
CNTKでの実装
CNTKでサンプルが提供されていますので、学習コードはそれに倣っていますが、ELLで実行するため修正している部分があります。また、推論処理にHalide Frameworkによって、ビットマップを高速処理するサンプルも同時に含まれていますが、Halide Framworkが動作する環境に限られるため、使っていません。ELLに付属するBinary Convolutionを使って、CNTKで学習し、ELLで実行するという流れで記事を書きたいと思います。
BNN(Binary convolution Neural Network)での画像認識の学習と実行
大枠の流れは下記になります。
1.Microsoft ELLをインストールする
2.CNTKをインストールする
3.BNNの学習コードを作成し学習を行う(Python)
4.CNTKのモデルをELLのモデルに変換する
5.ELLのモデルからコード生成を行う
6.ELL用の実行コードを作成し実行(Python)
1.Microsoft ELLをインストールする
Microsoft ELLの紹介は別記事を参照ください
Microsoft ELLのセットアップ部分は下記記事を参照ください
-ELLのWSLへの導入
https://analyticsai.wordpress.com/2017/11/29/microsoft-ell%e3%81%aewsl%e3%81%b8%e3%81%ae%e5%b0%8e%e5%85%a5/
2.CNTKをインストールする
CNTKで学習を行うので、Scriptベースでのインストールを行っていただくのがよいかと思います。
またBatch NormalizationはGPU環境しかサポートされないため、CNTKでモダンなCNNの実行は、GPUが必要です。AzureのDSVMを使用する方法もありますので下記を参照ください。
https://docs.microsoft.com/en-us/cognitive-toolkit/CNTK-on-Azure
学習を行わず、モデルのインポートのみの場合は、ELLをActivateした環境で下記を入力します。バージョン等も変わっていきますので、詳細は以下を確認してから実行ください
https://docs.microsoft.com/en-us/cognitive-toolkit/setup-linux-python?tabs=cntkpy23
pip install https://cntk.ai/PythonWheel/CPU-Only/cntk-2.3-cp36-cp36m-linux_x86_64.whl
3.BNNの学習コードを作成し学習を行う(Python)
学習環境準備
CIFAR10のデータセットを使用しますので、データの準備を行います。
CNTKをアクティベートして下記を実行します。
python <cntkのインストールルート>/Examples/Image/DataSets/CIFAR-10/install_cifar10.py
minibatch作成と学習-評価部分は基本的には定型なのでさらにCNTKのサンプルを流用します。
以下にコードとトレーニング済みのモデルががまとめてあります。binary_convnet.pyが本体です。
また、比較のためにBinary Convolutionを使わない類似の構造を持つCNNのコードを添付しています。convnet.pyです
https://github.com/yasukit1414/binaryconv
-custom_functions.py #これは、<ELL_ROOT>/build/tools/import/CNTK/custom_functions.pyです
-ConvNet_CIFAR10_DataAug.py #これは<cntkのインストールルート>/Examples/Image/Classification/ConvNet/ConvNet_CIFAR10_DataAug.pyです
CNTKのレイヤー定義
CNTKでのレイヤー定義になります。
こちら論文:XNOR-Net: ImageNet Classification Using Binary Convolutional Neural Networksを参照ください。
この論文によると下記の単位が一つのブロックとなります。
-Batch Normalization
-Binary Activation
-Binary Convolution
-Pooling
ただ、すべてConvolutionをBinary化すると認識率が下がり方が大きい等の事情があり、このような構成がよいようです。正直、データにより差がでますし、実行速度とのトレードオフになるため、色々なパターンを試す必要がありますが、下記のギャラリーにモデルとRaspberry PIでの実行速度が記載されていますので、参考にしてください。また、下記のBinaryConvolutionはパラメータのビットマップとパラーメータサイズを指定しなければ、int型になります。下記のコードの場合は、int(64bit)全体をBitmapして使用する動作になります。
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)
またLoss関数はsoftmax付きのcross_entropyになります。にパラメータを足すと、収束がよくなるという事情により追加しています。
weight_sum = C.constant(0)
for p in z.parameters:
if (p.name == "filter"):
weight_sum = C.plus(weight_sum, C.reduce_sum(C.minus(1, C.square(p))))
bin_reg = C.element_times(.000005, weight_sum)
ce = C.cross_entropy_with_softmax(z, label_var)
ce = C.plus(ce, bin_reg)
pe = C.classification_error(z, label_var)
Epochを100回で、15%前後のエラー率になります。
最後モデルの保存は、Softmax関数を適用してから保存します。
modelz=softmax(z)
model_path = data_path + "/model2.cntk"
modelz.save(model_path)
4.CNTKのモデルをELLのモデルに変換する
学習が終了すれば、ELLの環境から、下記を実行します。model2.cntkが作成されていると思いますので、下記を実行します。
python <ELL_ROOT>/build/tools/importers/CNTK/cntk_import.py <work>/model2.cntk
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
出力のように出力されます。コード上はPlusとPReLUが明示されていませんが、ELLに付属するBinaryConvolutionを使用すると標準として組み込まれます。成功するとmodel2.ell が生成されます。
5.ELLのモデルからコード生成を行う
python <ELL_ROOT>/build/tools/wrap.py <work>/model2.ell --language python --target host
-languageはC++の場合CPPを指定します。また、--targetはhost/pi3/aarch64を選択できます。aarch64はdoragon boardで動作したとELLのサイトに書いてありましたが、PINE64等の他のCortex-A53環境で試しましたががうまく動作していません。PINE64の場合はPINE64上でELLを導入して、.ellファイルをコピーしてコード生成を行うことで動作は確認できました。
6.ELL用の実行コードを作成し実行(Python)
hostというディレクトリが生成されているので、下記を実行するとコードがビルドされて使える状態になります。
cd <work>/host
cmake .
make
下記のpythonコードとELLのtutorialにあるhelperライブラリを使って、画像をリサイズしてRBGをBGRに並べ替える部分は流用します。
import cv2
import numpy as np
import tutorial_helpers as helpers
import model
input_shape = model.get_default_input_shape()
output_shape = model.get_default_output_shape()
print("Model input shape: [{0.rows}, {0.columns}, {0.channels}]".format(
input_shape))
print("Model output shape: [{0.rows}, {0.columns}, {0.channels}]".format(
output_shape))
predictions = model.FloatVector(output_shape.Size())
sample_image = cv2.imread("airplane.jpg")
input_data = helpers.prepare_image_for_model(sample_image,input_shape.columns,
input_shape.rows)
model.predict(input_data, predictions)
prediction_index = int(np.argmax(predictions))
print("Category index: {}".format(prediction_index))
print("Confidence: {}".format(predictions[prediction_index]))
CIFAR10でも最初の画像であれば、Confidencelevel 0.99以上で、飛行機と認識できます。