AdaDeltaを試す
はじめに
勉強会で、学習率を改善(自動調整)する事で学習時間を短縮し、ファンタジスタドールを見る時間を多く確保できる事が示されていた。
AdaGrad等をさらに改良したらしいAdaDeltaがあるようなので、ロジスティック回帰に適用してみた。
AdaDeltaとは
- M. D. Zeiler, ADADELTA: AN ADAPTIVE LEARNING RATE METHOD
- 学習率を自動調整する方法の一つ
- 他の関連手法の問題点等を改良
関連手法
- Momentum法
- 過去のパラメータの値を指数関数的に減衰させつつ考慮
- AdaGrad法
- 過去すべての勾配の2乗和の平方根の逆数を利用
- Hessian diagonal approximation(Becker&LecCun)
- ヘッシアンの対角化の逆数を利用
- Schaulらの方法
- ヘッシアンの対角化の逆数に、直近w回の勾配を使うAdaGradに似た項の追加
コード
http://d.hatena.ne.jp/jetbead/20131129/1385658526
上記の学習率を定数ではなくAdaDeltaに置き換える。
#include <iostream> #include <fstream> #include <sstream> #include <vector> #include <string> #include <unordered_map> #include <cmath> //乱数計算用関数 //xorshift // 注意: longではなくint(32bit)にすべき unsigned long xor128(){ static unsigned long x=123456789, y=362436069, z=521288629, w=88675123; unsigned long t; t=(x^(x<<11)); x=y; y=z; z=w; return w=(w^(w>>19))^(t^(t>>8)); } //[0,1)の一様乱数 double frand(){ return xor128()%10000000000/static_cast<double>(10000000000); } //ロジスティック回帰クラス class LR_adadelta { double rho_, eps_; double C_; std::unordered_map<std::string,double> weights, eg2, edx2; //シグモイド関数 double sigmoid(double z){ return 1.0 / ( 1.0 + exp(-z) ); } //σ関数 double sigma(const std::vector< std::pair<std::string,double> >& x){ double z = 0; for(size_t i=0; i<x.size(); i++){ z += weights[ x[i].first ] * x[i].second ; } return sigmoid(z); } //RMS[x] double RMS(double x){ return sqrt( x + eps_ ); } public: LR_adadelta(double rho, double eps, double C): rho_(rho),eps_(eps),C_(C) {} //素性の重みをランダム値で初期化 void setRandWeight(const std::string& feat){ weights[ feat ] = frand(); } //予測関数 int predict(const std::vector< std::pair<std::string,double> >& x){ return sigma(x) > 0.5 ? 1 : 0; } //確率値の計算 double prob(const std::vector< std::pair<std::string,double> >& x){ return sigma(x); } //L2正則化ロジスティック回帰 + AdaDelta void trainL2(int t, const std::vector< std::pair<std::string,double> >& x){ double pred = sigma(x); std::unordered_map<std::string,double> xx; for(size_t i=0; i<x.size(); i++){ xx[x[i].first] = x[i].second; } std::unordered_map<std::string,double>::iterator itr; for(itr = weights.begin(); itr != weights.end(); ++itr){ //Compute Gradient: g_t double g = ( (pred-t) * xx[itr->first] + C_ * weights[ itr->first ] ); //Accumulate Gradient: E[g^2]_t = \rho E[g^2]_{t-1} + (1-\rho) * g^2_t eg2[ itr->first ] = rho_ * eg2[ itr->first ] + (1.0 - rho_) * (g * g); //Compute Update: \Delta x_t = - (RMS[\Delta x]_{t-1}) / (RMS[g]_t) * g_t double deltax = - RMS(edx2[ itr->first ]) / RMS(eg2[ itr->first ]) * g; //Accumulate Updates: E[\Delta x^2]_t = \rho E[\Delta x^2]_{t-1} + (1-\rho) * \Delta x^2_t edx2[ itr->first ] = rho_ * edx2[ itr->first ] + (1.0 - rho_) * (deltax * deltax); //Apply Update: x_{t+1} = x_t + \Delta x_t weights[ itr->first ] += deltax; } } //素性の数 int weights_num(){ return weights.size(); } //ファイルへ重みを出力 void save(const std::string& filename){ std::ofstream ofs(filename); std::unordered_map<std::string,double>::iterator itr = weights.begin(); for(; itr != weights.end(); ++itr){ ofs << itr->first << " " << itr->second << std::endl; } } //ファイルから重みを入力 void load(const std::string& filename){ std::ifstream ifs(filename); weights.clear(); std::string name; double value; while(ifs >> name >> value){ weights[ name ] = value; } } }; int main(){ //パラメータ int iter_num = 1; //学習の反復回数 double rho = 0.95; //減衰率 double eps = 0.00001; //小さい定数 double C = 0.001; //正則化のパラメータ std::ifstream trainfile("./a9a"); //学習データファイル std::ifstream testfile("./a9a.t"); //評価データファイル LR_adadelta lr(rho, eps, C); std::vector<int> dat_t; std::vector< std::vector< std::pair<std::string,double> > > dat; std::string line, feat; //学習//////////////////////////////////////////////// //学習データの読み込み while(std::getline(trainfile, line)){ int t; std::stringstream ss(line); ss >> t; if(t>0) t = 1; else t = 0; dat_t.push_back(t); std::vector< std::pair<std::string,double> > v; while(ss >> feat){ std::string::size_type idx = feat.find(":"); std::string name = feat.substr(0, idx); double value = atof(feat.substr(idx+1).c_str()); v.push_back(std::make_pair(name, value)); lr.setRandWeight(name); //素性の重みをランダム値で初期化しておく } dat.push_back(v); } //学習ループ for(int t=0; t<iter_num; t++){ for(int i=0; i<dat.size(); i++){ lr.trainL2(dat_t[i], dat[i]); //現在の学習データ全部での評価値を表示 //int cnt = 0, cnt_zero = 0; //for(int j=0; j<dat.size(); j++){ // if(dat_t[j] == lr.predict(dat[j])) cnt++; //} //std::cerr << (double)cnt / dat.size() << std::endl; } } //重みデータの出力 lr.save("weights.txt"); //予測//////////////////////////////////////////////// dat_t.clear(); dat.clear(); lr.load("weights.txt"); //重みデータの読み込み //テストデータの読み込み while(std::getline(testfile, line)){ int t; std::stringstream ss(line); ss >> t; if(t>0) t = 1; else t = 0; dat_t.push_back(t); std::vector< std::pair<std::string,double> > v; while(ss >> feat){ std::string::size_type idx = feat.find(":"); std::string name = feat.substr(0, idx); double value = atof(feat.substr(idx+1).c_str()); v.push_back(std::make_pair(name, value)); } dat.push_back(v); } //評価 { int cnt = 0, cnt_zero = 0; for(int i=0; i<dat.size(); i++){ //std::cout << dat_t[i] << "\t" << lr.predict(dat[i]) << "\t"; printf("%.8lf\n", lr.prob(dat[i])); if(dat_t[i] == lr.predict(dat[i])) cnt++; } //最終結果の表示 std::cout << cnt << "/" << dat.size() << " Acc=" << (double)cnt / dat.size() << std::endl; } return 0; }
結果
$ ./a.out 13750/16281 Acc=0.844543
学習データをt事例まで学習したとき(横軸)、学習データの事例全部での評価値(縦軸)の変化をプロット。
青線が、学習率が定数の場合。赤線が、AdaDeltaの場合。
ちゃんと定数の場合より速く収束している(のでたぶん大丈夫)。。。
ハイパーパラメータは、epsilonが小さいと評価値は高まる傾向みたいだけど、収束が遅くなってしまうみたい。。
参考
- http://www.matthewzeiler.com/pubs/googleTR2012/googleTR2012.pdf
- http://laughing.hatenablog.com/entry/2014/11/30/%E3%82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%B3%E5%AD%A6%E7%BF%92%E5%91%A8%E3%82%8A%E3%82%92%E8%AA%BF%E3%81%B9%E3%81%A6%E3%81%84%E3%82%8B
- http://www.willamette.edu/~gorr/classes/cs449/momrate.html
- http://d.hatena.ne.jp/echizen_tm/20140914/1410697535
- http://ja.scribd.com/doc/240371365/30minutes-Adagrad-Rda#scribd
- http://www.logos.t.u-tokyo.ac.jp/~hassy/deep_learning/adagrad/