Come posso generare facilmente numeri casuali seguendo una normale distribuzione in C o C ++?
Non voglio usare Boost.
So che Knuth ne parla a lungo, ma al momento non ho i suoi libri a portata di mano.
Come posso generare facilmente numeri casuali seguendo una normale distribuzione in C o C ++?
Non voglio usare Boost.
So che Knuth ne parla a lungo, ma al momento non ho i suoi libri a portata di mano.
Risposte:
Esistono molti metodi per generare numeri distribuiti in gaussiano da un normale RNG .
La trasformata Box-Muller è comunemente usata. Produce correttamente valori con una distribuzione normale. La matematica è facile. Si generano due numeri casuali (uniformi) e, applicandovi una formula, si ottengono due numeri casuali distribuiti normalmente. Restituine uno e salva l'altro per la successiva richiesta di un numero casuale.
std::normal_distribution
che fa esattamente quello che chiedi senza approfondire i dettagli matematici.
Offerte C ++ 11 std::normal_distribution
, che è il modo in cui andrei oggi.
Ecco alcune soluzioni in ordine crescente di complessità:
Aggiungi 12 numeri casuali uniformi da 0 a 1 e sottrai 6. Ciò corrisponderà alla media e alla deviazione standard di una variabile normale. Un ovvio svantaggio è che l'intervallo è limitato a ± 6, a differenza di una vera distribuzione normale.
La trasformazione di Box-Muller. Questo è elencato sopra ed è relativamente semplice da implementare. Se hai bisogno di campioni molto precisi, tuttavia, tieni presente che la trasformazione Box-Muller combinata con alcuni generatori uniformi soffre di un'anomalia chiamata Effetto Neave 1 .
Per una migliore precisione, suggerisco di disegnare uniformi e applicare la distribuzione normale cumulativa inversa per arrivare a variabili distribuite normalmente. Ecco un ottimo algoritmo per le distribuzioni normali cumulative inverse.
1. HR Neave, "On using the Box-Muller transformation with multiplicative congruential pseudorandom number generators", Applied Statistics, 22, 92-97, 1973
Un metodo rapido e semplice consiste nel sommare un numero di numeri casuali distribuiti uniformemente e prendere la loro media. Vedi il Teorema del limite centrale per una spiegazione completa del perché funziona.
Ho creato un progetto open source C ++ per benchmark di generazione di numeri casuali distribuiti normalmente .
Confronta diversi algoritmi, incluso
cpp11random
usa C ++ 11 std::normal_distribution
con std::minstd_rand
(in realtà è la trasformazione Box-Muller in clang).I risultati della versione a precisione singola ( float
) su iMac Corei5-3330S@2.70GHz, clang 6.1, 64-bit:
Per correttezza, il programma verifica la media, la deviazione standard, l'asimmetria e la curtosi dei campioni. È stato scoperto che il metodo CLT sommando 4, 8 o 16 numeri uniformi non ha una buona curtosi come gli altri metodi.
L'algoritmo Ziggurat ha prestazioni migliori rispetto agli altri. Tuttavia, non è adatto per il parallelismo SIMD poiché necessita di ricerche di tabelle e rami. Box-Muller con il set di istruzioni SSE2 / AVX è molto più veloce (x1.79, x2.99) rispetto alla versione non SIMD dell'algoritmo ziggurat.
Pertanto, suggerirò di utilizzare Box-Muller per l'architettura con set di istruzioni SIMD, altrimenti potrebbe essere ziggurat.
PS il benchmark utilizza un PRNG LCG più semplice per la generazione di numeri casuali distribuiti uniformi. Quindi potrebbe non essere sufficiente per alcune applicazioni. Ma il confronto delle prestazioni dovrebbe essere equo perché tutte le implementazioni utilizzano lo stesso PRNG, quindi il benchmark verifica principalmente le prestazioni della trasformazione.
Ecco un esempio C ++, basato su alcuni dei riferimenti. Questo è veloce e sporco, è meglio non reinventare e usare la libreria boost.
#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;
}
Puoi usare un grafico QQ per esaminare i risultati e vedere quanto bene si avvicina a una distribuzione normale reale (classifica i tuoi campioni 1..x, trasforma i ranghi in proporzioni del conteggio totale di x cioè quanti campioni, ottieni i valori z e tracciarli. Una linea retta verso l'alto è il risultato desiderato).
Usa std::tr1::normal_distribution
.
Lo spazio dei nomi std :: tr1 non fa parte di boost. È lo spazio dei nomi che contiene le aggiunte alla libreria dal rapporto tecnico 1 di C ++ ed è disponibile nei compilatori Microsoft e in gcc aggiornati, indipendentemente dal boost.
Questo è il modo in cui generi gli esempi su un moderno compilatore 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
realtà dovrebbe essere testa di serie.
Puoi usare GSL . Vengono forniti alcuni esempi completi per dimostrare come usarlo.
Dai un'occhiata a: http://www.cplusplus.com/reference/random/normal_distribution/ . È il modo più semplice per produrre distribuzioni normali.
Se stai usando C ++ 11, puoi usare 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);
Ci sono molte altre distribuzioni che puoi usare per trasformare l'output del motore dei numeri casuali.
Ho seguito la definizione del PDF fornita in http://www.mathworks.com/help/stats/normal-distribution.html e ho trovato questo:
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);
}
Forse non è l'approccio migliore, ma è abbastanza semplice.
rand()
di RANDU
restituisce uno zero, poiché Ln (0) non è definito.
cos(2*pi*rand/RAND_MAX)
, mentre tu moltiplichi con (rand()%2 ? -1.0 : 1.0)
.
L' elenco delle FAQ di comp.lang.c condivide tre diversi modi per generare facilmente numeri casuali con una distribuzione gaussiana.
Puoi dare un'occhiata: http://c-faq.com/lib/gaussian.html
Implementazione di 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;
}
Esistono vari algoritmi per la distribuzione normale cumulativa inversa. I più popolari nella finanza quantitativa sono testati su http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/
Secondo me, non c'è molto incentivo a usare qualcos'altro oltre all'algoritmo AS241 di Wichura : è la precisione della macchina, affidabile e veloce. I colli di bottiglia si verificano raramente nella generazione di numeri casuali gaussiani.
Inoltre, mostra l'inconveniente degli approcci simili a Ziggurat.
La risposta principale qui sostiene Box-Müller, dovresti essere consapevole che ha carenze note. Cito https://www.sciencedirect.com/science/article/pii/S0895717710005935 :
in letteratura, Box-Muller è talvolta considerato leggermente inferiore, principalmente per due ragioni. In primo luogo, se si applica il metodo Box – Muller ai numeri di un generatore congruente lineare non valido, i numeri trasformati forniscono una copertura estremamente scarsa dello spazio. Trame di numeri trasformati con code a spirale si possono trovare in molti libri, in particolare nel classico libro di Ripley, che fu probabilmente il primo a fare questa osservazione "
1) Il modo graficamente intuitivo per generare numeri casuali gaussiani è utilizzando qualcosa di simile al metodo Monte Carlo. Dovresti generare un punto casuale in un riquadro attorno alla curva gaussiana usando il tuo generatore di numeri pseudo-casuali in C. Puoi calcolare se quel punto è dentro o sotto la distribuzione gaussiana usando l'equazione della distribuzione. Se quel punto è all'interno della distribuzione gaussiana, allora hai il tuo numero casuale gaussiano come valore x del punto.
Questo metodo non è perfetto perché tecnicamente la curva gaussiana va verso l'infinito e non è possibile creare una scatola che si avvicini all'infinito nella dimensione x. Ma la curva Guassiana si avvicina a 0 nella dimensione y abbastanza velocemente, quindi non mi preoccuperei di questo. Il vincolo della dimensione delle tue variabili in C potrebbe essere più un fattore limitante per la tua precisione.
2) Un altro modo sarebbe usare il Teorema del limite centrale che afferma che quando vengono aggiunte variabili casuali indipendenti, formano una distribuzione normale. Tenendo presente questo teorema, puoi approssimare un numero casuale gaussiano aggiungendo una grande quantità di variabili casuali indipendenti.
Questi metodi non sono i più pratici, ma c'è da aspettarselo quando non si desidera utilizzare una libreria preesistente. Tieni presente che questa risposta proviene da qualcuno con poca o nessuna esperienza di calcolo o statistica.
Metodo Monte Carlo
Il modo più intuitivo per farlo sarebbe utilizzare un metodo Monte Carlo. Prendi un intervallo adatto -X, + X. Valori più grandi di X risulteranno in una distribuzione normale più accurata, ma richiede più tempo per convergere. un. Scegli un numero casuale z compreso tra -X e X. b. Mantenere una probabilità di N(z, mean, variance)
dove N è la distribuzione gaussiana. Altrimenti rilascia e torna al passaggio (a).
Il computer è un dispositivo deterministico. Non c'è casualità nel calcolo. Inoltre il dispositivo aritmetico nella CPU può valutare la somma su un insieme finito di numeri interi (eseguendo la valutazione in un campo finito) e un insieme finito di numeri razionali reali. E ha anche eseguito operazioni bit per bit. La matematica si prende un patto con set più grandi come [0.0, 1.0] con un numero infinito di punti.
Puoi ascoltare qualche filo all'interno del computer con un controller, ma avrebbe distribuzioni uniformi? Non lo so. Ma se si presume che il suo segnale sia il risultato di valori accumulati, una quantità enorme di variabili casuali indipendenti, si riceverà una variabile casuale distribuita approssimativamente normale (è stato dimostrato nella teoria della probabilità)
Esistono algoritmi chiamati - generatore di pseudo casuali. Come ho sentito, lo scopo del generatore di pseudo casuali è emulare la casualità. E il criterio di goodnes è: - la distribuzione empirica converge (in un certo senso - puntuale, uniforme, L2) a quella teorica - i valori che ricevi dal generatore casuale sembrano essere idependent. Ovviamente non è vero dal "punto di vista reale", ma supponiamo che sia vero.
Uno dei metodi più diffusi - puoi sommare 12 irv con distribuzioni uniformi .... Ma per essere onesti durante la derivazione Teorema del limite centrale con l'aiuto della trasformata di Fourier, serie di Taylor, è necessario avere n -> + ipotesi inf un paio di volte. Quindi, ad esempio, in teoria - Personalmente non capisco come le persone eseguano la somma di 12 irv con distribuzione uniforme.
Avevo la teoria della probabilità all'università. E soprattutto per me è solo una questione di matematica. All'università ho visto il seguente modello:
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;
}
In questo modo come farlo era solo un esempio, immagino che esistano altri modi per implementarlo.
La prova che è corretto può essere trovata in questo libro "Moscow, BMSTU, 2004: XVI Probability Theory, Example 6.12, p.246-247" di Krishchenko Alexander Petrovich ISBN 5-7038-2485-0
Purtroppo non so su esistenza di traduzione di questo libro in inglese.