使用硬币生成均匀分布的随机数


25

你有一枚硬币。您可以根据需要翻转它多次。

要生成的随机数[R,使得ar<b,其中。r,a,bZ+

数字的分配应统一。

如果很容易ba=2ñ

r = a + binary2dec(flip n times write 0 for heads and 1 for tails) 

如果ba2ñ怎么办?


使用Han-Hoshi算法-基本上将时间间隔一分为二,使用随机位(硬币翻转)随机选择两个时间间隔之一,然后在您选择的一侧重复此过程,直到用完位为止。这将为您提供从实线分区均匀分布的间隔。翻转次数越多,间隔越精确。
zenna

Answers:


13

您要查找的内容基于拒绝采样或accept-reject方法(请注意,Wiki页面有点技术性)。

此方法在以下情况下很有用:您想从集合中选择一些随机对象(在您的情况下为集合中的随机整数),但是您不知道该怎么做,但是您可以选择一些随机对象由含有第一组中的较大的一组(在你的情况下,[ 2 ķ + ]对于一些ķ使得2 ķ + b ;这对应于ķ硬币翻转)。[a,b][a,2k+a]k2k+abk

在这种情况下,您只需从较大的集合中选择元素,直到您随机选择较小的集合中的元素。如果较小的集合与较大的集合相比足够大(在您的情况下包含的整数最多是[ a b ]的两倍,这已经足够好了),那么这是有效的。[a,2k+a][a,b]

一个替代示例:假设您要在半径为1的圆内选择一个随机点。现在,为此找到直接方法并不是一件容易的事。我们转向accept-reject方法:我们在围绕圆的1x1正方形中对点进行采样,并测试绘制的数字是否位于圆内。


3
注意,如果我们拒绝来自样本以便获得B的分布,则预期的迭代次数为|。A |AB(当我们执行几何分布实验时)。|A||B|
拉斐尔

我回想起在某个地方,除非范围是2的幂,否则无法完全做到这一点(合理地说,例如1/3不会终止二进制扩展)。
vonbrand 2013年

7

(技术问题:答案适合数目的选择一种X<b

既然你被允许翻转你的硬币多次如你所愿,你可以关闭,随你,希望统一通过挑选一小部分得到您的概率你翻转:(使用二进制基数硬币的点之后的每个数位)和乘法- [RBA得到0和BA-1](四舍五入至的整数)之间的数。加入此号码一个,你就大功告成了。[R[01个][Rb-一种一种

例如:说。二进制中的1/3是0.0101010101 ....然后,如果翻转在0到0.010101之间...则选择b。如果它介于0.010101 ..和0.10101010 ...之间,则您的选择+ 1,否则+ 2b-一种=3b一种+1个一种+2

如果您将硬币翻转次,则将选择ab之间每个数字,概率为1Ť一种b1个b-一种±2-Ť+1个


1
这不会给出均匀的分布。对于某些应用程序(例如,有时是加密),这可能非常糟糕。
吉尔(Gilles)'所以

3
@Gilles:可以固定它,通过翻转直到不再可能改变结果来提供完美均匀的分布。这是最有效的答案。
尼尔·G

@NeilG我知道可以解决此问题,但修复它将是答案的关键部分。
吉尔斯(Gillles)“所以-别再邪恶了”

2
@吉尔斯:你是对的。他可以修改答案,说如果在翻转,则可以产生完全均匀的分布。从我这里+1可获得最佳的平均情况和最坏的情况。(ba)(f+2t1)(ba)(f2t1)
尼尔·G

@NeilG,它不能被“固定”,因为有相当大的一组整数,它们没有终止的二进制分数。
vonbrand

7

在2的下一个更大的幂中选择一个数字,并丢弃大于答案。b-一种

n = b-a;
N = round_to_next_larger_power_of_2(n)
while (1) {
  x = random(0 included to N excluded);
  if (x < n) break;
}
r = a + x;

4
为何这样做呢?
拉斐尔

@Raphael表示怀疑,还是只想让海报详细解释?
Suresh'3

1
@Suresh:后者。伪代码可以稍微完善一点,但是它可以实现其他答复者所解释的内容。没有理由,这个答案本身就不值钱。
拉斐尔

6

没有人提到这一点,因此让我正式证明,如果是一个大小不是2的幂的域,那么有限的多次抛硬币不足以生成D的均匀随机成员。假设您使用k个硬币生成D的成员。对于每个d d,生成你的算法的概率d是形式的/ 2 ķ对于某个整数。算术基本定理表明A / 2 k1 / | D | ddķdddd一种/2ķ一种一种/2ķ1个/|d|

如果要生成独立的D均匀样本,则所需的硬币抛弃次数(在最佳算法下)约为n log 2 | n D | 。更一般而言,如果要从熵H的分布生成,则期望中大约需要n个H随机位。实现此目的的算法是算术解码,应用于(表面上)无限的随机位序列。ñdñ日志2|d|HñH


3

如果ba不是2的幂,那么您可能必须翻转许多硬币才能得到结果。您甚至可能永远都不会获得结果,但这在极端情况下是不可能的。

方法

最简单的方法是在[a,a + 2 ^ n)中生成一个数字,其中2 ^ n> = ba,直到一个碰巧出现在[a,b)中。使用这种方法会浪费很多熵。

一种更昂贵的方法可以保留所有熵,但是随着硬币翻转/掷骰子数量的增加,计算量变得非常昂贵。从直觉上讲,这就像将硬币翻转视为小数点右边的二进制数字的数字,然后将数字从2转换为ab,然后在变成“卡住”时返回该数字的数字一样。

以下代码将n面公平的模具的卷转换为m面公平的模具的卷(在您的情况下,n = 2,m = ab),随着卷数的增加,边际成本也随之增加。注意需要具有任意精度的有理数类型。一个不错的特性是,从n面转换为m面然后再转换为n面将返回原始数据流,尽管由于数字必须卡住而可能会延迟几次滚动。

public static IEnumerable<BigInteger> DigitConversion(this IEnumerable<BigInteger> inputStream, BigInteger modIn, BigInteger modOut) {
    //note: values are implicitly scaled so the first unfixed digit of the output ranges from 0 to 1
    Rational b = 0; //offset of the chosen range
    Rational d = 1; //size of the chosen range
    foreach (var r in inputStream) {
        //narrow the chosen range towards the real value represented by the input
        d /= modIn;
        b += d * r;
        //check for output digits that have become fixed
        while (true) {
            var i1 = (b * modOut).Floor();
            var i2 = ((b + d) * modOut).Floor(); //note: ideally b+d-epsilon, but another iteration makes that correction unnecessary
            if (i1 != i2) break; //digit became fixed?
            //fix the next output digit (rescale the range to make next digit range from 0 to 1)
            d *= modOut;
            b *= modOut;
            b -= i1;
            yield return i1;
        }
    }
}

“但是在极端情况下不太可能”-此事件的概率为;我们说“几乎可以肯定”不会发生。0
拉斐尔

2

生成二进制十进制。无需明确存储它,只需跟踪最小和最大可能值。一旦这些值位于同一整数内,则返回该整数。代码草图如下。

(编辑)更为详尽的解释:假设您要生成一个1到3之间(含1/3概率)的随机整数。为此,我们生成一个随机的二进制十进制实数,x在(0,1)范围内。如果x <1/3,则返回1,否则,如果x <2/3,则返回2,否则,返回3。我们没有跟踪x的位数,而是跟踪x的最小和最大值。最初,x的最小值为0,最大值为1。如果先翻转头部,则x的小数点后的第一位(二进制)为1。则x的最小可能值(二进制)为0.100000 = 1/2,最大值为0.111111111 =1。现在,如果您的下一个翻转是尾巴,则x以0.10开始。最小可能值为0.1000000 = 1/2,最大为0.1011111 = 3/4。x的最小可能值为1/2,所以你知道 s没有机会返回1,因为这需要x <1/3。如果x最终为1/2 <x <2/3,或者2/3 <x <3/4,则返回3,仍然可以返回2。现在假设第三次翻转是尾巴。然后x必须以0.100开头。最小值= 0.10000000 = 1/2,最大值= 0.100111111 = 5/8。现在既然1/3 <1/2 <5/8 <2/3,我们知道x必须落在间隔(1/3,2/3)中,所以我们可以停止生成x的数字而只返回2。

该代码基本上执行此操作,除了在a和b之间生成x而不是在0和1之间生成x之外,但是原理是相同的。

def gen(a, b):
  min_possible = a
  max_possible = b

  while True:
    floor_min_possible = floor(min_possible)
    floor_max_possible = floor(max_possible)
    if max_possible.is_integer():
      floor_max_possible -= 1

    if floor_max_possible == floor_min_possible:
      return floor_max_possible

    mid = (min_possible + max_possible)/2
    if coin_flip():
      min_possible = mid
    else:
      max_possible = mid

备注:我针对接受/拒绝方法测试了此代码,并且均产生了均匀分布。此代码所需的硬币翻转次数比接受拒绝次数少,除非b-a接近下一个2的幂。例如,如果要生成a = 0,b = 62,则接受/拒绝效果更好。我能够证明,此代码平均可以比接受/拒绝多使用2次硬币翻转。根据我的阅读,看来Knuth和Yao(1976)提供了一种解决此问题的方法,并证明了他们的方法在预期的硬币翻转次数中是最佳的。他们进一步证明,预期的翻转次数必须大于分布的香农熵。但是我找不到本文的副本,因此很想知道他们的方法是什么。(更新:刚刚在这里找到了Knuth Yao 1976的展览:http://www.nrbook.com/devroye/Devroye_files/chapter_fifteen_1.pdf, 但我尚未阅读)。有人在此主题中还提到了Han Hoshi,这似乎更为笼统,并使用偏向硬币来解决。另请参阅Pae(2009)撰写的http://paper.ijcsns.org/07_book/200909/20090930.pdf,以获得对文献的良好讨论。


1

简单的答案?

b-一种[R,请检查它是否在范围内,如果不是,请再次生成它。

您最可能需要重新生成的时间 [R 那时候 b-一种=2ñ+1个 对于一些整数 ñ,但即使那样,每一代都将有超过50%的机会落入范围内。


这似乎还不完整。
Dave Clarke

1

对于b-a不等于2 ^ k的情况,这是一种建议的解决方案。它应该以固定的步骤数运行(无需丢弃超出您预期范围的候选者)。

但是,我不确定这是否正确。请批评并帮助描述此随机数发生器(如果有)中的确切不均匀性,以及如何对其进行测量/量化。

首先转换为生成范围为[0,z-1]的均匀分布随机数的等效问题,其中z = b-a。

同样,令m = 2 ^ k是2> = z的最小幂。

根据上述解决方案,我们已经有一个范围为[0,m-1]的均匀分布的随机数生成器R(m)(可以通过扔k个硬币(每个比特一个)来完成)。

    Keep a random seed s and initialize with s = R(m).   

    function random [0, z-1] :
        x = R(m) + s 
        while x >= z:
            x -= z
        s = x
        return x

while循环最多运行3次,以固定的步数给出下一个随机数(最佳情况=最坏情况)。

在此处查看数字[0,2]的测试程序:http : //pastebin.com/zuDD2V6H


这不是统一的。采取ž=3。你得到=4 并且概率是 1个/21个/41个/4
Yuval

请更仔细地查看伪代码以及链接的代码。它确实以几乎相同的频率发出0、1和2 ...
vpathak

没错,当您单独查看输出时,它们是一致的(固定概率是一致的)。但是他们不是独立的。如果您刚刚输出0例如,那么下一个输出的概率为零 1个/2 和一两个可能 1个/4 每。
Yuval Filmus 2013年

您可以用单行替换整个函数:return s =(s + R(m))%z;
Yuval Filmus 2013年

1

理论上最优算法

这是我发布的其他答案的改进。另一个答案的优点是,它更容易扩展到从另一个生成一个离散分布的更一般的情况。实际上,由于Han和Hoshi,另一个答案是该算法的特殊情况。

我将在此处描述的算法基于Knuth和Yao(1976)。在他们的论文中,他们还证明了该算法实现了最小的硬币翻转预期次数。

为了说明这一点,请考虑其他答案描述的拒绝采样方法。例如,假设您要统一生成5个数字之一[0,4]。下一个2的幂是8,因此您掷硬币3次并生成最多8的随机数。如果数字是0到4,则将其返回。否则,将其丢弃,并生成最多8个数字,然后重试,直到成功。但是当您丢掉数字时,您只是浪费了一些熵。取而代之的是,您可以限制所扔出的号码的数量,以减少将来所需的抛硬币次数。具体而言,一旦生成数字[0,7],则返回[0,4]。否则,它是5、6或7,并且每种情况下您都会做不同的事情。如果是5,则再次翻转硬币,然后根据翻转返回0或1。如果是6 掷硬币并返回2或3。如果是7,则掷硬币;如果是头,则返回4;如果是头,则返回4。

我们最初失败尝试的剩余熵给了我们3种情况(5、6或7)。如果我们仅把它扔掉,就扔掉log2(3)硬币翻转。我们取而代之,将其与另一次翻转的结果结合起来以生成6种可能的情况(5H,5T,6H,6T,7H,7T),这让我们立即再次尝试生成具有5/6成功概率的最终答案。

这是代码:

# returns an int from [0, b)
def __gen(b):
  rand_num = 0
  num_choices = 1

  while True:
    num_choices *= 2
    rand_num *= 2
    if coin.flip():
      rand_num += 1

    if num_choices >= b:
      if rand_num < b:
        return rand_num
      num_choices -= b
      rand_num -= b

# returns an int from [a, b)
def gen(a, b):
  return a + __gen(b - a)
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.