WindowsでCaffeを使う：学習

前回は学習に用いるデータセットの作成について解説しました。
今回はいよいよ学習を実行し、識別器を生成します。

#学習の実行コマンド
初回でビルドした学習実行ファイルtrain_net.exeを以下のように実行します。

`train_net.exe [solver file]`
-[solver file]：学習データのパスやパラメータが記載されたソルバファイル（～.prototxt）を指定します。

#ソルバファイルの記載例
サンプルデータセットcifar10のソルバファイルを例に、記載項目を解説します。ソルバファイルとは、学習に使うパラメータを記載したもので、ネットワーク構成等の必要なものは全てこのファイルに紐付いています。
cifar10のソルバファイルは`.\examples\cifar10`にあるcifar10_full_solver.prototxtになり、以下にその内容を示します。


\# 学習用のレイヤ定義ファイル(～.prototxt)を指定
train_net: "cifar10_full_train.prototxt"
\# テスト用のレイヤ定義ファイルを指定
test_net: "cifar10_full_test.prototxt"
\# テスト時にバッチを順伝播するイテレーション回数
\# バッチサイズが100枚であれば、100*100=10000枚テストする
test_iter: 100
\# テストする間隔（学習イテレーションを何回やったらテストするか）
test_interval: 1000
\# 学習率
base_lr: 0.001
\# 平滑化係数
momentum: 0.9
\# 正則化係数
weight_decay: 0.004
\# 学習率のタイプ（固定・減衰する等）
lr_policy: "fixed"
\# 指定イテレーション回数おきに精度を表示
display: 200
\# 最大イテレーション数
max_iter: 60000
\# 一時的なモデル(スナップショット)を保存するイテレーション間隔
snapshot: 10000
\# スナップショットファイル名の先頭文字列。snapshot_prefix + イテレーション回数がモデルのファイル名
snapshot_prefix: "cifar10_full"
\# GPUかCPUを指定
solver_mode: GPU

#レイヤ定義ファイルの記載例
レイヤ定義ファイルもcifar10を例に記載項目を解説します。レイヤ定義ファイルは、CNNの構成を表すもので、各演算機構（コンボリューション、プーリング等）をレイヤとして表現しています。Caffeでは画像や特徴マップをblobというデータ形式で保持しており、バッチ数分の複数枚の画像を一度に処理することができます。各レイヤはこのblobを入出力とする演算機構となり、演算のタイプ、パラメータ、入力blob、出力blob等をレイヤ定義ファイルに記載していきます。
cifar10のレイヤ定義ファイルはソルバファイルに記されていたように`.\examples\cifar10`にあるcifar10_full_train.prototxt（学習用）とcifar10_full_test.prototxt（テスト用）になります。
以下に学習用の内容を示します。

name: "CIFAR10_full_train" # レイヤ定義ファイルの名前
layers {
　name: "cifar"
　type: DATA # 入力データレイヤ
　top: "data" # 出力されるblobの名前(画像データ用)
　top: "label" # 出力されるblobの名前(ラベル用)
　data_param {
　　source: "cifar10-leveldb/cifar-train-leveldb" # 学習用leveldbファイルを配置したフォルダパスを指定
　　mean_file: "mean.binaryproto" # 平均画像ファイルパスを指定
　　batch_size: 100 　 # バッチサイズを指定
　}
}
layers {
　name: "conv1"
　type: CONVOLUTION # コンボリューションレイヤ
　bottom: "data" # 入力されるblobの名前(データレイヤから出力された画像データ)
　top: "conv1" # 出力されるblobの名前(畳み込み後の画像データ)
　blobs_lr: 1 # ソルバファイルで指定した学習率に乗算(重み更新用)
　blobs_lr: 2 # ソルバファイルで指定した学習率に乗算(バイアス更新用)
　convolution_param {
　　num_output: 32 # 出力する畳み込み画像の数
　　pad: 2 # 畳み込みで欠けた周囲の画素を埋めるサイズ
　　kernel_size: 5 # カーネルサイズ
　　stride: 1 # ストライド数分スキップしてカーネルをスライド
　　weight_filler {
　　　type: "gaussian" # カーネルの初期値を生成するタイプ
　　　std: 0.0001 # 標準偏差
　　}
　}
}
layers {
　name: "pool1"
　type: POOLING # プーリングレイヤ
　bottom: "conv1" # コンボリューションレイヤから出力された畳み込み画像データ
　top: "pool1" # プーリング後の画像データ
　pooling_param {
　　pool: MAX # プーリングのタイプ
　　kernel_size: 3
　　stride: 2
　}
}
layers {
　name: "relu1"
　type: RELU # 活性化関数レイヤ(ReLU)
　bottom: "pool1" # プーリングされた画像データを入力
　top: "pool1" # ReLUで変換後のデータを”pool1”のblobに戻す
}
layers {
　name: "norm1"
　type: LRN # Local response normalizationレイヤ。特徴マップ間や空間上で正規化。
　bottom: "pool1"
　top: "norm1" # 正規化後の画像データ
　lrn_param {
　　norm_region: WITHIN_CHANNEL # 空間上で正規化
　　local_size: 3 # 正規化する局所領域サイズ
　　alpha: 5e-05
　　beta: 0.75
　}
}

…
この間をconvolution→pooling→relu→LRNの単位で繰り返すため省略します。
この繰り返しがセオリーとなりますが、必須ではありません。
…

layers {
　name: "ip1"
　type: INNER_PRODUCT # 全結合レイヤ
　bottom: "pool3"
　top: "ip1" # 全結合(入力ニューロンを内積)したblob出力
　blobs_lr: 1
　blobs_lr: 2
　weight_decay: 250 # ソルバファイルで指定した正則化係数に乗算(重み更新用)
　weight_decay: 0 # ソルバファイルで指定した正則化係数に乗算(バイアス更新用)
　inner_product_param {
　　num_output: 10 # 出力するニューロン数。識別クラス数を指定。
　}
}
layers {
　name: "loss"
　type: SOFTMAX_LOSS # 誤差評価レイヤ。ソフトマックスをしてから誤差を評価。
　bottom: "ip1"
　bottom: "label" # データレイヤから出力されたラベルデータのblob
}

以上のレイヤ定義をテスト用にも作成しますが、先頭のレイヤ（データレイヤ）以外は学習用と同じものとします。

name: "CIFAR10_full_test"
layers {
　name: "cifar"
　type: DATA
　top: "data"
　top: "label"
　data_param {
　　source: "cifar10-leveldb/cifar-test-leveldb"　# テスト用leveldbファイルを配置したフォルダパスを指定
　　mean_file: "mean.binaryproto" # 学習用と同じ平均画像ファイル
　　batch_size: 100 # テストに使用するバッチサイズ
　}
}

##レイヤ定義ファイルの確認点
学習を開始する前に以下の点を確認しておきましょう。
 - leveldbファイルパス、平均画像ファイルパスがカレントディレクトリからのパスになっているか
 - 最後の 誤差評価レイヤに入力されるニューロン数が識別クラス数分になっているか
→ 全結合レイヤの inner_product_param内のnum_outputを確認

#学習の実行
cifar10の学習を実行すると以下のように実行過程が表示されます。

I0710 20:22:11.596309 5636 solver.cpp:106] Iteration 0, Testing net
I0710 20:22:14.406994 5636 solver.cpp:142] Test score #0: 0.0997
I0710 20:22:14.406994 5636 solver.cpp:142] Test score #1: 2.30261
I0710 20:22:33.862334 5636 solver.cpp:237] Iteration 200, lr = 0.001
I0710 20:22:33.862334 5636 solver.cpp:87] Iteration 200, loss = 1.82118
I0710 20:22:53.345878 5636 solver.cpp:237] Iteration 400, lr = 0.001
I0710 20:22:55.406580 5636 solver.cpp:87] Iteration 400, loss = 1.79293
…

ソルバファイルのdisplayに指定したとおり、イテレーション200回ごとに現在の誤差(loss)が表示されます。
イテレーションが進むにつれ誤差が減少していけば、学習がうまくいっている証拠です。

※CPU利用時はLRNレイヤがあると誤差が減らないバグがあります。LRNレイヤを削除するか、GPUを利用してください。

イテレーションが1000回(test_interval)進むとテストデータを用いて精度評価します。

I0710 20:26:57.952163 1384 solver.cpp:87] Iteration 1000, loss = 1.40562
I0710 20:26:57.952163 1384 solver.cpp:106] Iteration 1000, Testing net
I0710 20:27:00.746814 1384 solver.cpp:142] Test score #0: 0.511
I0710 20:27:00.746814 1384 solver.cpp:142] Test score #1: 1.34971

Test score #0が識別精度(accuracy)で#1が汎化誤差です。現時点ではテスト画像に対する識別率が51％、訓練誤差1.40に対し汎化誤差1.34 となります。
学習が進むにつれて汎化誤差は訓練誤差より下がりにくくなっていきます。イテレーションが10000回(snapshot)に達すると、途中結果のモデルファイルcifar10_full_iter_10000を出力します。この時点でも識別器として動かすことが可能です。
最後までイテレーション(max_iter: 60000)が終了したらモデルファイル cifar10_full_iter_60000が吐かれます。

***
これで学習の解説を終了しますが、cifar10のデータセットはbinデータとなってますので前回のやり方では作成できず、上記の学習は行えません。また、cifar10のデータセット作成については次回以降の記事で解説したいと思います。

次回は作成した識別器による画像の識別を解説します。