2018年5月6日日曜日

tiny-dnnでディープニューラルネット超入門

最近話題のディープニューラルネットを使って色々応用できたらいいなぁと思ったのでtiny-dnnを使って入門してみることにしました。tensorflowとか有名どころのnnプラットフォームはみんなpythonなので、C++で使えるtiny-dnnは数少ない選択肢で非常に有り難いです。以前、仕事で画像判別にOpenCV機械学習ライブラリのCvANN_MLPを使ったことあるので似たようなクラス判別処理のスケルトンをtiny-dnnで実装してみます。

このtiny-dnnですが、C++11のテンプレート機能をゴリゴリ使っているためコンパイラがかなり最近のものに限定されます。
  • C++ Builder XE3 … 少し古いためC++11が部分対応なのでコンパイル不可
  • Visual C++ 2017 … コンパイラのテンプレート展開のバグらしく、不可解なエラーが出てコンパイル不可
  • Visual C++ 2015 … 正式に対応が謳われているため問題なし
なのでVC2015を使うことに決定。

どんなニューラルネットを作るか?
以下のような単純な4次元の入力データを4つ用意し、それぞれクラス0,1に分類するだけの超簡単なクラス判別NNを作成してみます。
S1 = [1,0,1,0] ; クラス0
S2 = [0,1,0,1] ; クラス0
S3 = [1,1,0,0] ; クラス1
S4 = [0,0,1,1] ; クラス1
入力データが4次元なので入力層は4点、 クラスが2つなので出力層は最低2点あれば良いことになります。
また、一層のパーセプトロンでは線形判別できないので、中間層は32点結合の3層としました。

#define _SCL_SECURE_NO_WARNINGS
#include <tiny_dnn/tiny_dnn.h>
using namespace tiny_dnn;
using namespace tiny_dnn::layers;

void main(int argc,...)
{
    //-------------------------------------------------------------
    // ニューラルネットの構造を定義
    //-------------------------------------------------------------

    network<sequential> net;
    net << fc(4, 32)  << activation::tanh()
        << fc(32, 32) << activation::tanh()
        << fc(32, 32) << activation::tanh()
        << fc(32, 3)  << activation::softmax();

    //-------------------------------------------------------------
    // 学習データ(ベクトルの次元 = 入力層の点数)
    //-------------------------------------------------------------

    vec_t sample1;
    vec_t sample2;
    vec_t sample3;
    vec_t sample4;

    sample1.assign(4, 0);
    sample1[0] = 1;
    sample1[2] = 1;

    sample2.assign(4, 0);
    sample2[1] = 1;
    sample2[3] = 1;

    sample3.assign(4, 0);
    sample3[0] = 1;
    sample3[1] = 1;

    sample4.assign(4, 0);
    sample4[2] = 1;
    sample4[3] = 1;

    std::vector<vec_t >  trainData;
    std::vector<label_t> trainLabels;

    trainData.assign(4, vec_t()); // クラス分類数
    trainData[0] = sample1;
    trainData[1] = sample2;
    trainData[2] = sample3;
    trainData[3] = sample4;

    // 教師データ
    // 学習サンプルがどのクラスに属するかを教示するデータ。
    // クラス番号は 0 to 出力層の点数M-1 の範囲。

    trainLabels.assign(4, 0);
    trainLabels[0] = 0;
    trainLabels[1] = 0;
    trainLabels[2] = 1;
    trainLabels[3] = 1;

    //-------------------------------------------------------------
    // 学習実行
    //-------------------------------------------------------------

    adagrad optimizer;

    if (!net.train<mse>(optimizer, trainData, trainLabels, 1, 200))
    {
        printf("train - error");
        return;
    }

    //-------------------------------------------------------------
    // 未知データを判定
    //-------------------------------------------------------------

    for(int i=0;i<trainData.size();++i)
    {
        printf("predict #%d\n", i+1);

        tensor_t result;
        std::vector<vec_t> unknownData;
        unknownData.assign(1, vec_t());

        unknownData[0] = trainData[i]; // 学習に使ったデータを判定してみる
  
        result = net.predict(unknownData);

        vec_t vec = result[0];

        for (int i = 0; i < vec.size(); ++i)
            printf("Class%d : %0.2f\n", i, vec[i]);

        printf("\n");
    }