답변:
일반 RNG에서 가우스 분포 숫자 를 생성하는 방법에는 여러 가지가 있습니다 .
박스 뮬러 변환 일반적으로 사용된다. 정규 분포로 값을 올바르게 생성합니다. 수학은 쉽습니다. 두 개의 (균일 한) 난수를 생성하고 여기에 공식을 적용하여 두 개의 정규 분포 난수를 얻습니다. 하나를 반환하고 난수에 대한 다음 요청을 위해 다른 하나를 저장합니다.
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 변환 사용”, Applied Statistics, 22, 92-97, 1973
빠르고 쉬운 방법은 균등하게 분포 된 난수를 합하고 평균을 취하는 것입니다. 이것이 작동하는 이유에 대한 전체 설명은 Central Limit Theorem 을 참조하십시오 .
정규 분포 난수 생성 벤치 마크를 위해 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 버전의 ziggurat 알고리즘보다 훨씬 빠릅니다 (x1.79, x2.99).
따라서 SIMD 명령어 세트가있는 아키텍처에 Box-Muller를 사용하는 것이 좋으며 그렇지 않으면 ziggurat 일 수 있습니다.
PS 벤치 마크는 균일 한 분산 난수를 생성하기 위해 가장 간단한 LCG PRNG를 사용합니다. 따라서 일부 응용 프로그램에는 충분하지 않을 수 있습니다. 그러나 모든 구현이 동일한 PRNG를 사용하므로 성능 비교는 공정해야하므로 벤치 마크는 주로 변환 성능을 테스트합니다.
다음은 일부 참조를 기반으로 한 C ++ 예제입니다. 이것은 빠르고 더럽습니다. 부스트 라이브러리를 재발 명하지 않고 사용하는 것이 좋습니다.
#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 값을 구합니다). 위쪽으로 직선이 원하는 결과입니다).
이것이 최신 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
정말 접종해야한다.
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);
}
최선의 접근 방식은 아니지만 아주 간단합니다.
rand()
of RANDU
가 0 을 반환 하면 매크로가 실패합니다 .
cos(2*pi*rand/RAND_MAX)
하면으로 곱하지만으로 곱합니다 (rand()%2 ? -1.0 : 1.0)
.
때 comp.lang.c 자주 묻는 질문 목록 을 공유 쉽게하는 방법에는 세 가지가 가우스 분포 난수를 생성합니다.
당신은 그것을 볼 수 있습니다 : 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 알고리즘 외에 다른 것을 사용하는 것에 대한 인센티브가 많지 않습니다 . 기계 정밀도, 신뢰성 및 속도입니다. 병목 현상은 가우스 난수 생성에서 거의 발생하지 않습니다.
또한 Ziggurat 방식의 단점을 보여줍니다.
여기에서 가장 큰 답변은 Box-Müller를 옹호하며 알려진 결함이 있음을 알아야합니다. https://www.sciencedirect.com/science/article/pii/S0895717710005935를 인용합니다 .
문헌에서 Box–Muller는 주로 두 가지 이유로 약간 열등하다고 간주됩니다. 첫째, Box–Muller 방법을 잘못된 선형 합동 생성기의 숫자에 적용하면 변환 된 숫자가 공간의 매우 열악한 커버리지를 제공합니다. 나선형 꼬리가있는 변환 된 숫자의 플롯은 많은 책에서 찾을 수 있습니다. 특히이 관찰을 처음으로 수행 한 Ripley의 고전적인 책에서 찾을 수 있습니다. "
1) 가우스 난수를 생성 할 수있는 그래픽 적으로 직관적 인 방법은 몬테카를로 방법과 유사한 방법을 사용하는 것입니다. C에서 의사 난수 생성기를 사용하여 가우시안 곡선 주위의 상자에 임의의 점을 생성합니다. 분포 방정식을 사용하여 해당 점이 가우스 분포 내부 또는 아래에 있는지 계산할 수 있습니다. 그 점이 가우시안 분포 내에 있으면 점의 x 값으로 가우스 난수를 얻은 것입니다.
이 방법은 완벽하지 않습니다. 기술적으로 가우시안 곡선이 무한대로 진행되고 x 차원에서 무한대에 접근하는 상자를 만들 수 없기 때문입니다. 그러나 Guassian 곡선은 y 차원에서 0에 매우 빠르게 접근하므로 그것에 대해 걱정하지 않을 것입니다. C에서 변수 크기의 제약은 정확도를 제한하는 요인이 될 수 있습니다.
2) 또 다른 방법은 독립 확률 변수가 추가되면 정규 분포를 형성한다는 중앙 한계 정리를 사용하는 것입니다. 이 정리를 염두에두고 많은 양의 독립 확률 변수를 추가하여 가우스 난수를 근사화 할 수 있습니다.
이러한 방법은 가장 실용적이지는 않지만 기존 라이브러리를 사용하고 싶지 않을 때 예상됩니다. 이 답변은 미적분 또는 통계 경험이 거의 또는 전혀없는 사람에게서 나온 것임을 명심하십시오.
컴퓨터는 결정 론적 장치입니다. 계산에는 무작위성이 없습니다. 또한 CPU의 산술 장치는 일부 유한 정수 세트 (유한 필드에서 평가 수행) 및 유한 실수 유리수 세트에 대한 합계를 평가할 수 있습니다. 또한 비트 연산을 수행했습니다. 수학은 무한한 포인트를 가진 [0.0, 1.0]과 같은 더 큰 세트를 처리합니다.
컨트롤러를 사용하여 컴퓨터 내부의 일부 와이어를들을 수 있지만 균일 한 분포를 가질 수 있습니까? 모르겠어요. 그러나 그것이 신호가 막대한 양의 독립 랜덤 변수 값을 축적 한 결과라고 가정하면 대략 정규 분포 랜덤 변수를 받게됩니다 (확률 이론에서 입증되었습니다)
의사 랜덤 생성기라는 알고리즘이 있습니다. 내가 생각했듯이 의사 랜덤 생성기의 목적은 랜덤 성을 모방하는 것입니다. 그리고 goodnes의 기준은 다음과 같습니다.-경험적 분포가 (어떤 의미에서-점적, 균일 한, L2) 이론적으로 수렴됩니다.-랜덤 생성기에서받는 값은 독립적 인 것처럼 보입니다. 물론 '실제 관점'에서는 사실이 아니지만 사실이라고 가정합니다.
인기있는 방법 중 하나-균일 한 분포로 12 irv를 합산 할 수 있습니다 .... 그러나 푸리에 변환, Taylor Series의 도움으로 Central Limit Theorem을 유도하는 동안 솔직히 말하면 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;
}
그렇게하는 방법은 단지 예일뿐, 구현하는 또 다른 방법이있는 것 같습니다.
그것이 정확하다는 증명은 Krishchenko Alexander Petrovich의 "Moscow, BMSTU, 2004 : XVI Probability Theory, Example 6.12, p.246-247"에서 찾을 수 있습니다. ISBN 5-7038-2485-0의 찾을 수 있습니다.
안타깝게도이 책이 영어로 번역되어 있는지 모르겠습니다.