C / C ++의 정규 분포에 따라 난수 생성


114

C 또는 C ++에서 정규 분포를 따르는 난수를 어떻게 쉽게 생성 할 수 있습니까?

Boost를 사용하고 싶지 않습니다.

나는 Knuth가 이것에 대해 길게 이야기한다는 것을 알고 있지만 지금 당장은 그의 책을 가지고 있지 않습니다.


답변:


92

일반 RNG에서 가우스 분포 숫자생성하는 방법에는 여러 가지가 있습니다 .

박스 뮬러 변환 일반적으로 사용된다. 정규 분포로 값을 올바르게 생성합니다. 수학은 쉽습니다. 두 개의 (균일 한) 난수를 생성하고 여기에 공식을 적용하여 두 개의 정규 분포 난수를 얻습니다. 하나를 반환하고 난수에 대한 다음 요청을 위해 다른 하나를 저장합니다.


10
속도가 필요하다면 극지 방법이 더 빠릅니다. 그리고 Ziggurat 알고리즘은 훨씬 더 많습니다 (쓰기가 훨씬 더 복잡하지만).
Joey

2
여기에서 Ziggurat의 구현을 찾았습니다 people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html 꽤 완벽합니다.
dwbrito

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 변환 사용”, Applied Statistics, 22, 92-97, 1973


혹시 Neave 효과에 대한 pdf에 대한 다른 링크가 있습니까? 아니면 원본 저널 기사 참조? 감사합니다
pyCthon 2011

2
@stonybrooknick 원본 참조가 추가됩니다. 멋진 말 : "box muller neave"를 검색하여 참조를 찾는 동안이 스택 오버플로 질문이 첫 번째 결과 페이지에 나타났습니다!
Peter G.

예, 특정 소규모 커뮤니티 및 관심 그룹 외부에서 모든 것이 잘 알려진 것은 아닙니다
pyCthon

@Peter G. 왜 누군가가 당신의 대답을 비하겠습니까? -아마도 같은 사람이 아래에 내 댓글을 달았을 것입니다. 나는 괜찮습니다.하지만 당신의 대답은 매우 좋다고 생각했습니다. SO가 다운 보트가 실제 코멘트를 강요한다면 좋을 것입니다. .. 오래된 주제의 대부분의 다운 보트가 경박하고 트로리라고 생각합니다.
Pete855217 2013

"0-1에서 12 개의 균일 한 숫자를 더하고 6을 뺍니다." -이 변수의 분포는 정규 분포를 갖습니까? 유도 중심 한계 정리 동안 n-> + inf는 매우 필요한 가정이기 때문에 유도와 링크를 제공 할 수 있습니까?
bruziuz

31

빠르고 쉬운 방법은 균등하게 분포 된 난수를 합하고 평균을 취하는 것입니다. 이것이 작동하는 이유에 대한 전체 설명은 Central Limit Theorem 을 참조하십시오 .


+1 매우 흥미로운 접근 방식. 실제로 소규모 그룹에 대해 정규 분포 된 하위 앙상블을 제공하는 것이 확인됩니까?
Morlock

4
@Morlock 평균 샘플 수가 많을수록 가우스 분포에 가까워집니다. 응용 프로그램에 배포의 정확성에 대한 엄격한 요구 사항이있는 경우 Box-Muller와 같은 더 엄격한 것을 사용하는 것이 더 나을 수 있지만 오디오 응용 프로그램에 대한 백색 잡음 생성과 같은 많은 응용 프로그램의 경우 상당히 적은 수로 벗어날 수 있습니다. 평균 샘플 수 (예 : 16).
Paul R

2
또한, 표준 편차가 1 인 평균 10을 원한다고 가정하여 일정량의 분산을 얻기 위해 이것을 매개 변수화하는 방법은 무엇입니까?
Morlock

1
@ 벤 : 이것에 대한 효율적인 알고리즘을 지적 해 주시겠습니까? 저는 실시간 제약이있는 오디오 및 이미지 처리를 위해 대략적인 가우시안 노이즈를 생성하기 위해 평균화 기술을 사용한 적이 있습니다. 더 적은 클럭 주기로이를 달성하는 방법이 있다면 매우 유용 할 수 있습니다.
Paul R

1
@Petter : 부동 소수점 값의 경우 일반적인 경우에 맞을 것입니다. 빠른 정수 (또는 고정 소수점) 가우시안 노이즈를 원하고 정확도가 너무 중요하지 않은 오디오와 같은 응용 분야가 있습니다. 간단한 평균화 방법이 더 효율적이고 유용합니다 (특히 임베디드 응용 프로그램의 경우 하드웨어 부동 소수점 지원).
Paul R

24

정규 분포 난수 생성 벤치 마크를 위해 C ++ 오픈 소스 프로젝트를 만들었습니다. .

다음을 포함한 여러 알고리즘을 비교합니다.

  • 중심 한계 정리 방법
  • Box-Muller 변환
  • Marsaglia 극지법
  • 지구라트 알고리즘
  • 역변환 샘플링 방법.
  • cpp11randomC ++ 11을 std::normal_distribution사용합니다 std::minstd_rand(실제로는 clang의 Box-Muller 변환입니다).

floatiMac Corei5-3330S@2.70GHz, clang 6.1, 64 비트 에서 단 정밀도 ( ) 버전 의 결과 :

normaldistf

정확성을 위해 프로그램은 표본의 평균, 표준 편차, 왜도 및 첨도를 확인합니다. 4, 8 또는 16 개의 균일 한 숫자를 합한 CLT 방법은 다른 방법과 달리 첨도가 좋지 않음을 확인했습니다.

Ziggurat 알고리즘은 다른 알고리즘보다 더 나은 성능을 제공합니다. 그러나 테이블 조회 및 분기가 필요하므로 SIMD 병렬 처리에는 적합하지 않습니다. SSE2 / AVX 명령어 세트가있는 Box-Muller는 비 SIMD 버전의 ziggurat 알고리즘보다 훨씬 빠릅니다 (x1.79, x2.99).

따라서 SIMD 명령어 세트가있는 아키텍처에 Box-Muller를 사용하는 것이 좋으며 그렇지 않으면 ziggurat 일 수 있습니다.


PS 벤치 마크는 균일 한 분산 난수를 생성하기 위해 가장 간단한 LCG PRNG를 사용합니다. 따라서 일부 응용 프로그램에는 충분하지 않을 수 있습니다. 그러나 모든 구현이 동일한 PRNG를 사용하므로 성능 비교는 공정해야하므로 벤치 마크는 주로 변환 성능을 테스트합니다.


2
"그러나 모든 구현이 동일한 PRNG를 사용하기 때문에 성능 비교는 공정해야합니다.".. BM이 출력 당 하나의 입력 RN을 사용하는 반면 CLT는 더 많은 것을 사용한다는 점을 제외하면 # 균일 한 랜덤을 생성하는 시간이 중요합니다.
greggo

14

다음은 일부 참조를 기반으로 한 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 값을 구합니다). 위쪽으로 직선이 원하는 결과입니다).


1
sampleNormalManual ()이란 무엇입니까?
solvingPuzzles

@solvingPuzzles-죄송합니다. 코드를 수정했습니다. 재귀 호출입니다.
Pete855217 2012

1
이것은 일부 드문 이벤트에서 충돌 할 수 있습니다 (상사에게 응용 프로그램을 보여 주면 벨이 울리나요?). 이것은 재귀를 사용하지 않고 루프를 사용하여 구현해야합니다. 방법이 생소 해 보입니다. 출처는 무엇이며 어떻게 부르나요?
돼지

Box-Muller는 자바 구현에서 옮겨졌습니다. 내가 말했듯이 빠르고 더럽 기 때문에 자유롭게 고칠 수 있습니다.
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

항상 시드됩니다. 기본 시드가 있습니다.
Petter 2011



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

난수 엔진의 출력을 변환하는 데 사용할 수있는 다른 많은 분포가 있습니다.


Ben이 이미 언급했습니다 ( stackoverflow.com/a/11977979/635608 )
Mat

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)에 대해 작동하지 않습니다. 매크로는 이것으로 악명이 높습니다.
Petter

Ln (0)이 정의되지 않았으므로 rand()of RANDU가 0 을 반환 하면 매크로가 실패합니다 .
interDist

이 코드를 실제로 사용해 보셨습니까? Rayleigh 분포 된 숫자를 생성하는 함수를 만든 것 같습니다 . Box–Muller 변환 과 비교 cos(2*pi*rand/RAND_MAX)하면으로 곱하지만으로 곱합니다 (rand()%2 ? -1.0 : 1.0).
HelloGoodbye


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 알고리즘 외에 다른 것을 사용하는 것에 대한 인센티브가 많지 않습니다 . 기계 정밀도, 신뢰성 및 속도입니다. 병목 현상은 가우스 난수 생성에서 거의 발생하지 않습니다.

또한 Ziggurat 방식의 단점을 보여줍니다.

여기에서 가장 큰 답변은 Box-Müller를 옹호하며 알려진 결함이 있음을 알아야합니다. https://www.sciencedirect.com/science/article/pii/S0895717710005935를 인용합니다 .

문헌에서 Box–Muller는 주로 두 가지 이유로 약간 열등하다고 간주됩니다. 첫째, Box–Muller 방법을 잘못된 선형 합동 생성기의 숫자에 적용하면 변환 된 숫자가 공간의 매우 열악한 커버리지를 제공합니다. 나선형 꼬리가있는 변환 된 숫자의 플롯은 많은 책에서 찾을 수 있습니다. 특히이 관찰을 처음으로 수행 한 Ripley의 고전적인 책에서 찾을 수 있습니다. "


0

1) 가우스 난수를 생성 할 수있는 그래픽 적으로 직관적 인 방법은 몬테카를로 방법과 유사한 방법을 사용하는 것입니다. C에서 의사 난수 생성기를 사용하여 가우시안 곡선 주위의 상자에 임의의 점을 생성합니다. 분포 방정식을 사용하여 해당 점이 가우스 분포 내부 또는 아래에 있는지 계산할 수 있습니다. 그 점이 가우시안 분포 내에 있으면 점의 x 값으로 가우스 난수를 얻은 것입니다.

이 방법은 완벽하지 않습니다. 기술적으로 가우시안 곡선이 무한대로 진행되고 x 차원에서 무한대에 접근하는 상자를 만들 수 없기 때문입니다. 그러나 Guassian 곡선은 y 차원에서 0에 매우 빠르게 접근하므로 그것에 대해 걱정하지 않을 것입니다. C에서 변수 크기의 제약은 정확도를 제한하는 요인이 될 수 있습니다.

2) 또 다른 방법은 독립 확률 변수가 추가되면 정규 분포를 형성한다는 중앙 한계 정리를 사용하는 것입니다. 이 정리를 염두에두고 많은 양의 독립 확률 변수를 추가하여 가우스 난수를 근사화 할 수 있습니다.

이러한 방법은 가장 실용적이지는 않지만 기존 라이브러리를 사용하고 싶지 않을 때 예상됩니다. 이 답변은 미적분 또는 통계 경험이 거의 또는 전혀없는 사람에게서 나온 것임을 명심하십시오.


0

몬테카를로 방법 가장 직관적 인 방법몬테카를로 방법 을 사용하는 것입니다. 적절한 범위 -X, + X를 선택하십시오. X 값이 클수록 정규 분포가 더 정확 해지지 만 수렴하는 데 더 오래 걸립니다. ㅏ. -X에서 X 사이 의 난수 z를 선택하십시오 . b. N(z, mean, variance)N이 가우스 분포 인 확률을 유지 합니다. 그렇지 않으면 삭제하고 (a) 단계로 돌아갑니다.



-3

컴퓨터는 결정 론적 장치입니다. 계산에는 무작위성이 없습니다. 또한 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의 찾을 수 있습니다.

안타깝게도이 책이 영어로 번역되어 있는지 모르겠습니다.


여러 개의 반대표가 있습니다. 여기서 무엇이 나쁜지 알려주세요?
bruziuz

문제는 컴퓨터에서 의사 난수를 생성하는 방법입니다 (나는 여기에서 언어가 느슨하다는 것을 알고 있습니다). 그것은 수학적 존재의 문제가 아닙니다.
user2820579

네 말이 맞아. 그리고 정답은 균일 분포를 갖는 생성기를 기반으로 정규 분포로 의사 난수를 생성하는 방법입니다. 소스 코드가 제공되었으므로 모든 언어로 다시 작성할 수 있습니다.
bruziuz

물론입니다. 예를 들어 "C / C ++의 Numerical Recipes"를 찾는 사람이라고 생각합니다. 그건 그렇고, 우리의 논의를 보완하기 위해이 마지막 책의 저자는 "괜찮은"생성자가되기위한 표준을 충족하는 몇 가지 의사 랜덤 생성기에 대한 흥미로운 참조를 제공합니다.
user2820579

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.