C veya C ++ 'da normal bir dağılımın ardından nasıl kolayca rastgele sayılar üretebilirim?
Boost'un herhangi bir şekilde kullanılmasını istemiyorum.
Knuth'un bundan uzun uzun bahsettiğini biliyorum ama şu anda kitapları elimde değil.
C veya C ++ 'da normal bir dağılımın ardından nasıl kolayca rastgele sayılar üretebilirim?
Boost'un herhangi bir şekilde kullanılmasını istemiyorum.
Knuth'un bundan uzun uzun bahsettiğini biliyorum ama şu anda kitapları elimde değil.
Yanıtlar:
Normal bir RNG'den Gauss olarak dağıtılmış sayılar oluşturmanın birçok yöntemi vardır .
Box-Muller dönüşümü yaygın olarak kullanılır. Normal dağılımla doğru şekilde değerler üretir. Matematik kolaydır. İki (tek tip) rastgele sayı üretirsiniz ve bunlara bir formül uygulayarak, normal olarak dağıtılmış iki rastgele sayı elde edersiniz. Birini döndürün ve diğerini bir sonraki rastgele sayı isteği için kaydedin.
std::normal_distribution
, matematiksel ayrıntılara girmeden tam olarak istediğiniz şeyi yapan ekler .
C ++ 11 teklifleri std::normal_distribution
, bugün gideceğim yol.
Artan karmaşıklık sırasına göre bazı çözümler şunlardır:
0'dan 1'e 12 düzgün rasgele sayı ekleyin ve 6 çıkarın. Bu, normal bir değişkenin ortalama ve standart sapmasıyla eşleşecektir. Bariz bir dezavantaj, gerçek bir normal dağılımın aksine aralığın ± 6 ile sınırlı olmasıdır.
Box-Muller dönüşümü. Bu yukarıda listelenmiştir ve uygulaması nispeten basittir. Bununla birlikte, çok hassas örneklere ihtiyacınız varsa, Box-Muller dönüşümünün bazı tek tip jeneratörlerle birlikte Neave Effect 1 adlı bir anormallikten muzdarip olduğunu unutmayın .
En iyi hassasiyet için, normal dağılımlı varyasyonlara ulaşmak için üniforma çizmeyi ve ters kümülatif normal dağılım uygulamayı öneriyorum. İşte ters kümülatif normal dağılımlar için çok iyi bir algoritma.
1. HR Neave, "Çarpımsal eşzamanlı sözde rasgele sayı oluşturucularla Box-Muller dönüşümünü kullanma üzerine" Applied Statistics, 22, 92-97, 1973
Hızlı ve kolay bir yöntem, eşit olarak dağıtılmış birkaç rastgele sayıyı toplamak ve ortalamalarını almaktır. Bunun neden işe yaradığına dair tam bir açıklama için Merkezi Limit Teoremine bakın .
Normal olarak dağıtılmış rastgele sayı üretme kıyaslaması için bir C ++ açık kaynak projesi oluşturdum .
Aşağıdakiler dahil çeşitli algoritmaları karşılaştırır:
cpp11random
C ++ 11 std::normal_distribution
ile birlikte kullanır std::minstd_rand
(aslında clang'da Box-Muller dönüşümüdür).float
İMac Corei5-3330S@2.70GHz, clang 6.1, 64-bit üzerindeki tek duyarlıklı ( ) sürümünün sonuçları :
Doğruluk için, program örneklerin ortalamasını, standart sapmasını, çarpıklığını ve basıklığını doğrular. 4, 8 veya 16 tek tip sayıların toplanmasıyla CLT yönteminin diğer yöntemlerde olduğu gibi iyi basıklığa sahip olmadığı görülmüştür.
Ziggurat algoritması diğerlerinden daha iyi performansa sahiptir. Ancak, tablo aramasına ve dallara ihtiyaç duyduğundan SIMD paralelliği için uygun değildir. SSE2 / AVX komut setine sahip Box-Muller, ziggurat algoritmasının SIMD olmayan versiyonundan çok daha hızlıdır (x1.79, x2.99).
Bu nedenle, SIMD komut setleriyle mimari için Box-Muller kullanılmasını önereceğim, aksi takdirde ziggurat olabilir.
PS, kıyaslama tek tip dağıtılmış rasgele sayılar oluşturmak için en basit LCG PRNG'yi kullanır. Bu yüzden bazı uygulamalar için yeterli olmayabilir. Ancak performans karşılaştırması adil olmalıdır çünkü tüm uygulamalar aynı PRNG'yi kullanır, bu nedenle kıyaslama esas olarak dönüşümün performansını test eder.
İşte bazı referanslara dayalı bir C ++ örneği. Bu hızlı ve kirli, yeniden keşfetmemeniz ve destek kitaplığını kullanmamanız daha iyi.
#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;
}
Sonuçları incelemek ve gerçek bir normal dağılıma ne kadar yakın olduğunu görmek için bir QQ grafiği kullanabilirsiniz (numunelerinizi 1..x sıralayın, sıraları toplam x sayısının oranlarına çevirin, yani kaç numune, z-değerlerini alın ve bunların grafiğini çizin. Yukarı doğru bir çizgi istenen sonuçtur).
Kullanın std::tr1::normal_distribution
.
Std :: tr1 ad alanı, artırmanın bir parçası değildir. Bu, C ++ Teknik Rapor 1'deki kitaplık eklemelerini içeren ad alanıdır ve yükseltmeden bağımsız olarak güncel Microsoft derleyicilerinde ve gcc'de mevcuttur.
Modern bir C ++ derleyicisinde örnekleri bu şekilde oluşturursunuz.
#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
gerçekten seribaşı edilmelidir.
GSL'yi kullanabilirsiniz . Nasıl kullanılacağını göstermek için bazı eksiksiz örnekler verilmiştir .
Bir göz atın: http://www.cplusplus.com/reference/random/normal_distribution/ . Normal dağılımlar oluşturmanın en basit yolu.
C ++ 11 kullanıyorsanız şunları kullanabilirsiniz 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);
Rastgele sayı motorunun çıktısını dönüştürmek için kullanabileceğiniz birçok başka dağıtım vardır.
Http://www.mathworks.com/help/stats/normal-distribution.html'de verilen PDF tanımını takip ettim ve şunu buldum :
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);
}
Belki de en iyi yaklaşım değil ama oldukça basit.
rand()
ve RANDU
Ln (0) tanımlanmamış olduğundan, geri dönüş sıfır.
cos(2*pi*rand/RAND_MAX)
size oysa çarpın ile (rand()%2 ? -1.0 : 1.0)
.
Comp.lang.c SSS listesi hisseleri kolaylıkla üç farklı yolu bir Gauss dağılımı ile rasgele sayı üretmek.
Şuna bir göz atabilirsiniz: http://c-faq.com/lib/gaussian.html
Box-Muller uygulaması:
#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;
}
Ters kümülatif normal dağılım için çeşitli algoritmalar vardır. Kantitatif finansta en popüler olanlar http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/ adresinde test edilir.
Bence, algoritmasını AS241 daha başka kullanım şeye çok teşvik yok Wichura : güvenilir ve hızlı makine hassas vardır. Darboğazlar nadiren Gauss rasgele sayı üretiminde görülür.
Ayrıca Ziggurat benzeri yaklaşımların dezavantajını gösterir.
Buradaki en önemli cevap Box-Müller'i savunuyor, bilinen eksiklikleri olduğunun farkında olmalısınız. Https://www.sciencedirect.com/science/article/pii/S0895717710005935 alıntı yapıyorum :
Literatürde Box-Muller, başlıca iki nedenden ötürü bazen biraz daha düşük olarak kabul edilir. Birincisi, Box-Muller yöntemini kötü bir doğrusal eşzamanlı jeneratörden gelen sayılara uygularsa, dönüştürülen sayılar uzayı son derece zayıf bir şekilde kaplar. Spiral kuyruklu dönüştürülmüş sayıların çizimleri pek çok kitapta bulunabilir, özellikle de bu gözlemi muhtemelen ilk yapan Ripley'in klasik kitabında bulunabilir. "
1) Gauss rasgele sayıları oluşturmanın grafiksel olarak sezgisel yolu, Monte Carlo yöntemine benzer bir şey kullanmaktır. C'deki sözde rasgele sayı oluşturucunuzu kullanarak Gauss eğrisinin etrafındaki bir kutuda rastgele bir nokta oluşturursunuz. Dağılımın denklemini kullanarak bu noktanın Gauss dağılımının içinde mi yoksa altında mı olduğunu hesaplayabilirsiniz. Bu nokta Gauss dağılımının içindeyse, noktanın x değeri olarak Gauss rastgele sayınız var demektir.
Bu yöntem mükemmel değildir çünkü teknik olarak Gauss eğrisi sonsuza doğru ilerler ve x boyutunda sonsuzluğa yaklaşan bir kutu oluşturamazsınız. Ancak Guassian eğrisi y boyutunda 0'a oldukça hızlı yaklaşıyor, bu yüzden endişelenmem. C'deki değişkenlerinizin boyutunun kısıtlaması, doğruluğunuz için daha çok sınırlayıcı bir faktör olabilir.
2) Başka bir yol, bağımsız rasgele değişkenler eklendiğinde normal bir dağılım oluşturduklarını belirten Merkezi Limit Teoremini kullanmak olabilir. Bu teoremi akılda tutarak, büyük miktarda bağımsız rasgele değişken ekleyerek bir Gauss rasgele sayı tahmin edebilirsiniz.
Bu yöntemler en pratik yöntemler değildir, ancak önceden var olan bir kitaplığı kullanmak istemediğinizde bu beklenmelidir. Bu cevabın matematik veya istatistik deneyimi çok az olan veya hiç olmayan birinden geldiğini unutmayın.
Monte Carlo yöntemi
Bunu yapmanın en sezgisel yolu bir monte carlo yöntemi kullanmak olacaktır. Uygun bir -X, + X aralığı alın. Daha büyük X değerleri daha doğru bir normal dağılım sağlar, ancak yakınsaması daha uzun sürer. a. -X ile X arasında rastgele bir z sayısı seçin . B. N(z, mean, variance)
N'nin gauss dağılımının nerede olduğuna dair bir olasılıkla tutun . Aksi takdirde bırakın ve (a) adımına geri dönün.
Bilgisayar deterministik bir araçtır. Hesaplamada rastgelelik yoktur. Ayrıca, CPU'daki aritmetik aygıt, bazı sonlu tam sayılar kümesi (sonlu alanda değerlendirme gerçekleştirme) ve sonlu gerçek rasyonel sayılar kümesi üzerinden toplamı değerlendirebilir. Ve ayrıca bitsel işlemler gerçekleştirdi. Matematik, sonsuz sayıda puana sahip [0.0, 1.0] gibi daha büyük setlerle bir anlaşma yapar.
Bir denetleyiciyle bilgisayarın içindeki bazı kabloları dinleyebilirsiniz, ancak tekdüze dağılımları olur mu? Bilmiyorum. Ancak sinyalinin biriken değerlerin sonucu olduğu varsayılırsa, büyük miktarda bağımsız rastgele değişkenler, yaklaşık olarak normal dağıtılmış rastgele değişken alırsınız (Olasılık Teorisinde kanıtlanmıştır)
Sözde rastgele üretici adı verilen algoritmalar var. Sözde rastgele oluşturucunun amacının rastgeleliği taklit etmek olduğunu hissettim. Ve iyilik kriterleri şudur: - ampirik dağılım (bir anlamda - noktasal, tekdüze, L2) teorik olarak birleştirilir - rastgele oluşturucudan aldığınız değerler bağımsız görünüyor. Elbette 'gerçek bakış açısından' doğru değil, ancak bunun doğru olduğunu varsayıyoruz.
Popüler yöntemlerden biri - tekdüze dağılımlarla 12 irv'yi toplayabilirsiniz .... Ancak, türetme sırasında dürüst olmak gerekirse, Merkezi Limit Teoremi Fourier Dönüşümünün, Taylor Serisinin yardımıyla, birkaç kez n -> + inf varsayımlarına sahip olmak gerekir. Örneğin teorik olarak - Şahsen insanların tekdüze dağılımla 12 irv'lik toplamı nasıl gerçekleştirdiğini anlamıyorum.
Üniversitede olasılık teorim vardı. Ve özellikle benim için bu sadece bir matematik sorusu. Üniversitede şu modeli gördüm:
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;
}
Bu şekilde nasıl yapılacağı sadece bir örnekti, sanırım onu uygulamanın başka yolları da var.
Doğrusu bu kitapta bulunabilir: "Moskova, BMSTU, 2004: XVI Olasılık Teorisi, Örnek 6.12, s.246-247" Krishchenko Alexander Petrovich'in ISBN 5-7038-2485-0
Ne yazık ki bu kitabın İngilizceye çevrilmesinin varlığını bilmiyorum.