「HGNN - HalfGammon Neural Network -」は、人工ニューラルネット(ANN)を使用し、ハーフギャモンの状態から得点期待値を予測する
ビルド環境:Visual Studio 2019、C++
HGNN の使用方法は以下の通り
ニューラルネットを構築するコード例を以下に示す。
#include <vector> #include "HGNNet.h" using namespace std; ..... HGNNet nn; // ニューラルネットオブジェクト生成 nn.init(vector<int>{2, 10}, TANH); // 入力層:2ノード、1隠れ層・10ノード、活性化関数:tanh
最初に標準ライブラリの vector をインクルードする。これはニューラルネットの層数・各層のノード数を指定するために使用する。
ニューラルネットクラス HGNNet のヘッダもインクルードしておく。
「using namespace std;」は好みで記述するといいだろう。記述しておけば、以下「std::」を省略できる。
「HGNNet nn;」で、ニューラルネットオブジェクトを生成する。引数は特に必要ない。
ついで、HGNNet::init(vector<int>&, ActFunc) をコールし、ニューラルネットを初期化する。重み係数は [-1, +1] でランダムに初期化される。
第一引数は層数・各層のノード数を指定する。HGNNet は出力に回帰だけをサポートしており、出力ノードは1固定なので、出力ノードは指定しない。
入力層ノード数に続けて、隠れ層の分だけ各層のノード数を記述する。下記のように vector オブジェクトの生成を分けてもいいが、1行で書く方が簡潔で筆者の好みだ。
vector<int> nodes = {2, 10}; nn.init(nodes, TANH);
隠れ層が複数ある場合は、その数だけ隠れ層のノード数を記述する。下記は隠れ層が2層の場合の例だ。
nn.init(vector<int>{2, 10, 10}, TANH);
HGNNet::init() の第2引数には活性化関数を指定する。SIGMOID, TANH, RELU が指定可能だ。各々、シグモイド関数、tanh関数、ReLU関数を示す。
これらの活性化関数は隠れ層の演算の時に用いられる。HGNNet の出力は回帰なので、活性化関数は適用されない(あえて言えば恒等関数だ)。
vector<data_t> input; input に入力データを設定 data_t T = 教師値; nn.train(input, T);
学習(トレーニング)を行うには、上記の様に、入力データを vector<data_t> に設定し、それと教師値を引数にして HGNNet::train() をコールする。
data_t は HGNNet.h で宣言されたデータ型識別子で、double が割り当てられている。メモリが不足する場合などは float に定義し直すという選択肢もある。
学習を行うと誤差逆伝播法を用いてネットワークの重み係数が教師値に近づくように少し変化する。
train() の第3引数で係数修正の度合いを学習率で指定できる。デフォルトは 0.01 である。この値が大きいと学習が速く進むが、その反面発散確率が高まる。
なので、徐々に学習率を下げながら学習を行うというテクニック(adagrad 等)も存在する。
vector<data_t> input; input に入力データを設定 data_t T = 教師値; nn.train(input, T, 0.001); // 学習係数指定
学習が終わったネットワークに対し、nn.predict() をコールすることで、入力値から教師値に近い値を予測することができる。
コードは以下のように、vector<data_t> に入力データを設定し、predict() をコールすると、予測値を返してくれる。
vector<data_t> input; input に入力データを設定 auto v = nn.predict(input);
「y = 3*x1 - 2*x2 + 1」、2層NN
data_t linearFunc(data_t x1, data_t x2) { return x1*3 - x2*2 + 1; } double linearRMS(HGNNet& nn, int N_LOOP = 100) { vector<double> input(2); double sum2 = 0; for (int i = 0; i < N_LOOP; ++i) { input[0] = g_rand11(g_mt); // [-1, +1] input[1] = g_rand11(g_mt); // [-1, +1] double err = nn.predict(input) - linearFunc(input[0], input[1]); sum2 += err * err; } return sqrt(sum2 / N_LOOP); } void test_linearFunc() { HGNNet nn; cout << "# node of layers: {2 1}\n\n"; nn.init(vector<int>{2}, SIGMOID); // 2入力のみ(隠れ層無し) vector<double> input(2); // 学習・評価 cout << "N\tRMS\n"; cout << "------- ----------\n"; for (int cnt = 1; cnt <= 10000; ++cnt) { input[0] = g_rand11(g_mt); // [-1, +1] input[1] = g_rand11(g_mt); // [-1, +1] nn.train(input, linearFunc(input[0], input[1])); if( log10(cnt) == (int)log10(cnt) ) { cout << "10^" << log10(cnt) << "\t" << linearRMS(nn) << endl; } } cout << "\n" << nn.dump() << "\n"; }