前回は軽くCNTKの設定の仕方について説明しました。今回は自分で学習させてみます。

データの準備

CIFAR-100

CIFAR-100というデータを用意します。
データはここから入手できます。
ページ下の CIFAR-100 binary version (suitable for C programs) からダウンロードできます。
このデータを用意したのはCaffeを説明した際に少しだけ名前が出てきた ALXENET の考案者である Alex Krizhevsky氏 です。感謝です。

CIFAR-10やMNISTをテストしているブログは多いですが、CIFAR-100をテストしている人は少ないので、これを選びました。
また、CNTKにもCIFAR-10しかないので、これを選んだのが理由です。

まずは、ダウンロードしたデータから教師データを作成します。

教師データ作成 画像編

結論から言いますと、ダウンロードしたデータはそのままでは使うことができません。
CIFAR-100のデータも画像そのもの、というわけではありません。

まず教師データである画像データは test.binとtrain.binにバイナリで含まれています。
binデータは

1
2
3
<1 x coarse label><1 x fine label><3072 x pixel>
...
<1 x coarse label><1 x fine label><3072 x pixel>

という順でバイナリに含まれています。
つまり1ファイル3074バイトです。
coarse labelは、雑なラベル付けで、「魚」、「昆虫」とかいう大雑把なラベルです。
fine labelは、もっと詳細なラベル付けで、「鮫」、「蝶」とかいう具体的なラベルです。
0から始まる整数値でそれぞれ、coarse_label_names.txt、fine_label_names.txtの先頭の行番号-1と対応します。
ここで注意するのが、画像領域の3072バイトが、先頭1024バイトに赤、次の1024バイトに緑、残りに青の画素が含まれています。
これをBGRの順で1ピクセルずつ格納しなおす必要があります。
つまり

1
2
3
4
5
6
7
8
// dst は出力先のビットマップ領域
// src は入力元の3072バイト領域
for (var index = 0; index < 1024; index++)
{
dst[index * 3 + 2] = src[1024 * 0 + index];
dst[index * 3 + 1] = src[1024 * 1 + index];
dst[index * 3 + 0] = src[1024 * 2 + index];
}

こういう感じです。
これに直してビットマップで出力すると、Windowsで表示できます。
例えば、test.binの最初のデータは

1
10 49 199 196...

となっています。これを画像にすると、large_natural_outdoor_scenes(10+1=11行目)のmountain(49+1=50行目)なので、

山

になります (実際は32x32ですが、8倍に拡大してあります)。
では、画像には変換できましたが、これをCNTKが理解できる形式にする必要があります。

前回の章で説明したReader Blockで指定できる形式ですので、UCIFastReaderで読み込める形式です。
これは、CSVと同じで、タブでバイトを1つずつ区切ります。一つの画像が終わったら改行します。
注意するのは改行コードですが、CR+LFにします。
左から順に、coarse label、fine label、1画素目の青、1画素目の緑、1画素目の赤、2画素目の青とつなげていきます。

ですので、下記のようになります

1
10\t49\t249\t215...

以上の変換を自動で実行するプログラムを作りました。

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

coarse_label_names.txt、fine_label_names.txt、test.bin、train.binと同一のフォルダで CNTK4.exe を、実行すると、下記のファイルとフォルダを出力します。

  • test.txt : テスト用のデータ
  • train.txt : 学習用のデータ
  • test : テスト用の画像データをラベルごとにフォルダわけしてBmpにしたもの
  • train : 学習用の画像データをラベルごとにフォルダわけしてBmpにしたもの
  • train_Label.txt : 学習用データのラベルのリスト
  • test_Label.txt : テスト用データのラベルのリスト

教師データ作成 ラベル編

次はラベルです。
CNTKはラベルを文字として受けるとことができません。
ですので、fine_label_names.txt等をそのままラベルとして使うことができません。
今回はfine_label_names.txtをラベルとして使いますので、このファイルを上書きするか別のファイルに0から99の値を1行ごとに改行したテキストファイルを作成します。
改行コードですが、CR+LFにします。

1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
..

こんな感じです。

設定ファイルデータ作成

流石に設定ファイルは1から作るのは厳しいので、\CNTK\Examples\Image\Miscellaneous\CIFAR-10 を改造します。CIFAR-10フォルダから下記のファイルをコピーして、どこか別のフォルダに移動します。
01_Conv.cntk 01_Convolution.ndl Macros.ndl ここでは、\CNTK\Examples\Image\Miscellaneous\CIFAR-100にコピーしました。
まず、01_Conv.cntkを修正します。

  1. Train.reader.features.start、Test.reader.features.startを 2 に修正
  2. Train.reader.labels.start、Test.reader.labels.startを 1 に修正
  3. Train.reader.labels.labelDim、Test.reader.labels.labelDimを 100 に修正
  4. Train.reader.labels.labelMappingFile、Test.reader.labels.labelMappingFileを $DataDir$/fine_label_names.txt に修正

Train.reader.labels.start値は、それぞれ、test.txt、train.txtの各行の左から何列目かを表しています。0基準で、今回はfine_labelを使うので、1になったというわけです。
Train.reader.features.start値も同様です。
labelDimはラベルの個数です。fine_labelは100個のラベルですので、100と入力します。
labelMappingFileは先ほど、0-99を入力したファイルを指定します。中止するのは、フルパスで入力してもよいですが、今回は、すべてのファイルが同一ディレクトリにある前提ですので、上のように直します。

次は、01_Convolution.ndlです。

  1. ndlMnistMacros.LabelDimを 100 に修正

これだけです。
以上で終了です。
Macros.ndlは修正しませんが、ないと動かないと思います。

学習!

CNTK\Examples\Image\Miscellaneous\CIFAR-100フォルダに、教師データ作成の段で出力されたファイルと修正したラベルファイルをコピーします。
現時点で、

  • 01_Conv.cntk
  • 01_Convolution.ndl
  • fine_label_names.txt
  • Macros.ndl
  • test.txt
  • train.txt

が存在します。
いよいよ実行します。
コマンドプロンプトで、CNTK.slnのあるディレクトリから

1
2
3
set PATH="%CD%\Release";%PATH%
cd Examples\Image\Miscellaneous\CIFAR-100
CNTK configFile=01_Conv.cntk deviceId=0 1> out.txt 2>&1

のようにたたきます。
しばらく待つと CNTK\Examples\Image\Miscellaneous\CIFAR-100\Output\01_Conv_Train_Test.log に結果が出力されます。
エラーがなければ末尾に COMPLETED と表示されます。
GPUを指定しましたが、私の環境で9分強かかりました。
また、学習済みモデルが CNTK\Examples\Image\Miscellaneous\CIFAR-100\Output\Models に結果が出力されます。

ところがどっこい

エラー 0

出力のログを見ると、末尾にエラー率等の情報が出ます。
ですが、なぜか0。

1
2
3
4
Minibatch[1-500]: Samples Seen = 8000    err: ErrorPrediction/Sample = 0    ce: CrossEntropyWithSoftmax/Sample = 0
Minibatch[501-625]: Samples Seen = 2000 err: ErrorPrediction/Sample = 0 ce: CrossEntropyWithSoftmax/Sample = 0
Final Results: Minibatch[1-625]: Samples Seen = 10000 err: ErrorPrediction/Sample = 0 ce: CrossEntropyWithSoftmax/Sample = 0 Perplexity = 1
COMPLETED

どう考えてもありえません。
100%の認識率などあり得ません。

なので、ネットワーク設定がおかしいのか?と思いいろいろ見直しました。
それでもわからない。

結論から言えば、GitHubでコンパイル済みのバイナリを使ったらうまくいきました。
下記はその結果です。

1
2
3
4
5
6
7
8
9
10
11
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]: Samples Seen = 8000 Err: ErrorPrediction/Sample = 0.58 CE: CrossEntropyWithSoftmax/Sample = 2.2598516
Minibatch[501-625]: Samples Seen = 2000 Err: ErrorPrediction/Sample = 0.5805 CE: CrossEntropyWithSoftmax/Sample = 2.2639628
Final Results: Minibatch[1-625]: Samples Seen = 10000 Err: ErrorPrediction/Sample = 0.5801 CE: CrossEntropyWithSoftmax/Sample = 2.2606739 Perplexity = 9.5895492
COMPLETED

エラー率58%。よくないですが、まぁこんなものでしょう。

なぜこうなったし。

Conclusion

ついに自分で学習できました。
が、ビルドしたものがおかしいという悲劇。CUDA 7.5をつかったのがダメだったのか? 次回はお待ちかねの評価を実施します。

Source Code

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