Algoritmo zigurate
O algoritmo zigurate é um algoritmo para amostragem de números pseudoaleatórios . Pertencente à classe de algoritmos de amostragem de rejeição, ele depende de uma fonte subjacente de números aleatórios uniformemente distribuídos, normalmente de um gerador de números pseudoaleatórios, bem como de tabelas pré-calculadas. O algoritmo é usado para gerar valores de uma distribuição de probabilidade monotonicamente decrescente . Também pode ser aplicado a distribuições unimodais simétricas, como a distribuição normal, escolhendo um valor de uma metade da distribuição e, em seguida, escolhendo aleatoriamente de qual metade o valor é considerado como tendo sido extraído. Foi desenvolvido por George Marsaglia e outros na década de 1960.
Um valor típico produzido pelo algoritmo requer apenas a geração de um valor de ponto flutuante aleatório e um índice de tabela aleatório, seguido por uma consulta de tabela, uma operação de multiplicação e uma comparação. Às vezes (2,5% do tempo, no caso de uma distribuição normal ou exponencial ao usar tamanhos de tabela típicos) mais cálculos são necessários. No entanto, o algoritmo é computacionalmente muito mais rápidodo que os dois métodos mais comumente usados para gerar números aleatórios distribuídos normalmente, o método polar de Marsaglia e a transformada de Box-Muller, que requerem pelo menos um logaritmo e um cálculo de raiz quadrada para cada par de valores gerados. No entanto, como o algoritmo zigurate apresenta maior complexidade de implementação, ele é melhor usado quando grandes quantidades de números aleatórios são necessárias.
O termo algoritmo zigurate data do artigo de Marsaglia com Wai Wan Tsang em 2000; é assim chamado porque é conceitualmente baseado na cobertura da distribuição de probabilidade com segmentos retangulares empilhados em ordem decrescente de tamanho, resultando em uma figura que se assemelha a um zigurate .
Teoria da operação
[editar | editar código-fonte]O algoritmo zigurate é um algoritmo de amostragem de rejeição; ele gera aleatoriamente um ponto em uma distribuição ligeiramente maior que a distribuição desejada e, então, testa se o ponto gerado está dentro da distribuição desejada. Caso contrário, ele tenta novamente. Dado um ponto aleatório abaixo de uma curva de densidade de probabilidade, sua coordenada x é um número aleatório com a distribuição desejada.
A distribuição escolhida pelo algoritmo zigurate é composta por n regiões de área igual; n − 1 retângulos que cobrem a maior parte da distribuição desejada, sobre uma base não retangular que inclui a cauda da distribuição.
Dada uma função de densidade de probabilidade decrescente monótona f(x), definida para todo x ≥ 0, a base do zigurate é definida como todos os pontos dentro da distribuição e abaixo de y 1 = f(x1). Consiste em uma região retangular de (0, 0) a (x1 , y1), e a cauda (tipicamente infinita) da distribuição, onde x > x1 (e y < y1 ).
Esta camada (vamos chamá-la camada 0) tem área A . Em cima dela, adicione uma camada retangular de largura x1 e altura A / x1, para que ela também tenha área A. O topo desta camada está na altura y 2 = y1 + A / x1, e intercepta a função de densidade em um ponto ( x2 , y2 ), onde y2 = f(x2). Esta camada inclui todos os pontos na função de densidade entre y 1 e y 2, mas (ao contrário da camada base) também inclui pontos como (x1 , y2) que não estão na distribuição desejada.
Outras camadas são então empilhadas em cima. Para usar uma tabela pré-computada de tamanho n ( n = 256 é típico), escolhe-se x1 tal que xn = 0, o que significa que a caixa superior, camada n − 1, atinge o pico da distribuição em (0, f(0)) exatamente.
A camada i se estende verticalmente de y i para yi +1, e pode ser dividido em duas regiões horizontalmente: a porção (geralmente maior) de 0 a xi +1 que está inteiramente contido na distribuição desejada, e a (pequena) porção de xi +1 para xi, que está apenas parcialmente contido.
Ignorando por um momento o problema da camada 0, e dadas as variáveis aleatórias uniformes U0 e U1 ∈ [0,1), o algoritmo do zigurate pode ser descrito como:
- Escolha uma camada aleatória 0 ≤ i < n .
- Seja x = U0xi .
- Se x < xi +1, retorna x .
- Seja y = yi + U1 ( yi +1 − yi ).
- Calcule f(x). Se y < f(x), retorne x .
- Caso contrário, escolha novos números aleatórios e volte para a etapa 1.
O passo 1 consiste em escolher uma coordenada y de baixa resolução. A etapa 3 testa se a coordenada x está claramente dentro da função de densidade desejada sem saber mais sobre a coordenada y. Caso contrário, a etapa 4 escolhe uma coordenada y de alta resolução e a etapa 5 faz o teste de rejeição.
Com camadas muito próximas, o algoritmo termina na etapa 3 em uma fração muito grande do tempo. Para a camada superior n − 1, no entanto, esse teste sempre falha, porque xn = 0.
A camada 0 também pode ser dividida em uma região central e uma borda, mas a borda é uma cauda infinita. Para usar o mesmo algoritmo para verificar se o ponto está na região central, gere um x0 = A / y1 fictício. Isso gerará pontos com x < x1 com a frequência correta e, no caso raro de a camada 0 ser selecionada e x ≥ x1, use um algoritmo de fallback especial para selecionar um ponto aleatoriamente na cauda. Como o algoritmo de fallback é usado menos de uma vez em mil, a velocidade não é essencial.
Assim, o algoritmo zigurate completo para distribuições unilaterais é:
- Escolha uma camada aleatória 0 ≤ i < n .
- Seja x = U0xi
- Se x < xi +1, retorna x .
- Se i = 0, gere um ponto a partir da cauda usando o algoritmo de fallback.
- Seja y = yi + U1(yi +1 − yi ).
- Calcule f (x). Se y < f (x), retorne x .
- Caso contrário, escolha novos números aleatórios e volte para a etapa 1.
Para uma distribuição bilateral, o resultado deve ser negado 50% das vezes. Isso pode ser feito convenientemente escolhendo U0 ∈ (−1,1) e, na etapa 3, testando se | x | < xi +1 .
Algoritmos de fallback para a cauda
[editar | editar código-fonte]Porque o algoritmo zigurate só gera a maioria das saídas muito rapidamente e requer um algoritmo de fallback sempre que x > x1, é sempre mais complexo do que uma implementação mais direta. O algoritmo de fallback específico depende da distribuição.
Para uma distribuição exponencial, a cauda se parece com o corpo da distribuição. Uma maneira é recorrer ao algoritmo mais elementar E = −ln( U1 ) e deixe x = x1 − em( U1 ). Outra é chamar o algoritmo do zigurate recursivamente e adicionar x1 ao resultado.
Para uma distribuição normal, Marsaglia sugere um algoritmo compacto:
- Seja x = −ln(U1)/ x1 .
- Seja y = −ln(U2).
- Se 2y > x2, retorne x + x1 .
- Caso contrário, volte para a etapa 1.
Como x 1 ≈ 3,5 para tamanhos de tabela típicos, o teste na etapa 3 quase sempre é bem-sucedido. Como −ln(U1) é uma variável distribuída exponencialmente, uma implementação da distribuição exponencial pode ser usada.
Otimizações
[editar | editar código-fonte]O algoritmo pode ser executado eficientemente com tabelas pré-calculadas de x i e y i = f (xi), mas há algumas modificações para torná-lo ainda mais rápido:
- Nada no algoritmo do zigurate depende da função de distribuição de probabilidade ser normalizada (integral sob a curva igual a 1). A remoção de constantes de normalização pode acelerar o cálculo de f(x).
- A maioria dos geradores uniformes de números aleatórios são baseados em geradores de números aleatórios inteiros que retornam um inteiro no intervalo [0, 232 − 1]. Uma tabela de 2−32 x i permite que você use esses números diretamente para U0 .
- Ao calcular distribuições de dois lados usando um U0 de dois lados, conforme descrito anteriormente, o inteiro aleatório pode ser interpretado como um número assinado no intervalo [−231 , 231 − 1], e um fator de escala de 2−31 pode ser usado.
- Em vez de comparar U0xi com xi +1 na etapa 3, é possível pré-calcular xi +1/x i e compare U0 com isso diretamente. Se U0 for um gerador de números aleatórios inteiros, esses limites podem ser pré-multiplicados por 232 (ou 231, conforme apropriado) para que uma comparação inteira possa ser usada.
- Com as duas alterações acima, a tabela de valores xi não modificados não é mais necessária e pode ser excluída.
- Ao gerar valores de ponto flutuante de precisão simples IEEE 754, que têm apenas uma mantissa de 24 bits (incluindo o 1 inicial implícito), os bits menos significativos de um número aleatório inteiro de 32 bits não são usados. Esses bits podem ser usados para selecionar o número da camada. (Veja as referências abaixo para uma discussão detalhada sobre isso.)
- Os três primeiros passos podem ser colocados em uma função inline, que pode chamar uma implementação fora de linha dos passos menos frequentemente necessários.
Gerando as tabelas
[editar | editar código-fonte]É possível armazenar a tabela inteira pré-calculada ou apenas incluir os valores n, y1, A e uma implementação de f −1(y) no código-fonte e calcule os valores restantes ao inicializar o gerador de números aleatórios.
Conforme descrito anteriormente, você pode encontrar xi = f −1(yi) e yi +1 = yi + A/xi . Repita n − 1 vez para cada um das camadas do zigurate. No final, você deve ter yn = f(0). Haverá algum erro de arredondamento, mas é um teste de sanidade útil para verificar se ele é aceitavelmente pequeno.
Ao preencher os valores da tabela, basta assumir que xn = 0 e yn = f(0), e aceitar a ligeira diferença na área da camada n − 1 como erro de arredondamento.
Encontrando x1 e A
[editar | editar código-fonte]Dado um valor inicial (estimado) x1, você precisa de uma maneira de calcular a área t da cauda para a qual x > x1. Para a distribuição exponencial, isto é apenas e− x1, enquanto para a distribuição normal, supondo que você esteja usando o não normalizado f (x) = e−x2/2, isto é √π/2 erfc (x /√2). Para distribuições mais complicadas, pode ser necessária integração numérica.
Com isso em mãos, a partir de x 1, você pode encontrar y 1 = f (x1), a área t na cauda e a área da camada base A = x1y1 + t .
Em seguida, calcule as séries yi e xi como acima. Se yi > f(0) para qualquer i < n, então a estimativa inicial x1 era muito baixa, levando a uma área A muito grande. Se yn < f (0), então a estimativa inicial x1 era muito alta.
Dado isso, use um algoritmo de busca de raízes (como o método da bissecção) para encontrar o valor x1 que produz yn −1 o mais próximo possível de f (0). Alternativamente, procure o valor que torna a área da camada superior, xn −1(f(0) − yn −1), o mais próximo possível do valor desejado A. Isso economiza uma avaliação de f −1( x ) e é na verdade a condição de maior interesse.
Variação de McFarland
[editar | editar código-fonte]Christopher D. McFarland propôs uma versão ainda mais otimizada.[1] Isso aplica três alterações algorítmicas, às custas de tabelas um pouco maiores.
Primeiro, o caso comum considera apenas as porções retangulares, de (0, yi −1 ) para (xi , yi) As regiões de formato estranho à direita destas (a maioria quase triangular, mais a cauda) são tratadas separadamente. Isso simplifica e acelera o caminho rápido do algoritmo.
Em segundo lugar, a área exata das regiões de formato ímpar é usada; elas não são arredondadas para incluir o retângulo inteiro para (xi -1 , yi ). Isso aumenta a probabilidade de que o caminho rápido seja usado.
Uma consequência importante disso é que o número de camadas é ligeiramente menor que n . Mesmo que a área das porções de formato ímpar seja tomada exatamente, o total soma mais do que o valor de uma camada. A área por camada é ajustada para que o número de camadas retangulares seja um número inteiro. Se o 0 inicial ≤ i < n excede o número de camadas retangulares, a fase 2 prossegue.
Se o valor procurado estiver em qualquer uma das regiões de formato ímpar, o método de alias será usado para escolher uma, com base em sua área real. Isso representa uma pequena quantidade de trabalho adicional e requer tabelas de alias adicionais, mas escolhe um dos lados direitos das camadas.
A região de formato ímpar escolhida é rejeitada, mas se uma amostra for rejeitada, o algoritmo não retorna ao início. A área real de cada região de formato estranho foi usada para escolher uma camada, de modo que o loop de rejeição-amostragem permanece nessa camada até que um ponto seja escolhido.
Terceiro, o formato quase triangular da maioria das porções de formatos estranhos é explorado, embora isso deva ser dividido em três casos, dependendo da segunda derivada da função de distribuição de probabilidade na camada selecionada.
Se a função for convexa (como a distribuição exponencial está em toda parte, e a distribuição normal é para |x| > 1), então a função está estritamente contida dentro do triângulo inferior. Dois desvios uniformes unitários U1 e U2 são escolhidos e, antes de serem dimensionados para o retângulo que envolve a região de formato ímpar, sua soma é testada. Se U1 + U2 > 1, o ponto está no triângulo superior e pode ser refletido para (1− U1 , 1− U2). Então, se U1 + U2 < 1− ε, para alguma tolerância adequada ε, o ponto está definitivamente abaixo da curva e pode ser imediatamente aceito. Somente para pontos muito próximos da diagonal é necessário calcular a função de distribuição f(x) para executar um teste de rejeição exato. (A tolerância ε deve, em teoria, depender da camada, mas um único valor máximo pode ser usado em todas as camadas com pouca perda.)
Se a função for côncava (como a distribuição normal é para |x| < 1), inclui uma pequena porção do triângulo superior, de modo que a reflexão é impossível, mas pontos cujas coordenadas normalizadas satisfazem U1 + U2 ≤ 1 podem ser imediatamente aceitos, e pontos para os quais U1 + U2 > 1+ε podem ser imediatamente rejeitados.
Na camada que atravessa |x| = 1, a distribuição normal tem um ponto de inflexão e o teste de rejeição exato deve ser aplicado se 1− ε < U1 + U2 < 1+ ε.
A cauda é tratada como no algoritmo zigurate original e pode ser considerada um quarto caso para o formato da região de formato ímpar à direita.
Referências
[editar | editar código-fonte]- ↑ McFarland, Christopher D. (24 junho 2015). «A modified ziggurat algorithm for generating exponentially and normally distributed pseudorandom numbers». Journal of Statistical Computation and Simulation. 86 (7): 1281–1294. arXiv:1403.6870. doi:10.1080/00949655.2015.1060234 Note that the Bitbucket repository mentioned in the paper is no longer available and the code is now at https://github.com/cd-mcfarland/fast_prng
- Marsaglia, George; Tsang, Wai Wan (2 outubro 2000). «The Ziggurat Method for Generating Random Variables». Journal of Statistical Software. 5 (8). Consultado em 20 de junho de 2007 Este artigo numera as camadas de 1 iniciando no topo, e a camada 0 fica na parte mais baixa.
- C implementation of the ziggurat method for the normal density function and the exponential density function, aquele é essencialmente uma cópia do código presente no artigo. (Usuários devem estar cientes que este código presume o uso de inteiros de 32 bits.)
- A C# implementation do algoritmo zigurate e explicação do método.
- Jurgen A. Doornik (2005). «An Improved Ziggurat Method to Generate Normal Random Samples» (PDF). Nuffield College, Oxford. Consultado em 20 de junho de 2007 Descreve os cuidados ao se usar bits de menor ordem do gerador de números aleatórios quando da escolha do número da camada.
- Normal Behavior By Cleve Moler, MathWorks, descrebe o algoritmo zigurate usado na versão 5 do MATLAB, de 2001.
- The Ziggurat Random Normal Generator Blogs da MathWorks, postado por Cleve Moler, 18 de maio de 2015.
- David B. Thomas; Philip H.W. Leong; Wayne Luk; John D. Villasenor (outubro 2007). «Gaussian Random Number Generators» (PDF). ACM Computing Surveys. 39 (4): 11:1–38. ISSN 0360-0300. doi:10.1145/1287620.1287622. Consultado em 27 de julho de 2009.
[W]hen maintaining extremely high statistical quality is the first priority, and subject to that constraint, speed is also desired, the Ziggurat method will often be the most appropriate choice.
Comparação de vários algoritmos para geração de números aleatórios de Distribuição Normal. - Nadler, Boaz (2006). «Design Flaws in the Implementation of the Ziggurat and Monty Python methods (And some remarks on Matlab randn)». arXiv:math/0603058.
- Edrees, Hassan M.; Cheung, Brian; Sandora, McCullen; Nummey, David; Stefan, Deian (13–16 julho 2009). Hardware-Optimized Ziggurat Algorithm for High-Speed Gaussian Random Number Generators (PDF). 2009 International Conference on Engineering of Reconfigurable Systems & Algorithms. Las Vegas
- Marsaglia, George (setembro 1963). Generating a Variable from the Tail of the Normal Distribution (Relatório). Boeing Scientific Research Labs. Mathematical Note No. 322, DTIC accession number AD0423993. Cópia arquivada em 10 setembro 2014 – via Defense Technical Information Center