C / C ++で正規分布に従って乱数を生成する


114

CまたはC ++で正規分布に従って乱数を簡単に生成するにはどうすればよいですか?

Boostを使用したくありません。

私はKnuthがこれについて長い間話していることを知っていますが、彼の本は今手元にありません。


回答:


92

通常のRNGからガウス分布数生成する方法はたくさんあります。

ボックス=ミュラー法一般的に使用されます。正規分布の値を正しく生成します。数学は簡単です。2つの(均一な)乱数を生成し、それらに数式を適用することで、2つの正規分布乱数を取得します。1つを返し、もう1つを乱数の次の要求のために保存します。


10
ただし、速度が必要な場合は、ポーラー法の方が高速です。また、Zigguratアルゴリズムはさらに複雑になります(ただし、記述がはるかに複雑になります)。
ジョーイ

2
ここでZigguratの実装を見つけましたpeople.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html完全です。
dwbrito 2013年

24
C ++ 11は、std::normal_distribution数学的な詳細を掘り下げることなく、ユーザーが求めることを正確に実行することに注意してください。

3
std :: normal_distributionは、すべてのプラットフォームで一貫しているとは限りません。現在テストを行っています。MSVCは、たとえばClangとは異なる値のセットを提供します。C ++ 11エンジンは同じシーケンス(同じシードが与えられた)を生成するようですが、C ++ 11ディストリビューションは、異なるプラットフォームで異なるアルゴリズムを使用して実装されているようです。
Arno Duvenhage

47

C ++ 11

C ++ 11はを提供しますstd::normal_distribution。これは、今日私が行く方法です。

C以前のC ++

いくつかの解決策は、複雑さの昇順です。

  1. 0から1までの12個の一様乱数を追加し、6を減算します。これは、正規変数の平均と標準偏差に一致します。明らかな欠点は、真の正規分布とは異なり、範囲が±6に制限されることです。

  2. Box-Muller変換。これは上記にリストされており、実装は比較的簡単です。ただし、非常に正確なサンプルが必要な場合は、Box-Muller変換といくつかの均一ジェネレーターを組み合わせると、Neave Effect 1と呼ばれる異常が発生することに注意してください。

  3. 最高の精度を得るには、ユニフォームを描画し、逆累積正規分布を適用して、正規分布の変量に到達することをお勧めします。以下は、累積累積正規分布の非常に優れたアルゴリズムです。

1. HR Neave、「乗法合同法による疑似乱数ジェネレーターでのBox-Muller変換の使用について」、応用統計、22、92-97、1973


ひょっとして、ニーブ効果に関するpdfへの別のリンクがありますか または元のジャーナル記事参照?ありがとう
pyCthon

2
@stonybrooknick元の参照が追加されます。クールな発言:「box muller neave」をグーグル検索して参照を見つけているときに、この非常にスタックオーバーフローの質問が最初の結果ページに表示されました。
Peter G.

ええ、特定の小さなコミュニティや利益団体以外ではあまり知られていない
pyCthon

@ピーター・G.なぜ誰かがあなたの答えに反対票を投じるのですか?-おそらく同じ人が私のコメントも下に書きましたが、私は大丈夫ですが、あなたの答えはとても良かったと思いました。SOによる反対投票が実際のコメントを強制するのは良いことです。
Pete855217 2013年

「0から1までの12の均一な数値を加算し、6を減算します。」-この変数の分布は正規分布になりますか?導出の中心極限定理の間、n-> + infは非常に必要な仮定であるため、導出とのリンクを提供できますか?
bruziuz

31

すばやく簡単な方法は、均等に分散された乱数の数を合計し、それらの平均を取ることです。これが機能する理由の詳細については、中心極限定理を参照してください。


+1非常に興味深いアプローチ。本当に小さなグループに正規分布のサブアンサンブルを与えることが検証されていますか?
モーロック

4
@Morlock平均するサンプル数が多いほど、ガウス分布に近づきます。アプリケーションに分布の正確さに関する厳密な要件がある場合は、Box-Mullerなどのより厳密なものを使用したほうがよいかもしれませんが、オーディオアプリケーションのホワイトノイズの生成など、多くのアプリケーションでは、かなり少ない数で十分です。平均サンプル(例:16)。
ポールR

2
さらに、一定の分散を得るために、これをどのようにパラメーター化しますか。たとえば、標準偏差が1で平均が10であるとします。
モーロック

1
@ベン:このための効率的なアルゴリズムを教えていただけますか?私は今まで、平均化手法を使用して、リアルタイム制約のあるオーディオおよび画像処理のためにほぼガウスノイズを生成しました。これをより少ないクロックサイクルで実現する方法がある場合、それは非常に役立ちます。
Paul R

1
@ペッター:浮動小数点値の場合、一般的なケースではおそらく正しいでしょう。ただし、オーディオなどのアプリケーション領域はまだありますが、高速整数(または固定小数点)ガウスノイズが必要であり、精度はそれほど重要ではありません。単純な平均化方法の方が効率的で便利です(特に組み込みアプリケーションでは、ハードウェア浮動小数点サポート)。
Paul R

24

正規分布の乱数生成ベンチマーク用のC ++オープンソースプロジェクトを作成しました。

以下を含むいくつかのアルゴリズムを比較します

  • 中心極限定理法
  • ボックスミュラー変換
  • マルサリア極法
  • Zigguratアルゴリズム
  • 逆変換サンプリング法。
  • cpp11randomC ++ 11 std::normal_distributionを使用しますstd::minstd_rand(実際にはclangのBox-Muller変換です)。

floatiMac 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を使用するため、パフォーマンスの比較は公平でなければなりません。したがって、ベンチマークは主に変換のパフォーマンスをテストします。


2
「しかし、すべての実装が同じPRNGを使用するため、パフォーマンスの比較は公平でなければなりません」.. BMが出力ごとに1つの入力RNを使用するのに対し、CLTはさらに多くを使用するなど...
greggo 2017年

14

以下は、いくつかの参照に基づいた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値を取得します。上向きの直線が望ましい結果です)。


1
sampleNormalManual()とは何ですか?
solverPuzzles 2012

@solvingPuzzles-申し訳ありませんが、コードを修正しました。再帰呼び出しです。
Pete855217 2012

1
これは、いくつかのまれなイベントでクラッシュするはずです(上司にアプリケーションを表示するとベルが鳴る?)これは、再帰を使用するのではなく、ループを使用して実装する必要があります。このメソッドは見慣れないものです。ソースは何ですか/それはどのように呼ばれますか?

Box-MullerはJava実装から転記されました。私が言ったように、それは速くて汚いです、それを自由に修正してください。
Pete855217 2013年

1
FWIW、多くのコンパイラーは、その特定の再帰呼び出しを「関数のトップへジャンプ」することができます。問題は、あなたがそれに頼りたいかどうかです:-)また、10回を超える反復が行われる確率は480万分の1です。P(> 20)など、その正方形である
greggo

12

を使用しstd::tr1::normal_distributionます。

std :: tr1名前空間は後押しの一部ではありません。これは、C ++テクニカルレポート1から追加されたライブラリを含む名前空間であり、ブーストとは関係なく、最新のMicrosoftコンパイラとgccで利用できます。


25
彼は標準を求めず、「ブーストしない」を求めました。
JoeG 2010

12

これは、最新の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;

これgeneratorは本当にシードされるべきです。
Walter

常にシードされています。デフォルトのシードがあります。
ペッター、



4

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);

乱数エンジンの出力を変換するために使用できる他の多くの分布があります。


それはすでにベン(stackoverflow.com/a/11977979/635608)によって言及されています
マット

3

私は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);
}

それはおそらく最善のアプローチではありませんが、非常に簡単です。


-1 RANDN2(0.0、d + 1.0)などでは機能しません。マクロはこのことで悪名高くなっています。
ペッター

Ln(0)が定義されていないため、ゼロrand()RANDU返された場合、マクロは失敗します。
interDist 2013

このコードを実際に試しましたか?レイリー分布の数値を生成する関数を作成したようです。Box–Muller変換と比較してください。ここでcos(2*pi*rand/RAND_MAX)は、と乗算し(rand()%2 ? -1.0 : 1.0)ますが、と乗算します。
HelloGoodbye 2014年


1

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;
}

1

逆累積正規分布にはさまざまなアルゴリズムがあります。量的金融で最も人気のあるものは、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法を適用すると、変換された数値は空間のカバレッジが非常に悪くなります。らせん状の尾を持つ変換された数値のプロットは、多くの本、特にこの観察を最初に行ったリプリーの古典的な本にあります。


0

1)グラフィカルに直感的にガウス乱数を生成する方法は、モンテカルロ法に似たものを使用することです。Cの疑似乱数ジェネレータを使用して、ガウス曲線の周りのボックスにランダムな点を生成します。分布の方程式を使用して、その点がガウス分布の内側か下かを計算できます。その点がガウス分布の内側にある場合、点のx値としてガウス乱数を取得しています。

技術的にはガウス曲線が無限大に向かって進み、x次元で無限大に近づくボックスを作成できなかったため、この方法は完璧ではありません。しかし、ガウス曲線はy次元で0にかなり速く近づくので、心配する必要はありません。Cでの変数のサイズの制約は、精度を制限する要素の1つになる可能性があります。

2)別の方法は、中央確率定理を使用することです。これは、独立確率変数が追加されると、正規分布を形成することを示します。この定理を念頭に置いて、大量の独立したランダム変数を追加することにより、ガウス乱数を近似できます。

これらの方法は最も実用的ではありませんが、既存のライブラリを使用したくない場合に期待されます。この答えは、微積分学または統計学の経験がほとんどまたはまったくない人からのものであることに注意してください。


0

モンテカルロ法 これを行う最も直感的な方法は、モンテカルロ法を使用することです。適切な範囲-X、+ Xを取ります。Xの値が大きいほど、正規分布はより正確になりますが、収束に時間がかかります。a。-XからXまでの乱数zを選択します。N(z, mean, variance)Nがガウス分布である確率を維持します。それ以外の場合はドロップして、ステップ(a)に戻ります。



-3

コンピュータは確定的なデバイスです。計算にランダム性はありません。さらに、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にあります。

残念ながら、この本の英語への翻訳の存在については知りません。


私はいくつかの反対票を持っています。ここで何が悪いのか教えてください。
bruziuz 2017

問題は、コンピューターで疑似乱数を生成する方法です(ここでは言語は緩いです)。これは、数学的な存在の問題ではありません。
user2820579

はい、あなたが正しい。そして答えは、一様分布を持つジェネレータに基づいて正規分布で疑似乱数を生成する方法です。ソースコードが提供されているので、任意の言語で書き直すことができます。
bruziuz

確かに、私はその人が「C / C ++の数値レシピ」などを探していると思います。ちなみに、私たちの議論を補足するために、この最後の本の著者は、「まともな」ジェネレータであるという基準を満たすいくつかの疑似ランダムジェネレータについて興味深いリファレンスを提供しています。
user2820579

1
ここにバックアップを作成しました:sites.google.com/site/burlachenkok/download
bruziuz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.