回答:
通常のRNGからガウス分布数を生成する方法はたくさんあります。
ボックス=ミュラー法一般的に使用されます。正規分布の値を正しく生成します。数学は簡単です。2つの(均一な)乱数を生成し、それらに数式を適用することで、2つの正規分布乱数を取得します。1つを返し、もう1つを乱数の次の要求のために保存します。
std::normal_distribution
数学的な詳細を掘り下げることなく、ユーザーが求めることを正確に実行することに注意してください。
C ++ 11はを提供しますstd::normal_distribution
。これは、今日私が行く方法です。
いくつかの解決策は、複雑さの昇順です。
0から1までの12個の一様乱数を追加し、6を減算します。これは、正規変数の平均と標準偏差に一致します。明らかな欠点は、真の正規分布とは異なり、範囲が±6に制限されることです。
Box-Muller変換。これは上記にリストされており、実装は比較的簡単です。ただし、非常に正確なサンプルが必要な場合は、Box-Muller変換といくつかの均一ジェネレーターを組み合わせると、Neave Effect 1と呼ばれる異常が発生することに注意してください。
最高の精度を得るには、ユニフォームを描画し、逆累積正規分布を適用して、正規分布の変量に到達することをお勧めします。以下は、累積累積正規分布の非常に優れたアルゴリズムです。
1. HR Neave、「乗法合同法による疑似乱数ジェネレーターでのBox-Muller変換の使用について」、応用統計、22、92-97、1973
すばやく簡単な方法は、均等に分散された乱数の数を合計し、それらの平均を取ることです。これが機能する理由の詳細については、中心極限定理を参照してください。
正規分布の乱数生成ベンチマーク用のC ++オープンソースプロジェクトを作成しました。
以下を含むいくつかのアルゴリズムを比較します
cpp11random
C ++ 11 std::normal_distribution
を使用しますstd::minstd_rand
(実際にはclangのBox-Muller変換です)。float
iMac Corei5-3330S @ 2.70GHz、clang 6.1、64ビットでの単精度()バージョンの結果:
正確さのために、プログラムはサンプルの平均、標準偏差、歪度および尖度を検証します。4、8、または16の均一な数を合計するCLTメソッドは、他のメソッドほど良好な尖度を持たないことがわかりました。
Zigguratアルゴリズムは、他のアルゴリズムよりもパフォーマンスが優れています。ただし、テーブルの検索と分岐が必要なため、SIMD並列処理には適していません。SSE2 / AVX命令セットを備えたBox-Mullerは、非SIMDバージョンのジッグラトアルゴリズムよりもはるかに高速(x1.79、x2.99)です。
したがって、SIMD命令セットを備えたアーキテクチャにはBox-Mullerを使用することをお勧めします。
PSベンチマークは、均一な分布乱数を生成するために最も単純な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値を取得します。上向きの直線が望ましい結果です)。
見てください: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);
}
それはおそらく最善のアプローチではありませんが、非常に簡単です。
rand()
がRANDU
返された場合、マクロは失敗します。
cos(2*pi*rand/RAND_MAX)
は、と乗算し(rand()%2 ? -1.0 : 1.0)
ますが、と乗算します。
comp.lang.cのFAQリストの共有を容易ガウス分布で乱数を生成するための3つの異なる方法。
あなたはそれを見てみるかもしれません:http : //c-faq.com/lib/gaussian.html
Box-Muller実装:
#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を引用します:
文献では、主に2つの理由により、Box–Mullerがわずかに劣ると見なされる場合があります。まず、悪い線形合同法ジェネレータからの数値にBox–Muller法を適用すると、変換された数値は空間のカバレッジが非常に悪くなります。らせん状の尾を持つ変換された数値のプロットは、多くの本、特にこの観察を最初に行ったリプリーの古典的な本にあります。
1)グラフィカルに直感的にガウス乱数を生成する方法は、モンテカルロ法に似たものを使用することです。Cの疑似乱数ジェネレータを使用して、ガウス曲線の周りのボックスにランダムな点を生成します。分布の方程式を使用して、その点がガウス分布の内側か下かを計算できます。その点がガウス分布の内側にある場合、点のx値としてガウス乱数を取得しています。
技術的にはガウス曲線が無限大に向かって進み、x次元で無限大に近づくボックスを作成できなかったため、この方法は完璧ではありません。しかし、ガウス曲線はy次元で0にかなり速く近づくので、心配する必要はありません。Cでの変数のサイズの制約は、精度を制限する要素の1つになる可能性があります。
2)別の方法は、中央確率定理を使用することです。これは、独立確率変数が追加されると、正規分布を形成することを示します。この定理を念頭に置いて、大量の独立したランダム変数を追加することにより、ガウス乱数を近似できます。
これらの方法は最も実用的ではありませんが、既存のライブラリを使用したくない場合に期待されます。この答えは、微積分学または統計学の経験がほとんどまたはまったくない人からのものであることに注意してください。
コンピュータは確定的なデバイスです。計算にランダム性はありません。さらに、CPUの算術デバイスは、整数の有限セット(有限体で評価を実行)と実有理数の有限セットで合計を評価できます。また、ビット単位の演算も実行しました。数学は、[0.0、1.0]のように無数のポイントを持つより優れたセットを扱います。
コンピューター内のワイヤーをコントローラーで聞くことができますが、分布は均一でしょうか?知りません。しかし、その信号が累積値の結果であると仮定すると、膨大な量の独立確率変数が得られます。ほぼ正規分布確率変数を受け取ります(確率論で証明されています)。
疑似ランダムジェネレータと呼ばれるアルゴリズムが存在します。疑似ランダムジェネレーターの目的は、ランダム性をエミュレートすることです。そして、グッドネスの基準は次のとおりです。-経験的分布は(ある意味では-点的に均一で、L2)理論に収束しています-ランダムジェネレーターから受け取る値は、独立しているようです。もちろんそれは「実際の観点」からは正しくありませんが、私たちはそれが正しいと想定しています。
一般的な方法の1つ-均一な分布で12 irvを合計できます....ただし、フーリエ変換、テイラー級数の助けを借りて、Central Limit定理の導出中に正直になるには、n-> + infの仮定を数回行う必要があります。したがって、たとえば理論的には-個人的には、人々が12 irvの合計を均一な分布でどのように実行するかを理解していません。
私は大学で能力論を持っていました。特に私にとっては、それは単なる数学の質問です。大学では、次のモデルを見ました。
double generateUniform(double a, double b)
{
return uniformGen.generateReal(a, b);
}
double generateRelei(double sigma)
{
return sigma * sqrt(-2 * log(1.0 - uniformGen.generateReal(0.0, 1.0 -kEps)));
}
double generateNorm(double m, double sigma)
{
double y2 = generateUniform(0.0, 2 * kPi);
double y1 = generateRelei(1.0);
double x1 = y1 * cos(y2);
return sigma*x1 + m;
}
そのようにする方法は単なる例であり、それを実装する別の方法が存在すると思います。
それが正しいことの証明は、この本「モスクワ、BMSTU、2004:XVI確率論、例6.12、p.246-247」のクリシュチェンコアレクサンドルペトロビッチ ISBN 5-7038-2485-0にあります。
残念ながら、この本の英語への翻訳の存在については知りません。