Gerando números aleatórios distribuídos uniformemente usando uma moeda


25

Você tem uma moeda. Você pode girá-lo quantas vezes quiser.

Você deseja gerar um número aleatório tal que onde .a r < b r , a , b Z +rar<br,a,bZ+

A distribuição dos números deve ser uniforme.

É fácil se :ba=2n

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

E se ba2n ?


Use o algoritmo Han-Hoshi - basicamente divida o intervalo em dois, use seu bit aleatório (coin flip) para escolher aleatoriamente um dos dois intervalos e repita esse processo do lado que você escolheu até ficar sem bits. Isso fornecerá um intervalo distribuído uniformemente a partir de uma partição da linha real. Quanto mais flips você tiver, mais preciso será o intervalo.
Zenna

Respostas:


13

O que você procura é baseado na amostragem de rejeição ou no método de aceitação / rejeição (observe que a página da Wiki é um pouco técnica).

Esse método é útil nesses tipos de situações: você deseja escolher algum objeto aleatório de um conjunto (um número inteiro aleatório no conjunto no seu caso), mas não sabe como fazer isso, mas pode escolher algum objeto aleatório de um conjunto maior contendo o primeiro conjunto (no seu caso, [ a , 2 k + a ] para alguns k, de modo que 2 k + a b ; isso corresponde a k lançamentos de moedas).[a,b][a,2k+a]k2k+abk

Nesse cenário, você continua escolhendo elementos do conjunto maior até escolher aleatoriamente um elemento no conjunto menor. Se o seu conjunto menor for grande o suficiente em comparação com o maior (no seu caso, contém no máximo o dobro de números inteiros que [ a , b ] , o que é bom o suficiente), isso é eficiente.[a,2k+a][a,b]

Um exemplo alternativo: suponha que você queira escolher um ponto aleatório dentro de um círculo com raio 1. Agora, não é realmente fácil criar um método direto para isso. Voltamos ao método de aceitação e rejeição: amostramos pontos em um quadrado 1x1 que abrange o círculo e testamos se o número que desenhamos está dentro do círculo.


3
Observe que, se rejeitarmos amostras de para obter uma distribuição em B , o número esperado de iterações será | Um |UMAB(enquanto realizamos um experimento com distribuição geométrica). |UMA||B|
Raphael

Lembro-me de ver em algum lugar que isso não pode ser feito exatamente, a menos que o intervalo seja uma potência de 2 (como é lógico, por exemplo, 1/3 não tem expansão binária final).
vonbrand

7

(detalhes técnicos: a resposta se encaixa na seleção do número )umax<b

Como você pode jogar sua moeda quantas vezes quiser, você pode obter a probabilidade o mais próximo possível de uniformizar escolhendo uma fração (usando o radical binário: você gira o moeda para cada dígito após o ponto) e multiplique r por b - a para obter um número entre 0 e [ba-1] (arredondando para baixo para um número inteiro). Adicionar este número para um e está feito.r[0 0,1 1]rb-umauma

Exemplo : diga . 1/3 em binário é 0,0101010101 .... Então, se seu flip estiver entre 0 e 0,010101 ... sua escolha será b . se estiver entre 0,010101 .. e 0,10101010 ... sua escolha será um + 1 e, caso contrário, será um + 2 .b-uma=3buma+1 1uma+2

Se você jogar sua moeda vezes, cada número entre a e b será escolhido com probabilidade 1tumab.1 1b-uma±2-(t+1 1)


11
Isso não dá uma distribuição uniforme. Para algumas aplicações (por exemplo, criptografia, às vezes), isso pode ser muito ruim.
Gilles 'SO- stop being evil'

3
@ Gilles: Ele pode ser corrigido para fornecer uma distribuição perfeitamente uniforme, girando até que não seja mais possível que o resultado mude. Esta é a resposta mais eficiente.
21412 Neil G

@ NeilG Eu sei que pode ser corrigido, mas corrigi-lo seria uma parte crucial da resposta.
Gilles 'SO- stop being evil'

2
@Gilles: Você está certo. Ele poderia modificar a resposta para dizer que você pode produzir uma distribuição perfeitamente uniforme se você girar enquanto . +1 de mim para o melhor caso médio e o pior caso. (b-uma)(f+2-t-1 1)(b-uma)(f-2-t-1 1)
21412 Neil G

@ NeilG, não pode ser "fixo", pois existe um conjunto considerável de números inteiros que não possuem uma fração binária final.
vonbrand

7

Escolha um número na próxima potência maior de 2 e descarte respostas maiores que .buma

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
E por que isso funciona?
Raphael

@Raphael, você é cético ou deseja apenas que o pôster explique com mais detalhes?
Suresh

11
@ Suresh: O último. O pseudo-código pode ser um pouco polido, mas implementa o que outros respondentes explicam. Sem justificativa, essa resposta não vale muito por si só.
Raphael

6

Ninguém mencionou isso, então deixe-me formalmente provar que, se é um domínio cujo tamanho não é uma potência de dois, em seguida, um número finito de jogadas justo moeda não são suficientes para gerar um membro uniformemente aleatória de D . Suponha que você use k moedas para gerar um membro da D . Para cada d D , a probabilidade de que o seu algoritmo gerado d é da forma A / 2 k para algum número inteiro um . O teorema fundamental da aritmética mostra que A / 2 k1 / | D | .DDkDdDdUMA/2kUMAUMA/2k1 1/|D|

Se você deseja gerar amostras uniformes independentes de D , o número esperado de lançamentos de moedas que você precisa (sob o algoritmo ideal) é aproximadamente n log 2 | D | . De maneira mais geral, se você deseja gerar a partir de uma distribuição de entropia H , precisa de aproximadamente n H bits aleatórios em expectativa. Um algoritmo que consegue isso é a decodificação aritmética, aplicada a uma sequência (aparentemente) infinita de bits aleatórios.nDnregistro2|D|HnH


3

Se ba não for uma potência de 2, talvez você precise jogar muitas moedas para obter um resultado. Você pode até nunca obter um resultado, mas isso é improvável ao extremo.

Métodos

O método mais simples é gerar um número em [a, a + 2 ^ n), onde 2 ^ n> = ba, até que aconteça em [a, b). Você joga fora muita entropia com esse método.

Um método mais caro permite manter toda a entropia, mas se torna muito caro computacionalmente à medida que o número de lançamentos de moedas / dados aumenta. Intuitivamente, é como tratar a moeda virar como os dígitos de um número binário à direita da vírgula decimal, convertendo esse número da base 2 para a base ab depois e retornando os dígitos desse número à medida que ficam "presos".

Exemplo

O código a seguir converte os rolos de uma matriz justa dos lados em rolos de uma matriz justa dos lados (no seu caso n = 2, m = ab), com um custo marginal crescente à medida que o número de análises aumenta. Observe a necessidade de um tipo de número Rational com precisão arbitrária. Uma boa propriedade é que a conversão de frente e verso para frente e verso retornará o fluxo original, embora talvez tenha sido adiado por alguns rolos, devido à inserção de dígitos.

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;
        }
    }
}

"mas isso é improvável ao extremo" - a probabilidade para este evento é ; dizemos que "quase certamente" não acontece. 0 0
Raphael

2

Gere um decimal binário. Em vez de armazená-lo explicitamente, basta acompanhar os valores mínimo e máximo possíveis. Quando esses valores estiverem dentro do mesmo número inteiro, retorne esse número inteiro. O esboço do código está abaixo.

(Editar) Explicação mais completa: digamos que você queira gerar um número inteiro aleatório de 1 a 3, inclusive com probabilidade de 1/3 cada. Fazemos isso gerando um real decimal binário aleatório, x no intervalo (0, 1). Se x <1/3, retornar 1, senão se x <2/3 retornar 2, senão retornar 3. Em vez de gerar os dígitos para x explicitamente, apenas controlamos os valores mínimos e máximos possíveis de x. Inicialmente, o valor mínimo de x é 0 e o máximo é 1. Se você virar a cabeça primeiro, o primeiro dígito de x atrás do ponto decimal (em binário) é 1. O valor mínimo possível de x (em binário) se torna 0,100000 = 1/2 e o máximo é 0.111111111 = 1. Agora, se o seu próximo flip for coroa, x começa com 0.10. O valor mínimo possível é 0.1000000 = 1/2 e o máximo é 0.1011111 = 3/4. O valor mínimo possível de x é 1/2, para que você saiba lá ' s sem chance de retornar 1, pois isso requer x <1/3. Você ainda pode retornar 2 se x acabar sendo 1/2 <x <2/3 ou 3 se 2/3 <x <3/4. Agora, suponha que o terceiro flip seja coroa. Então x deve começar com 0.100. Min = 0.10000000 = 1/2 e max = 0.100111111 = 5/8. Agora, já que 1/3 <1/2 <5/8 <2/3 sabemos que x deve cair no intervalo (1/3, 2/3), para que possamos parar de gerar dígitos de x e retornar 2.

O código faz isso essencialmente, exceto que, em vez de gerar x entre 0 e 1, gera x entre a e b, mas o princípio é o mesmo.

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

Observação: testei esse código contra o método de aceitação / rejeição e ambos produzem distribuições uniformes. Esse código requer menos lançamentos de moedas do que aceitar rejeitar, exceto quando b - a estiver próximo da próxima potência de 2. Ex, se você quiser gerar a = 0, b = 62, aceitar / rejeitar será melhor. Eu pude provar que esse código pode usar, em média, mais de 2 lançamentos de moedas do que aceitar / rejeitar. Pela minha leitura, parece que Knuth e Yao (1976) deram um método para resolver esse problema e provaram que o método deles é ideal no número esperado de lançamentos de moedas. Eles provaram ainda que o número esperado de lançamentos deve ser maior que a entropia de Shannon da distribuição. Não consegui encontrar uma cópia do texto do artigo e ficaria curioso para saber qual é o método deles. (Atualização: acabou de encontrar uma exposição de Knuth Yao 1976 aqui:http://www.nrbook.com/devroye/Devroye_files/chapter_fifteen_1.pdf, mas ainda não o li). Alguém também mencionou Han Hoshi neste tópico, que parece ser mais geral e o resolve usando uma moeda tendenciosa. Veja também http://paper.ijcsns.org/07_book/200909/20090930.pdf de Pae (2009) para uma boa discussão da literatura.



1

Esta é uma solução proposta para o caso em que b - a não é igual a 2 ^ k. Supõe-se que ele funcione em um número fixo de etapas (não é necessário descartar candidatos que estão fora do intervalo esperado).

No entanto, não tenho certeza se isso está correto. Critique e ajude a descrever a não uniformidade exata neste gerador de números aleatórios (se houver) e como medi-los / quantificá-los.

Primeiro, converta para o problema equivalente de gerar números aleatórios uniformemente distribuídos no intervalo [0, z-1] onde z = b - a.

Além disso, seja m = 2 ^ k a menor potência de 2> = z.

Conforme a solução acima, já temos um gerador de números aleatórios distribuído uniformemente R (m) no intervalo [0, m-1] (pode ser feito jogando k moedas, uma para cada bit).

    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

O loop while é executado no máximo três vezes, fornecendo o próximo número aleatório em número fixo de etapas (melhor caso = pior caso).

Veja um programa de teste para números [0,2] aqui: http://pastebin.com/zuDD2V6H


z=3m=41 1/2,1 1/4,1 1/4

Por favor, dê uma olhada no pseudo-código e também no código vinculado. Ele emite 0, 1 e 2 quase com igual frequência ...
vpathak

0 01 1/21 1/4

Você pode substituir toda a função por uma única linha: return s = (s + R (m))% z;
usar o seguinte código

1

Algoritmo teoricamente ideal

Aqui está uma melhoria da outra resposta que eu postei. A outra resposta tem a vantagem de ser mais fácil estender ao caso mais geral de gerar uma distribuição discreta a partir de outra. De fato, a outra resposta é um caso especial do algoritmo devido a Han e Hoshi.

O algoritmo que descreverei aqui é baseado em Knuth e Yao (1976). Em seu artigo, eles também provaram que esse algoritmo atinge o número mínimo possível de lançamentos de moedas.

Para ilustrá-lo, considere o método de amostragem Rejeição descrito por outras respostas. Como exemplo, suponha que você queira gerar um de 5 números de maneira uniforme [0, 4]. A próxima potência de 2 é 8, para que você jogue a moeda 3 vezes e gere um número aleatório até 8. Se o número for de 0 a 4, você o retornará. Caso contrário, você o joga fora e gera outro número até 8 e tenta novamente até conseguir. Mas quando você joga fora o número, acaba de desperdiçar um pouco de entropia. Em vez disso, você pode condicionar o número que jogou para reduzir o número de lançamentos futuros de moedas que você precisa em expectativa. Concretamente, depois de gerar o número [0, 7], se for [0, 4], retorne. Caso contrário, são 5, 6 ou 7 e você fará algo diferente em cada caso. Se for 5, jogue a moeda novamente e retorne 0 ou 1 com base no flip. Se é 6, jogue a moeda e retorne 2 ou 3. Se for 7, jogue a moeda; se for cara, retorne 4, se a coroa começar de novo.

A entropia restante de nossa tentativa inicial fracassada nos deu três casos (5, 6 ou 7). Se jogarmos isso fora, jogamos fora o log2 (3) lançamentos de moedas. Em vez disso, mantemos e combinamos com o resultado de outro flip para gerar 6 casos possíveis (5H, 5T, 6H, 6T, 7H, 7T), os quais vamos imediatamente tentar novamente para gerar uma resposta final com probabilidade de sucesso 5/6 .

Aqui está o código:

# 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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.