2014年12月15日月曜日

CaffeによるMNISTのLeNetの学習を訳してみた

原文 Training LeNet on MNIST with Caffe は、ここ


Caffeがコンパイルできているものとする。このチュートリアルでは、CAFFE_ROOTにCaffeがインストールされているものとする

データセットの準備

MNISTのWebサイトからデータをダウンロードし変換する必要がある。以下のコマンドを実行する
cd $CAFFE_ROOT
./data/mnist/get_mnist.sh
./examples/mnist/create_mnist.sh

wgetやgunzipをインストールしておくこと コマンド終了後に、mnist_train_lmdb と mnist_test_lmdbのデータができている

LeNet: the MNIST Classification Model

学習プログラムを実行する前に、何が行われるか説明する。数字分類タスクで有名なLeNetを使用する。
オリジナルと若干異なるバージョンを使用する。
sigmoid activationsの代わりに、ニューロン用のRectified Linear Unit (ReLU) activationsを代わりに使用する

LeNetのデザインは、ImageNetのようなおおきなモデルに依然として使用されているCNNの本質を含んでいる。
一般的に、プーリング層に続く畳み込み層、別のプーリング層に続く畳み込み層、従来の多層パーセプトロンに似ている2つの全結合層からなる。
$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxtに、層の定義を行う

MNISTネットワークの定義

MNISTの手書き数字分類タスクのためのLeNetモデル向け、lenet_train_test.prototxt モデル定義の説明を行う。
Google Protobufに関して知っているものとする。また、$CAFFE_ROOT/src/caffe/proto/caffe.proto にある、
Caffeが使用しているprotobufの定義を読んでいるものとする。

具体的には、caffe::NetParameter protobufを記述する

ネットワーク名から始める
name: "LeNet"

データ層の記述

デモで作成したlmdbからMNISTデータを読み込む。データ層に定義される

layers {
  name: "mnist"
  type: DATA
  data_param {
    source: "mnist_train_lmdb"
    backend: LMDB
    batch_size: 64
    scale: 0.00390625
  }
  top: "data"
  top: "label"
}

具体的にはこの層は、data タイプで、mnistの名前を有する。lmdbからデータを読み込む
バッチサイズ64を使用する。入力されるピクセルのレンジが[0,1)にスケーリングする

なぜ、0.00390625なのか、それは、1を256で割った値である。
この層は、データブロブとラベルブロブを出力する。

畳み込み層の記述

最初の畳み込み層を定義しよう

layers {
  name: "conv1"
  type: CONVOLUTION
  blobs_lr: 1.
  blobs_lr: 2.
  convolution_param {
    num_output: 20
    kernelsize: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
  bottom: "data"
  top: "conv1"
}

データ層により与えられるデータブロブを受け取り、conv1層を作成する。畳み込み関数のカーネルサイズ5で、ストライド1で、畳み込まれ、20チャンネルを出力する。
fillerは、ランダムに、重みとバイアスの初期化を行う。weight fillerには、入力と出力ニューロンの数を基準にし、初期化のスケールを決定する、xavierアルゴリズムを使用する。
bias fillerには、デフォルト値0の定数を単純に使用する。
blobs_lrは、層の学習可能なパラメータために、学習率の調整用である。

このケースでは、重み用の学習率を、実行時のソルバーにより与えられた学習率よ同じとし、バイアス学習率は、実行時のソルバーにより与えられた学習率の2倍とする。
通常より良く収束する、

プーリング層の記述

プーリング層の定義は簡単である

layers {
  name: "pool1"
  type: POOLING
  pooling_param {
    kernel_size: 2
    stride: 2
    pool: MAX
  }
  bottom: "conv1"
  top: "pool1"
}

プールカーネルのサイズを2でストライド2(隣の領域との重なりはないことを意味する)として、max poolingを行う。

同様に、2回めの畳込みとプーリングを記述する。詳細は、
$CAFFE_ROOT/examples/mnist/lenet_train_test.prototxt
をみること

全結合層の記述
結合層記述は単純

layers {
  name: "ip1"
  type: INNER_PRODUCT
  blobs_lr: 1.
  blobs_lr: 2.
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
  bottom: "pool2"
  top: "ip1"
}

出力500の、内部的事情で、innerproductとcaffeでは呼んでいる全結合層の定義である。ほかは馴染ものものである。

ReLU層の記述
ReLU層の記述は単純

layers {
  name: "relu1"
  type: RELU
  bottom: "ip1"
  top: "ip1"
}

ReLU は要素ごとの操作なので、メモリー節約のために、 in-place操作とする

これは単に、top ブロブと bottomブロブの名前を同じにする。
もちろん、他の層と同じブロブ名を使用しないこと。

ReLU層のあとに、別のinnerproduct層を記述できる

layers {
  name: "ip2"
  type: INNER_PRODUCT
  blobs_lr: 1.
  blobs_lr: 2.
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
  bottom: "ip1"
  top: "ip2"
}

Loss層の記述
最後にLoss層

layers {
  name: "loss"
  type: SOFTMAX_LOSS
  bottom: "ip2"
  bottom: "label"
}

Loss層には、softmaxとmultinomial logistic loss (時間の節約と安定性の向上)を定義できる。2つのブロブを持つ。
1つは、予測であり、もうひとつは、データ層により与えられたラベルである。これは、何も出力しない。損失関数の値を計算することではない。
バックプロパゲーションが開始され、ip2に関して勾配を開始した時に報告する。
すべての魔法が始まるところである。

記述ルール
層の定義は、ネットワークの定義の中に、含めるべきかどうか、あるいはいつ含めるかのルールを、下記のように含めることができる。
layers {
  // ...layer definition...
  include: { phase: TRAIN }
}

現在のネットワークの状態に基づき、ネットワーク層のインクルードを制御するルールである。
 $CAFFE_ROOT/src/caffe/proto/caffe.protoを参照すれば、モデルのスキーマと、層のルールの情報を得ることができる。

上記の例では、この層は学習フェーズでインクルードされる。もし、学習をテストに変えれば、テストフェーズのインクルードされるだろう。
デフォルトでは、層のルールがない場合、層は常にネットワークにインクルードされる。
lenet_train_test.prototxtでは、異なるバッチサイズの2つのデータ層を有し、1つは学習用、1つはテスト用である。
lenet_solver.prototxtに定義されているように、テストフェーズだけに含まれる、100回の繰り返しごとに、モデルの精度を報告する、精度層がある。

MNISTソルバーの定義

$CAFFE_ROOT/examples/mnist/lenet_solver.prototxtの各行の説明用のコメントを調べてみて下さい。

(別の機会に)

学習とテストモデル

ネットワーク定義protobufとソルバーprotobufを記述すれば、モデルのテストは簡単である。
train_lenet.shを実行するか、下記のコマンドを実行する。
cd $CAFFE_ROOT
./examples/mnist/train_lenet.sh

train_lenet.shは、簡単なスクリプトであり、簡単な説明を行う。学習用のメインのツールはcaffeであり、学習を行い、solver protobuf ファイルが引数である。

コードを実行すると下記のメッセージが表示され流れていく。このメッセージは、各層の詳細(各層の接続や出力形式)を示しており、デバッグに役立つだろう。

初期化のあと学習が始まる。
I1203 net.cpp:142] Network initialization done.
I1203 solver.cpp:36] Solver scaffolding done.
I1203 solver.cpp:44] Solving LeNet

ソルバーの設定により、100回の繰り返し事に学習ロスが表示される。
そして1000回の繰り返しごとに、ネットワークのテストを行い、下記のようなメッセージを見るだろう。

I1203 solver.cpp:204] Iteration 100, lr = 0.00992565
I1203 solver.cpp:66] Iteration 100, loss = 0.26044
...
I1203 solver.cpp:84] Testing net
I1203 solver.cpp:111] Test score #0: 0.9785
I1203 solver.cpp:111] Test score #1: 0.0606671

学習の繰り返しごとに、lrは、その繰り返しにおける、学習率である。lossは学習関数である。
学習フェーズの出力の場合、 score #0 は、精度で、score #1は、 損失関数である。

数分後に完了する。

I1203 solver.cpp:84] Testing net
I1203 solver.cpp:111] Test score #0: 0.9897
I1203 solver.cpp:111] Test score #1: 0.0324599
I1203 solver.cpp:126] Snapshotting to lenet_iter_10000
I1203 solver.cpp:133] Snapshotting solver state to lenet_iter_10000.solverstate
I1203 solver.cpp:78] Optimization Done.

バイナリーのprotobufファイルとして、lenet_iter_10000にモデルは保存される。
もし、実世界のデータを使用していたなら、あなたのアプリにモデルとして展開できる

CPUでの学習

全ての学習はGPUで行われた。もし、CPUで学習を行う場合、lenet_solver.prototxt ファイルの1行を

# solver mode: CPU or GPU
solver_mode: CPU

に変更すれば良い

MNISTのデータセットは小さいので、GPUは、データの転送のオーバヘッドが大きいので、恩恵が少ない。ImageNetのような巨大なデータセットでは、計算速度は非常に重要である。

以上

0 件のコメント:

コメントを投稿