ギブスサンプリングを試す(2次元正規分布の場合)
はじめに
ギブスサンプリングで2次元正規分布からサンプリングを試してみる。
サンプリングの流れ
2次元正規分布なので(x,y)がサンプリングされる。
- 初期値(x0,y0)を決める
- y0を固定した場合の条件付き正規分布からx1をサンプリングする→(x1,y0)
- 次に、x1を固定した場合の条件付き正規分布からy1をサンプリングする→(x1,y1)
- 繰り返し
- 1次元の(条件付き)正規分布からのサンプリングはBox-Muller法で行ってみる
条件付き正規分布
コード
#include <iostream> #include <vector> #include <utility> #include <cmath> //2次元正規乱数(Gibbsサンプリング) class normal_rand_gibbs { static const double PI = 3.14159265358979323846264338; //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)の一様乱数 // 注意: int_maxぐらいで割るべき double frand(){ return xor128()%10000000000/10000000000.0; //上の返り値は偏りがありそうなので、以下を用いた方がよさそう //return xor128()%ULONG_MAX/static_cast<double>(ULONG_MAX); } //1次元正規乱数(Box-Muller法) double normal_rand(double mu, double sigma2){ double sigma = sqrt(sigma2); double u1 = frand(), u2 = frand(); double z1 = sqrt(-2*log(u1)) * cos(2*PI*u2); //double z2 = sqrt(-2*log(u1)) * sin(2*PI*u2); return mu + sigma*z1; } public: normal_rand_gibbs(double x0, double y0, double mu_x, double mu_y, double sig_xx, double sig_xy, double sig_yy ): tx(x0), ty(y0), mu_x(mu_x), mu_y(mu_y), sig_xx(sig_xx), sig_xy(sig_xy), sig_yy(sig_yy), turn(false){} //2次元正規乱数生成 std::pair<double, double> rand(){ if(!turn){ //xをサンプリング tx = normal_rand(mu_x + (sig_xy/sig_yy)*(ty-mu_y), sig_xx + sig_xy*sig_xy/sig_yy ); }else{ //yをサンプリング ty = normal_rand(mu_y + (sig_xy/sig_xx)*(tx-mu_x), sig_yy + sig_xy*sig_xy/sig_xx ); } if(turn) turn = false; else turn = true; return std::make_pair(tx,ty); } private: double tx, ty; //現在の位置 bool turn; //false->x, true->y double mu_x, mu_y; //平均 double sig_xx, sig_xy, sig_yy; //分散 }; int main(){ // 平均 = ( mu_x, mu_y ) const double mu_x = 2.0; const double mu_y = 3.0; // 共分散行列 = ( sig_xx sig_xy ) // ( sig_xy sig_yy ) const double sig_xx = 5.0; const double sig_xy = -1.5; const double sig_yy = 3.0; //スタート位置 const double start_x = 0.0; const double start_y = 0.0; normal_rand_gibbs nrg(start_x, start_y, mu_x, mu_y, sig_xx, sig_xy, sig_yy); std::cout << start_x << "\t" << start_y << std::endl; for(int i=2; i<=3000; i++){ std::pair<double,double> pos = nrg.rand(); std::cout << pos.first << "\t" << pos.second << std::endl; //if(i%100==0) std::cout << "\n\n" << std::endl; } return 0; }
結果
- ケース1
- 平均:(0, 0)
- 分散:( (1.0, 0.5), (0.5, 1.0) )
- 初期位置:(10.0, 10.0)
- 100ターンごとに色を変えて
- ケース2
- 平均:(2.0, 3.0)
- 分散:( (5.0, -1.5), (-1.5, 3.0) )
- 初期位置:(0.0, 0.0)
- 3000回分
一応、分布に従ってるよう。
例えば、サンプリングした全ての点を使って平均や分散を計算すれば指定の分布のパラメータの近似になる(はず)
ケース1のように、初期値によっては適切なサンプリングに収束するまである程度かかる(burn-in)。
なので、最初の何ターンかは捨てる必要がある。
参考文献
- 「パターン認識と機械学習-ベイズ理論による統計的予測- 上下」
- http://d.hatena.ne.jp/n_shuyo/20100507/gibbs