前回は評価データをExcelで可視化しました。今回は.NETから使ってみます。

注意

CNTKのソースコード自体、現在進行形で進化しています。入手できるバイナリも現時点 (2016/03/18) でも動作しません。
実行は最新ソースを取得してビルドする必要があります。
また、設定ファイルやソースコードもトライアンドエラーで試した結果です。

準備

対象データは、MNISTとします。
ネットワークモデルは、Examples\Image\MNIST にあります。
ただし、データそのものは、含まれていません。

THE MNIST DATABASE から入手できるのですが、ここから入手しても、CNTKが読み込める形式にはなっていません。
Examples\Image\MNIST\AdditionalFiles に、データの入手、変換を行うPython のスクリプトが入っているのですが、Python環境がないと使えません。
ですので、後述のサンプルプログラムにデータを含めておきました。
data\mnist.zip になります。これを解凍してできる、Train-28x28.txtTest-28x28.txtExamples\Image\MNIST\Data にコピーします。

次に設定ファイルを変更します。
Examples\Image\MNIST\Config\01_OneHidden.cntk を開いて、

1
2
deviceId = 0
imageLayout = "cudnn"

1
2
deviceId = -1
imageLayout = "legacy"

に変更します。

1
# stderr = "$OutputDir$/01_OneHidden_out"

1
stderr = "$OutputDir$/01_OneHidden_out"

に変更して学習途中のログを生成することもおすすめしておきます。
以上で準備が完了です。

ビルド

最新ソースを取得してビルドします。
個人的には、GPUを使わない方が良いと思います。

ただし、3/18の時点では、ディープラーニング CNTK 雑談1 動かない api-ms-win-core-path-l1-1-0.dll がない で報告したバグが直っていないので、Windows7で動かすなら、記事を参考にソースを修正してからビルドしてください。

学習

ビルド完了後、出来上がるパスをメモします。CNTK.slnがあるディレクトリから見て、x64\Release_CpuOnly または x64\Release_CpuOnly になると思います。
コマンドプロンプトを開いて、CNTK.slnがあるディレクトリにカレントディレクトリを移動します。
そして、次のコマンドを実行します。

1
2
3
4
5
6
set PATH="%CD%\x64\Release_CpuOnly";%PATH%
set CURRENT=%CD%
rmdir "Examples\Image\MNIST\Output" /s /q
cd "Examples\Image\MNIST\Config"
CNTK configFile=01_OneHidden.cntk deviceId=-1
cd %CURRENT%

これでしばらくすると、Examples\Image\MNIST\Output\Models に学習済みのモデルファイルが出来上がっているはずです。
ログは、Examples\Image\MNIST\Output\01_OneHidden_out_MNISTtrain_MNISTtest.log になります。

さらっとながめてみますと、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Allocating matrices for forward and/or backward propagation.
UCIFastReader: Starting at epoch 0, counting lines to determine record count...
10000 records found.
starting epoch 0 at record count 0, and file position 0
already there from last epoch
RandomOrdering: 1989 retries for 10000 elements (19.9%) to ensure window condition
RandomOrdering: recached sequence for seed 0: 2334, 3830, ...
Minibatch[1-500]: SamplesSeen = 8000 err: ErrorPrediction/Sample = 0.024125 ce: CrossEntropyWithSoftmax/Sample = 0.076922514
Minibatch[501-625]: SamplesSeen = 2000 err: ErrorPrediction/Sample = 0.0155 ce: CrossEntropyWithSoftmax/Sample = 0.060455356
Final Results: Minibatch[1-625]: SamplesSeen = 10000 err: ErrorPrediction/Sample = 0.0224 ce: CrossEntropyWithSoftmax/Sample = 0.073629082 Perplexity = 1.0764075

Action "test" complete.

COMPLETED

とあり、エラー率2.24%と出ています。まずまずです。

.NETからテスト

テスト自体は、既にログを見るとおりわかっているのですが、これを.NETから可視化します。
実は、.NETのラッパーライブラリ自体は既にサンプル付きでCNTKに含まれています。
https://github.com/Microsoft/CNTK/tree/master/Source/Extensibility がそれです。
ソースである、Program.csを見ると非常に簡単であることがわかります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using (var model = new IEvaluateModelManagedF())
{
// Initialize model evaluator
string config = GetFileContents(Path.Combine(Environment.CurrentDirectory, @"..\Config\01_OneHidden.cntk"));
model.Init(config);

// Load model
string modelFilePath = Path.Combine(Environment.CurrentDirectory, @"..\Output\Models\01_OneHidden");
model.CreateNetwork(string.Format("deviceId=-1\nmodelPath=\"{0}\"", modelFilePath));

// Generate random input values in the appropriate structure and size
var inputs = GetDictionary("features", 28*28, 255);

// We can call the evaluate method and get back the results (single layer)...
outputs = model.Evaluate(inputs, "ol.z", 10);
}

です。
Init関数には、設定ファイルのパスを、CreateNetworkには、コマンドプロンプトで渡す記述を渡すだけです。

ただし、わかりにくいのが、inputsoutputs です。

inputsは、System.Collections.Generic.Dictionary<string, System.Collections.Generic.List> ですが、GetDictionaryで得られる戻り値は、**”features”** というキー、長さ28_28の System.Collections.Generic.List の値のペアが一つです。
値の中身は0-255の値がランダムに入っています。
MNISTは28_28なのでそういう条件なのです。
*.cntkにも784という数字が書いてありましたが、まさしくこれです。

outputs は、System.Collections.Generic.List です。
今回は長さ10で、添え字0-9が、MNISTで認識する0-9の数値にそのまま対応しています。
また、各要素の値は、入力データが、添え字に対応する数字のどれに対応するか、という確率を返します。

では、**”features”** と “ol.z” は何を意味するのでしょうか? これは、入力データと出力データを識別するキーになります。
ディープラーニングでは、各レイヤー毎に複数の入出力を持つことが可能です。その入出力を識別するためのキーになります。
Caffe でいう、Bottom (入力)、Top (出力)になります。

では、この定義はどこにあるのか、というと Examples\Image\MNIST\Config\01_OneHidden.ndl を見るとわかります。

1
2
3
4
5
FeatureNodes = (features)
LabelNodes = (labels)
CriterionNodes = (ce)
EvalNodes = (err)
OutputNodes = (ol)

という記述がまさにそれです。
ただし、ol だけは、**”.z”** を付与しないと、実行時にエラーを投げてしまいますので、そうしています。

さて、実行するとわかりますが、戻ってくる値には、負数が含まれており、全部の数値を合計しても確率を表すと思われる、1または100になりません。
これは、後述のサンプルソースで含まれるSoftmaxメソッドで確率に変換すること目的を達成できます。

ディープラーニングでは、出力層で得られた結果をSoftmaxで確率に変換することで識別を行います。
説明はSoftmax_function でわかりますが、簡単にソースで示すと、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private IList<double> Softmax(IList<float> outputs)
{
var results = new double[outputs.Count];

var total = 0d;
for (int index = 0, length = results.Length; index < length; index++)
{
results[index] = Math.Exp(outputs[index]);
total += results[index];
}

for (int index = 0, length = results.Length; index < length; index++)
{
results[index] /= total;
}

return results;
}

になります。簡単ですね。

テストする

が、先のサンプルは、入力データがランダムな値で全くもって意味がありません。
ですので、MNISTの画像を使って実際にテストしてみたいと思います。
入力画像は28*28の8bit画像です。

これをGUIで実行するのが、

https://github.com/takuya-takeuchi/Demo/tree/master/MachineLearning/CNTK/CNTK6

になります。
ビルドする際は、CNTK6\assemblies\CNTK というフォルダを作って、そこに EvalWrapper.dll をコピーしてください。
また、実行する際は、CNTKで出力される全てのバイナリを実行フォルダにコピーしてください。

実行すると、こんな感じです。

サンプルアプリ

下のリストビューに確率がでており、この画像だと6である可能性が一番高いことを示唆しています。

入力画像は8、24、32bitに対応しており、それぞれバイナリに変換します。
ですので、MNIST以外にも使えます。CIFAR-100 でも利用できます。
(CIFAR-100で最初はテストしていましたが、結果がいまいちなので、MNISTにしまいた。)

Conclusion

ついに.NET から利用できるようになりました。
これで利用までの敷居がぐっと下がったような気がします。
アプリへの組み込みも他のディープラーニングのフレームワークに比べて楽になります。

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/MachineLearning/CNTK/CNTK6/source/CNTK6