やねうら王の学習部、う○こではないかと書いたばかりだが、コードを全面的に刷新した。tanuki-さんの協力もあって、とてもシンプルで美しいコードが書けたし、おまけに極めて省メモリで学習が出来るようになり(評価関数パラメーター用のメモリの4.5倍の学習用の重み配列があれば良い)、かつ、とても高速になった。
省メモリ化のためにKKP/KPP配列をfloat型やdouble型などで別途持つような実装にはせず、生のKKP/KPP配列を用いて、この小数部として8bit(int8_t)を補助bitとして1バイト変数に持つような実装になっている。つまり、やねうら王のAdaGradの更新式は以下のように実装されている。
私が太古の昔(40年近く前)に見たことのあるテクノロジー(もはやロストテクノロジー?)で、とてもセコい実装ではあるが、これで万事うまくいく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// AdaGradの更新式 // 勾配ベクトルをg、更新したいベクトルをv、η(eta)は定数として、 // g2 = g2 + g^2 // v = v - ηg/sqrt(g2) constexpr double epsilon = 0.000001; for (int i = 0; i < 2; ++i) { if (g[i] == 0) continue; g2[i] += g[i] * g[i]; // v8は小数部8bitを含んでいるのでこれを復元する。 // 128倍にすると、-1を保持できなくなるので127倍にしておく。 // -1.0~+1.0を-127~127で保持している。 // std::round()限定なら-0.5~+0.5の範囲なので255倍でも良いが、 // どんな丸め方をするかはわからないので余裕を持たせてある。 double V = v[i] + ((double)v8[i] / 127); V -= eta * (double)g[i] / sqrt((double)g2[i] + epsilon); // Vの値をINT16の範囲に収まるように制約を課す。 V = min((double)INT16_MAX * 3 / 4, V); V = max((double)INT16_MIN * 3 / 4, V); v[i] = (T)round(V); v8[i] = (s8)((V - v[i]) * 127); // この要素に関するmini-batchの1回分の更新が終わったのでgをクリア //g[i] = 0; // これは呼び出し側で行なうことにする。 } |
また、新しい学習部による実験結果などはのちほど別の記事に詳しく書く。
エルモに勝ち越すような実験結果を期待してます!
まず正常に動作していることを証明するところからではないですか…。
26行目の V – v[i] の結果って、-0.5~+0.5ですよね?
そうすると、127倍じゃなくて、254倍でも良いのでは?
まあ、そうなんですけど…。
https://github.com/yaneurao/YaneuraOu/blob/7e6e5f81e61e93941a8a132e4f7cceaf0fac8407/source/learn/evaluate_kppt_learn.cpp#L108
失礼しました。既にgitの更新でコメント入れてくださってたんですね。先にそっちを見とけば良かったです。申し訳ない。
いえいえ…。本記事も同様にコメント追記しておきますね。