CまたはC++の正規分布に従って乱数を簡単に生成するにはどうすればよいですか?
Boostを使用したくありません。
クヌースがこれについて長々と語っていることは知っていますが、彼の本は今のところ手元にありません。
通常のRNGからガウス分布の数値を生成する には多くの方法があります。
Box-Muller変換 が一般的に使用されます。正規分布で値を正しく生成します。数学は簡単です。 2つの(一様な)乱数を生成し、それらに数式を適用することにより、2つの正規分布乱数を取得します。 1つを返し、次の乱数のリクエストのためにもう1つを保存します。
C++ 11は std::normal_distribution
を提供します。これが今日の方法です。
複雑さの昇順でいくつかのソリューションを示します。
0から1までの12個の一様乱数を加算し、6を減算します。これにより、正規変数の平均と標準偏差が一致します。明らかな欠点は、真の正規分布とは異なり、範囲が±6に制限されていることです。
Box-Muller変換。これは上にリストされており、実装は比較的簡単です。ただし、非常に正確なサンプルが必要な場合は、Box-Muller変換といくつかの均一ジェネレーターを組み合わせると、Neaveエフェクトと呼ばれる異常が発生することに注意してください1。
最高の精度を得るには、ユニフォームを描画し、逆累積正規分布を適用して、正規分布の変量に到達することをお勧めします。 ここ は、逆累積正規分布の非常に優れたアルゴリズムです。
1. H. R. Neave、「乗法的合同擬似乱数ジェネレーターでのBox-Muller変換の使用について」、Applied Statistics、22、92-97、1973
すばやく簡単な方法は、均等に分散された多数の乱数を合計し、それらの平均を取ることです。これが機能する理由の完全な説明については、 中央極限定理 を参照してください。
正規分布乱数生成ベンチマーク用のC++オープンソースプロジェクト を作成しました。
以下を含むいくつかのアルゴリズムを比較します
cpp11random
はC++ 11 std::normal_distribution
をstd::minstd_Rand
とともに使用します(実際にはclangのBox-Muller変換です)。IMac Corei5-3330S @ 2.70GHz、clang 6.1、64ビットでの単精度(float
)バージョンの結果:
正確さのために、プログラムはサンプルの平均、標準偏差、歪度、尖度を検証します。 4、8、または16の一様数を合計するCLTメソッドは、他のメソッドのように尖度が高くないことがわかりました。
Zigguratアルゴリズムは、他のアルゴリズムよりもパフォーマンスが優れています。ただし、テーブル検索と分岐が必要なため、SIMD並列処理には適していません。 SSE2/AVX命令セットを備えたBox-Mullerは、非SIMDバージョンのzigguratアルゴリズムよりもはるかに高速です(x1.79、x2.99)。
したがって、SIMD命令セットを使用するアーキテクチャにBox-Mullerを使用することをお勧めします。
追伸ベンチマークは、均一な分散乱数を生成するために最も単純なLCG PRNGを使用します。そのため、一部のアプリケーションでは不十分な場合があります。ただし、すべての実装が同じPRNGを使用するため、パフォーマンスの比較は公平である必要があります。そのため、ベンチマークは主に変換のパフォーマンスをテストします。
いくつかの参照に基づいたC++の例を次に示します。これは手早くて汚いので、boostライブラリを再発明して使用しない方がいいでしょう。
#include "math.h" // for Rand, and Rand
double sampleNormal() {
double u = ((double) Rand() / (Rand_MAX)) * 2 - 1;
double v = ((double) Rand() / (Rand_MAX)) * 2 - 1;
double r = u * u + v * v;
if (r == 0 || r > 1) return sampleNormal();
double c = sqrt(-2 * log(r) / r);
return u * c;
}
QQプロットを使用して結果を調べ、それが実際の正規分布にどれほど近似しているかを確認できます(サンプルを1.xにランク付けし、ランクをxの合計カウントの割合に変換します。つまり、サンプル数、z値を取得します。プロットします。上向きの直線が望ましい結果です)。
std::tr1::normal_distribution
を使用します。
Std :: tr1名前空間は、boostの一部ではありません。これは、C++ Technical Report 1から追加されたライブラリを含む名前空間であり、boostとは関係なく、最新のMicrosoftコンパイラーとgccで利用できます。
これは、最新のC++コンパイラでサンプルを生成する方法です。
#include <random>
...
std::mt19937 generator;
double mean = 0.0;
double stddev = 1.0;
std::normal_distribution<double> normal(mean, stddev);
cerr << "Normal: " << normal(generator) << endl;
GSL を使用できます。いくつかの 完全な例が示されています 使用方法を示します。
http://www.cplusplus.com/reference/random/normal_distribution/ をご覧ください。これは、正規分布を生成する最も簡単な方法です。
C++ 11を使用している場合は、 std::normal_distribution
を使用できます。
#include <random>
std::default_random_engine generator;
std::normal_distribution<double> distribution(/*mean=*/0.0, /*stddev=*/1.0);
double randomNumber = distribution(generator);
乱数エンジンの出力を変換するために使用できる他の多くの分布があります。
http://www.mathworks.com/help/stats/normal-distribution.html で指定されているPDFの定義に従って、これを思い付きました。
const double DBL_EPS_COMP = 1 - DBL_EPSILON; // DBL_EPSILON is defined in <limits.h>.
inline double RandU() {
return DBL_EPSILON + ((double) Rand()/Rand_MAX);
}
inline double RandN2(double mu, double sigma) {
return mu + (Rand()%2 ? -1.0 : 1.0)*sigma*pow(-log(DBL_EPS_COMP*RandU()), 0.5);
}
inline double RandN() {
return RandN2(0, 1.0);
}
これは最善のアプローチではないかもしれませんが、非常に簡単です。
ボックスミュラーの実装:
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
using namespace std;
// return a uniformly distributed random number
double RandomGenerator()
{
return ( (double)(Rand()) + 1. )/( (double)(Rand_MAX) + 1. );
}
// return a normally distributed random number
double normalRandom()
{
double y1=RandomGenerator();
double y2=RandomGenerator();
return cos(2*3.14*y2)*sqrt(-2.*log(y1));
}
int main(){
double sigma = 82.;
double Mi = 40.;
for(int i=0;i<100;i++){
double x = normalRandom()*sigma+Mi;
cout << " x = " << x << endl;
}
return 0;
}
逆累積正規分布にはさまざまなアルゴリズムがあります。量的金融で最も人気のあるものは http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/ でテストされています
私の意見では、 Wichura のアルゴリズムAS241以外の何かを使用するインセンティブはあまりありません。これは機械の精度、信頼性、高速性です。ガウス乱数生成でボトルネックが発生することはほとんどありません。
さらに、ジグラットのようなアプローチの欠点を示しています。
ここでの一番の答えはBox-Müllerを支持しています。あなたはそれが既知の欠陥を持っていることを知っているべきです。 https://www.sciencedirect.com/science/article/pii/S0895717710005935 :
文献では、Box–Mullerは、主に2つの理由で、わずかに劣ると見なされることがあります。まず、Box-Mullerメソッドを不良な線形合同ジェネレータからの数値に適用すると、変換された数値は空間のカバレッジが非常に悪くなります。らせん状の尾を持つ変換された数字のプロットは、多くの本、特に注目すべき最初のリプリーの古典本に見られます。
1)ガウス乱数を生成できるグラフィカルに直感的な方法は、モンテカルロ法に似た方法を使用することです。 Cの擬似乱数ジェネレーターを使用して、ガウス曲線の周囲のボックスにランダムポイントを生成します。分布の方程式を使用して、そのポイントがガウス分布の内側または下にあるかどうかを計算できます。その点がガウス分布内にある場合、点のx値としてガウス乱数を取得しています。
技術的にはガウス曲線が無限に向かって進むため、この方法は完全ではなく、x次元で無限に近づくボックスを作成できませんでした。しかし、Guassian曲線はy次元で非常に速く0に近づくので、心配する必要はありません。 Cでの変数のサイズの制約は、精度の制限要因になる可能性があります。
2)別の方法は、独立したランダム変数が追加されると正規分布を形成するという中央極限定理を使用することです。この定理を念頭に置いて、大量の独立したランダム変数を追加することにより、ガウス乱数を近似できます。
これらの方法は最も実用的ではありませんが、既存のライブラリを使用したくない場合に期待されます。この答えは、微積分または統計の経験がほとんどまたはまったくない人からのものであることを忘れないでください。
comp.lang.c FAQ listは、ガウス分布で乱数を簡単に生成する3つの異なる方法を共有しています。