Variabili gaussiane casuali


118

Esiste una classe nella libreria standard di .NET che mi dà la funzionalità per creare variabili casuali che seguono la distribuzione gaussiana?


http://mathworld.wolfram.com/Box-MullerTransformation.html Utilizzando due variabili casuali, puoi generare valori casuali lungo una distribuzione gaussiana. Non è affatto un compito difficile.
Jarrett Meyer,

1
Vorrei solo aggiungere un risultato matematico che non è immediatamente utile per le distribuzioni normali (a causa di CDF complessi), ma è utile per molte altre distribuzioni. Se metti numeri casuali distribuiti uniformemente in [0,1] (con Random.NextDouble()) nell'inverso del CDF di QUALSIASI distribuzione, otterrai numeri casuali che seguono QUELLA distribuzione. Se la tua applicazione non necessita di variabili distribuite in modo preciso, la distribuzione logistica è un'approssimazione molto vicina al normale e ha un CDF facilmente invertibile.
Ozzah

1
Il pacchetto MedallionRandom NuGet contiene un metodo di estensione per il recupero di valori distribuiti normalmente da un Randomutilizzo della trasformazione Box-Muller (menzionato in diverse risposte di seguito).
ChaseMedallion

Risposte:


181

Il suggerimento di Jarrett di utilizzare una trasformazione Box-Muller è utile per una soluzione rapida e sporca. Una semplice implementazione:

Random rand = new Random(); //reuse this if you are generating many
double u1 = 1.0-rand.NextDouble(); //uniform(0,1] random doubles
double u2 = 1.0-rand.NextDouble();
double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) *
             Math.Sin(2.0 * Math.PI * u2); //random normal(0,1)
double randNormal =
             mean + stdDev * randStdNormal; //random normal(mean,stdDev^2)

3
L'ho testato e confrontato con Mersenne Twister RNG e NormalDistribution di MathNet. La tua versione è più del doppio più veloce e il risultato finale è sostanzialmente lo stesso (ispezione visiva delle "campane").
Johann Gerell,

4
@Johann, se stai cercando la velocità pura, l' algoritmo di Zigorat è generalmente riconosciuto come l'approccio più veloce. Inoltre l'approccio di cui sopra può essere reso più veloce trasferendo un valore da una chiamata all'altra.
Drew Noakes

Ciao, su cosa dovrebbe stdDevessere impostata la variabile? Capisco che questo può essere configurato in base a requisiti specifici, ma ci sono limiti (cioè valori max / min)?
hofnarwillie

@hofnarwillie stdDev è il parametro di scala della distribuzione normale, che può essere qualsiasi numero positivo. Più è grande, più dispersi saranno i numeri generati. Per una distribuzione normale standard, usare i parametri mean = 0 e stdDev = 1.
yoyoyoyosef

1
@ Jack non credo proprio. Solo il -2 * Math.Log (u1) è all'interno di sqrt e il log sarà sempre negativo o zero poiché u1 <= 1
yoyoyoyosef

63

Questa domanda sembra essersi spostata su Google per la generazione gaussiana .NET, quindi ho pensato di pubblicare una risposta.

Ho creato alcuni metodi di estensione per la classe .NET Random , inclusa un'implementazione della trasformazione Box-Muller. Dato che sono estensioni, fintanto che il progetto è incluso (o fai riferimento alla DLL compilata), puoi comunque farlo

var r = new Random();
var x = r.NextGaussian();

Spero che a nessuno importi la spina spudorata.

Istogramma di esempio dei risultati (è inclusa un'app demo per disegnare questo):

inserisci qui la descrizione dell'immagine


La tua classe di estensione ha alcune cose che stavo cercando! Grazie!
Thomas

1
hai un piccolo bug nel tuo metodo NextGaussian. NextDouble () Restituisce un numero a virgola mobile casuale maggiore o uguale a 0,0 e minore di 1,0. Quindi dovresti avere u1 = 1.0 - NextDouble () .... altri log (0) salteranno in aria
Mitch Wheat

21

Math.NET fornisce questa funzionalità. Ecco come:

double mean = 100;
double stdDev = 10;

MathNet.Numerics.Distributions.Normal normalDist = new Normal(mean, stdDev);
double randomGaussianValue=   normalDist.Sample();

È possibile trovare la documentazione qui: http://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm


Bella risposta! Questa funzione è disponibile in NuGet nel pacchetto MathNet.Numerics . È sempre bello non dover rotolare il tuo.
jpmc26

8

Ho creato una richiesta per tale funzionalità su Microsoft Connect. Se questo è qualcosa che stai cercando, votalo e aumentane la visibilità.

https://connect.microsoft.com/VisualStudio/feedback/details/634346/guassian-normal-distribution-random-numbers

Questa funzionalità è inclusa in Java SDK. La sua implementazione è disponibile come parte della documentazione ed è facilmente trasferibile in C # o in altri linguaggi .NET.

Se stai cercando la velocità pura, l' algoritmo di Zigorat è generalmente riconosciuto come l'approccio più veloce.

Tuttavia, non sono un esperto in questo argomento: ho riscontrato la necessità di questo durante l'implementazione di un filtro antiparticolato per la mia libreria di calcio robotico simulato RoboCup 3D e sono rimasto sorpreso quando questo non è stato incluso nel framework.


Nel frattempo, ecco un wrapper Randomche fornisce un'implementazione efficiente del metodo polare Box Muller:

public sealed class GaussianRandom
{
    private bool _hasDeviate;
    private double _storedDeviate;
    private readonly Random _random;

    public GaussianRandom(Random random = null)
    {
        _random = random ?? new Random();
    }

    /// <summary>
    /// Obtains normally (Gaussian) distributed random numbers, using the Box-Muller
    /// transformation.  This transformation takes two uniformly distributed deviates
    /// within the unit circle, and transforms them into two independently
    /// distributed normal deviates.
    /// </summary>
    /// <param name="mu">The mean of the distribution.  Default is zero.</param>
    /// <param name="sigma">The standard deviation of the distribution.  Default is one.</param>
    /// <returns></returns>
    public double NextGaussian(double mu = 0, double sigma = 1)
    {
        if (sigma <= 0)
            throw new ArgumentOutOfRangeException("sigma", "Must be greater than zero.");

        if (_hasDeviate)
        {
            _hasDeviate = false;
            return _storedDeviate*sigma + mu;
        }

        double v1, v2, rSquared;
        do
        {
            // two random values between -1.0 and 1.0
            v1 = 2*_random.NextDouble() - 1;
            v2 = 2*_random.NextDouble() - 1;
            rSquared = v1*v1 + v2*v2;
            // ensure within the unit circle
        } while (rSquared >= 1 || rSquared == 0);

        // calculate polar tranformation for each deviate
        var polar = Math.Sqrt(-2*Math.Log(rSquared)/rSquared);
        // store first deviate
        _storedDeviate = v2*polar;
        _hasDeviate = true;
        // return second deviate
        return v1*polar*sigma + mu;
    }
}

Tuttavia, ne ho ricavato alcuni valori. qualcuno può controllare cosa c'è che non va?
mk7

@ mk7, una funzione di probabilità gaussiana centrata attorno allo zero ha la stessa probabilità di fornire valori negativi quanto valori positivi.
Drew Noakes

Hai ragione! Dato che vorrei ottenere un elenco di pesi in una popolazione tipica con PDF gaussiano, imposto mu a, diciamo, 75 [in kg] e sigma a 10. Devo impostare una nuova istanza di GaussianRandom per generare ogni peso casuale?
mk7

Puoi continuare a disegnare campioni da un'istanza.
Drew Noakes


4

Ecco un'altra soluzione rapida e sporca per generare variabili casuali distribuite normalmente . Disegna un punto casuale (x, y) e controlla se questo punto si trova sotto la curva della tua funzione di densità di probabilità, altrimenti ripeti.

Bonus: puoi generare variabili casuali per qualsiasi altra distribuzione (ad esempio la distribuzione esponenziale o la distribuzione di poisson ) semplicemente sostituendo la funzione di densità.

    static Random _rand = new Random();

    public static double Draw()
    {
        while (true)
        {
            // Get random values from interval [0,1]
            var x = _rand.NextDouble(); 
            var y = _rand.NextDouble(); 

            // Is the point (x,y) under the curve of the density function?
            if (y < f(x)) 
                return x;
        }
    }

    // Normal (or gauss) distribution function
    public static double f(double x, double μ = 0.5, double σ = 0.5)
    {
        return 1d / Math.Sqrt(2 * σ * σ * Math.PI) * Math.Exp(-((x - μ) * (x - μ)) / (2 * σ * σ));
    }

Importante: selezionare l'intervallo di y ei parametri σ e μ in modo che la curva della funzione non sia tagliata nei suoi punti massimo / minimo (ad esempio in x = media). Pensare degli intervalli di x ed y da un riquadro, in cui la curva deve adattarsi.


4
Tangenial, ma questa è in realtà la prima volta che mi sono reso conto che puoi usare simboli Unicode per le variabili invece di qualcosa di stupido come _sigma o _phi ...
Slothario

@Slothario Ringrazio gli sviluppatori di tutto il mondo per aver usato "qualcosa di stupido": |
user2864740

2

Vorrei espandere la risposta di @ yoyoyoyosef rendendola ancora più veloce e scrivendo una classe wrapper. L'overhead sostenuto potrebbe non significare due volte più veloce, ma penso che dovrebbe essere quasi il doppio più veloce. Tuttavia, non è thread-safe.

public class Gaussian
{
     private bool _available;
     private double _nextGauss;
     private Random _rng;

     public Gaussian()
     {
         _rng = new Random();
     }

     public double RandomGauss()
     {
        if (_available)
        {
            _available = false;
            return _nextGauss;
        }

        double u1 = _rng.NextDouble();
        double u2 = _rng.NextDouble();
        double temp1 = Math.Sqrt(-2.0*Math.Log(u1));
        double temp2 = 2.0*Math.PI*u2;

        _nextGauss = temp1 * Math.Sin(temp2);
        _available = true;
        return temp1*Math.Cos(temp2);
     }

    public double RandomGauss(double mu, double sigma)
    {
        return mu + sigma*RandomGauss();
    }

    public double RandomGauss(double sigma)
    {
        return sigma*RandomGauss();
    }
}

2

Espandendo le risposte di @Noakes e @ Hameer, ho anche implementato una classe 'gaussiana', ma per semplificare lo spazio di memoria, l'ho resa una figlia della classe Random in modo che tu possa anche chiamare il Next (), NextDouble () di base , ecc. anche dalla classe Gaussian senza dover creare un oggetto Random aggiuntivo per gestirlo. Ho anche eliminato le proprietà delle classi globali _available e _nextgauss, poiché non le vedevo necessarie poiché questa classe è basata sull'istanza, dovrebbe essere thread-safe, se si assegna a ciascun thread il proprio oggetto gaussiano. Ho anche spostato tutte le variabili allocate in fase di esecuzione fuori dalla funzione e le ho rese proprietà di classe, questo ridurrà il numero di chiamate al gestore della memoria poiché i 4 doppi non dovrebbero teoricamente essere de-allocati fino a quando l'oggetto non viene distrutto.

public class Gaussian : Random
{

    private double u1;
    private double u2;
    private double temp1;
    private double temp2;

    public Gaussian(int seed):base(seed)
    {
    }

    public Gaussian() : base()
    {
    }

    /// <summary>
    /// Obtains normally (Gaussian) distrubuted random numbers, using the Box-Muller
    /// transformation.  This transformation takes two uniformly distributed deviates
    /// within the unit circle, and transforms them into two independently distributed normal deviates.
    /// </summary>
    /// <param name="mu">The mean of the distribution.  Default is zero</param>
    /// <param name="sigma">The standard deviation of the distribution.  Default is one.</param>
    /// <returns></returns>

    public double RandomGauss(double mu = 0, double sigma = 1)
    {
        if (sigma <= 0)
            throw new ArgumentOutOfRangeException("sigma", "Must be greater than zero.");

        u1 = base.NextDouble();
        u2 = base.NextDouble();
        temp1 = Math.Sqrt(-2 * Math.Log(u1));
        temp2 = 2 * Math.PI * u2;

        return mu + sigma*(temp1 * Math.Cos(temp2));
    }
}

Anche le variabili locali sono amiche.
user2864740

1

Espandendo la risposta di Drew Noakes, se hai bisogno di prestazioni migliori di Box-Muller (circa il 50-75% più veloce), Colin Green ha condiviso un'implementazione dell'algoritmo Ziggurat in C #, che puoi trovare qui:

http://heliosphan.org/zigguratalgorithm/zigguratalgorithm.html

Ziggurat utilizza una tabella di ricerca per gestire i valori che cadono sufficientemente lontano dalla curva, che accetterà o rifiuterà rapidamente. Circa il 2,5% delle volte, deve eseguire ulteriori calcoli per determinare su quale lato della curva si trova un numero.


0

Potresti provare Infer.NET. Tuttavia, non è ancora concesso in licenza commerciale. Ecco il link

È un framework probabilistico per .NET sviluppato dalla mia ricerca Microsoft. Hanno tipi .NET per le distribuzioni di Bernoulli, Beta, Gamma, Gaussian, Poisson e probabilmente altri che ho tralasciato.

Può realizzare quello che vuoi. Grazie.


0

Questa è la mia semplice implementazione ispirata a Box Muller. È possibile aumentare la risoluzione in base alle proprie esigenze. Anche se per me funziona benissimo, si tratta di un'approssimazione a raggio limitato, quindi tieni presente che le code sono chiuse e finite, ma certamente puoi espanderle secondo necessità.

    //
    // by Dan
    // islandTraderFX
    // copyright 2015
    // Siesta Key, FL
    //    
// 0.0  3231 ********************************
// 0.1  1981 *******************
// 0.2  1411 **************
// 0.3  1048 **********
// 0.4  810 ********
// 0.5  573 *****
// 0.6  464 ****
// 0.7  262 **
// 0.8  161 *
// 0.9  59 
//Total: 10000

double g()
{
   double res = 1000000;
   return random.Next(0, (int)(res * random.NextDouble()) + 1) / res;
}

public static class RandomProvider
{
   public static int seed = Environment.TickCount;

   private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
       new Random(Interlocked.Increment(ref seed))
   );

   public static Random GetThreadRandom()
   {
       return randomWrapper.Value;
   }
} 

Questa è la mia semplice implementazione ispirata a Box Muller. È possibile aumentare la risoluzione in base alle proprie esigenze. Questo è molto veloce, semplice e funziona per le mie app di rete neurale che richiedono un tipo approssimativo di funzione di densità di probabilità gaussiana per portare a termine il lavoro. Spero che aiuti qualcuno a risparmiare tempo e cicli della CPU. Anche se per me funziona benissimo, si tratta di un'approssimazione a raggio limitato, quindi tieni presente che le code sono chiuse e finite, ma certamente puoi espanderle secondo necessità.
Daniel Howard,

1
Ehi Daniel, ho suggerito una modifica che incorpori la descrizione del tuo commento nella risposta stessa. Rimuove anche il "//" che commentava il codice reale nella tua risposta. Puoi fare la modifica da solo se vuoi / se viene rifiutata :)
mbrig

-1

Non credo che ci sia. E spero davvero che non ci sia, dato che il framework è già abbastanza gonfio, senza che funzionalità così specializzate lo riempiano ancora di più.

Dai un'occhiata a http://www.extremeoptimization.com/Statistics/UsersGuide/ContinuousDistributions/NormalDistribution.aspx e http://www.vbforums.com/showthread.php?t=488959 per soluzioni .NET di terze parti.


7
Da quando la distribuzione gaussiana è "specializzata"? È molto più generale di, diciamo, AJAX o DataTables.
TraumaPony

@TraumaPony: stai seriamente cercando di suggerire a più sviluppatori di utilizzare la distribuzione gaussiana piuttosto che utilizzare AJAX su base regolare?
David Arno

3
Possibilmente; quello che sto dicendo è che è molto più specializzato. Ha solo un utilizzo: app web. Le distribuzioni gaussiane hanno un numero incredibile di usi non correlati.
TraumaPony

@DavidArno, stai seriamente suggerendo che meno funzionalità migliorino un framework.
Jodrell

1
@ Jodrell, per citare un esempio specifico, penso che la decisione di rendere MVC un framework separato, piuttosto che parte del framework .NET principale, sia stata buona.
David Arno
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.