MATLABによる軽量NNの開発(1:環境の整備)
ゼロから作るDeepLearning
前回の話題からの続きになります。
こんな本をいただきました。有難うございます。
5章の誤差逆伝播を重点的にお勉強して、4~7章を読みました。SURF/SIFT/k-means/KL変換など古典的な画像処理をしていた人は4,5章だけでもいいかもしれません。それにしてもとても良い本ですね。この本。最近のNN論文を読むスピードが格段に上がります。
そういえば学生時代はまだ勾配から誤差を求めていて遅すぎて死にかけたことがありました。懐かしい。
MATLABを使用
MATLAB Deep Learning Toolboxいいですよね。僕もライセンス持ってて、よく使います。
しかし今回は、論文を見ながら各層をプリミティブに変更していくと思うので、自前で用意してしまってもいいかなという気持ちです。
今回の最終的に実装するエッジデバイスはFPGAですので、GPUとアプローチがかなり異なります。
そう考えると最終的にフレームワーク以外全部自前、ということになりそうです。もちろん、ある程度レイヤが固まったら、DLTつかってもいいかも。高速に動作するはずだし。兎にも角にも、Simulinkとの連携への期待も込めてMATLABで設計していきます。
雛形を作る
ニューラルネットワークの各レイヤの理論はすごいですが、プログラム上はそんなに複雑ではない*1ので、ほぼフルスクラッチでも大変ではないと思います。
上記の「ゼロD」本のコードをそのままMATLABに逐次翻訳は、できなくはないと思いますが、やる意味はなさそうです。
PythonとMATLABは1:1で逐次翻訳できそうでいて、全く思想が違う言語同士なので、動きを理解したら、1から書いた方が早いでしょう。
とはいえ、全部1から書くのも骨が折れます。雛形を用意し改造していく方がいいでしょう。今回のエンジニアリングの目的はコードを書くことではなく、論文を実機レベルに落とし込む設計をし、検証、実装することです。
で、いいものをMATLAB File Exchangeで見つけました。フルスクラッチの単純なMNIST向けCNNです。
jp.mathworks.com
ここの要となるコードは勾配を求めるnet_grad.m
ですが、ニューラルネットワーク各レイヤの伝播はこうなってました。
%%順伝播 z = w3*fc+b3; % Affine z = relu(z); % ReLu out = w4*z + b4; %Affine out = relu(out); probs = softmax(out); L = cross_entropy(probs, lab); %%------(中略)------------ %% 逆伝播 dw4= dout*z'; db4 = sum(dout,2); dz = w4'*dout; % ん?どこからどこまでが一つのレイヤかがわからん dw4 = sum(dw4,5); dz= diff_relu(z).*dz; dw3 = dz*fc'; db3 = dz; db3 = sum(db3,2); dfc = w3'*dz; %% うんこうんこ
ちょっと順伝播、逆伝播がわかりづらかったので書き直しました。
例えば、Affineレイヤを「ゼロD」本の様に記述すると、MATLABではこうなると思います。
%% Affineレイヤ classdef my_affine properties W b x dW db end methods function obj = my_affine(W, b) if nargin == 2 obj.W = W; obj.b = b; obj.dW = []; end end %% 順方向 function [obj, out] = forward(obj, x) obj.x = x; out = obj.W * obj.x + obj.b; end %% 逆方向 function [obj, dx] = backward(obj, dout) %size(obj.x) dx = obj.W' * dout; obj.dW = dout * obj.x'; obj.db = sum(dout, 2); end end end
こんな感じで各レイヤを記述すると、先程のnet_grad.m
のコードは以下の様になります。
%% 順伝播 [Affine1,out] = Affine1.forward(fc); [Relu3,out] = Relu3.forward(out); [Affine2,out] = Affine2.forward(out); [lastLayer, ~] = lastLayer.forward(out, label); %%------(中略)------------ %% 逆伝播 [lastLayer, dout] = lastLayer.backward(dout); [Affine2, dout] = Affine2.backward(dout); [Relu3, dout] = Relu3.backward(dout); [Affine1, dfc] = Affine1.backward(dout);
スッキリ書けます。今回各レイヤ(Convolution/Affine/ReLu/Softmax/Cross-entropy-error)を書き直し、この雛形に押し込んだ形にしました。
コード全文は以下から。
github.com
MNISTの文字の学習です。最初は文字認識率が80%後半台と畳み込みをしてる割にあまり向上しません。
4~5epochくらい回せば、95%付近まで改善します。暇な時に回してみるのもいいかもしれません。
↓ 半日くらい根気強く回せば、このくらい(96~97%ほど)
これから
FPGA向けBinaryNetの検証と、その派生論文を調べ、実装していきます。畳み込み層の計算量の低減を当面の課題とします。
このネタも、去年のRISCVネタ並みに引き摺りまくっていく次第ですのでよろしくお願いします。
*1:カルマンフィルタなどもそうですね