在C / C ++中遵循正态分布生成随机数


Answers:


92

有许多方法可以从常规RNG生成高斯分布数

箱穆勒变换是常用的。它会正确产生具有正态分布的值。数学很简单。您生成两个(均匀)随机数,然后对它们应用公式,就得到两个正态分布的随机数。返回一个,并将另一个保存为下一个随机数请求。


10
如果需要速度,则极坐标法会更快。Ziggurat算法甚至更多(尽管编写起来要复杂得多)。
乔伊,2010年

2
发现通灵塔的实现这里people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html这是相当齐全。
dwbrito

24
请注意,C ++ 11会添加所需std::normal_distribution功能,而无需深入研究数学细节。

3
std :: normal_distribution不能保证在所有平台上都是一致的。我现在正在进行测试,并且MSVC提供了一组与Clang不同的值。C ++ 11引擎似乎生成相同的序列(给定相同的种子),但是C ++ 11发行版似乎是在不同的平台上使用不同的算法实现的。
亚诺·杜文哈格

47

C ++ 11

C ++ 11提供了std::normal_distribution,这就是我今天要去的方式。

C或更旧的C ++

以下是一些按升序排列的解决方案:

  1. 将0到1之间的12个均匀随机数相加并减去6。这将与正常变量的均值和标准差相匹配。一个明显的缺点是范围限制为±6,这与真实的正态分布不同。

  2. Box-Muller变换。这已在上面列出,并且实现起来相对简单。但是,如果您需要非常精确的样本,请注意,将Box-Muller变换与某些均匀生成器结合使用会遇到一个称为Neave Effect 1的异常现象。

  3. 为了获得最佳精度,我建议绘制制服并应用逆累积正态分布以得出正态分布变量。是逆累积正态分布的很好算法。

1. HR Neave,“关于将Box-Muller变换与乘法同余伪随机数生成器一起使用”,《应用统计》,1973年第22、92-97页


您是否有机会在Neave效果上找到指向pdf的另一个链接?还是原始期刊文章参考?谢谢
pyCthon

2
@stonybrooknick添加了原始参考。很酷的一句话:在搜索“ box muller neave”以查找参考时,这个非常有stackoverflow的问题出现在第一个结果页面上!
Peter G.

是的,在某些小型社区和利益集团之外,并不是每一个众所周知的地方
pyCthon

@Peter G.为什么有人会否决您的答案?-可能是同一个人也在下面发表了我的评论,我也很好,但我认为您的回答很好。如果SO做出的反对意见可以提出真正的意见,那将是很好的。我怀疑大多数旧主题的反对意见都是轻浮而无聊的。
Pete855217 2013年

“从0-1加12统一数字并减去6。” -这个变量的分布会不会有正态分布?你能提供一个与微分的联系,因为在微分中心极限定理中,n-> + inf是非常需要的假设。
bruziuz

31

一种快速简便的方法是将多个均匀分布的随机数求和并取其平均值。有关为何有效的完整说明,请参见中心极限定理


+1非常有趣的方法。是否已验证是否确实为较小的群体提供了正态分布的子集成?
莫洛克

4
@Morlock平均样本数越大,越接近高斯分布。如果您的应用程序对分配的准确性有严格的要求,那么您最好使用更严格的方法,例如Box-Muller,但对于许多应用程序(例如,为音频应用程序产生白噪声),您可以选择一个很小的数目平均样本数(例如16)。
Paul R

2
另外,如何对它进行参数化以获得一定数量的方差,比如说您希望平均值为10,标准差为1?
莫洛克

1
@本:您能为此指出一个有效的算法吗?我只使用过平均技术来为具有实时约束的音频和图像处理生成近似高斯噪声-如果有一种方法可以在较少的时钟周期内实现这一点,那么这将非常有用。
Paul R

1
@Petter:在一般情况下,对于浮点值,您可能是正确的。但是,仍然存在诸如音频之类的应用领域,在这些领域中您需要快速整数(或定点)高斯噪声,并且精度不是太重要,在这里简单的平均方法更有效,更有用(尤其是对于嵌入式应用,甚至可能没有)是硬件浮点支持)。
Paul R

24

为正态分布的随机数生成基准创建了一个C ++开源项目

它比较了几种算法,包括

  • 中心极限定理
  • Box-Muller变换
  • 马尔萨里亚极地法
  • Ziggurat算法
  • 逆变换采样方法。
  • cpp11randomstd::normal_distribution与C ++ 11 配合使用std::minstd_rand(实际上是clang中的Box-Muller转换)。

float在iMac Corei5-3330S @ 2.70GHz,clang 6.1、64位上的单精度()版本的结果:

正态分布

为了正确起见,程序将验证样本的平均值,标准偏差,偏度和峰度。已经发现,通过将4、8或16个统一数相加得到的CLT方法不具有其他方法的峰度。

Ziggurat算法具有比其他算法更好的性能。但是,它不适合SIMD并行性,因为它需要表查找和分支。具有SSE2 / AVX指令集的Box-Muller比ziggurat算法的非SIMD版本要快得多(x1.79,x2.99)。

因此,我建议将Box-Muller用于具有SIMD指令集的体系结构,否则可能是之字形。


PS基准测试使用最简单的LCG PRNG生成统一的分布式随机数。因此,对于某些应用程序可能还不够。但是性能比较应该是公平的,因为所有实现都使用相同的PRNG,因此基准测试主要测试转换的性能。


2
“但是性能比较应该是公平的,因为所有实现都使用相同的PRNG” ..除了BM每个输出使用一个输入RN,而CLT使用更多的输入,等等,因此生成统一随机数的时间很重要。
greggo

14

这是一个基于某些参考的C ++示例。这既快速又肮脏,最好不要重新发明和使用Boost库。

#include "math.h" // for RAND, and rand
double sampleNormal() {
    double u = ((double) rand() / (RAND_MAX)) * 2 - 1;
    double v = ((double) rand() / (RAND_MAX)) * 2 - 1;
    double r = u * u + v * v;
    if (r == 0 || r > 1) return sampleNormal();
    double c = sqrt(-2 * log(r) / r);
    return u * c;
}

您可以使用QQ图来检查结果,并查看其与真实正态分布的近似程度(对样本1..x进行排名,将等级转换为x总数的比例,即有多少样本获得z值并绘制它们。向上的直线是理想的结果)。


1
什么是sampleNormalManual()?
SolvingPuzzles 2012年

@solvingPuzzles-对不起,更正了代码。这是一个递归调用。
Pete855217 2012年

1
这势必会在某些罕见的事件中崩溃(向老板展示应用程序会响起铃声?)。应该使用循环而不是递归来实现。该方法看起来不熟悉。来源/名称是什么?

从Java实现转录的Box-Muller。正如我说的,它又快又脏,可以随时对其进行修复。
Pete855217

1
FWIW,许多编译器将能够将特定的递归调用转换为“跳转到函数顶部”。问题是您是否要依靠它:-)另外,它需要> 10次迭代的概率是480万中的1。p(> 20)是该平方的平方,等等
greggo

12

使用std::tr1::normal_distribution

std :: tr1命名空间不是boost的一部分。它是包含C ++技术报告1中的库添加内容的名称空间,并且可以独立于boost使用最新的Microsoft编译器和gcc。


25
他不要求标准,他要求“不提高”。
JoeG 2010年

12

这是在现代C ++编译器上生成样本的方式。

#include <random>
...
std::mt19937 generator;
double mean = 0.0;
double stddev  = 1.0;
std::normal_distribution<double> normal(mean, stddev);
cerr << "Normal: " << normal(generator) << endl;

generator确实应该接种。
Walter

它总是种子。有一个默认种子。
2015年




3

我遵循了http://www.mathworks.com/help/stats/normal-distribution.html中提供的PDF的定义,并提出了以下内容:

const double DBL_EPS_COMP = 1 - DBL_EPSILON; // DBL_EPSILON is defined in <limits.h>.
inline double RandU() {
    return DBL_EPSILON + ((double) rand()/RAND_MAX);
}
inline double RandN2(double mu, double sigma) {
    return mu + (rand()%2 ? -1.0 : 1.0)*sigma*pow(-log(DBL_EPS_COMP*RandU()), 0.5);
}
inline double RandN() {
    return RandN2(0, 1.0);
}

这可能不是最好的方法,但是很简单。


-1不适用于RANDN2(0.0,d + 1.0)。宏为此而臭名昭著。
皮特

如果rand()of RANDU返回零,则宏将失败,因为Ln(0)未定义。
interDist 2013年

您是否实际尝试过此代码?看来您已经创建了一个函数,该函数生成Rayleigh分布的数字。与Box-Muller变换进行比较,后者与相乘cos(2*pi*rand/RAND_MAX),而与相乘(rand()%2 ? -1.0 : 1.0)
HelloGoodbye 2014年


1

Box-Muller实施:

#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
using namespace std;
 // return a uniformly distributed random number
double RandomGenerator()
{
  return ( (double)(rand()) + 1. )/( (double)(RAND_MAX) + 1. );
}
 // return a normally distributed random number
double normalRandom()
{
  double y1=RandomGenerator();
  double y2=RandomGenerator();
  return cos(2*3.14*y2)*sqrt(-2.*log(y1));
}

int main(){
double sigma = 82.;
double Mi = 40.;
  for(int i=0;i<100;i++){
double x = normalRandom()*sigma+Mi;
    cout << " x = " << x << endl;
  }
  return 0;
}

1

存在用于逆累积正态分布的各种算法。在http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/上测试了数量最多的量化金融

在我看来,除了使用Wichura的AS241算法之外,没有太多动机去使用其他东西:机器精度高,可靠且快速。在高斯随机数生成中,瓶颈很少出现。

此外,它还显示了类似Ziggurat方法的缺点。

Box-Müller的倡导者在这里是最好的答案,您应该意识到它具有已知的缺陷。我引用https://www.sciencedirect.com/science/article/pii/S0895717710005935

在文献中,Box-Muller有时被认为稍逊一筹,主要有两个原因。首先,如果将Box-Muller方法应用于不良线性同余生成器中的数字,则转换后的数字将提供极差的空间覆盖率。在许多书中都可以找到带有螺旋尾巴的变换后的数字图,最著名的是里普利的经典书,他可能是第一位进行此观察的人。”


0

1)使用类似于蒙特卡洛方法的方法,可以直观地生成高斯随机数。您将使用C中的伪随机数生成器在高斯曲线周围的框中生成一个随机点。您可以使用分布方程式计算该点是在高斯分布内部还是下方。如果该点在高斯分布内,那么您将获得高斯随机数作为该点的x值。

这种方法并不完美,因为从技术上讲,高斯曲线会朝着无穷大方向前进,并且您无法创建一个在x维度上接近无穷大的框。但是,高斯曲线在y维度上非常快地接近0,因此我不必为此担心。C语言中变量大小的约束可能更多地限制了您的准确性。

2)另一种方法是使用中央极限定理,该定理指出当添加独立随机变量时,它们形成正态分布。牢记这个定理,您可以通过添加大量独立的随机变量来近似高斯随机数。

这些方法不是最实用的方法,但是当您不想使用预先存在的库时,这是可以预期的。请记住,这个答案来自没有或没有微积分或统计经验的人。


0

蒙特卡洛方法 最直观的方法是使用蒙特卡洛方法。取一个合适的范围-X,+ X。X的值越大,将导致更准确的正态分布,但收敛时间越长。一个。在-X到X之间选择一个随机数z。保持N(z, mean, variance)N 在哪里是高斯分布的可能性。否则放回步骤(a)。



-3

计算机是确定性设备。计算中没有随机性。此外,CPU中的算术设备可以评估一些有限的整数集(在有限域中执行评估)和有限的实有理数集。并且还执行了按位运算。数学可以处理更多无穷大的集合,例如[0.0,1.0]。

您可以使用某些控制器收听计算机内部的某些电线,但是它的分布均匀吗?我不知道。但是,如果假设信号是累积大量独立随机变量值的结果,那么您将收到近似正态分布的随机变量(概率论中已证明)

存在称为“伪随机发生器”的算法。如我所见,伪随机发生器的目的是模拟随机性。Goodnes的标准是:-经验分布已收敛(从某种意义上说是逐点均匀L2)到理论上-从随机生成器收到的值似乎是独立的。从“真实的观点”来看,这当然是不正确的,但我们认为这是正确的。

一种流行的方法-您可以求和具有均匀分布的12个irv ....但是老实说,在使用傅立叶变换,泰勒级数进行推导中心极限定理时,它需要两次n-> + inf个假设。因此,例如从理论上讲-就我个人而言,我不理解人们如何以均匀分布执行12 irv的求和。

我在大学里有能力理论。对我来说,这尤其是一个数学问题。在大学里,我看到了以下模型:


double generateUniform(double a, double b)
{
  return uniformGen.generateReal(a, b);
}

double generateRelei(double sigma)
{
  return sigma * sqrt(-2 * log(1.0 - uniformGen.generateReal(0.0, 1.0 -kEps)));
}
double generateNorm(double m, double sigma)
{
  double y2 = generateUniform(0.0, 2 * kPi);
  double y1 = generateRelei(1.0);
  double x1 = y1 * cos(y2);
  return sigma*x1 + m;
}

这样的方式只是一个例子,我想它是实现它的另一种方式。

证明正确的证据可以在克里希琴科·亚历山大·彼得罗维奇( Krishchenko Alexander Petrovich)的书“莫斯科,BMSTU,2004年:XVI概率论,示例6.12,第246-247页”中找到,ISBN 5-7038-2485-0

不幸的是,我不知道这本书有没有翻译成英文。


我有几票。让我知道这里有什么不好的吗?
bruziuz

问题是如何在计算机中生成伪随机数(我知道,这里的语言很松散),这不是数学存在的问题。
user2820579 '18

你是对的。答案是如何基于具有均匀分布的生成器生成具有正态分布的伪随机数。提供了源代码,您可以用任何语言重写它。
bruziuz

当然,我认为这家伙正在寻找“ C / C ++的数字食谱”。顺便说一下,为了补充我们的讨论,最后本书的作者为几个伪随机生成器提供了有趣的参考,这些伪随机生成器满足了作为“体面”生成器的标准。
user2820579 '18

1
我在这里进行了备份:sites.google.com/site/burlachenkok/download
bruziuz
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.