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ờ.
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:
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.
std::normal_distribution
nă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.
C ++ 11 cung cấp std::normal_distribution
, đó là cách tôi sẽ đi ngày hôm nay.
Dưới đây là một số giải pháp theo thứ tự độ phức tạp tăng dần:
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ự.
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 .
Để 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
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.
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
cpp11random
sử dụng C ++ 11 std::normal_distribution
với std::minstd_rand
(nó thực sự là biến đổi Box-Muller trong tiếng kêu).Kết quả của float
phiên bản single-precision ( ) trên iMac Corei5-3330S@2.70GHz, clang 6.1, 64-bit:
Đố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.
Đâ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).
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.
Đâ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;
generator
thực sự cần được gieo mầm.
Bạn có thể sử dụng GSL . Một số ví dụ hoàn chỉnh được đưa ra để chứng minh cách sử dụng nó.
Hãy xem trên: http://www.cplusplus.com/reference/random/normal_distribution/ . Đó là cách đơn giản nhất để tạo ra các bản phân phối bình thường.
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.
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.
rand()
các RANDU
lợi nhuận một số không, vì Ln (0) được xác định.
cos(2*pi*rand/RAND_MAX)
, trong khi bạn nhân với (rand()%2 ? -1.0 : 1.0)
.
Các danh sách comp.lang.c FAQ cổ phiếu ba cách khác nhau để dễ dàng tạo ra các số ngẫu nhiên với một phân phối Gaussian.
Bạn có thể xem qua: http://c-faq.com/lib/gaussian.html
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;
}
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 "
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ê.
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).
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.