Генерація рівномірно розподілених випадкових чисел за допомогою монети


25

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

Ви хочете створити випадкове число таке, що a r < b, де r , a , b Z + .rar<br,a,bZ+

Розподіл чисел повинен бути рівномірним.

Легко, якщо :ba=2n

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

Що робити, якщо ?ba2n


Використовуйте алгоритм Хан-Хоші - в основному розділіть інтервал на два, використовуйте випадковий біт (фліп монети), щоб випадковим чином вибрати один з двох інтервалів, а потім повторіть цей процес на стороні, яку ви вибрали, поки у вас не закінчиться біт. Це дасть вам інтервал, рівномірно розподілений від розділу реальної лінії. Чим більше фліп у вас, тим точніший інтервал.
zenna

Відповіді:


13

Те, що ви шукаєте, ґрунтується на вибірці відхилення або методі прийняття-відхилення (зауважте, що сторінка 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. Тепер не дуже легко придумати для цього прямий метод. Переходимо до методу прийняття-відхилення: відбираємо точки в квадраті 1х1, що охоплює коло, і перевіряємо, чи число, яке ми малюємо, лежить всередині кола.


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

Я пригадую, що десь бачив, що це неможливо зробити точно, якщо діапазон не є силою 2 (як очевидно, наприклад, 1/3 не має закінчуваного бінарного розширення).
фонбранд

7

(технічні характеристики: відповідь відповідає вибору числа )ax<b

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

Приклад : скажіть . 1/3 у двійковій формі - 0,0101010101 .... Тоді, якщо ваш фліп буде від 0 до 0,010101 ... ваш вибір - b . якщо це beween 0.010101 .. і 0.10101010 ... ваш вибір буде + 1 , а в іншому випадку вона буде + 2 .ba=3ba+1a+2

Якщо ви перевернете монету разів, то кожне число між a і b буде вибрано з вірогідністю 1tab.1ba±2(t+1)


1
Це не дає рівномірного розподілу. Для деяких додатків (наприклад, крипто, іноді) це може бути дуже погано.
Жил "ТАК - перестань бути злим"

3
@Gilles: Це можна виправити, щоб надати ідеально рівномірний розподіл, гортаючи, доки результат уже не зміниться. Це найефективніша відповідь.
Ніл Г

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

2
(ba)(f+2t1)(ba)(f2t1)

@NeilG, його неможливо "виправити", оскільки існує досить великий набір цілих чисел, у яких немає бічного дробу, що закінчується.
vonbrand

7

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
І чому це працює?
Рафаель

@Raphael Ви скептично налаштовані чи просто хочете, щоб плакат пояснив більш детально?
Суреш

1
@Suresh: Останнє. Псевдо-код можна трохи відполірувати, але він реалізує те, що пояснюють інші відповіді. Без обґрунтування ця відповідь сама по собі не варта.
Рафаель


3

Якщо ба - не потужність 2, то, можливо, вам доведеться перевернути багато монет, щоб отримати результат. Можливо, ви навіть ніколи не отримаєте результату, але це вкрай малоймовірно.

Методи

Найпростіший метод - це генерувати число в [a, a + 2 ^ n), де 2 ^ n> = ba, поки не трапиться посадка в [a, b). Цим методом ви викидаєте багато ентропії.

Більш дорогий метод дозволяє зберегти всю ентропію, але стає дуже дорогим обчислювально, оскільки збільшується кількість переворотів / кісток монети. Інтуїтивно це схоже на обробку монети, яка розгортається як цифри двійкового числа праворуч від десяткової крапки, перетворення цього числа з бази 2 в базу ab після і повернення цифр цього числа, коли вони "застрягли".

Приклад

Наступний код перетворює рулони справедливої ​​n-сторонньої штампи в рулони справедливого m-sided штампу (у вашому випадку n = 2, m = ab) зі збільшенням граничної вартості у міру збільшення кількості рулонів. Зверніть увагу на необхідність типу раціонального числа з довільною точністю. Одним із приємних властивостей є те, що перетворення з n-sided на m-sided та назад у n-sided поверне початковий потік, хоча, можливо, затримається на пару рулонів через цифри, які мають застрягти.

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. Тепер, якщо ваш наступний фліп - хвости, х починається з 0,10. Мінімальне можливе значення - 0,1000000 = 1/2, а максимальне - 0,1011111 = 3/4. Мінімальне можливе значення х становить 1/2, щоб ви знали, що там ' s немає шансів повернути 1, оскільки для цього потрібно x <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 ми знаємо, що х повинен потрапляти в інтервал (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=41/2,1/4,1/4

Будь ласка, уважніше подивіться на псевдо-код, а також на пов'язаний код. Він випромінює 0, 1 і 2 майже з однаковою частотою ...
vpathak

01/21/4

Ви можете замінити всю функцію одним рядком: 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.