Tạo số ngẫu nhiên theo phân phối chuẩn trong C / C ++


114

Làm cách nào để tôi có thể dễ dàng tạo các số ngẫu nhiên theo phân phối chuẩn trong C hoặc C ++?

Tôi không muốn sử dụng Boost.

Tôi biết rằng Knuth nói về điều này khá dài nhưng tôi không có sách của anh ấy ngay bây giờ.


Câu trả lời:


92

Có nhiều phương pháp để tạo các số được phân phối theo Gaussian từ một RNG thông thường .

Biến đổi Box-Muller thường được sử dụng. Nó tạo ra một cách chính xác các giá trị có phân phối chuẩn. Toán học rất dễ dàng. Bạn tạo hai số ngẫu nhiên (đồng nhất) và bằng cách áp dụng công thức cho chúng, bạn sẽ nhận được hai số ngẫu nhiên có phân phối chuẩn. Trả lại một cái và lưu cái kia cho lần yêu cầu tiếp theo cho một số ngẫu nhiên.


10
Tuy nhiên, nếu bạn cần tốc độ, thì phương pháp cực nhanh hơn. Và thuật toán Ziggurat thậm chí còn nhiều hơn nữa (mặc dù viết phức tạp hơn nhiều).
Joey

2
đã tìm thấy một triển khai của Ziggurat tại đây people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html Nó khá hoàn chỉnh.
dwbrito

24
Lưu ý, C ++ 11 bổ sung tính std::normal_distributionnăng này thực hiện chính xác những gì bạn yêu cầu mà không đi sâu vào chi tiết toán học.

3
std :: normal_distribution không được đảm bảo nhất quán trên tất cả các nền tảng. Bây giờ tôi đang thực hiện các bài kiểm tra và MSVC cung cấp một bộ giá trị khác với ví dụ: Clang. Các công cụ C ++ 11 dường như tạo ra các trình tự giống nhau (cho cùng một hạt giống), nhưng các bản phân phối C ++ 11 dường như được thực hiện bằng cách sử dụng các thuật toán khác nhau trên các nền tảng khác nhau.
Arno Duvenhage

47

C ++ 11

C ++ 11 cung cấp std::normal_distribution, đó là cách tôi sẽ đi ngày hôm nay.

C trở lên C ++

Dưới đây là một số giải pháp theo thứ tự độ phức tạp tăng dần:

  1. Cộng 12 số ngẫu nhiên đồng nhất từ ​​0 đến 1 và trừ đi 6. Điều này sẽ khớp với giá trị trung bình và độ lệch chuẩn của một biến bình thường. Một nhược điểm rõ ràng là phạm vi bị giới hạn ở ± 6 - không giống như phân phối chuẩn thực sự.

  2. Biến hình Box-Muller. Điều này được liệt kê ở trên và tương đối đơn giản để thực hiện. Tuy nhiên, nếu bạn cần các mẫu rất chính xác, hãy lưu ý rằng phép biến đổi Box-Muller kết hợp với một số bộ tạo đồng nhất gặp phải hiện tượng dị thường gọi là Hiệu ứng Neave 1 .

  3. Để có độ chính xác tốt nhất, tôi khuyên bạn nên vẽ đồng phục và áp dụng phân phối chuẩn tích lũy nghịch đảo để đi đến các biến thể phân phối chuẩn. Đây là một thuật toán rất tốt cho các phân phối chuẩn tích lũy nghịch đảo.

1. HR Neave, “Về việc sử dụng phép biến đổi Box-Muller với bộ tạo số giả ngẫu nhiên đồng dư đa bội,” Thống kê Ứng dụng, 22, 92-97, 1973


tình cờ bạn có một liên kết khác đến pdf trên hiệu ứng Neave không? hoặc tài liệu tham khảo bài báo tạp chí gốc? cảm ơn bạn
pyCthon

2
@stonybrooknick Tham chiếu gốc đã được thêm vào. Nhận xét thú vị: Trong khi googling "box muller neave" để tìm tài liệu tham khảo, câu hỏi rất ngăn xếp này đã xuất hiện trên trang kết quả đầu tiên!
Peter G.

vâng, nó không phải mọi thứ đều được biết đến bên ngoài một số cộng đồng nhỏ và nhóm sở thích
pyCthon

@Peter G. Tại sao mọi người lại phản đối câu trả lời của bạn? - có thể chính người đó cũng đã nhận xét của tôi bên dưới, điều này tôi thấy ổn, nhưng tôi nghĩ câu trả lời của bạn rất hay. Sẽ thật tốt nếu SO đưa ra những phản đối buộc phải đưa ra một bình luận thực sự..Tôi nghi ngờ rằng hầu hết những phản đối từ chối của các chủ đề cũ chỉ là phù phiếm và dễ dãi.
Pete855217

"Cộng 12 số đồng nhất từ ​​0-1 và trừ đi 6." - phân phối của biến này sẽ có phân phối chuẩn? Bạn có thể cung cấp một liên kết với đạo hàm, bởi vì trong định lý giới hạn trung tâm đạo hàm, n -> + inf là rất cần giả định.
bruziuz

31

Một phương pháp nhanh chóng và dễ dàng chỉ là tính tổng một số số ngẫu nhiên phân bố đều và lấy giá trị trung bình của chúng. Xem Định lý Giới hạn Trung tâm để có lời giải thích đầy đủ về lý do tại sao điều này hoạt động.


+1 Cách tiếp cận rất thú vị. Nó có được xác minh để thực sự cung cấp các nhóm phụ được phân phối bình thường cho các nhóm nhỏ hơn không?
Morlock

4
@Morlock Số lượng mẫu trung bình của bạn càng lớn thì bạn càng tiến gần đến phân phối Gauss. Nếu ứng dụng của bạn có các yêu cầu nghiêm ngặt về độ chính xác của việc phân phối thì bạn có thể tốt hơn nên sử dụng thứ gì đó nghiêm ngặt hơn, như Box-Muller, nhưng đối với nhiều ứng dụng, chẳng hạn như tạo tiếng ồn trắng cho các ứng dụng âm thanh, bạn có thể bỏ qua với một số lượng khá nhỏ của các mẫu lấy trung bình (ví dụ: 16).
Paul R

2
Thêm vào đó, làm cách nào để bạn tham số hóa điều này để có được một lượng phương sai nhất định, giả sử bạn muốn giá trị trung bình là 10 với độ lệch chuẩn là 1?
Morlock

1
@Ben: bạn có thể chỉ cho tôi một thuật ngữ hiệu quả cho việc này không? Tôi chỉ từng sử dụng kỹ thuật lấy trung bình để tạo ra nhiễu Gaussian xấp xỉ để xử lý âm thanh và hình ảnh với các hạn chế thời gian thực - nếu có cách đạt được điều này trong ít chu kỳ xung nhịp hơn thì điều đó có thể rất hữu ích.
Paul R

1
@Petter: bạn có thể đúng trong trường hợp chung, đối với các giá trị dấu phẩy động. Tuy nhiên, vẫn có các lĩnh vực ứng dụng như âm thanh, nơi bạn muốn tiếng ồn gaussian số nguyên (hoặc điểm cố định) nhanh và độ chính xác không quá quan trọng, nơi phương pháp tính trung bình đơn giản hiệu quả và hữu ích hơn (đặc biệt đối với các ứng dụng nhúng, nơi thậm chí có thể không được hỗ trợ dấu phẩy động phần cứng).
Paul R

24

Tôi đã tạo một dự án mã nguồn mở C ++ cho điểm chuẩn tạo số ngẫu nhiên được phân phối thông thường .

Nó so sánh một số thuật toán, bao gồm

  • Phương pháp định lý giới hạn trung tâm
  • Biến đổi Box-Muller
  • Phương pháp cực Marsaglia
  • Thuật toán Ziggurat
  • Phương pháp lấy mẫu biến đổi nghịch đảo.
  • cpp11randomsử dụng C ++ 11 std::normal_distributionvới std::minstd_rand(nó thực sự là biến đổi Box-Muller trong tiếng kêu).

Kết quả của floatphiên bản single-precision ( ) trên iMac Corei5-3330S@2.70GHz, clang 6.1, 64-bit:

normaldistf

Đối với tính đúng đắn, chương trình xác minh giá trị trung bình, độ lệch chuẩn, độ lệch và độ lệch của các mẫu. Người ta thấy rằng phương pháp CLT bằng cách tính tổng 4, 8 hoặc 16 số đồng nhất không có khả năng sinh sản tốt như các phương pháp khác.

Thuật toán Ziggurat có hiệu suất tốt hơn các thuật toán khác. Tuy nhiên, nó không phù hợp với song song SIMD vì nó cần tra cứu bảng và các nhánh. Box-Muller với tập lệnh SSE2 / AVX nhanh hơn nhiều (x1.79, x2.99) so với phiên bản không SIMD của thuật toán ziggurat.

Do đó, tôi sẽ đề xuất sử dụng Box-Muller cho kiến ​​trúc với các tập lệnh SIMD, và có thể là ziggurat nếu không.


PS điểm chuẩn sử dụng LCG PRNG đơn giản nhất để tạo ra các số ngẫu nhiên được phân phối đồng nhất. Vì vậy, nó có thể không đủ cho một số ứng dụng. Nhưng so sánh hiệu suất phải công bằng vì tất cả các triển khai đều sử dụng cùng một PRNG, vì vậy điểm chuẩn chủ yếu kiểm tra hiệu suất của chuyển đổi.


2
"Nhưng so sánh hiệu suất phải công bằng vì tất cả các triển khai đều sử dụng cùng một PRNG" .. Ngoại trừ việc BM sử dụng một RN đầu vào cho mỗi đầu ra, trong khi CLT sử dụng nhiều hơn nữa, v.v ... vì vậy thời gian để tạo ra một # ngẫu nhiên đồng nhất là vấn đề.
greggo

14

Đây là một ví dụ C ++, dựa trên một số tài liệu tham khảo. Điều này nhanh chóng và bẩn thỉu, bạn không nên phát minh lại và sử dụng thư viện tăng cường.

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

Bạn có thể sử dụng biểu đồ QQ để kiểm tra kết quả và xem nó gần đúng như thế nào với phân phối chuẩn thực tế (xếp hạng các mẫu của bạn 1..x, chuyển các hạng thành tỷ lệ của tổng số x tức là bao nhiêu mẫu, lấy giá trị z và vẽ chúng. Một đường thẳng hướng lên là kết quả mong muốn).


1
SampleNormalManual () là gì?
PlayingPuzzles,

@solvingPuzzles - xin lỗi, đã sửa mã. Đó là một cuộc gọi đệ quy.
Pete855217

1
Điều này chắc chắn sẽ gặp sự cố tại một số sự kiện hiếm hoi (trình diễn ứng dụng cho sếp của bạn rung chuông?). Điều này nên được thực hiện bằng cách sử dụng một vòng lặp, không sử dụng đệ quy. Phương pháp này có vẻ không quen thuộc. Nguồn là gì / nó được gọi là gì?
con lợn

Box-Muller đã sao chép từ việc triển khai java. Như tôi đã nói, nó nhanh và bẩn, hãy sửa nó.
Pete855217

1
FWIW, nhiều trình biên dịch sẽ có thể biến lời gọi đệ quy cụ thể đó thành 'bước nhảy lên đầu chức năng'. Câu hỏi là bạn có muốn tính tiếp hay không :-) Ngoài ra, xác suất để nó có> 10 lần lặp là 1 trên 4,8 triệu. p (> 20) là hình vuông đó vv
greggo

12

Sử dụng std::tr1::normal_distribution.

Không gian tên std :: tr1 không phải là một phần của boost. Đó là không gian tên chứa các bổ sung thư viện từ Báo cáo kỹ thuật C ++ 1 và có sẵn trong các trình biên dịch và gcc cập nhật của Microsoft, độc lập với boost.


25
Anh ấy không yêu cầu tiêu chuẩn, anh ấy yêu cầu 'không tăng'.
JoeG

12

Đây là cách bạn tạo các mẫu trên trình biên dịch C ++ hiện đại.

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

các generatorthực sự cần được gieo mầm.
Walter

Nó luôn luôn được gieo hạt. Có một hạt giống mặc định.
Petter



4

Nếu bạn đang sử dụng C ++ 11, bạn có thể sử dụng 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);

Có nhiều bản phân phối khác mà bạn có thể sử dụng để biến đổi đầu ra của công cụ số ngẫu nhiên.


Điều đó đã được đề cập bởi Ben ( stackoverflow.com/a/11977979/635608 )
Mat

3

Tôi đã làm theo định nghĩa của PDF được cung cấp trong http://www.mathworks.com/help/stats/normal-distribution.html và nghĩ ra điều này:

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

Nó có thể không phải là cách tiếp cận tốt nhất, nhưng nó khá đơn giản.


-1 Không hoạt động cho ví dụ RANDN2 (0,0, d + 1,0). Macro nổi tiếng về điều này.
Petter

Vĩ mô sẽ thất bại nếu rand()các RANDUlợi nhuận một số không, vì Ln (0) được xác định.
interDist

Bạn đã thực sự thử mã này chưa? Có vẻ như bạn đã tạo một hàm tạo các số được phân phối bởi Rayleigh . So sánh với phép biến đổi Box – Muller , trong đó chúng nhân với cos(2*pi*rand/RAND_MAX), trong khi bạn nhân với (rand()%2 ? -1.0 : 1.0).
HelloGoodbye


1

Triển khai 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

Tồn tại các thuật toán khác nhau cho phân phối chuẩn tích lũy nghịch đảo. Phổ biến nhất trong tài chính định lượng được thử nghiệm trên http://chasethedevil.github.io/post/monte-carlo--inverse-cummental-normal-distribution/

Theo ý kiến ​​của tôi, không có nhiều động lực để sử dụng một thứ khác ngoài thuật toán AS241 từ Wichura : đó là độ chính xác của máy móc, đáng tin cậy và nhanh chóng. Các nút cổ chai hiếm khi xảy ra trong hệ số ngẫu nhiên Gaussian.

Ngoài ra, nó cho thấy mặt hạn chế của Ziggurat như cách tiếp cận.

Câu trả lời hàng đầu ở đây ủng hộ Box-Müller, bạn nên biết rằng nó có những khiếm khuyết đã biết. Tôi trích dẫn https://www.sciasedirect.com/science/article/pii/S0895717710005935 :

trong tài liệu, Box – Muller đôi khi bị coi là hơi kém cỏi, chủ yếu vì hai lý do. Đầu tiên, nếu người ta áp dụng phương pháp Box – Muller cho các số từ bộ tạo đồng dư tuyến tính xấu, các số được biến đổi sẽ cung cấp vùng phủ cực kỳ kém của không gian. Các lô của các con số được biến đổi có đuôi xoắn ốc có thể được tìm thấy trong nhiều cuốn sách, đáng chú ý nhất là trong cuốn sách kinh điển của Ripley, người có lẽ là người đầu tiên đưa ra quan sát này "


0

1) Cách trực quan về mặt đồ họa mà bạn có thể tạo các số ngẫu nhiên Gaussian là sử dụng một thứ tương tự như phương pháp Monte Carlo. Bạn sẽ tạo một điểm ngẫu nhiên trong một hộp xung quanh đường cong Gaussian bằng cách sử dụng trình tạo số giả ngẫu nhiên trong C. Bạn có thể tính toán xem điểm đó nằm bên trong hay bên dưới phân phối Gauss bằng cách sử dụng phương trình của phân phối. Nếu điểm đó nằm trong phân phối Gaussian, thì bạn có số ngẫu nhiên Gaussian của mình làm giá trị x của điểm.

Phương pháp này không hoàn hảo vì về mặt kỹ thuật, đường cong Gauss đi về phía vô cùng và bạn không thể tạo một hộp tiến tới vô cùng trong kích thước x. Nhưng đường cong Guassian tiếp cận 0 theo chiều y khá nhanh nên tôi sẽ không lo lắng về điều đó. Hạn chế về kích thước của các biến của bạn trong C có thể là một yếu tố hạn chế độ chính xác của bạn.

2) Một cách khác là sử dụng Định lý Giới hạn Trung tâm, trong đó nói rằng khi các biến ngẫu nhiên độc lập được thêm vào, chúng sẽ tạo thành một phân phối chuẩn. Ghi nhớ định lý này, bạn có thể tính gần đúng số ngẫu nhiên Gauss bằng cách thêm một lượng lớn các biến ngẫu nhiên độc lập.

Những phương pháp này không thực tế nhất, nhưng điều đó được mong đợi khi bạn không muốn sử dụng thư viện có sẵn. Hãy nhớ rằng câu trả lời này đến từ một người có ít hoặc không có kinh nghiệm tính toán hoặc thống kê.


0

Phương pháp Monte Carlo Cách trực quan nhất để làm điều này là sử dụng phương pháp monte carlo. Lấy một phạm vi phù hợp -X, + X. Giá trị lớn hơn của X sẽ dẫn đến phân phối chuẩn chính xác hơn, nhưng mất nhiều thời gian hơn để hội tụ. a. Chọn một số ngẫu nhiên z từ -X đến X. b. Giữ với một xác suất trong N(z, mean, variance)đó N là phân phối gaussian. Bỏ qua nếu không và quay lại bước (a).



-3

Máy tính là thiết bị xác định. Không có sự ngẫu nhiên trong tính toán. Hơn nữa, thiết bị số học trong CPU có thể đánh giá tổng trên một số tập hợp số nguyên hữu hạn (thực hiện đánh giá trong trường hữu hạn) và tập hợp hữu hạn các số hữu tỉ thực. Và cũng thực hiện các hoạt động bitwise. Toán học gặp nhiều khó khăn hơn như [0,0, 1,0] với số điểm vô hạn.

Bạn có thể nghe một số dây bên trong máy tính với một số bộ điều khiển, nhưng liệu nó có phân bố đồng nhất không? Tôi không biết. Nhưng nếu giả định rằng tín hiệu đó là kết quả của các giá trị tích lũy một lượng lớn các biến ngẫu nhiên độc lập thì bạn sẽ nhận được biến ngẫu nhiên có phân phối chuẩn (Nó đã được chứng minh trong Lý thuyết xác suất)

Có tồn tại các thuật toán được gọi là - trình tạo ngẫu nhiên giả. Như tôi cảm thấy mục đích của trình tạo ngẫu nhiên giả là để mô phỏng tính ngẫu nhiên. Và tiêu chí của goodnes là: - phân phối theo kinh nghiệm được hội tụ (theo một nghĩa nào đó - theo chiều kim, đồng nhất, L2) về lý thuyết - các giá trị mà bạn nhận được từ trình tạo ngẫu nhiên dường như là phụ thuộc. Tất nhiên nó không đúng theo 'quan điểm thực tế', nhưng chúng tôi cho rằng nó đúng.

Một trong những phương pháp phổ biến - bạn có thể tính tổng 12 irv với các phân bố đồng đều .... Nhưng thành thật mà nói trong quá trình lấy Định lý Giới hạn Trung tâm với sự trợ giúp của Biến đổi Fourier, Chuỗi Taylor, cần có n -> + giả thiết hai lần. Vì vậy, ví dụ về lý thuyết - Cá nhân tôi không nhấn mạnh cách mọi người thực hiện tổng 12 irv với phân phối đồng đều.

Tôi đã có lý thuyết xác suất trong trường đại học. Và đặc biệt đối với tôi nó chỉ là một câu hỏi toán học. Ở trường đại học, tôi thấy mô hình sau:


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

Cách thực hiện như vậy chỉ là một ví dụ, tôi đoán nó tồn tại những cách khác để thực hiện nó.

Chứng minh rằng điều đó là đúng có thể được tìm thấy trong cuốn sách này "Moscow, BMSTU, 2004: Lý thuyết xác suất lần thứ XVI, Ví dụ 6.12, tr.246-247" của Krishchenko Alexander Petrovich ISBN 5-7038-2485-0

Rất tiếc là tôi không biết về sự tồn tại của bản dịch cuốn sách này sang tiếng Anh.


Tôi có một số phiếu phản đối. Hãy cho tôi biết điều gì tồi tệ ở đây?
bruziuz,

Câu hỏi đặt ra là làm thế nào để tạo ra các số ngẫu nhiên giả trong máy tính (tôi biết, ngôn ngữ ở đây là lỏng lẻo), nó không phải là một câu hỏi về sự tồn tại của toán học.
user2820579 26/02

Vâng bạn đã đúng. Và câu trả lời là cách tạo số ngẫu nhiên giả có phân phối chuẩn dựa trên trình tạo có phân phối đồng nhất. Mã nguồn đã được cung cấp, bạn có thể viết lại nó bằng bất kỳ ngôn ngữ nào.
bruziuz

Chắc chắn, tôi nghĩ rằng anh chàng đang tìm kiếm ví dụ: "Công thức nấu ăn số trong C / C ++". Nhân tiện, chỉ để bổ sung cho cuộc thảo luận của chúng ta, các tác giả của cuốn sách cuối cùng này đưa ra những tham khảo thú vị về một số máy phát điện giả ngẫu nhiên đáp ứng các tiêu chuẩn để trở thành máy phát điện "tốt".
user2820579 27/02

1
Tôi đã thực hiện sao lưu tại đây: sites.google.com/site/burlachenkok/download
bruziuz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.