Variables gaussianas aleatorias

118

¿Existe una clase en la biblioteca estándar de .NET que me brinde la funcionalidad para crear variables aleatorias que sigan la distribución gaussiana?

Sebastián Müller
fuente
http://mathworld.wolfram.com/Box-MullerTransformation.html Utilizando dos variables aleatorias, puede generar valores aleatorios a lo largo de una distribución gaussiana. No es una tarea difícil en absoluto.
Jarrett Meyer
1
Solo me gustaría agregar un resultado matemático que no es inmediatamente útil para las distribuciones normales (debido a la compleja CDF), pero es útil para muchas otras distribuciones. Si coloca números aleatorios distribuidos uniformemente en [0,1] (con Random.NextDouble()) en el inverso del CDF de CUALQUIER distribución, obtendrá números aleatorios que siguen ESA distribución. Si su aplicación no necesita variables exactamente distribuidas normalmente, entonces la Distribución Logística es una aproximación muy cercana a la normal y tiene un CDF fácilmente invertible.
Ozzah
1
El paquete MedallionRandom NuGet contiene un método de extensión para recuperar valores distribuidos normalmente de un Randomuso de la transformación Box-Muller (mencionado en varias respuestas a continuación).
ChaseMedallion

Respuestas:

181

La sugerencia de Jarrett de usar una transformación Box-Muller es buena para una solución rápida y sucia. Una implementación simple:

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)
yoyoyoyosef
fuente
3
Lo probé y lo comparé con Mersenne Twister RNG y NormalDistribution de MathNet. Su versión es más del doble de rápida y el resultado final es básicamente el mismo (inspección visual de las "campanas").
Johann Gerell
4
@Johann, si está buscando velocidad pura, el algoritmo Zigorat generalmente se reconoce como el enfoque más rápido. Además, el enfoque anterior se puede hacer más rápido llevando un valor de una llamada a la siguiente.
Drew Noakes
Hola, ¿en qué debería establecerse la stdDevvariable? Entiendo que esto se puede configurar para requisitos específicos, pero ¿hay algún límite (es decir, valores máximo / mínimo)?
hofnarwillie
@hofnarwillie stdDev es el parámetro de escala de la distribución normal, que puede ser cualquier número positivo. Cuanto más grande sea, más dispersos serán los números generados. Para una distribución normal estándar, use los parámetros mean = 0 y stdDev = 1.
yoyoyoyosef
1
@Jack No lo creo. Solo el -2 * Math.Log (u1) está dentro de la raíz cuadrada, y el registro siempre será negativo o cero desde u1 <= 1
yoyoyoyosef
62

Esta pregunta parece haberse movido sobre Google para la generación gaussiana de .NET, así que pensé que publicaría una respuesta.

He creado algunos métodos de extensión para la clase .NET Random , incluida una implementación de la transformación Box-Muller. Dado que son extensiones, siempre que el proyecto esté incluido (o haga referencia a la DLL compilada), aún puede hacer

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

Espero que a nadie le importe el desvergonzado enchufe.

Ejemplo de histograma de resultados (se incluye una aplicación de demostración para dibujar esto):

ingrese la descripción de la imagen aquí

Supermejor
fuente
¡Tu clase de extensión tiene algunas cosas que estaba buscando! ¡Gracias!
Thomas
1
tiene un pequeño error en su método NextGaussian. NextDouble () Devuelve un número de punto flotante aleatorio que es mayor o igual que 0.0 y menor que 1.0. Entonces debería tener u1 = 1.0 - NextDouble () .... otro log (0) explotará
Mitch Wheat
21

Math.NET proporciona esta funcionalidad. Así es cómo:

double mean = 100;
double stdDev = 10;

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

Puede encontrar documentación aquí: http://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm

Gordon Slysz
fuente
¡Gran respuesta! Esta función está disponible en NuGet en el paquete MathNet.Numerics . Siempre es bueno no tener que enrollar el tuyo.
jpmc26
8

Creé una solicitud para dicha función en Microsoft Connect. Si esto es algo que está buscando, vote por él y aumente su visibilidad.

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

Esta función está incluida en el SDK de Java. Su implementación está disponible como parte de la documentación y se traslada fácilmente a C # u otros lenguajes .NET.

Si busca velocidad pura, el algoritmo Zigorat generalmente se reconoce como el enfoque más rápido.

Sin embargo, no soy un experto en este tema; me encontré con la necesidad de esto al implementar un filtro de partículas para mi biblioteca de fútbol robótico simulado RoboCup 3D y me sorprendió cuando esto no se incluyó en el marco.


Mientras tanto, aquí hay una envoltura Randomque proporciona una implementación eficiente del método polar de 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;
    }
}
Dibujó Noakes
fuente
Sin embargo, obtuve algunos valores -ve. ¿Alguien puede comprobar lo que está mal?
mk7
@ mk7, una función de probabilidad gaussiana centrada alrededor de cero es tan probable que dé valores negativos como positivos.
Drew Noakes
¡Tienes razón! Como me gustaría obtener una lista de peso en una población típica con PDF gaussiano, establezco mu en, digamos, 75 [en kg] y sigma en 10. ¿Necesito establecer una nueva instancia de GaussianRandom para generar cada peso aleatorio?
mk7
Puede seguir dibujando muestras de una instancia.
Drew Noakes
5

Math.NET Iridium también pretende implementar "generadores aleatorios no uniformes (normal, poisson, binomial, ...)".

Jason DeFontes
fuente
Pero no está funcionando correctamente. Intenté trazarlo, dando uniforme aleatorio no.
Nikhil Chilwant
4

Aquí hay otra solución rápida y sucia para generar variables aleatorias que tienen una distribución normal . Dibuja algún punto aleatorio (x, y) y verifica si este punto se encuentra debajo de la curva de su función de densidad de probabilidad; de lo contrario, repita.

Bonificación: puede generar variables aleatorias para cualquier otra distribución (por ejemplo, la distribución exponencial o la distribución de Poisson ) simplemente reemplazando la función de densidad.

    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: Seleccione el intervalo de y y los parámetros σ y μ para que la curva de la función no se corte en sus puntos máximo / mínimo (por ejemplo, en x = media). Piense en los intervalos de x y y como un cuadro delimitador, en el que la curva debe encajar.

Doomjunky
fuente
4
Tangenial, pero esta es la primera vez que me doy cuenta de que puedes usar símbolos Unicode para variables en lugar de algo tonto como _sigma o _phi ...
Slothario
@Slothario Agradezco a los desarrolladores de todo el mundo por usar 'algo tonto': |
user2864740
2

Me gustaría ampliar la respuesta de @ yoyoyoyosef haciéndola aún más rápida y escribiendo una clase contenedora. Los gastos generales incurridos pueden no significar el doble de rápido, pero creo que debería ser casi el doble de rápido. Sin embargo, no es seguro para subprocesos.

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();
    }
}
Hameer Abbasi
fuente
2

Expandiéndome de las respuestas de @Noakes y @ Hameer, también he implementado una clase 'Gaussiana', pero para simplificar el espacio de memoria, la convertí en un elemento secundario de la clase Random para que también pueda llamar al básico Next (), NextDouble () , etc. de la clase Gaussiana sin tener que crear un objeto aleatorio adicional para manejarlo. También eliminé las propiedades de clase global _available y _nextgauss, ya que no las vi como necesarias ya que esta clase está basada en instancias, debería ser segura para subprocesos, si le da a cada subproceso su propio objeto gaussiano. También moví todas las variables asignadas en tiempo de ejecución fuera de la función y las convertí en propiedades de clase, esto reducirá la cantidad de llamadas al administrador de memoria, ya que teóricamente los 4 dobles nunca deberían desasignarse hasta que se destruya el objeto.

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

fuente
Las variables locales también son amigas.
user2864740
1

Ampliando la respuesta de Drew Noakes, si necesita un mejor rendimiento que Box-Muller (alrededor de un 50-75% más rápido), Colin Green ha compartido una implementación del algoritmo Ziggurat en C #, que puede encontrar aquí:

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

Ziggurat utiliza una tabla de búsqueda para manejar valores que caen lo suficientemente lejos de la curva, que rápidamente aceptará o rechazará. Aproximadamente el 2,5% del tiempo, tiene que hacer más cálculos para determinar en qué lado de la curva se encuentra un número.

Neil
fuente
0

Puede probar Infer.NET. Sin embargo, aún no tiene licencia comercial. Aquí hay un enlace

Es un marco probabilístico para .NET desarrollado por mi investigación en Microsoft. Tienen tipos .NET para distribuciones de Bernoulli, Beta, Gamma, Gaussian, Poisson y probablemente algunos más los dejé fuera.

Puede lograr lo que desea. Gracias.

Aaron Stainback
fuente
0

Esta es mi sencilla implementación inspirada en Box Muller. Puede aumentar la resolución para adaptarla a sus necesidades. Aunque esto funciona muy bien para mí, esta es una aproximación de rango limitado, así que tenga en cuenta que las colas son cerradas y finitas, pero ciertamente puede expandirlas según sea necesario.

    //
    // 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;
   }
} 
Daniel Howard
fuente
Esta es mi sencilla implementación inspirada en Box Muller. Puede aumentar la resolución para adaptarla a sus necesidades. Esto es muy rápido, simple y funciona para mis aplicaciones de red neuronal que necesitan un tipo aproximado de función de densidad de probabilidad gaussiana para hacer el trabajo. Espero que ayude a alguien a ahorrar tiempo y ciclos de CPU. Aunque esto funciona muy bien para mí, esta es una aproximación de rango limitado, así que tenga en cuenta que las colas son cerradas y finitas, pero ciertamente puede expandirlas según sea necesario.
Daniel Howard
1
Hola Daniel, sugerí una edición que incorpora la descripción de tu comentario en la respuesta. También elimina el '//' que estaba comentando el código real en su respuesta. Puede hacer la edición usted mismo si lo desea / si se rechaza :)
mbrig
-1

No creo que lo haya. Y realmente espero que no lo haya, ya que el marco ya está lo suficientemente inflado, sin una funcionalidad tan especializada que lo llene aún más.

Sin embargo, eche un vistazo a http://www.extremeoptimization.com/Statistics/UsersGuide/ContinuousDistributions/NormalDistribution.aspx y http://www.vbforums.com/showthread.php?t=488959 para obtener soluciones .NET de terceros.

David Arno
fuente
7
¿Desde cuándo la distribución gaussiana es "especializada"? Es mucho más general que, digamos, AJAX o DataTables.
TraumaPony
@TraumaPony: ¿estás intentando sugerir seriamente que más desarrolladores usen la distribución gaussiana que AJAX de forma regular?
David Arno
3
Posiblemente; lo que digo es que es mucho más especializado. Solo tiene un uso: aplicaciones web. Las distribuciones gaussianas tienen una increíble cantidad de usos no relacionados.
TraumaPony
@DavidArno, ¿estás sugiriendo en serio que una menor funcionalidad mejora un marco?
Jodrell
1
@Jodrell, para citar un ejemplo específico, creo que la decisión de hacer de MVC un marco separado, en lugar de parte del marco principal .NET, fue una buena decisión.
David Arno