Mac OS X と Linux で rand () の実装が違うのか

時間計測用のサーバ (Mac OS X 10.5) は 32GB しかメモリがないので,20GB ほどメモリを消費する L1-LLM (SGD) のハイパーパラメタの調整には 256GB のサーバ (Red Hat Enterprise Linux 5.4) を使っていたが,ライブラリまで含めて同じバージョンの gcc (4.5.0) でコンパイルした学習器が二つのサーバで微妙に異なるモデルを学習するのに気がついた.少し調べると,どうも(学習を安定させるため)訓練例を random_shuffle しているところが良くないらしい.gcc 4.5.0 の実装 (include/c++/4.5.0/bits/stl_algo.h) では,どちらも rand () を呼んで乱数を生成しているのだが,実験の再現性を確保するため srand () は敢えて呼んでないので,rand () の実装が同じなら一緒の結果が返るはず(実際,それぞれのサーバ上では常に同じモデルが学習される).

さらに調べると,BSDLinux で rand () の実装が違うらしいことが分かった.古典的な rand () は生成する乱数の性質が悪いので,Linux では random () と同じ実装になっているが,BSD (Mac OS X) では rand () はそのままにしてあるらしい (man srand しても分かる).というわけで,rand () を内部で呼び出す random_shuffle () は返す結果が変わってしまう.random () を乱数生成に使ってみると両サーバで一緒の結果になったが,どうせならと tr1/random のメルセンヌ・ツイスタを使うことにした.

// #include <tr1/random>
std::tr1::mt19937 mt;         // メルセンヌ・ツイスタ
std::tr1::uniform_int <> uni; // 一様分布
std::tr1::variate_generator
  <std::tr1::mt19937, std::tr1::uniform_int <> > gen (mt, uni); // 乱数生成器
std::random_shuffle (ex.begin (), ex.end (), gen); // 訓練例 (ex) を shuffle

これで問題なし.BSD の rand () は使わないよう,以後気をつけよう.オンライン学習で random_shuffle を第三引数を省略して使うと,学習結果に影響が出るので注意したほうが良さそうだ.なお,gcc の実装がどうなっているかは知らないが,間違いだらけの疑似乱数選びを見る限り,mt19937 と rand () との速度差はあまり気にしなくて良さそうだ.
擬似乱数エンジンに限らず,ハッシュテーブルなど必要なライブラリが多いので「c++0x の 0x は 16進数」とか言ってないで,c++0x はなるべく早くリリースして欲しいところ.