【windowsでtiny-dnn】簡単なクラス分類を行う

 

六花です。
前回の記事で、とりあえずインクルードができるようになりました。
今回は、簡単な関数を使って学習データを作り、クラス分類できるかやってみましょう。

 

■環境

Visual Studio Community 2017
tiny-dnn (2018/12/16ダウンロード)

 

■「簡単な関数」の定義

0~3の値域を持つ10個の変数があります。
これをすべて足し合わせると、0~30のどれかになります。
今回はこれを使って、入力10次元、出力31次元の3層ニューラルネットワークを作りましょう。

 

■実装


#include <iostream>
#include <random>
#include <vector>

#include "tiny_dnn/tiny_dnn.h"

using namespace std;
//using namespace tiny_dnn; // わかりづらくなるので省略しない

int main()
{
    // 乱数生成用
    random_device rd;
    mt19937_64 mt(rd());

    // 学習用データ
    vector<tiny_dnn::vec_t> input_train;
    vector<tiny_dnn::vec_t> teach_train;
    vector<tiny_dnn::label_t> label_train;
    vector<tiny_dnn::vec_t> input_test;
    vector<tiny_dnn::vec_t> teach_test;
    vector<tiny_dnn::label_t> label_test;

tiny-dnnはtiny-dnn名前空間に記述されていますが、これを省略すると、エラーが起きた時に自分が正しいのか情報が古いのかわからなくなったことがあったので、今回はusingで省略しません。

今回は学習用データの作成に乱数を使用します。

学習用データは独自の定義のように見えますが、中身はvectorとsize_tです。
ただ、vectorの方はちょっとひと手間加えているようなので、おとなしく独自定義の名前で使ったほうが良さそうです。


    // 学習用データ生成
    int max_record = 1000;
    for (int i_record = 0; i_record < max_record; ++i_record)
    {
        auto makeData = [&rd](vector<tiny_dnn::vec_t>& input, vector<tiny_dnn::vec_t> &teach, vector<tiny_dnn::label_t> &label)
        {
            auto dist = uniform_int_distribution<int>(0, 3);

            input.emplace_back();
            for (int i_param = 0; i_param < 10; ++i_param)
            {
                input.back().emplace_back(dist(rd));
            }
            int sum = 0;
            for (auto itm : input.back()) { sum += (int)itm; }

            teach.emplace_back(31, 0.0f);
            teach.back().at(sum) = 1.0f;

            label.emplace_back(sum);
        };

        if (i_record < (int)((float)max_record * 0.7f))
        {
            makeData(input_train, teach_train, label_train);
        }
        else
        {
            makeData(input_test, teach_test, label_test);
        }
    }

学習用データ生成編です。

inputは乱数で0~3までの数字が10個連結されたtiny_dnn::vec_tを、データ件数分だけ連結したデータです。

そして、関数の答えとなる10個の数を足した数ですが、このデータの格納方法が学習時と誤差の計測時で違います。(teachとlabel)
labelが学習用でteachが誤差計測用です。

また、それらのデータをtrainとtestに分けてあります。(汎化性能確認用……この規模だと必要ないかと思いますが)


    //ネットワークの構築
    tiny_dnn::network<tiny_dnn::sequential> net;
    {
        net << tiny_dnn::fully_connected_layer(10, 50);
        net << tiny_dnn::tanh_layer();

        net << tiny_dnn::fully_connected_layer(50, 31);
        net << tiny_dnn::softmax_layer();
    }

    // オプティマイザの決定
    tiny_dnn::adam optimizer;

ネットワークの構築です。

二年前くらいの記事とは定義方法ががらりと異なっています。
中間層は何となく50にしてありますが、ただの直感で根拠はありません。

tiny_dnn::fully_connected_layerは(入力層の要素数, 出力層の要素数)です。
当然前後の整合性が取れていなければならず、実行時にエラーを出します。

オプティマイザはadamをはじめいくつかあります。
ちなみに、オプティマイザにはpublicメンバ変数があり、学習率などを変更できる場合があります。


    // 学習実行
    net.train<tiny_dnn::cross_entropy_multiclass>(optimizer, input_train, label_train, 64, 1);

    // 誤差の計測
    auto loss_train = net.get_loss<tiny_dnn::mse>(input_train, teach_train) / (int)((float)max_record * 0.7f);
    auto loss_test  = net.get_loss<tiny_dnn::mse>(input_test,  teach_test ) / (int)((float)max_record * 0.3f);

    // 出力
    cout << loss_train << "\t" << loss_test << endl;

    // 処理を止める
    while (true) { this_thread::yield(); }

    return 0;
}

クラス分類には.trainが使用されます。また、cross_entropy_multiclassのほかにmseも使用できます。
64はミニバッチサイズ、1はエポック数です。

get_lossで得られる誤差は各データで算出した誤差の合計値です。
そのため、データ件数で割ると直感的な値になるかと思います。

実行すると、誤差が大体0.03くらいでした。

 

■終わりに

tiny-dnnはビルド時間が結構長いです。(特にEigenなどと比べると)
処理時間も特段速いとは言えませんが、c++で手軽にニューラルネットワーク試してみようとすると他に選択肢がありません。
(Eigenなどを使って自前で実装する手段はあります!)

私の投稿を機にニューラルネットワークに触れる人のすそ野が広がればいいなあと思います。

それでは!

おすすめ

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です