将均匀分布转换为正态分布


106

如何将均匀分布(如大多数随机数生成器产生的,例如0.0到1.0之间)转换为正态分布?如果我要选择平均值和标准偏差怎么办?


3
您是否有语言规范,或者这仅仅是一个通用算法问题?
比尔蜥蜴

3
通用算法问题。我不在乎哪种语言。但我希望答案不依赖仅该语言提供的特定功能。
Terhorst,

Answers:



47

有很多方法:

  • 千万不能用箱穆勒。特别是当您绘制许多高斯数时。Box Muller得出的结果被限制在-6和6之间(假设双精度,浮点会使情况恶化)。而且它的效率确实比其他可用方法低。
  • Ziggurat很好,但是需要进行表查找(由于缓存大小问题,需要进行一些特定于平台的调整)
  • 均匀率是我的最爱,只有几次加法/乘法和对数的1/50(例如,看那里)。
  • 反转CDF 高效的(并且被忽略了,为什么?),如果您搜索google,就可以快速实现它。准随机数是必需的。

2
您确定[-6,6]夹紧吗?如果为真,这是非常重要的一点(值得在维基百科页面上注意)。
redcalx

1
@locster:这是我的一位老师告诉我的(他研究了这种发电机,我相信他的话)。我也许可以找到您的参考。
Alexandre C.

7
@locster:逆CDF方法也共享此不良属性。参见cimat.mx/~src/prope08/randomgauss.pdf。这可以通过使用均匀的RNG来缓解,该RNG具有非零的概率来产生非常接近零的浮点数。大多数RNG不会,因为它们会生成一个(通常为64位)整数,然后将其映射到[0,1]。这使得这些方法不适合对高斯变量的尾部进行采样(考虑对计算金融中的低/高行使价进行定价)。
Alexandre C.

6
@AlexandreC。只需在两点上清楚一点,使用64位数字,尾巴将变为8.57或9.41(取对数前的下限值,对应于转换为[0,1))。即使将其限制在[-6,6]之外,被排除在此范围之外的机会也约为1.98e-9,对于大多数人甚至在科学领域来说也足够了。对于8.57和9.41数字,这将变为1.04e-17和4.97e-21。这些数字是如此之小,以至于就所述极限而言,Box Muller采样与真实高斯采样之间的差异几乎纯粹是学术上的。如果你需要更好的,只是加起来他们鸿沟四乘2
CrazyCasta

6
我认为不使用Box Muller变换的建议对大多数用户都具有误导性。知道这个限制真是太好了,但是正如CrazyCasta指出的那样,对于大多数不高度依赖离群值的应用程序,您可能不必担心这一点。例如,如果您曾经依靠使用numpy的法线采样,则依赖于Box Muller变换(极坐标形式)github.com/numpy/numpy/blob/…
Andreas Grivas

30

将任何函数的分布更改为另一个函数都涉及使用所需函数的逆函数。

换句话说,如果您针对特定的概率函数p(x),则可以通过对其积分-> d(x)=积分(p(x))并使用其反函数来获得分布:Inv(d(x)) 。现在使用随机概率函数(具有均匀分布)并通过函数Inv(d(x))转换结果值。您应该根据选择的功能获得带有分布的随机值。

这是通用的数学方法-通过使用它,您现在可以选择具有逆或良好逆近似的任何概率或分布函数。

希望这会有所帮助,并感谢您对使用分布而不是概率本身的一些评论。


4
+1这是生成高斯变量的一种被忽略的方法,效果很好。在这种情况下,可以使用牛顿法高效地计算逆CDF(导数为e ^ {-t ^ 2}),很容易获得有理分数的初始近似值,因此您需要3-4次erf和exp的求值。如果使用准随机数,则必须执行此操作,在这种情况下,必须使用一个统一的正整数来获得高斯数。
Alexandre C.

9
请注意,您需要反转累积分布函数,而不是概率分布函数。Alexandre暗示了这一点,但我认为更明确地提及它可能不会有伤害-因为答案似乎暗示了PDF
ltjax 2012年

如果您准备随机选择相对于均值的方向,则可以使用PDF。我明白吗?
Mark McKenna 2014年

2
这就是所谓的逆变换采样
破译,2015年

1
是SE中的一个相关问题,带有更广泛的答案和很好的解释。
2015年

23

这是使用Box-Muller变换的极坐标形式的javascript实现。

/*
 * 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

使用中心极限定理维基百科条目 mathworld条目可发挥您的优势。

生成n个均匀分布的数字,将它们相加,减去n * 0.5,您将得到一个近似正态分布的输出,均值等于0,方差等于(1/12) * (1/sqrt(N))有关最后一个的均匀分布,请参阅Wikipedia

n = 10可以使您快得快一半。如果您想要的东西超过一半,请选择轮胎解决方案(如正态分布 Wikipedia条目中


1
这不会给出特别接近的正态(“尾巴”或端点不会接近真实的正态分布)。如其他人所言,Box-Muller更好。
Peter K.

1
Box Muller的尾巴也错了(它以双精度返回-6到6之间的数字)
AlexandreC。2010年

n = 12(将12个随机数相加在0到1之间,然后减去6)将导致stddev = 1和mean = 0。然后可以将其用于生成任何正态分布。只需将结果乘以所需的标准差并加上平均值即可。
JerryM

3

我会使用Box-Muller。关于以下两点:

  1. 您每次迭代都会得到两个值
    通常,您缓存一个值,然后返回另一个值。在下一次调用样本时,您返回缓存的值。
  2. Box-Muller提供Z分数
    然后,您必须按标准偏差缩放Z分数,并添加均值以获得正态分布的完整值。

您如何缩放Z分数?
Terhorst,

3
标度=平均值+ stdDev * zScore //给出正常的(mean,stdDev ^ 2)
yoyoyoyosef

2

其中R1,R2是随机统一数:

正态分布,SD为1:sqrt(-2 * log(R1))* cos(2 * pi * R2)

这是正确的……不需要执行所有这些慢循环!


在有人纠正我之前...这是我得出的近似值:(1.5-(R1 + R2 + R3))* 1.88。我也喜欢这个。
Erik Aronesty

2

八年后我可以添加一些东西似乎令人难以置信,但是对于Java,我想向读者介绍Random.nextGaussian()方法,该方法为您生成均值0.0和标准偏差1.0的高斯分布。

简单的加法和/或乘法将改变均值和标准差,以满足您的需求。


1

标准的Python库模块random具有您想要的:


正态分布(mu,sigma)正态分布。mu是平均值,而sigma是标准偏差。

对于算法本身,请查看Python库中random.py中的函数。

人工输入是在这里


2
不幸的是,python的库使用Kinderman,AJ和Monahan,JF,“使用均匀偏差的比率生成随机变量的计算机”,ACM Trans Math Software,第3卷,(1977),pp257-260。它使用两个统一的随机变量来生成正常值,而不是一个值,因此,如何将其用作OP所需的映射并不明显。
伊恩

1

这是我从Donald Knuth的书《计算机编程的艺术》的第3.4.1节中对算法P用于法向偏离的极坐标法)的JavaScript实现:

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

我想你应该在EXCEL中尝试一下: =norminv(rand();0;1)。这将产生应该以零均值和单位方差正态分布的随机数。可以提供任何值“ 0”,以便数字具有期望的均值,并且通过更改“ 1”,您将获得与输入平方相等的方差。

例如:=norminv(rand();50;3)将得出MEAN = 50 VARIANCE = 9的正态分布数字。


0

问:如何将均匀分布(如大多数随机数生成器产生的,例如0.0到1.0之间)转换为正态分布?

  1. 对于软件实现,我知道几个随机生成器名称,它们在[0,1]中为您提供了伪统一的随机序列(Mersenne Twister,线性一致生成器)。我们称它为U(x)

  2. 存在于数学领域中称为概率论。第一件事:如果要对具有整数分布F的rv建模,则可以尝试仅评估F ^ -1(U(x))。在理论上证明了该rv具有积分分布F。

  3. 如果可以无问题地解析得出F ^ -1,则第2步可适用于生成rv〜F,而无需使用任何计数方法。(例如exp.distribution)

  4. 为了建模正态分布,您可以计算y1 * cos(y2),其中y1〜[2pi]是均匀的。y2是relei分布。

问:如果我想选择均值和标准差怎么办?

您可以计算sigma * N(0,1)+ m。

可以证明,这种移位和缩放导致N(m,sigma)


0

这是使用Box-Muller变换的极坐标形式的Matlab实现:

功能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

调用histfit(randn_box_muller(10000000),100);结果如下: Box-Muller Matlab历史记录

显然,与Matlab内置randn相比,它确实效率很低。


0

我有以下代码可能会有所帮助:

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

使用实现的函数rnorm()也比使用正态分布编写随机数生成器要快,因为它比写随机数生成器要快。参见以下代码作为证明

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

但是,不能保证会回来吗?;-)
Peter K.

5
随机数太重要了,不要碰运气。
德鲁·诺阿克斯

没有回答这个问题-正态分布具有无限域。
马特
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.