Генерация равномерно распределенных случайных чисел с использованием монеты


26

У вас есть одна монета. Вы можете перевернуть его столько раз, сколько захотите.

Вы хотите сгенерировать случайное числоr такое, чтогде.ar<br,a,bZ+

Распределение чисел должно быть равномерным.

Это легко, если :ba=2n

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

Что если ?ba2n


Используйте алгоритм Хан-Хоши - в основном разбейте интервал на два, используйте ваш случайный бит (подбрасывание монеты), чтобы случайным образом выбрать один из двух интервалов, затем повторяйте этот процесс на той стороне, которую вы выбрали, пока у вас не закончатся биты. Это даст вам интервал, равномерно распределенный по отрезку реальной линии. Чем больше у вас флипов, тем точнее интервал.
Зенна

Ответы:


14

То, что вы ищете, основано на выборке Rejection или методе accept-reject (обратите внимание, что страница Wiki немного техническая).

Этот метод полезен в таких ситуациях: вы хотите выбрать некоторый случайный объект из набора (случайное целое число в наборе в вашем случае), но вы не знаете, как это сделать, но вы Можно выбрать некоторый случайный объект из большего набора, содержащего первый набор (в вашем случае [ a , 2 k + a ] для некоторого k такого, что 2 k + a b ; это соответствует k броскам монет).[a,b][a,2k+a]k2k+abk

В таком сценарии вы просто продолжаете выбирать элементы из большего набора, пока не выберете случайный элемент из меньшего набора. Если ваш меньший набор достаточно велик по сравнению с вашим большим набором (в вашем случае содержит самое большее в два раза больше целых чисел, чем [ a , b ] , что достаточно хорошо), это эффективно.[a,2k+a][a,b]

Альтернативный пример: предположим, что вы хотите выбрать случайную точку внутри круга с радиусом 1. Теперь не совсем легко придумать прямой метод для этого. Обратимся к методу принятия-отклонения: мы выбираем точки в квадрате 1x1, охватывающем окружность, и проверяем, лежит ли нарисованное число внутри окружности.


3
Обратите внимание, что если мы отклоняем выборки из , чтобы получить распределение по B , ожидаемое количество итераций будет | A |AB(как мы проводим эксперимент с геометрическим распределением). |A||B|
Рафаэль

Я помню, что где-то видел, что это не может быть сделано точно, если диапазон не является степенью 2 (как понятно, например, 1/3 не имеет конечного двоичного расширения).
vonbrand

7

(технические характеристики: ответ соответствует выбору числа )aИкс<б

Так как вам разрешено подбрасывать свою монету столько раз, сколько вы хотите, вы можете получить свою вероятность как можно ближе к униформе, выбрав дробь (используя двоичный ось: вы переворачиваете монеты за каждую цифру после точки) и умножьте r на b - a, чтобы получить число от 0 до [ba-1] (округление до целого числа). Добавьте это число к и вы сделали.р[0,1]рб-aa

Пример : скажем, . 1/3 в двоичном виде - 0,0101010101 .... Тогда, если ваш флип находится в диапазоне от 0 до 0,010101 ... ваш выбор b . если это beween 0.010101 .. и 0.10101010 ... ваш выбор будет + 1 , а в противном случае она будет + 2 .б-aзнак равно3бa+1a+2

Если вы перевернете свою монету раз, то каждое число между a и b будет выбрано с вероятностью 1Taб.1б-a±2-(T+1)


1
Это не дает равномерного распределения. Для некоторых приложений (например, крипто, иногда) это может быть очень плохо.
Жиль "ТАК - перестань быть злым"

3
@ Жиль: Это можно исправить, чтобы получить идеально равномерное распределение, переворачивая, пока не станет невозможным изменение результата. Это самый эффективный ответ.
Нил Дж

@NeilG Я знаю, что это можно исправить, но исправление будет важной частью ответа.
Жиль "ТАК - перестань быть злым"

2
@ Жиль: Ты прав. Он мог бы изменить ответ, чтобы сказать, что вы можете создать абсолютно равномерное распределение, если перевернете, пока . +1 от меня за лучший средний случай и худшее время. (ba)(f+2t1)(ba)(f2t1)
Нил Г

@NeilG, это не может быть «исправлено», так как есть довольно большой набор целых чисел, которые не имеют двоичной дроби в конце.
vonbrand

7

Выберите число в следующей большей степени диапазона 2 и отбросьте ответы, превышающие .ba

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
И почему это работает?
Рафаэль

@ Рафаэль, ты скептически относишься или просто хочешь, чтобы постер объяснил более подробно?
Суреш

2
@ Суреш: последнее. Псевдокод может быть немного отшлифован, но он реализует то, что объясняют другие ответчики. Без обоснования этот ответ сам по себе ничего не стоит.
Рафаэль

6

Никто не говорил об этом, так что позвольте мне формально доказать , что если является областью, размер которого не является степенью двойки, то конечное число бросков справедливой монеты не достаточно , чтобы создать равномерно случайный член D . Предположим , вы используете K монеты для создания члена D . Для каждого д D , вероятность того, что ваш алгоритм генерируется д имеет вид А / 2 к для некоторого целого числа А . Основная теорема арифметики показывает, что A / 2 k1 / | D | ,DDkDdDdA/2kAA/2k1/|D|

Если вы хотите сгенерировать независимых одинаковых выборок D , то ожидаемое количество бросков монет (по оптимальному алгоритму) примерно равно n log 2 | D | , В более общем смысле, если вы хотите генерировать из распределения энтропии H , вам нужно примерно n H случайных битов в ожидании. Алгоритм достижения этого - арифметическое декодирование, применяемое к (якобы) бесконечной последовательности случайных битов.nDnlog2|D|HnH


Тем не менее, это можно увеличить область фиктивное значением , пока ADи затем отбросить любые фиктивные результаты - в этом случае можно сгенерировать равномерно случайный член исходного домена с конечным числом бросков монет. A2k=1|D|
Ричард Эмблер

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, так что вы знаете, что нет шансов вернуть 1, так как для этого требуется х <1/3. Вы все равно можете вернуть 2, если х в конечном итоге будет 1/2 <x <2/3 или 3, если 2/3 <x <3/4. Теперь предположим, что третий бросок - это хвосты. Тогда х должен начинаться с 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.

Код делает это по существу, за исключением того, что вместо генерации x между 0 и 1 он генерирует x между a и b, но принцип тот же.

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 броска монеты больше, чем принять / отклонить. Из моего прочтения похоже, что Кнут и Яо (1976) дали метод решения этой проблемы и доказали, что их метод является оптимальным при ожидаемом количестве подбрасываний монет. Они также доказали, что ожидаемое число переворотов должно быть больше энтропии Шеннона распределения. Однако я не смог найти копию текста статьи, и мне было бы интересно узнать, каков их метод. (Обновление: только что нашел экспозицию Кнута Яо 1976 года здесь:http://www.nrbook.com/devroye/Devroye_files/chapter_fifteen_1.pdf, но я еще не читал его). Кто-то также упомянул Хана Хоши в этой теме, которая кажется более общей, и решает ее с помощью пристрастной монеты. Смотрите также http://paper.ijcsns.org/07_book/200909/20090930.pdf Pae (2009) для хорошего обсуждения литературы.



1

Это предлагаемое решение для случая, когда b - a не равно 2 ^ k. Предполагается, что он работает с фиксированным числом шагов (не нужно выбрасывать кандидатов, которые находятся за пределами ожидаемого диапазона).

Однако я не уверен, что это правильно. Пожалуйста, критикуйте и помогите описать точную неравномерность в этом генераторе случайных чисел (если есть) и как его измерить / измерить.

Сначала перейдем к эквивалентной задаче генерации равномерно распределенных случайных чисел в диапазоне [0, z-1], где z = b - a.

Кроме того, пусть m = 2 ^ k будет наименьшей степенью 2> = z.

Согласно приведенному выше решению, у нас уже есть равномерно распределенный генератор случайных чисел R (m) в диапазоне [0, m-1] (это можно сделать, подбросив 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


z=3m=4 and the probabilities are 1/2,1/4,1/4.
Юваль Фильмус

Please have a look at the pseudo code as well as the linked code more closely. It does emit 0, 1, and 2 almost with equal frequency...
vpathak

You're right, when you look at the outputs individually, they are uniform (the stationary probability is uniform). But they're not independent. If you have just output 0, say, then the next output is zero with probability 1/2 and one or two with probability 1/4 each.
Юваль Фильмус

You can replace the entire function with a single line: return s = (s + R(m)) % z;
Yuval Filmus

1

Теоретически оптимальный алгоритм

Вот улучшение другого ответа, который я отправил. Другой ответ имеет то преимущество, что его легче распространить на более общий случай генерации одного дискретного распределения из другого. На самом деле, другой ответ является частным случаем алгоритма из-за Хана и Хоши.

Алгоритм, который я опишу здесь, основан на Кнуте и Яо (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, если хвосты начнутся сначала.

Оставшаяся энтропия от нашей первоначальной неудачной попытки дала нам 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)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.