Variables gaussiennes aléatoires


118

Existe-t-il une classe dans la bibliothèque standard de .NET qui me donne la fonctionnalité pour créer des variables aléatoires qui suivent la distribution gaussienne?


http://mathworld.wolfram.com/Box-MullerTransformation.html En utilisant deux variables aléatoires, vous pouvez générer des valeurs aléatoires le long d'une distribution gaussienne. Ce n'est pas du tout une tâche difficile.
Jarrett Meyer

1
Je voudrais juste ajouter un résultat mathématique qui n'est pas immédiatement utile pour les distributions normales (en raison de CDF complexes), mais qui est utile pour de nombreuses autres distributions. Si vous mettez des nombres aléatoires uniformément distribués dans [0,1] (avec Random.NextDouble()) dans l'inverse de la CDF de N'IMPORTE QUELLE distribution, vous obtiendrez des nombres aléatoires qui suivent CETTE distribution. Si votre application n'a pas besoin de variables distribuées avec précision, alors la distribution logistique est une approximation très proche de la normale et a un CDF facilement inversible.
Ozzah

1
Le package MedallionRandom NuGet contient une méthode d'extension pour récupérer des valeurs normalement distribuées à partir d'un Randomutilisant la transformation Box-Muller (mentionnée dans plusieurs réponses ci-dessous).
ChaseMedallion

Réponses:


182

La suggestion de Jarrett d'utiliser une transformation Box-Muller est bonne pour une solution rapide et sale. Une implémentation 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)

3
Je l'ai testé et comparé à Mersenne Twister RNG et NormalDistribution de MathNet. Votre version est plus de deux fois plus rapide et le résultat final est fondamentalement le même (inspection visuelle des "cloches").
Johann Gerell

4
@Johann, si vous recherchez une vitesse pure, alors l' algorithme Zigorat est généralement reconnu comme l'approche la plus rapide. En outre, l'approche ci-dessus peut être accélérée en portant une valeur d'un appel à l'autre.
Drew Noakes le

Salut, à quoi la stdDevvariable doit-elle être définie? Je comprends que cela peut être configuré selon des exigences spécifiques, mais y a-t-il des limites (c'est-à-dire des valeurs max / min)?
hofnarwillie

@hofnarwillie stdDev est le paramètre d'échelle de la distribution normale, qui peut être n'importe quel nombre positif. Plus il est grand, plus les nombres générés seront dispersés. Pour une distribution normale standard, utilisez les paramètres mean = 0 et stdDev = 1.
yoyoyoyosef

1
@Jack je ne pense pas. Seul le -2 * Math.Log (u1) est à l'intérieur du sqrt, et le journal sera toujours négatif ou nul depuis u1 <= 1
yoyoyoyosef

63

Cette question semble avoir été déplacée au-dessus de Google pour la génération gaussienne .NET, alors j'ai pensé que je publierais une réponse.

J'ai créé des méthodes d'extension pour la classe .NET Random , y compris une implémentation de la transformation Box-Muller. Puisqu'il s'agit d'extensions, tant que le projet est inclus (ou que vous référencez la DLL compilée), vous pouvez toujours faire

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

J'espère que personne ne se soucie de la prise sans vergogne.

Exemple d'histogramme des résultats (une application de démonstration pour dessiner ceci est incluse):

entrez la description de l'image ici


Votre classe d'extension a quelques choses que je recherchais! Merci!
Thomas

1
vous avez un petit bogue dans votre méthode NextGaussian. NextDouble () Renvoie un nombre à virgule flottante aléatoire supérieur ou égal à 0,0 et inférieur à 1,0. Donc, vous devriez avoir u1 = 1.0 - NextDouble () .... autre log (0) va exploser
Mitch Wheat

21

Math.NET fournit cette fonctionnalité. Voici comment:

double mean = 100;
double stdDev = 10;

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

Vous pouvez trouver la documentation ici: http://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm


Très bonne réponse! Cette fonction est disponible sur NuGet dans le package MathNet.Numerics . Toujours génial de ne pas avoir à rouler le vôtre.
jpmc26

8

J'ai créé une demande pour une telle fonctionnalité sur Microsoft Connect. Si c'est quelque chose que vous recherchez, votez pour cela et augmentez sa visibilité.

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

Cette fonctionnalité est incluse dans le SDK Java. Son implémentation est disponible dans le cadre de la documentation et est facilement portée en C # ou dans d'autres langages .NET.

Si vous recherchez de la vitesse pure, l' algorithme Zigorat est généralement reconnu comme l'approche la plus rapide.

Je ne suis pas un expert sur ce sujet cependant - je suis tombé sur le besoin de cela lors de l'implémentation d'un filtre à particules pour ma bibliothèque de football robotique simulée RoboCup 3D et j'ai été surpris de ne pas l'inclure dans le cadre.


En attendant, voici un wrapper pour Randomqui fournit une implémentation efficace de la méthode polaire 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;
    }
}

J'en ai cependant obtenu quelques valeurs. quelqu'un peut-il vérifier ce qui ne va pas?
mk7

@ mk7, une fonction de probabilité gaussienne centrée autour de zéro est tout aussi susceptible de donner des valeurs négatives que de donner des valeurs positives.
Drew Noakes

Vous avez raison! Puisque je voudrais obtenir une liste de poids dans une population typique avec un PDF gaussien, je règle mu à, disons, 75 [en kg] et sigma à 10. Dois-je définir une nouvelle instance de GaussianRandom pour générer chaque poids aléatoire?
mk7

Vous pouvez continuer à dessiner des échantillons à partir d'une seule instance.
Drew Noakes

5

Math.NET Iridium prétend également implémenter "des générateurs aléatoires non uniformes (normal, poisson, binomial, ...)".


Mais ça ne fonctionne pas correctement. J'ai essayé de le tracer, donnant un uniforme aléatoire non.
Nikhil Chilwant

4

Voici une autre solution rapide et sale pour générer des variables aléatoires qui sont distribuées normalement . Il dessine un point aléatoire (x, y) et vérifie si ce point se trouve sous la courbe de votre fonction de densité de probabilité, sinon répétez.

Bonus: Vous pouvez générer des variables aléatoires pour toute autre distribution (par exemple la distribution exponentielle ou la distribution de poisson ) simplement en remplaçant la fonction de 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 * σ * σ));
    }

Important: Sélectionnez l'intervalle de y et les paramètres σ et μ pour que la courbe de la fonction ne soit pas coupée à ses points maximum / minimum (par exemple à x = moyenne). Considérez les intervalles de x et y comme une boîte englobante dans laquelle la courbe doit s'inscrire.


4
Tangenial, mais c'est en fait la première fois que je réalise que vous pouvez utiliser des symboles Unicode pour les variables au lieu de quelque chose de stupide comme _sigma ou _phi ...
Slothario

@Slothario Je remercie les développeurs du monde entier d'avoir utilisé «quelque chose de stupide»: |
user2864740

2

J'aimerais développer la réponse de @ yoyoyoyosef en la rendant encore plus rapide et en écrivant une classe wrapper. Les frais généraux encourus ne signifient peut-être pas deux fois plus vite, mais je pense que cela devrait être presque deux fois plus rapide. Ce n'est cependant pas sûr pour les threads.

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

En développant les réponses de @Noakes et @ Hameer, j'ai également implémenté une classe 'Gaussian', mais pour simplifier l'espace mémoire, j'en ai fait un enfant de la classe Random afin que vous puissiez également appeler la base Next (), NextDouble () , etc. de la classe Gaussian sans avoir à créer un objet Random supplémentaire pour le gérer. J'ai également éliminé les propriétés de classe globale _available et _nextgauss, car je ne les considérais pas comme nécessaires puisque cette classe est basée sur une instance, elle devrait être thread-safe, si vous donnez à chaque thread son propre objet gaussien. J'ai également déplacé toutes les variables allouées à l'exécution hors de la fonction et en ai fait des propriétés de classe, cela réduira le nombre d'appels au gestionnaire de mémoire car les 4 doubles ne devraient théoriquement jamais être désalloués jusqu'à ce que l'objet soit détruit.

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

Les variables locales sont aussi des amis.
user2864740

1

S'étendant sur la réponse de Drew Noakes, si vous avez besoin de meilleures performances que Box-Muller (environ 50 à 75% plus rapide), Colin Green a partagé une implémentation de l'algorithme Ziggurat en C #, que vous pouvez trouver ici:

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

Ziggurat utilise une table de consultation pour gérer les valeurs qui tombent suffisamment loin de la courbe, qu'il acceptera ou rejettera rapidement. Environ 2,5% du temps, il doit effectuer des calculs supplémentaires pour déterminer de quel côté de la courbe se trouve un nombre.


0

Vous pouvez essayer Infer.NET. Ce n'est pas encore sous licence commerciale. Voici le lien

C'est un cadre probabiliste pour .NET développé mes recherches Microsoft. Ils ont des types .NET pour les distributions de Bernoulli, Beta, Gamma, Gaussian, Poisson, et probablement d'autres que j'ai laissées de côté.

Cela peut accomplir ce que vous voulez. Merci.


0

C'est ma simple implémentation inspirée de Box Muller. Vous pouvez augmenter la résolution en fonction de vos besoins. Bien que cela fonctionne très bien pour moi, il s'agit d'une approximation de plage limitée, alors gardez à l'esprit que les queues sont fermées et finies, mais vous pouvez certainement les étendre si nécessaire.

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

C'est ma simple implémentation inspirée de Box Muller. Vous pouvez augmenter la résolution en fonction de vos besoins. C'est très rapide, simple et fonctionne pour mes applications de réseau neuronal qui ont besoin d'un type approximatif de fonction de densité de probabilité gaussienne pour faire le travail. J'espère que cela aidera quelqu'un à gagner du temps et des cycles CPU. Bien que cela fonctionne très bien pour moi, il s'agit d'une approximation de plage limitée, alors gardez à l'esprit que les queues sont fermées et finies, mais vous pouvez certainement les étendre si nécessaire.
Daniel Howard

1
Salut Daniel, j'ai suggéré une modification qui intègre la description de votre commentaire dans la réponse elle-même. Il supprime également le «//» qui commentait le vrai code dans votre réponse. Vous pouvez faire la modification vous-même si vous le souhaitez / si elle est rejetée :)
mbrig

-1

Je ne pense pas qu'il y en ait. Et j'espère vraiment qu'il n'y en a pas, car le cadre est déjà suffisamment gonflé, sans une telle fonctionnalité spécialisée le remplissant encore plus.

Jetez un œil à http://www.extremeoptimization.com/Statistics/UsersGuide/ContinuousDistributions/NormalDistribution.aspx et http://www.vbforums.com/showthread.php?t=488959 pour une solution tierce .NET.


7
Depuis quand la distribution gaussienne est-elle «spécialisée»? C'est beaucoup plus général que, disons, AJAX ou DataTables.
TraumaPony

@TraumaPony: essayez-vous sérieusement de suggérer à plus de développeurs d'utiliser la distribution gaussienne que d'utiliser AJAX sur une base régulière?
David Arno

3
Peut-être; ce que je dis, c'est que c'est beaucoup plus spécialisé. Il n'a qu'une seule utilisation: les applications Web. Les distributions gaussiennes ont un nombre incroyable d'utilisations indépendantes.
TraumaPony

@DavidArno, suggérez-vous sérieusement que moins de fonctionnalités améliorent un cadre.
Jodrell

1
@Jodrell, pour citer un exemple spécifique, je pense que la décision de faire de MVC un framework distinct, plutôt qu'une partie du framework .NET principal, était une bonne décision.
David Arno
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.