2017年1月8日日曜日

DeepLearningで手書き数字認識Windowsアプリを作ってみた

2016年はDeepLearningが研究レベルの何の役に立つのか分からないところから、ビジネスまでは行かないけど実際にこんなことできちゃうよ!っていう具体例が色々出てきたなぁという感じの年でした。

Googleのディープラーニング「AlphaGo」がプロ棋士に完勝 - ITmedia ニュース
個人的には、これがインパクト強かった。AIの時代来たかっ!って感じw

というわけで、かなり興味が出てきたので実際に学習した過程をメモがてら残しておく・・・

読んだ参考書とか


定番、オライリーのDeep Learning本

これが一番参考になりました。Pythonでサンプルが書かれてます。私も最初Pythonなんて使ったこと無くて、C#とかで書いてほしいなぁとか思ってたんだけど、実際読んでみたらパイソンの環境セットアップから基本的な言語仕様まで書かれてて、何の問題もなく読めるし書けるし試せる感じでした。


どうも、Deep Learning関連の研究者はPythonを好む人が多いみたいで、いろんなライブラリや学習データの整形なんかの処理がPythonで書かれてることが多いので、ここで一度軽く学んで環境を設定してみると良さそうな感じでした。


「よくわかる人工知能 最先端の人だけが知っているディープラーニングのひみつ」
こちらは、学習の参考というよりは完全に読み物として・・・

かなりドリーミーな話を含んだ内容になっていて読んでて面白かった。「さすがにねぇわ!」とか思いながらw

未来に夢描くもののイメージとか、最先端の人はこういう未来を考えてるのか~とかそういう感じで読ませてもらいました。

環境設定

Python Anaconda
まずはPythonのセットアップ。どうもこのアナコンダ?とかいうパッケージがDeepLearningで使うにはいいらしい。
Anaconda を利用した Python のインストール (Windows) – Python でデータサイエンス
言われるがままインストール。パス通してやればもうpythonってコマンドラインで打つと使えるようになります。
起動するとインタプリタモードで動くので試しに「1+3」とかやると「4」と返ってくるので、なるほどヨシヨシと・・・


CNTK(Microsoft Cognitive Toolkit)
Windowsアプリを作るんで、Windows環境と相性の良さそうなマイクロソフト製のライブラリを使います。
CNTK環境構築からMNISTサンプル実行まで (Windows環境, 2016/06/29時点) - Qiita
詳しいセットアップ手順はここを参考にさせてもらいました。
ライブラリがGPU版とCPU版があってもちろんGPU版の方が何倍も早いんだけど、CUDAとかのセットアップが必要なんで、とりあえずはCPU版でテストしました。
あとで、GPU版に置き換えましたが、すんなり置き換わりました。

ここに書いてある、コンソールでのテストまでは実行しておいてください。その過程で作られたバイナリを使うので・・・

VisualStudio C#
えっと、これはいいよね。普通にセットアップしてください。

cntkライブラリをC#から使う

VisualC#でWindowsフォームアプリ新規作成でまず作り
「ソリューションエクスプローラー」の「参照設定」を右クリックして「参照の追加」
「参照マネージャー」の「参照(B)」ボタンから、CNTKをインストールしたフォルダに有る「EvalWrapper.dll」を選択するとC#から使うためのクラス定義とかがインポートされます。

Form1.csの先頭に
using Microsoft.MSR.CNTK.Extensibility.Managed;
で、まず使いやすいように・・・

List outputs;
            using (var model = new IEvaluateModelManagedF()) //CNTKで評価するための基本オブジェクトかな?
            {
                //上のCNTKのコンソールテスト時に使った.cntkファイルを指定して初期化
                //下記コードはカレントディレクトリにファイル名をmodel.cntkに
                //リネームしてコピーしてあることを前提
                string config = File.ReadAllText(@"model.cntk"); 
                model.Init(config);

                //上のCNTKのコンソールテスト時学習で出力された学習済みネットワークバイナリファイルを指定
                //Output/Models/以下に吐き出された01_OneHidden とかの拡張子が無いファイルが出力結果になってる
                //下記コードはカレントディレクトリにファイル名をmodel(拡張子なし)に
                //リネームしてコピーしてあることを前提
                string modelFilePath = @"model";
                model.CreateNetwork(string.Format("deviceId=-1\nmodelPath=\"{0}\"", modelFilePath));

                //入力用のデータ 詳しくはGetInputs関数で解説
                Dictionary< string, List< float >> inputs = GetInputs();

                //セットアップした学習済みネットワークモデルで入力データを評価
                //結果がoutputs配列に0~9のそれぞれのスコアみたいな形で入る。一番大きな値が認識結果
                //「ol.z」とかは、model.cntkの定義に従うので別の定義の場合は別の文字列になる
                //「ol.z」はどうも「OutputNodes = (ol)」の時はこれでいいみたい
                //「outputNodes = (z)」の時は「z」みたいな感じ。
                outputs = model.Evaluate(inputs, "ol.z", 10);
            }

続いてGetInputs関数
var result = new Dictionary< string, List< float >>();
            //inputsの形式は Dictionary<ラベルID, 入力パラメータリスト> となってるみたい
            //cntkで指定してる「features」というラベルが入力データとなる
            result.Add("features", new List());
            var v = result["features"];

            //PictureBoxコンポーネントにマウスで自由に白で手書きできるようにしておいて、その結果を使う。
            var bitmap = (Bitmap)pictureBox1.Image;

            //IMAGE_SIZEはMNIST形式の28ピクセル
            //28x28だとあまりにも小さいのでSCALE_NUMを4として4倍の大きさの
            //PictureBoxコンポーネントに書いたものを平均化して使う
            for (int y = 0; y < IMAGE_SIZE; y++)
            {
                for (int x = 0; x < IMAGE_SIZE; x++)
                {
                    var param = 0.0f;
                    for (int xx = 0; xx < SCALE_NUM; xx++)
                    {
                        for (int yy = 0; yy < SCALE_NUM; yy++)
                        {
                            //白で描画してるのでRGBのどの値でもいい
                            param += bitmap.GetPixel(x * SCALE_NUM + xx, y * SCALE_NUM + yy).R;
                        }
                    }
                    param /= (SCALE_NUM * SCALE_NUM); //4x4の16ピクセルの合計値を平均化0~255の値に丸める
                    v.Add(((float)param));
                }
            }
            return result;
これでコアな部分の実装は完了。あとはどのタイミングで認識するかとか、手書き描画の線の太さをどうするかとかそのへんは各自お好みで・・・

実験結果

01_OneHidden
シンプルな1次元配列でのネットワークだと、認識率は5割ぐらいな印象。8とかまず認識しない。
02_Convolution
二次元畳み込み?のネットワークだと認識率は7割ぐらいのイメージ。7と9を良く誤認する。あと真ん中に書かないと結果が狂いやすい
03_ConvBatchNorm
これは、どうもGPUが必要みたいでCPUのだと未実装な関数みたいなエラーで止まってしまう。 グラボを買ってCUDAをインストールしてってやらないといけない。 結果はあまり印象としては変わらず7割ぐらい。ただ学習はメチャはや!100倍ぐらい早いかもしれん。さすがGPU
04_OneConvBN とかのGPU用
ここまでくるとかなり認識率が上がってくる印象。8割~9割ぐらい思った通りになる。 ただ、やっぱり7と9が誤認されやすい・・・ どうも7のサンプルデータの書き方で縦棒にチョンと横線いれる文化があるところの数字のサンプルが多いのか9の真ん中の横棒と混乱しやすくなってるのかも?このあたりからは実際に使う環境にあった学習データがあったほうが思い通りになりそうな感じだったな

まとめ

というわけで、DeepLearning使ってみたまとめレポートでした。アプリのバイナリ?いや、なんかライセンスとかどこまで配っていいのかわからないのでちと配布しづらいっす。cntkフォルダにある関連するDLLをがさごとコピーして学習済みネットワークデータとセットにすれば、とりあえずPythonもCNTKも環境の無いマシンでも動きました。
学習は時間掛かるけど、評価自体は結構早い。マウスMoveイベントごとに評価してリアルタイムに文字を書き換えを評価し続けるような作りをしても割りともたつくこと無く動きました。

0 件のコメント: