C / C ++ 'da normal dağılımın ardından rastgele sayılar üretin


114

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:


92

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.


10
Hıza ihtiyacınız varsa, kutup yöntemi daha hızlıdır. Ve Ziggurat algoritması daha da fazla (yazmak çok daha karmaşık olsa da).
Joey

2
Ziggurat'ın bir uygulamasını burada bulabilirsiniz people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html Tamamen tamamlandı.
dwbrito

24
Unutmayın, C ++ 11 std::normal_distribution, matematiksel ayrıntılara girmeden tam olarak istediğiniz şeyi yapan ekler .

3
std :: normal_distribution'ın tüm platformlarda tutarlı olması garanti edilmez. Şimdi testleri yapıyorum ve MSVC, örneğin Clang'dan farklı bir değer kümesi sağlıyor. C ++ 11 motorları aynı dizileri oluşturuyor gibi görünüyor (aynı çekirdek verildiğinde), ancak C ++ 11 dağıtımları farklı platformlarda farklı algoritmalar kullanılarak uygulanıyor gibi görünüyor.
Arno Duvenhage

47

C ++ 11

C ++ 11 teklifleri std::normal_distribution, bugün gideceğim yol.

C veya daha eski C ++

Artan karmaşıklık sırasına göre bazı çözümler şunlardır:

  1. 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.

  2. 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 .

  3. 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


Neave etkisi ile ilgili pdf'ye başka bir bağlantınız olabilir mi? veya orijinal dergi makalesi referansı? teşekkür ederim
pyCthon

2
@stonybrooknick Orijinal referans eklendi. Harika bir açıklama: Referansı bulmak için "box muller neave" google'da arama yaparken, bu yığın aşımı sorusu ilk sonuç sayfasında ortaya çıktı!
Peter G.

evet, belirli küçük toplulukların ve ilgi gruplarının dışında pek iyi bilinmez
pyCthon

@Peter G. Neden birisi cevabınıza olumsuz oy versin? - muhtemelen aynı kişi aşağıdaki yorumumu yaptı, ben de iyiyim, ama cevabınızın çok iyi olduğunu düşündüm. SO'nun olumsuz oyları gerçek bir yorumu zorunlu kılarsa iyi olur ... Eski konuların çoğu olumsuz oylarının sadece anlamsız ve berbat olduğundan şüpheleniyorum.
Pete855217

"0-1'den 12 tek tip sayı toplayın ve 6'yı çıkarın." - bu değişkenin dağılımı normal dağılıma sahip olacak mı? Türev ile bir bağlantı sağlayabilir misiniz, çünkü türetme merkezi limit teoremi sırasında n -> + inf çok gerekli bir varsayımdır.
bruziuz

31

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 .


+1 Çok ilginç bir yaklaşım. Daha küçük gruplar için gerçekten normal dağıtılmış alt topluluklar verdiği doğrulanıyor mu?
Morlock

4
@Morlock Ortalama aldığınız örnek sayısı arttıkça, Gauss dağılımına yaklaşırsınız. Uygulamanızın dağıtımın doğruluğu için katı gereksinimleri varsa, Box-Muller gibi daha titiz bir şey kullanmanız daha iyi olabilir, ancak birçok uygulama için, örneğin ses uygulamaları için beyaz gürültü oluşturma, oldukça küçük bir sayı ile kurtulabilirsiniz. Ortalamalı örneklerin (örneğin 16).
Paul R

2
Artı, belirli bir varyans elde etmek için bunu nasıl parametrize edersiniz, mesela 1 standart sapma ile ortalama 10 istiyorsunuz?
Morlock

1
@Ben: Bunun için beni verimli bir algoya yönlendirebilir misin? Gerçek zamanlı kısıtlamalarla ses ve görüntü işleme için yaklaşık Gauss gürültüsü üretmek için yalnızca ortalama alma tekniğini kullandım - eğer bunu daha az saat döngüsünde gerçekleştirmenin bir yolu varsa, o zaman bu çok yararlı olabilir.
Paul R

1
@Petter: Kayan nokta değerleri için muhtemelen genel durumda haklısınız. Yine de ses gibi, hızlı tam sayı (veya sabit nokta) gauss gürültüsü istediğiniz ve doğruluğun çok önemli olmadığı, basit ortalama yönteminin daha verimli ve kullanışlı olduğu (özellikle gömülü uygulamalar için, hatta bulunmayabileceği) uygulama alanları vardır. donanım kayan nokta desteği olabilir).
Paul R

24

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:

  • Merkezi limit teoremi yöntemi
  • Box-Muller dönüşümü
  • Marsaglia polar yöntemi
  • Ziggurat algoritması
  • Ters dönüşüm örnekleme yöntemi.
  • cpp11randomC ++ 11 std::normal_distributionile 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ı :

normaldistf

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.


2
"Ancak performans karşılaştırması adil olmalıdır çünkü tüm uygulamalar aynı PRNG'yi kullanır" .. BM'nin her çıktı için bir girdi RN kullanması, CLT'nin çok daha fazlasını kullanması, vb. Dışında tek tip rastgele # önemli.
greggo

14

İş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).


1
SampleNormalManual () nedir?
solvingPuzzles

@solvingPuzzles - üzgünüm, kodu düzelttim. Yinelemeli bir çağrı.
Pete855217

1
Bu, bazı nadir olaylarda çökmeye mahkumdur (patronunuza uygulama göstermek bir zil çalar mı?). Bu, özyineleme kullanılmadan bir döngü kullanılarak uygulanmalıdır. Yöntem tanıdık gelmiyor. Kaynak nedir / nasıl adlandırılır?
the swine

Box-Muller bir java uygulamasından yazılmıştır. Dediğim gibi, hızlı ve kirli, düzeltmekten çekinmeyin.
Pete855217

1
FWIW, birçok derleyici bu belirli özyinelemeli çağrıyı bir 'işlevin tepesine sıçrayışa' dönüştürebilecek. Soru, ona güvenmek isteyip istemediğinizdir :-) Ayrıca,> 10 yineleme alma olasılığı 4,8 milyonda 1'dir. p (> 20) bunun karesidir, vb.
greggo

12

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.


25
Standart istemedi, 'güçlendirme' istemedi.
JoeG

12

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;

generatorgerçekten seribaşı edilmelidir.
Walter

Her zaman tohumlanır. Varsayılan bir tohum var.
Petter



4

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.


Bundan daha önce Ben tarafından bahsedilmişti ( stackoverflow.com/a/11977979/635608 )
Mat

3

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.


-1 Örneğin RANDN2 (0.0, d + 1.0) için çalışmıyor. Makrolar bununla ünlüdür.
Petter

Makro başarısız olur rand()ve RANDULn (0) tanımlanmamış olduğundan, geri dönüş sıfır.
interDist

Bu kodu gerçekten denediniz mi? Görünüşe göre Rayleigh tarafından dağıtılan sayılar üreten bir işlev oluşturmuşsunuz . İle karşılaştır Box-Muller dönüşümü , neredeler çarpın ile cos(2*pi*rand/RAND_MAX)size oysa çarpın ile (rand()%2 ? -1.0 : 1.0).
HelloGoodbye


1

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

1

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. "


0

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.


0

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.



-3

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.


Birkaç olumsuz oyum var. Burada neyin kötü olduğunu bana bildirin.
bruziuz

Soru, bilgisayarda sözde rastgele sayıların nasıl üretileceğidir (biliyorum, burada dil gevşek), bu matematiksel bir varoluş sorunu değil.
user2820579

Evet haklısın. Ve cevap, tekdüze dağılıma sahip jeneratöre dayalı normal dağılıma sahip sözde rasgele sayının nasıl üretileceğidir. Kaynak kodu sağlanmıştır, herhangi bir dilde yeniden yazabilirsiniz.
bruziuz

Elbette, sanırım adam örneğin "C / C ++ 'da Sayısal Tarifler" arıyor. Bu arada, sırf tartışmamızı tamamlamak için, bu son kitabın yazarları, "düzgün" üreteçler olma standartlarını karşılayan birkaç sözde-rastgele oluşturucuya ilginç referanslar veriyorlar.
user2820579

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.