【问题标题】:random over a range, is number bias present for new rand() version?在一个范围内随机,新 rand() 版本是否存在数字偏差?
【发布时间】:2014-06-05 18:42:26
【问题描述】:

从其他各种 SO 问题中阅读,在使用 rand() % N 时,您可能会碰巧修改您获得的伪数的偏差,因此您通常必须引入一些范围处理。

但是在所有情况下,总是提到 rand(),而不是较新的 random() 或 arcrandom4() 函数或本机 C++11 方法。当你在一个集合上运行这些例程时会发生什么?你有像 rand() 这样的偏见吗?

谢谢。

【问题讨论】:

  • std::uniform_int_distribution,就我所知,它解决了这个问题。 IE。除了 PRNG 之外,现在还有适当且独立的分布。
  • 如果你还没有看过,STL's talk 是一个非常规范的介绍。
  • 了解 为什么 执行 rand() % N 会引入偏见会很有帮助。我认为有了这种理解,这个问题的答案就会更加明显。 Eric Lippert 在这里写了一篇关于这个特定主题的非常好的博客:ericlippert.com/2013/12/16/…
  • @PeteBaughman 您应该将其发布为答案,因为无论底层 PRNG 有多好,当使用% 设置范围时,这种影响几乎总是存在。 (需要注意的是 PRNG 的范围是模数的精确倍数。)

标签: c++ math random distribution modulo


【解决方案1】:

下面的答案没有Eric Lippert's blog post on the same topic那么详细。此外,this question and its answers 处理相同的主题。

大部分来自rand() % N 的偏见不是来自rand() 部分,而是来自% N 部分。

让我们考虑一个 rand() 的“好”实现,它以相等的概率生成从 0 到 100(为简单起见)的所有数字 - 均匀分布。接下来假设我们要使用 rand() 的这个实现来生成 0 到 80 之间的随机数,所以我们使用rand() % 80。让我们分解一下接下来会发生什么的可能性:

  1. rand() 生成一个从 0 到 79 的数字。从 0 到 79 % 80 的任何数字都保持不变
  2. rand() 生成一个从 80 到 100 的数字。从 80 到 100 % 80 的任何数字都被转换为 0 到 20

这意味着有两种方法可以得到 0 到 20 之间的数字,但只有 一种方法可以得到 21 到 79 之间的数字。从 0 到 20 的数字比从 21 到 79 的数字更有可能。这通常不是一个理想的属性。

N 的任何值均分到 rand() 的最大值都不会有这个问题,因为生成任何值的方法数量都是相等的。此外,对于较小的 N 值,偏差比对于接近 rand() 的最大值的 N 值要小得多。

那么,除了 rand() 之外的函数呢? 如果它们返回某个固定范围的值,而您执行 mod 操作,它们将遭受相同的偏差。如果您正在调用一个将范围作为参数的随机函数,那么您不需要执行 mod 操作。该函数可能会在内部处理任何偏差。

【讨论】:

  • @koda 你可以在下面我的帖子中找到更多的技术答案
【解决方案2】:

当你在一个集合上运行这些例程时会发生什么?你有偏见吗 像 rand()?

答案是:这取决于生成器返回的范围大小与模运算中的除数之间的关系。如果除数不均匀地划分范围,则分布将偏斜。偏置比在 [1, 2] 范围内,其中 1 表示没有偏置(对于均匀分布),偏置随除数增加。关于arcrandom4(),这转化为在模除数不是 2^32 的偶数除数的所有情况下获得的偏态分布。下面解释其背后的基本原理。


介绍。偏见

假设我们正在尝试使用 [0, 99] 模拟区间内的均匀 int 分布

int x = rand() % 100;

运算符 % 使 X 的概率分布发生倾斜,因为 rand() 的最大值 RAND_MAX 不能等于 k ​​ 100 + 99。这导致如果您想象 0-RAND_MAX 范围的所有 100 长度部分那么您可以看到最后一部分可能不会产生完整的 0-99。因此,您有更多的数字可以生成 0, 1, 2..., p 但不是必需的 p + 1, ..., 98, 99 (0、1、2、...、p 中的每个数字再出现 1 次) )。这种方法的不准确性会随着不均匀划分范围的除数越大而增加,并且与均匀分布相比,最大偏差等于 2。

在下面的部分中,我们展示了作为从 [ 0, p] 获得数字的概率与从 [ p + 1, n] 获得数字的概率之比来衡量的偏差等于 ( k + 1 ) / k,我们用 2 个例子来证实这一点。


公式

我们将展示模运算引入的偏差究竟是什么(应用于均匀分布生成器以修剪输出范围的运算)。我们将按照公式进行操作

x = rand() % ( n + 1)

其中rand() 是某个生成器,( n + 1) 是模运算中的除数。下图显示了我们的立场:

我们可以看到[ 0, n] 范围内的数字如何在一次试验中分为重复k + 1 次(数字[ 0, p])和重复k 次(数字[ p + 1, n])的数字,即“从x = rand() % (n+1)获得的分布中取数”。 p 定义为生成器给出的最大数(即 Rand_MAX)除以所需范围的大小(n + 1)时的余数:

p = ( N - 1) % ( n + 1)

N - 1 = k * (n + 1) + p

k是商

k = ( N - 1 - p) / ( n + 1)

在一个单独的试验中有

( p + 1) * ( k + 1) + ( n - p) * k =

= p + 1 + k( n + 1) = N

可能的结果。因此接收到重复 k 次的元素的概率是 k / N。让我们表示

f_0 = ( k + 1) / N, [ 0, p]中每个元素的概率

f_1 = k / N, [p + 1, n]中每个元素的概率

假设我们将从这个采样的偏差表达为均匀分布上的变换分布为属于[ 0, p]的元素概率与来自范围@的元素概率之比987654338@:

偏差 = f_0 / f_1 = ( k + 1) / k

那么,数字的频率是两倍吗?

没有。当我们查看图片数字重复时,这一事实并不意味着比率为 2。如果生成器的范围恰好被划分为 2 个子范围,则该比率只是一种特殊情况。一般情况下,偏置比为(k + 1) / k,当除数n + 1趋于1,(且k趋于N)时,偏置比逐渐减小。


示例

我们现在考虑两个简单的例子(正如@dyp 所建议的那样)。首先,我们将从给定的分布中生成 1000 * 1000 个样本

x = rand() % m

生成器为 std::uniform_int_distribution<> dist(0, 19),除数 m = n + 1 等于 15,下一个等于 6。

示例 1

int x = rand() % 15; // n + 1 = 15, rand is uniform distribution over [0,19]

测试程序是:

#include <iostream>
#include <random>
#include <vector>

int main()
{
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<> dist(0, 19);
    std::vector<int> v(15);
    const int runs = 1000 * 1000;
    for (int i = 0; i < runs; ++i)
    {
        ++v[dist(mt) % v.size()];
    }

    for (int i = 0; i < v.size(); ++i)
    {
        std::cout << i << ": " << v[i] << "\n";
    }
}

code

结果:

0: 100500 1:100016 2:99724 3:99871 4:99936 5:50008 6:49762 7:50023 8:50123 9: 49963 10: 50117 11:50049 12: 49885 13: 49760 14: 50263

我们可以看到,在这种情况下,[ 0, p] = [ 0, 4] 范围内的数字出现的频率大约是其余数字的两倍。这符合我们的偏差公式

偏差 = f_0 / f_1 = ( k + 1) / k = 2 / 1

示例 2

int x = rand() % 6; // n + 1 = 6, rand is uniform distribution over [0,19]

测试程序是:

#include <iostream>
#include <random>
#include <vector>

int main()
{
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<> dist(0, 19);
    std::vector<int> v(6);
    const int runs = 1000 * 1000;
    for (int i = 0; i < runs; ++i)
    {
        ++v[dist(mt) % v.size()];
    }

    for (int i = 0; i < v.size(); ++i)
    {
        std::cout << i << ": " << v[i] << "\n";
    }
}

code

结果:

0: 199875 1:199642 2:149852 3:149789 4:150237 5:150605

在这种情况下,我们观察到 [ 0, p] = [ 0, 1] 范围内的数字出现的频率不是其他数字的两倍,而是大约 20/15。实际上这是 4/3,因为我们在这种情况下的偏差公式是

偏差 = f_0 / f_1 = ( k + 1) / k = 4 / 3

下图有助于理解这一结果。

full code

【讨论】:

  • @koda 我可以再解释一下吗?
  • 当然,我的荣幸先生
【解决方案3】:

C++11 通过添加替代随机生成器引擎解决了这个问题。

使用 %(modulo) 将随机数限制在一个范围内不好的原因与偏差无关,而与线性同余生成器 (LCG) rand() 的典型实现有关。大多数语言运行时使用 LCG 来实现其随机功能;只有最近设计的语言才会有所不同。

LCG 只是一个乘法和一个加法(模数通常通过整数的最大大小来实现)。很明显,这样一个序列的低位遵循一个规则模式——乘法不会将高位混合到低位中,而加法每次迭代都会以恒定的方式改变低位。

通过了解不同的随机生成器(linear_congruential_engine、mersenne_twister_engine、subtract_with_carry_engine)引擎,您可以找到最适合您的应用程序的引擎。

Random Engines in c++11 中对新的 c++ 实现有很好的参考

正如@dpy 所说,std::uniform_int_distribution 是 c++ 为随机分布提供的一个选项。即使随机生成器引擎有 .但是,如果您将范围设置为 1-19 并使用 % 操作将其存储在一个 15 大小的数组中,则会重新引入偏差问题,正如此处许多帖子中所讨论的那样。

【讨论】:

  • MSVC 的 rand()RAND_MAX 为 32768,因此当您执行 rand() % 20000 时,您获得 [0, 12768] 中的值的频率是 [12769, 19999] 中的值的两倍(可能是一个一个)。因此,将 RNG 输出映射到某个分布的分布对象也非常重要。
  • @dyp 当你按照你所说的那样做 rand() % 20000 时,你会得到一个范围 [0,19999]
  • @bits_international 这不是我说的吗?您得到 [0, 12768] 中的值和 [12769, 19999] 中的值,因此组合范围为 [0, 19999]
  • C++ &lt;random&gt; 解决模数问题的关键部分不是随机数引擎,而是随机数分布
  • @dyp 不是RAND_MAX 32767?
猜你喜欢
  • 2023-04-07
  • 2010-10-01
  • 2018-04-08
  • 1970-01-01
  • 1970-01-01
  • 2012-07-30
  • 2020-07-18
  • 2017-01-01
  • 2011-08-02
相关资源
最近更新 更多