Conversione di una distribuzione uniforme in una distribuzione normale


106

Come posso convertire una distribuzione uniforme (come produce la maggior parte dei generatori di numeri casuali, ad esempio tra 0,0 e 1,0) in una distribuzione normale? E se desidero una deviazione media e standard di mia scelta?


3
Hai una specifica del linguaggio o è solo una domanda generale sull'algoritmo?
Bill the Lizard

3
Domanda generale sull'algoritmo. Non mi interessa quale lingua. Ma preferirei che la risposta non si basasse su funzionalità specifiche fornite solo da quel linguaggio.
Terhorst,

Risposte:



47

Esistono molti metodi:

  • Do Non utilizzare Box Muller. Soprattutto se disegni molti numeri gaussiani. Box Muller fornisce un risultato che è bloccato tra -6 e 6 (assumendo una doppia precisione. Le cose peggiorano con i float). Ed è davvero meno efficiente di altri metodi disponibili.
  • Ziggurat va bene, ma necessita di una ricerca nella tabella (e alcune modifiche specifiche della piattaforma a causa di problemi di dimensione della cache)
  • Il rapporto di uniformi è il mio preferito, solo poche addizioni / moltiplicazioni e un registro 1/50 del tempo (ad esempio, guarda lì ).
  • L'inversione del CDF è efficiente (e trascurato, perché?), Ne hai implementazioni veloci disponibili se cerchi su google. È obbligatorio per i numeri quasi casuali.

2
Sei sicuro del serraggio [-6,6]? Questo è un punto piuttosto significativo se vero (e degno di una nota sulla pagina di wikipedia).
redcalx

1
@locster: questo è ciò che mi ha detto un mio insegnante (ha studiato tali generatori e mi fido della sua parola). Potrei essere in grado di trovarti un riferimento.
Alexandre C.

7
@locster: questa proprietà indesiderabile è condivisa anche dal metodo CDF inverso. Vedi cimat.mx/~src/prope08/randomgauss.pdf . Questo può essere alleviato utilizzando un RNG uniforme che ha una probabilità diversa da zero di produrre un numero in virgola mobile molto vicino allo zero. La maggior parte degli RNG non lo fa, poiché generano un numero intero (tipicamente a 64 bit) che viene quindi mappato a [0,1]. Ciò rende questi metodi inadatti per il campionamento di code di variabili gaussiane (si pensi al prezzo delle opzioni di strike basso / alto nella finanza computazionale).
Alexandre C.

6
@AlexandreC. Per essere chiari su due punti, usando numeri a 64 bit le code vanno a 8.57 o 9.41 (il valore più basso corrispondente alla conversione in [0,1) prima di prendere il logaritmo). Anche se fissate a [-6, 6] le possibilità di essere al di fuori di questo intervallo sono di circa 1,98e-9, abbastanza buone per la maggior parte delle persone anche nel campo della scienza. Per le cifre 8.57 e 9.41 questo diventa 1.04e-17 e 4.97e-21. Questi numeri sono così piccoli che la differenza tra un campionamento Box Muller e un vero campionamento gaussiano in termini di detto limite è quasi puramente accademica. Se hai bisogno di meglio, somma quattro di loro e dividi per 2.
CrazyCasta

6
Penso che il suggerimento di non utilizzare la trasformazione Box Muller sia fuorviante per una grande percentuale di utenti. È bello conoscere la limitazione, ma come sottolinea CrazyCasta, per la maggior parte delle applicazioni che non dipendono fortemente da valori anomali, probabilmente non devi preoccuparti di questo. Ad esempio, se ti sei mai affidato al campionamento da un normale utilizzando numpy, sei dipeso dalla trasformazione di Box Muller (modulo coordinate polari) github.com/numpy/numpy/blob/… .
Andreas Grivas

30

Modificare la distribuzione di qualsiasi funzione in un'altra implica l'utilizzo dell'inverso della funzione desiderata.

In altre parole, se miri a una specifica funzione di probabilità p (x) ottieni la distribuzione integrando su di essa -> d (x) = integrale (p (x)) e usa il suo inverso: Inv (d (x)) . Ora usa la funzione di probabilità casuale (che ha una distribuzione uniforme) e lancia il valore del risultato attraverso la funzione Inv (d (x)). Dovresti ottenere valori casuali espressi con distribuzione in base alla funzione che hai scelto.

Questo è l'approccio matematico generico: utilizzandolo ora puoi scegliere qualsiasi funzione di probabilità o di distribuzione a condizione che abbia un'approssimazione inversa o inversa buona.

Spero che questo sia stato d'aiuto e grazie per la piccola osservazione sull'uso della distribuzione e non della probabilità stessa.


4
+1 Questo è un metodo trascurato per generare variabili gaussiane che funziona molto bene. La CDF inversa può essere calcolata in modo efficiente con il metodo di Newton in questo caso (la derivata è e ^ {- t ^ 2}), un'approssimazione iniziale è facile da ottenere come frazione razionale, quindi sono necessarie 3-4 valutazioni di erf ed exp. È obbligatorio se usi numeri quasi casuali, un caso in cui devi usare esattamente un numero uniforme per ottenere uno gaussiano.
Alexandre C.

9
Nota che devi invertire la funzione di distribuzione cumulativa, non la funzione di distribuzione di probabilità. Alexandre lo implica, ma ho pensato che menzionarlo in modo più esplicito potrebbe non far male - dal momento che la risposta sembra suggerire il PDF
ltjax

Puoi utilizzare il PDF se sei disposto a selezionare in modo casuale una direzione relativa alla media; lo capisco bene?
Mark McKenna


1
Ecco la domanda correlata in SE con una risposta più generalizzata con una bella spiegazione.
dashesy

23

Ecco un'implementazione javascript che utilizza la forma polare della trasformazione Box-Muller.

/*
 * Returns member of set with a given mean and standard deviation
 * mean: mean
 * standard deviation: std_dev 
 */
function createMemberInNormalDistribution(mean,std_dev){
    return mean + (gaussRandom()*std_dev);
}

/*
 * Returns random number in normal distribution centering on 0.
 * ~95% of numbers returned should fall between -2 and 2
 * ie within two standard deviations
 */
function gaussRandom() {
    var u = 2*Math.random()-1;
    var v = 2*Math.random()-1;
    var r = u*u + v*v;
    /*if outside interval [0,1] start over*/
    if(r == 0 || r >= 1) return gaussRandom();

    var c = Math.sqrt(-2*Math.log(r)/r);
    return u*c;

    /* todo: optimize this algorithm by caching (v*c) 
     * and returning next time gaussRandom() is called.
     * left out for simplicity */
}

5

Usa la voce mathworld del teorema del limite centrale a tuo vantaggio.

Genera n dei numeri uniformemente distribuiti, sommali, sottrai n * 0,5 e hai l'output di una distribuzione approssimativamente normale con media uguale a 0 e varianza uguale a (1/12) * (1/sqrt(N))(vedi wikipedia sulle distribuzioni uniformi per quest'ultima)

n = 10 ti dà qualcosa di abbastanza decente velocemente. Se vuoi qualcosa di più della metà decente, scegli la soluzione tylers (come indicato nella voce di wikipedia sulle distribuzioni normali )


1
Questo non darà una normale particolarmente vicina (le "code" o i punti finali non saranno vicini alla distribuzione normale reale). Box-Muller è migliore, come altri hanno suggerito.
Peter K.

1
Anche Box Muller sbaglia croce (restituisce un numero compreso tra -6 e 6 in doppia precisione)
Alexandre C.

n = 12 (somma 12 numeri casuali nell'intervallo da 0 a 1 e sottrai 6) restituisce stddev = 1 e media = 0. Questo può quindi essere utilizzato per generare qualsiasi distribuzione normale. Basta moltiplicare il risultato per lo stddev desiderato e aggiungere la media.
JerryM

3

Userei Box-Muller. Due cose su questo:

  1. Si finisce con due valori per iterazione
    In genere, si memorizza un valore nella cache e si restituisce l'altro. Alla successiva chiamata per un campione, restituisci il valore memorizzato nella cache.
  2. Box-Muller fornisce un punteggio Z
    È quindi necessario ridimensionare il punteggio Z in base alla deviazione standard e aggiungere la media per ottenere il valore completo nella distribuzione normale.

Come ridimensionate il punteggio Z?
Terhorst,

3
scaled = mean + stdDev * zScore // ti dà normale (mean, stdDev ^ 2)
yoyoyoyosef

2

Dove R1, R2 sono numeri uniformi casuali:

DISTRIBUZIONE NORMALE, con SD di 1: sqrt (-2 * log (R1)) * cos (2 * pi * R2)

Questo è esatto ... non c'è bisogno di fare tutti quei cicli lenti!


Prima che qualcuno mi correggesse ... ecco l'approssimazione che ho trovato: (1.5- (R1 + R2 + R3)) * 1.88. Piace anche a me.
Erik Aronesty

2

Sembra incredibile che io possa aggiungere qualcosa a questo dopo otto anni, ma per il caso di Java vorrei indirizzare i lettori al metodo Random.nextGaussian () , che genera una distribuzione gaussiana con media 0.0 e deviazione standard 1.0 per te.

Una semplice aggiunta e / o moltiplicazione cambierà la media e la deviazione standard in base alle tue esigenze.


1

Il modulo della libreria Python standard random ha quello che vuoi:

normalvariate (mu, sigma)
Distribuzione normale. mu è la media e sigma è la deviazione standard.

Per l'algoritmo stesso, dai un'occhiata alla funzione in random.py nella libreria Python.

L' inserimento manuale è qui


2
Sfortunatamente, la libreria di python usa Kinderman, AJ e Monahan, JF, "Generazione computerizzata di variabili casuali utilizzando il rapporto di deviazioni uniformi", ACM Trans Math Software, 3, (1977), pp257-260. Questo utilizza due variabili casuali uniformi per generare il valore normale, piuttosto che uno singolo, quindi non è ovvio come usarlo come mappatura voluta dall'OP.
Ian

1

Questa è la mia implementazione JavaScript dell'algoritmo P ( metodo Polar per deviazioni normali ) dalla sezione 3.4.1 del libro di Donald Knuth The Art of Computer Programming :

function normal_random(mean,stddev)
{
    var V1
    var V2
    var S
    do{
        var U1 = Math.random() // return uniform distributed in [0,1[
        var U2 = Math.random()
        V1 = 2*U1-1
        V2 = 2*U2-1
        S = V1*V1+V2*V2
    }while(S >= 1)
    if(S===0) return 0
    return mean+stddev*(V1*Math.sqrt(-2*Math.log(S)/S))
}

0

Penso che dovresti provare questo in EXCEL: =norminv(rand();0;1) . Questo produrrà i numeri casuali che dovrebbero essere normalmente distribuiti con la media zero e unirà la varianza. "0" può essere fornito con qualsiasi valore, in modo che i numeri avranno la media desiderata, e cambiando "1", otterrai la varianza uguale al quadrato del tuo input.

Ad esempio: =norminv(rand();50;3)restituirà i numeri normalmente distribuiti con MEAN = 50 VARIANCE = 9.


0

D Come posso convertire una distribuzione uniforme (come produce la maggior parte dei generatori di numeri casuali, ad esempio tra 0,0 e 1,0) in una distribuzione normale?

  1. Per l'implementazione del software conosco un paio di nomi di generatori casuali che danno una sequenza casuale pseudo uniforme in [0,1] (Mersenne Twister, Linear Congruate Generator). Chiamiamolo U (x)

  2. Esiste un'area matematica chiamata teoria della probabilità. Prima cosa: se vuoi modellare rv con distribuzione integrale F, puoi provare a valutare F ^ -1 (U (x)). Nella teoria pr. È stato dimostrato che tale rv avrà distribuzione integrale F.

  3. Il passaggio 2 può essere applicabile per generare rv ~ F senza l'utilizzo di alcun metodo di conteggio quando F ^ -1 può essere derivato analiticamente senza problemi. (ad es. distribuzione exp)

  4. Per modellare la distribuzione normale si può cacculare y1 * cos (y2), dove y1 ~ è uniforme in [0,2pi]. e y2 è la distribuzione relei.

D: Cosa succede se desidero una media e una deviazione standard di mia scelta?

Puoi calcolare sigma * N (0,1) + m.

Si può dimostrare che tale spostamento e ridimensionamento portano a N (m, sigma)


0

Questa è un'implementazione Matlab che utilizza la forma polare del Box-Muller trasformazione :

Funzione randn_box_muller.m:

function [values] = randn_box_muller(n, mean, std_dev)
    if nargin == 1
       mean = 0;
       std_dev = 1;
    end

    r = gaussRandomN(n);
    values = r.*std_dev - mean;
end

function [values] = gaussRandomN(n)
    [u, v, r] = gaussRandomNValid(n);

    c = sqrt(-2*log(r)./r);
    values = u.*c;
end

function [u, v, r] = gaussRandomNValid(n)
    r = zeros(n, 1);
    u = zeros(n, 1);
    v = zeros(n, 1);

    filter = r==0 | r>=1;

    % if outside interval [0,1] start over
    while n ~= 0
        u(filter) = 2*rand(n, 1)-1;
        v(filter) = 2*rand(n, 1)-1;
        r(filter) = u(filter).*u(filter) + v(filter).*v(filter);

        filter = r==0 | r>=1;
        n = size(r(filter),1);
    end
end

E invocare histfit(randn_box_muller(10000000),100);questo è il risultato: Box-Muller Matlab Histfit

Ovviamente è davvero inefficiente rispetto al randn integrato di Matlab .


0

Ho il seguente codice che forse potrebbe aiutare:

set.seed(123)
n <- 1000
u <- runif(n) #creates U
x <- -log(u)
y <- runif(n, max=u*sqrt((2*exp(1))/pi)) #create Y
z <- ifelse (y < dnorm(x)/2, -x, NA)
z <- ifelse ((y > dnorm(x)/2) & (y < dnorm(x)), x, z)
z <- z[!is.na(z)]

0

È anche più facile usare la funzione implementata rnorm () poiché è più veloce che scrivere un generatore di numeri casuali per la distribuzione normale. Vedere il codice seguente come prova

n <- length(z)
t0 <- Sys.time()
z <- rnorm(n)
t1 <- Sys.time()
t1-t0

-2
function distRandom(){
  do{
    x=random(DISTRIBUTION_DOMAIN);
  }while(random(DISTRIBUTION_RANGE)>=distributionFunction(x));
  return x;
}

Non è garantito il ritorno, però, vero? ;-)
Peter K.

5
I numeri casuali sono troppo importanti per essere lasciati al caso.
Drew Noakes

Non risponde alla domanda: la distribuzione normale ha un dominio infinito.
Matt
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.