Genera numeri casuali seguendo una distribuzione normale in C / C ++


Risposte:


92

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.


10
Se hai bisogno di velocità, il metodo polare è però più veloce. E l'algoritmo Ziggurat ancora di più (anche se molto più complesso da scrivere).
Joey

2
ho trovato un'implementazione dello Ziggurat qui people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html È abbastanza completo.
dwbrito

24
Nota, C ++ 11 aggiunge std::normal_distributionche fa esattamente quello che chiedi senza approfondire i dettagli matematici.

3
Non è garantito che std :: normal_distribution sia coerente su tutte le piattaforme. Sto eseguendo i test ora e MSVC fornisce un diverso insieme di valori, ad esempio, Clang. I motori C ++ 11 sembrano generare le stesse sequenze (dato lo stesso seme), ma le distribuzioni C ++ 11 sembrano essere implementate utilizzando algoritmi diversi su piattaforme diverse.
Arno Duvenhage

47

C ++ 11

Offerte C ++ 11 std::normal_distribution , che è il modo in cui andrei oggi.

C o C ++ precedente

Ecco alcune soluzioni in ordine crescente di complessità:

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

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

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


per caso avresti un altro link al pdf sull'effetto Neave? o il riferimento dell'articolo della rivista originale? grazie
pyCthon

2
@stonybrooknick Il riferimento originale è stato aggiunto. Nota interessante: mentre cercavi su Google "box muller neave" per trovare il riferimento, questa domanda molto stackoverflow è apparsa nella prima pagina dei risultati!
Peter G.

sì, non è tutti ben noto al di fuori di alcune piccole comunità e gruppi di interesse
pyCthon

@Peter G. Perché qualcuno dovrebbe sottovalutare la tua risposta? - forse la stessa persona ha fatto anche il mio commento qui sotto, cosa con cui sto bene, ma ho pensato che la tua risposta fosse molto buona. Sarebbe positivo se i voti negativi così fossero forzati a un commento reale. Sospetto che la maggior parte dei voti negativi su vecchi argomenti siano semplicemente frivoli e banali.
Pete855217

"Aggiungi 12 numeri uniformi da 0-1 e sottrai 6." - la distribuzione di questa variabile avrà distribuzione normale? Puoi fornire un collegamento con la derivazione, perché durante la derivazione teorema del limite centrale, n -> + inf è un'assunzione molto necessaria.
bruziuz

31

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.


+1 Approccio molto interessante. È verificato per fornire effettivamente sottoinsiemi distribuiti normalmente per gruppi più piccoli?
Morlock

4
@Morlock Maggiore è il numero di campioni media, più ci si avvicina a una distribuzione gaussiana. Se la tua applicazione ha requisiti rigidi per l'accuratezza della distribuzione, potresti fare meglio a usare qualcosa di più rigoroso, come Box-Muller, ma per molte applicazioni, ad esempio la generazione di rumore bianco per le applicazioni audio, puoi farla franca con un numero abbastanza piccolo di campioni mediati (es. 16).
Paul R,

2
Inoltre, come lo parametrizzi per ottenere una certa quantità di varianza, supponiamo che tu voglia una media di 10 con una deviazione standard di 1?
Morlock

1
@ Ben: potresti indicarmi un algoritmo efficiente per questo? Ho sempre utilizzato solo la tecnica della media per generare rumore approssimativamente gaussiano per l'elaborazione di audio e immagini con vincoli in tempo reale - se c'è un modo per ottenere questo risultato in meno cicli di clock, allora potrebbe essere molto utile.
Paul R,

1
@Petter: probabilmente hai ragione nel caso generale, per i valori in virgola mobile. Esistono ancora aree di applicazione come l'audio, in cui si desidera un rumore gaussiano veloce intero (o punto fisso) e l'accuratezza non è troppo importante, dove il semplice metodo di calcolo della media è più efficiente e utile (specialmente per le applicazioni incorporate, dove potrebbe non esserci nemmeno essere supporto in virgola mobile hardware).
Paul R,

24

Ho creato un progetto open source C ++ per benchmark di generazione di numeri casuali distribuiti normalmente .

Confronta diversi algoritmi, incluso

  • Metodo del teorema del limite centrale
  • Trasformata di Box-Muller
  • Metodo polare Marsaglia
  • Algoritmo di Ziggurat
  • Metodo di campionamento della trasformazione inversa.
  • cpp11randomusa C ++ 11 std::normal_distributioncon 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:

normaldistf

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.


2
"Ma il confronto delle prestazioni dovrebbe essere equo perché tutte le implementazioni usano lo stesso PRNG" .. Tranne che BM usa un input RN per output, mentre CLT ne usa molti di più, ecc ... quindi il tempo per generare un # casuale uniforme è importante.
greggo

14

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


1
Cos'è sampleNormalManual ()?
solvingPuzzles

@solvingPuzzles - scusa, ho corretto il codice. È una chiamata ricorsiva.
Pete855217

1
Questo è destinato a bloccarsi in un evento raro (mostrare l'applicazione al tuo capo suona un campanello?). Questo dovrebbe essere implementato usando un ciclo, non usando la ricorsione. Il metodo sembra poco familiare. Qual è la fonte / come si chiama?
il suino

Box-Muller trascritto da un'implementazione java. Come ho detto, è veloce e sporco, sentiti libero di aggiustarlo.
Pete855217

1
FWIW, molti compilatori saranno in grado di trasformare quella particolare chiamata ricorsiva in un "salto all'inizio della funzione". La domanda è se vuoi contare su di esso :-) Inoltre, la probabilità che richieda> 10 iterazioni è 1 su 4,8 milioni. p (> 20) è il quadrato di quello, ecc.
greggo

12

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.


25
Non ha chiesto standard, ha chiesto "non boost".
JoeG

12

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;

la generatorrealtà dovrebbe essere testa di serie.
Walter

È sempre seminato. C'è un seme predefinito.
Petter



4

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.


Questo è già stato menzionato da Ben ( stackoverflow.com/a/11977979/635608 )
Mat

3

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.


-1 Non funziona ad esempio per RANDN2 (0,0, d + 1,0). Le macro sono famose per questo.
Petter

La macro fallirà se rand()di RANDUrestituisce uno zero, poiché Ln (0) non è definito.
interDist

Hai davvero provato questo codice? Sembra che tu abbia creato una funzione che genera numeri distribuiti da Rayleigh . Confronta con la trasformata Box-Muller , dove si moltiplicano con cos(2*pi*rand/RAND_MAX), mentre tu moltiplichi con (rand()%2 ? -1.0 : 1.0).
Ciao Arrivederci


1

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

1

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 "


0

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.


0

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


-1

Dai un'occhiata a quello che ho trovato.

Questa libreria utilizza l'algoritmo Ziggurat.


-3

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.


Ho diversi voti negativi. Fammi sapere cosa c'è di male qui?
bruziuz

La domanda è come generare numeri pseudo casuali nel computer (lo so, il linguaggio è sciolto qui), non è una questione di esistenza matematica.
user2820579

Sì hai ragione. E la risposta è come generare un numero pseudo casuale con distribuzione normale basata su un generatore che ha una distribuzione uniforme. Il codice sorgente è stato fornito, puoi riscriverlo in qualsiasi lingua.
bruziuz

Certo, penso che il ragazzo stia cercando ad esempio "Ricette numeriche in C / C ++". A proposito, solo per completare la nostra discussione, gli autori di questo ultimo libro forniscono riferimenti interessanti per un paio di generatori pseudo-casuali che soddisfano gli standard per essere generatori "decenti".
user2820579

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.