伪随机数生成器 - 指数分布

68

我想生成一些伪随机数,迄今为止,我一直很满意.Net库的Random.Next(int min, int max)函数。这种类型的PRNG应该使用均匀分布,但我非常希望使用指数分布来生成一些数字。

我正在使用C#编程,尽管我也接受伪代码、C++、Java或类似语言。

有什么建议/代码片段/算法/想法吗?


https://dev59.com/DXNA5IYBdhLWcg3wjOlS 不完全是一个重复的问题,只是因为所需分布不同。它确实有正确的答案... - dmckee --- ex-moderator kitten
1
http://ftp.arl.mil/random/random.pdf 是一个包含多种概率分布实现算法的集合,其中包括指数分布。 - mdup
将均匀分布转换为指数分布: 随机数 r = new Random(); 双精度浮点数 u = r.NextDouble(); 双精度浮点数 R = -Math.Log(u) / (λ); - zahrakhani
9个回答

115

既然您拥有一种均匀随机数生成器,那么使用反演法生成其他分布的随机数就很容易了,只要你知道它们的累积分布函数。

首先,在 [0,1) 范围内生成一个均匀随机数 u ,然后通过以下公式计算 x

x = log(1-u)/(-λ)

x = log(1-uniformRand(0, 1))/(-λ)

其中,λ 是指数分布的速率参数。现在,x 是具有指数分布的随机数。请注意,上述的log指的是自然对数(即ln)。


1
是的..这就是我需要的。读完维基百科页面后,反演法很有道理。对于其他人阅读您的答案,可能会对您的对数函数的底数产生一些混淆。严格来说,它应该是以e为底,即ln()。 - Charlie Salts
5
当u在[0,1)之间时,u对应的值等同于(0,1]。因此,您可以使用log((0,1])/(-λ)来计算。 - varela
6
指数分布的取值范围为[0,无穷),因此生成大于1的数字并不奇怪。 - Alok Singhal
@DavidDoria 是的。我认为这个问题的一个答案也使用了另一种形式。 - Alok Singhal
5
@varela:完全正确,但请允许我添加一个小警告:由于我所知道的大多数随机数生成器都在[0,1)中生成数字,因此某些人可能会意外尝试进行log([0,1))/(-λ)的计算。这可能非常罕见地创建+infinity,即如果随机数生成器生成0(由于[0,1))。我认为这种情况发生的几率非常非常小,但不是零。此外,分布可能会稍微有些错误。通过使用1-[0,1),可以避免这个错误。如果没有这个,我们需要一个if-then来检查0,这将增加运行时间。无论如何,你的评论是正确的。 - Thomas Weise
显示剩余6条评论

16

抽样的基本定理是:如果你能够对所需的分布进行归一化、积分和反演,那么你就可以轻松获得所需的结果。

如果你有一个定义在区间[a,b]上的已归一化的所需分布F(x),则计算:

C(y) = \int_a^y F(x) dx

反转它以获取C^{-1},在[0,1)上均匀分布地抛出z并找到

x_i = C^{-1}(z_i)

对于你的情况:F(x) = ke^{-kx},我将假设你想要[0,无穷大]范围内的分布。我们得到:

C(y) = 1 - e^{-ky}

可逆以得到

x = -1/k  ln(1 - z)

如果你不是出于个人兴趣,使用一个经过充分调试的库会更明智。


7
如果您需要好的随机数,请考虑链接到gsl例程:http://www.gnu.org/software/gsl/。它们有一个名为的例程。如果您想要使用内置生成器生成在[0, 1)上均匀分布的随机数(例如u=Random.Next(0, N-1)/N,其中N很大),则只需使用:
-mu * log (1-u)

请参考gsl源码中的randist/exponential.c。
编辑说明:为了方便后续的对比,这里的mu等价于1/lambda。此处的mu是分布的均值,在OP链接的维基百科页面上称为比例参数,而lambda则是速率参数。

7

我在维基百科上找到了以下公式:

T = -Ln(u) / λ

我们使用均匀分布的随机数(u)在[0,1]之间生成一个随机数,并得到x:

Random R = new Random();

double u = R.NextDouble();

double x = -Math.Log(u)/(λ);


4

指数分布的一个有趣属性:考虑到一个到达过程,其到达间隔时间服从指数分布。取任意一段时间(t1,t2)和该时段内的到达次数。这些到达次数在t1和t2之间是均匀分布的。(Sheldon Ross, 随机过程)

如果我有一个伪随机数生成器,并且由于某种原因(例如,我的软件无法计算对数),您不想执行上述转换,但想要具有平均值为1.0的指数随机变量。

你可以:

1)创建1001个U(0,1)的随机变量。

2)按顺序排序

3)从第一个中减去第二个,从第二个中减去第三个,...得到1000个差异。

4)这些差异是来自平均值为1.0的分布的指数随机变量。

我认为效率更低,但达到相同目的的方法。


有趣的想法。我如何控制λ值? - Charlie Salts
哦 - 我有点错误地陈述了这个过程。实际上,我在区间(0,1000)上创建了1001个随机变量,并取了1000个差值。结果是指数分布,平均值为1.0,因为平均差值为1.0。要获得另一个平均值,只需将差值乘以所需的平均值即可。顺便说一下,我在@Risk中检查了结果,以确保分布是平均值为1.0的指数分布。 - Grembo

2
开源的Dan Dyer的Uncommons Maths库为Java提供了随机数生成器、概率分布、组合学和统计学等功能。
除了其他有价值的类之外,ExponentialGenerator本质上实现了@Alok Singhal解释的想法。在它的教程博客中,给出了一个代码片段来模拟平均每分钟发生10次的某个随机事件:
final long oneMinute = 60000;
Random rng = new MersenneTwisterRNG();

// Generate events at an average rate of 10 per minute.
ExponentialGenerator gen = new ExponentialGenerator(10, rng);
boolean running = true;
while (true)
{
    long interval = Math.round(gen.nextValue() * oneMinute);
    Thread.sleep(interval);

    // Fire event here.
}

当然,如果您更喜欢时间单位为“每秒”(而不是这里的“一分钟”),您只需要设置final long oneMinute = 1000
深入研究ExponentialGenerator方法nextValue()源代码,您会发现所谓的反变换抽样,描述在生成指数变量[维基百科]中:
public Double nextValue()
{
    double u;
    do
    {
        // Get a uniformly-distributed random double between
        // zero (inclusive) and 1 (exclusive)
        u = rng.nextDouble();
    } while (u == 0d); // Reject zero, u must be positive for this to work.
    return (-Math.log(u)) / rate.nextValue();
}  

P.S.:最近我正在使用Uncommons Maths库。感谢Dan Dyer。

1

还有一种生成指数分布(rate)随机变量的方法,虽然现在使用对数更加方便。这个算法来自John von Neumann (1951),只使用比较。

  1. scale1/rate。将highpart设置为0。
  2. 在(0, scale)内生成均匀分布的随机变量,称为u
  3. val设置为u,并将accept设置为1。
  4. 在(0, scale)内生成均匀分布的随机变量,称为v
  5. 如果u大于v,则将u设置为v,然后将accept设置为1减去accept,然后转到第4步。
  6. 如果接受为1,则返回val + highpart
  7. scale添加到highpart,并转到第2步。

参考文献:

  • Von Neumann, J.,“与随机数字相关的各种技术”,1951年。

0

如果我理解你的问题,并且你可以接受有限数量的伪随机数生成器(PRNG),那么你可以采用以下方法:

  • 创建一个数组,其中每个元素都在指数分布中
  • 生成一个 PRNG,它是数组中的整数索引。返回该索引处数组中的元素。

这种近似方法适用于困难情况,但这次并不必要。指数分布可以被精确地抛出。 - dmckee --- ex-moderator kitten
也许一个相关的建议是准蒙特卡罗方法? - John Madden

-2

在面对类似要求时,这是我使用的方法:

// sorry.. pseudocode, mine was in Tcl:

int weighted_random (int max) {
    float random_number = rand();
    return floor(max - ceil( max * random_number * random_number))
}

当然,这是平方随机数的公式,因此您正在沿着二次曲线生成随机数。


是的。最初我在结尾处写了“请随意使用适当的公式进行替换”,但在编辑时不小心删除了。我的想法是给出一个实现方法,然后通过谷歌或阅读相关资料来找到适当的公式。我不会重新编辑答案,因为否则这条评论就没有意义了。在点踩之前,请先阅读这条评论。 - slebetman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接