前回の日記でCaffeをC++から使うことができたので、3層パーセプトロンを学習させてみた。
Caffeではモデルをprototxt形式で記述する。
学習方法を記述したsolver.prototxtとネットワークを記述したnet.prototxtの2つのファイルを作成する。
ファイル名は別の名前でもよい。
3層パーセプトロンを学習する場合は以下のように記述する。
solver.prototxt
net: "net.prototxt" type: "RMSProp" base_lr: 0.01 lr_policy: "step" gamma: 0.1 stepsize: 1000 max_iter: 1000 display: 10
net.prototxt
name: "MultilayerPerceptrons" layer { name: "input" type: "MemoryData" top: "input" top: "label" memory_data_param { batch_size: 10 channels: 2 height: 1 width: 1 } } layer { name: "hidden1" type: "InnerProduct" bottom: "input" top: "hidden1" inner_product_param { num_output: 3 weight_filler { type: "xavier" } bias_filler { type: "constant" } } } layer { name: "relu1" type: "ReLU" bottom: "hidden1" top: "hidden1" } layer { name: "hidden2" type: "InnerProduct" bottom: "hidden1" top: "hidden2" inner_product_param { num_output: 2 weight_filler { type: "xavier" } bias_filler { type: "constant" } } } layer { name: "loss" type: "SoftmaxWithLoss" bottom: "hidden2" bottom: "label" top: "loss" include { phase: TRAIN } } layer { name: "prob" type: "Softmax" bottom: "hidden2" top: "prob" include { phase: TEST } }
記述方法の詳細は、Caffeの公式ページのSolverやLayersを参考にしてほしい。
入力層は、C++のプログラムからデータを与えるため、
type: "MemoryData"
としている。
出力層は、
include { phase: TRAIN }
と
include { phase: TEST }
を記述したものの2つあるが、学習用と予測用で分けているためである。
学習用と予測用を別ファイルにしてもよい。
続いてC++のプログラムについて解説する。
学習
初期化
FLAGS_alsologtostderr = 1; GlobalInit(&argc, &argv); Caffe::set_mode(Caffe::CPU);
初期化とモードの設定を行う。この場合はCPUモードとしている。
prototxtを読み込んでSolverを作成する
SolverParameter solver_param; ReadProtoFromTextFileOrDie("solver.prototxt", &solver_param); std::shared_ptr<Solver<float>> solver(SolverRegistry<float>::CreateSolver(solver_param)); const auto net = solver->net();
入力データを設定する
const int data_size = 100; const int input_num = 2; float input_data[data_size][input_num] = { { 0.18, 0.15 }, { 0.20, 0.90 }, { 0.94, 0.11 }, ・・・(略) }; float label[data_size] = { 1, 0, 0, ・・・(略) }; const auto input_layer = boost::dynamic_pointer_cast<MemoryDataLayer<float>>( net->layer_by_name("input")); input_layer->Reset((float*)input_data, (float*)label, data_size);
予測
初期化
Net<float> net_test("net.prototxt", TEST); net_test.CopyTrainedLayersFrom("_iter_1000.caffemodel");
ネットワーク定義とモデルパラメータを読み込む。引数をTESTにすることで、phase: TESTとしたlayerが有効になる。
予測する
const auto input_test_layer = boost::dynamic_pointer_cast<MemoryDataLayer<float>>( net_test.layer_by_name("input")); for (int batch = 0; batch < 10; batch++) { input_test_layer->Reset((float*)input_data + batch * 20, (float*)label + batch * 10, 10); const auto result = net_test.Forward(); const auto data = result[1]->cpu_data(); for (int i = 0; i < 10; i++) { LOG(INFO) << data[i * 2] << ", " << data[i * 2 + 1] << ", " << (data[i * 2] < data[i * 2 + 1]) ? 0 : 1; } }
学習では、入力データは複数ミニバッチのデータをまとめて入力できたが、予測では、ミニバッチサイズ分のデータを入力とする。
MemoryDataLayerはラベルが必須のためダミーのデータを与えている。
入力データを入力層に設定した後、「Forward()」で順伝播を実行し、結果を戻り値で受け取る。
結果のvectorの1番目の要素は、ダミーで与えたラベルになっているので無視する。
2番目の要素がこの例では、softmaxの出力になっている。
実行結果
入力データとして、x1とx2が0.7未満の場合にクラス0、それ以外がクラス1となる、非線形な識別面のデータを学習させた。
学習を行った結果、以下のグラフとなった。正解率は96%である。
非線形な識別面が正しく学習できている。
(サンプルのため、学習データと同一のデータを与えているので、汎化能力については検証していない。)
学習方法は、RMSPropとした場合に一番うまくいった。
なお、SGDの場合は、識別面がほぼ直線となり正しく学習できなかった。
Visual Studio 2013のプロジェクトをGithubに公開しているので参考にしてほしい。
補足
普通にCaffeで学習させる場合は、caffeコマンドを使った方が簡単です。
予測を行う場合は、pycaffeを使う方が簡単です。
CaffeをC++のプログラムに組み込んで使いたい方向けの説明です。
Caffeのprototxtの記述方法については、以下の本を参考にしました。