随机高斯变量


118

.NET标准库中是否有一个类可以为我提供创建遵循高斯分布的随机变量的功能?


http://mathworld.wolfram.com/Box-MullerTransformation.html使用两个随机变量,您可以沿高斯分布生成随机值。这根本不是一件困难的任务。
Jarrett Meyer

1
我只想添加一个数学结果,该结果对于正态分布(由于复杂的CDF)不是立即有用的,但是对于许多其他分布却很有用。如果将均匀分布的随机数放在[0,1]中(带有Random.NextDouble())到ANY分布的CDF的倒数中,您将获得遵循THAT分布的随机数。如果您的应用程序不需要精确的正态分布变量,则Logistic分布与正态分布非常接近,并且具有易于反转的CDF。
奥扎(Ozzah)2012年

1
所述MedallionRandom NuGet包包含用于从一个检索正态分布的值的扩展方法Random使用框-穆勒变换(在下文若干答案中提到)。
ChaseMedallion's

Answers:


181

Jarrett建议使用Box-Muller变换非常适合快速而又肮脏的解决方案。一个简单的实现:

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
我对其进行了测试,并与MathNet的Mersenne Twister RNG和NormalDistribution进行了比较。您的版本的运行速度快两倍以上,并且最终结果基本相同(目视检查“钟声”)。
Johann Gerell,2009年

4
@Johann,如果您正在寻找纯速度,那么通常认为Zigorat算法是最快的方法。此外,通过将值从一个调用传递到下一个调用,可以使上述方法更快。
Drew Noakes

嗨,该stdDev变量应设置为什么?我知道可以将其配置为特定要求,但是是否有任何界限(即最大/最小值)?
hofnarwillie

@hofnarwillie stdDev是正态分布的scale参数,可以是任何正数。它越大,生成的数字将越分散。对于标准正态分布,请使用参数mean = 0和stdDev = 1。
yoyoyoyosef

1
@杰克我不这么认为。只有-2 * Math.Log(u1)在sqrt内,并且日志将始终为负或零,因为u1 <= 1
yoyoyoyosef

62

这个问题似乎已经超越了Google的.NET高斯一代,所以我想我会发布一个答案。

为.NET Random类提供了一些扩展方法,包括Box-Muller转换的实现。由于它们是扩展名,因此只要包含项目(或引用已编译的DLL),您仍然可以

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

希望没人介意这个无耻的插件。

结果样本直方图(包括用于绘制此结果的演示应用程序):

在此处输入图片说明


您的扩展程序类有一些我正在寻找的东西!谢谢!
托马斯

1
您的NextGaussian方法中有一个小错误。NextDouble()返回一个大于或等于0.0且小于1.0的随机浮点数。因此,您应该具有u1 = 1.0-NextDouble()....其他log(0)将爆炸
米奇·


8

我在Microsoft Connect上创建了对此功能的请求。如果这是您要找的东西,请投票并提高其知名度。

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

此功能包含在Java SDK中。它的实现作为文档的一部分提供,并且可以轻松移植到C#或其他.NET语言。

如果您正在寻找纯速度,那么Zigorat算法通常被认为是最快的方法。

我不是这个主题的专家,但是在为我的RoboCup 3D模拟机器人足球库实现粒子过滤器时遇到了对此的需求,当它没有包含在框架中时,我感到很惊讶。


同时,这里的包装器Random提供了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;
    }
}

我从中得到了一些-ve的价值。有人可以检查出什么问题吗?
2016年

@ mk7,一个以零为中心的高斯概率函数,给出负值的可能性与给出正值的可能性一样。
Drew Noakes

你是对的!由于我想得到一份使用高斯PDF的典型人群的体重清单,因此我将mu设置为75 [in kg],sigma设置为10。是否需要设置一个新的GaussianRandom实例来生成每个随机的重量?
2016年

您可以保留一个实例的绘图样本。
Drew Noakes


4

这是用于生成正态分布的随机变量的另一个快速而肮脏的解决方案。它绘制一个随机点(x,y)并检查该点是否在概率密度函数的曲线下,否则重复。

奖励:您可以通过替换密度函数,为任何其他分布(例如指数分布泊松分布)生成随机变量。

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

重要提示:选择y的间隔以及参数σμ,以使函数的曲线在最大/最小点处(例如x =平均值)不被截断。将xy的间隔视为一个边界框,曲线必须适合该边界框。


4
Tangenial,但这实际上是我第一次意识到可以将Unicode符号用于变量,而不是像_sigma或_phi这样的笨拙的词...
Slothario

@Slothario我感谢世界各地的开发人员使用“愚蠢的东西”:|
user2864740

2

我想通过加快@yoyoyoyosef的答案并编写包装器类来扩展它的答案。产生的开销可能并不意味着要快两倍,但我认为应该几乎快一倍。但是,它不是线程安全的。

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

扩展了@Noakes和@Hameer的答案,我还实现了一个'Gaussian'类,但是为了简化内存空间,我将其设为Random类的子类,以便您也可以调用基本的Next(),NextDouble() ,也无需创建额外的Random对象来处理它。我还消除了_available和_nextgauss全局类属性,因为由于此类是基于实例的,所以我不认为它们是必需的,如果给每个线程分配自己的高斯对象,则它应该是线程安全的。我还将所有运行时分配的变量移出了函数,并使其成为类属性,这将减少对内存管理器的调用次数,因为从理论上讲,在对象被销毁之前,永远不应该取消分配4个双打。

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

局部变量也是朋友。
user2864740

1

扩展Drew Noakes的答案,如果您需要比Box-Muller更好的性能(快50-75%),Colin Green已在C#中共享了Ziggurat算法的实现,您可以在这里找到:

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

Ziggurat使用查找表来处理与曲线相距足够远的值,它将迅速接受或拒绝该曲线。大约有2.5%的时间,它必须做进一步的计算才能确定数字在曲线的哪一边。


0

您可以尝试Infer.NET。它尚未获得商业许可。这里有链接

这是我研究Microsoft开发的.NET的概率框架。它们具有用于伯努利,Beta,伽玛,高斯,泊松分布的.NET类型,可能还有一些我遗漏的类型。

它可以完成您想要的。谢谢。


0

这是我受Box Muller启发的简单实现。您可以提高分辨率以满足您的需求。尽管这对我来说效果很好,但是这是一个有限范围的近似值,因此请记住尾部是闭合且有限的,但是可以确定的是您可以根据需要扩展它们。

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

这是我受Box Muller启发的简单实现。您可以提高分辨率以满足您的需求。这非常快速,简单,并且适用于需要近似高斯类型的概率密度函数的神经网络应用才能完成工作。希望它可以帮助某人节省时间和CPU周期。尽管这对我来说效果很好,但是这是一个有限范围的近似值,因此请记住尾部是闭合且有限的,但是可以确定的是您可以根据需要扩展它们。
丹尼尔·霍华德

1
嘿丹尼尔,我建议您进行修改,将您评论中的描述纳入答案本身。它还会删除在答案中注释掉真实代码的“ //”。如果需要/可以拒绝,可以自己编辑:)
mbrig '16

-1

我认为没有。我真的希望没有,因为框架已经足够膨胀,而没有更多的专用功能。

请查看http://www.extremeoptimization.com/Statistics/UsersGuide/ContinuousDistributions/NormalDistribution.aspxhttp://www.vbforums.com/showthread.php?t=488959,以获取第三方.NET解决方案。


7
从什么时候起高斯分布是“专业的”?它比AJAX或DataTables更为通用。
TraumaPony

@TraumaPony:您是否在认真建议更多的开发人员使用高斯分布而不是定期使用AJAX?
David Arno

3
可能 我的意思是说它更加专业。它只有一个用途-网络应用程序。高斯分布具有不可思议的无数用途。
TraumaPony

@DavidArno,您是认真地建议减少功能来改善框架吗?
2012年

1
@Jodrell,举一个具体的例子,我认为让MVC成为一个独立的框架而不是主要.NET框架的一部分的决定是一个很好的决定。
David Arno
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.