Numpy: Decidir sobre o novo padrão PRNG BitGenerator

Criado em 27 mai. 2019  ·  166Comentários  ·  Fonte: numpy/numpy

13163 trará a tão esperada substituição da infraestrutura PRNG da numpy. No interesse de manter esse PR gerenciável, iremos mesclá-lo ao master antes que todas as decisões sejam finalizadas, por exemplo, BitGenerator será indicado como padrão.

Devemos tomar uma decisão antes do primeiro lançamento com a nova infraestrutura. Uma vez liberados, ficaremos presos em nossa escolha por um tempo, então devemos ter certeza de que estamos confortáveis ​​com nossa decisão.

Por outro lado, a escolha do padrão não têm que muitas conseqüências. Não estamos falando sobre o BitGenerator padrão subjacente às funções de conveniência numpy.random.* . De acordo com o NEP 19, eles permanecem como apelidos do legado RandomState , cujo BitGenerator permanece MT19937 . O único lugar em que o padrão entra é quando Generator() é instanciado sem argumentos; ou seja, quando um usuário solicita um Generator com um estado arbitrário, presumivelmente para então chamar o método .seed() nele. Isso provavelmente pode ser muito raro, pois seria tão fácil apenas instanciá-lo explicitamente com o BitGenerator propagado que eles realmente desejam. Uma escolha legítima aqui pode ser não nomear BitGenerator .

No entanto, teremos recomendações sobre quais BitGenerator pessoas devem usar na maior parte do tempo e, embora possamos alterar as recomendações com bastante liberdade, aquele que tiver o lugar de honra provavelmente será mais escrito em livros, blogs, tutoriais , e tal.

IMO, existem algumas opções principais (com o meu comentário, sinta-se à vontade para discordar; não tentei transferir todos os comentários relevantes de # 13163):

Nenhum padrão

Sempre exija Generator(ChosenBitGenerator(maybe_seed)) . Isso é um pouco hostil, mas como é uma maneira muito conveniente de inicializar o gerador corretamente para reprodutibilidade, as pessoas podem acabar fazendo isso de qualquer maneira, mesmo que tenhamos um padrão.

MT19937

Esta seria uma boa escolha conservadora. Certamente não é pior do que o status quo. Como o Mersenne Twister ainda é amplamente considerado como a escolha "padrão", ele pode ajudar os usuários acadêmicos que precisam que seus artigos sejam revisados ​​por pessoas que possam questionar escolhas "fora do padrão", independentemente das qualidades específicas do PRNG. "Ninguém nunca foi demitido por contratar a IBM." As principais desvantagens de MT19937 são principalmente que ele é mais lento do que algumas das alternativas disponíveis, devido ao seu estado muito grande, e que falha em alguns testes de qualidade estatística. Ao escolher outro PRNG, temos uma _oportunidade_ (mas não uma _obrigação_, IMO) de sermos opinativos aqui e tentarmos mudar "o padrão", se desejarmos.

PCG64

Provavelmente é aquele que usarei com mais frequência, pessoalmente. A principal desvantagem é que ele usa aritmética inteira de 128 bits, que é emulada em C se o compilador não fornecer esse tipo de número inteiro. As duas plataformas principais para as quais esse é o caso são CPUs de 32 bits e MSVC de 64 bits, que simplesmente não suporta inteiros de 128 bits, mesmo quando a CPU o faz. Pessoalmente, não sugiro que as CPUs de 32 bits cada vez mais raras ditem nossas escolhas. Mas o desempenho do MSVC é importante, já que nossas compilações do Windows precisam desse compilador e não de outros compiladores do Windows. Provavelmente, pode ser resolvido com alguns intrínsecos de assembly / compilador, mas alguém teria que escrevê-los. O fato de ser _apenas_ MSVC para fazer isso torna isso um pouco mais palatável do que outras vezes quando somos confrontados com montagem.

Xoshiro256

Outra opção moderna para um PRNG pequeno e rápido. Ele tem algumas peculiaridades estatísticas conhecidas, mas é improvável que sejam um fator importante para a maioria dos usos. Essas peculiaridades me fazem fugir disso, mas essa é minha escolha pessoal para o código que irei escrever.

15 - Discussion numpy.random

Comentários muito úteis

Muito inspirado por este tópico, tenho algumas novidades para relatar ...

fundo

Sob muitos aspectos, pcg64 é muito bom; por exemplo, sob as medidas usuais de qualidade estatística, obtém um atestado de saúde limpo. Ele foi testado de várias maneiras; mais recentemente, executei todo o caminho até meio petabyte com PractRand. Funciona bem em casos de uso normais.

MAS, as patologias que surgiram neste tópico não me agradaram. Claro, eu poderia dizer “ bem, não pense assim ”, mas o ponto principal de um PRNG de propósito geral é que ele deve ser robusto. Eu queria fazer melhor ...

Então, cerca de 25 dias atrás, comecei a pensar em projetar um novo membro da família PCG ...

Objetivo

Meu objetivo era projetar um novo membro da família PCG que pudesse substituir a variante pcg64 atual. Assim sendo:

  • A função de saída deve embaralhar os bits mais do que XSL RR (porque isso evitará os problemas que surgiram neste encadeamento).
  • O desempenho deve ser quase tão rápido (ou mais rápido) que o atual pcg64 .
  • O design deve ser semelhante ao PCG (ou seja, não ser trivialmente previsível e, portanto, não permitir que _qualquer_ do trabalho da função de saída seja facilmente desfeito).

Como sempre, há uma compensação, pois tentamos obter a melhor qualidade possível o mais rápido possível. Se não nos importássemos com a velocidade, poderíamos ter mais etapas na função de saída para produzir uma saída mais fortemente embaralhada, mas o ponto do PCG era que o LCG subjacente era "quase bom o suficiente" e, portanto, não precisávamos para fazer tanto esforço quanto faríamos com algo como um contador incrementando em 1.

Spoiler

Tenho o prazer de relatar o sucesso! Cerca de 25 dias atrás, quando pensei sobre isso pela primeira vez, eu estava de férias. Quando voltei, há cerca de dez dias, experimentei as ideias que tinha e fiquei satisfeito ao descobrir que funcionaram bem. O tempo subsequente foi gasto principalmente em vários tipos de teste. Ontem fiquei satisfeito o suficiente para colocar o código na versão C ++ do PCG. Testes em tamanhos pequenos indicam que é muito melhor do que o XSL RR e competitivo com o RXS M, mas realmente brilha em tamanhos maiores. Ele atende a todos os outros objetivos também.

Detalhes

FWIW, a nova função de saída é (para o caso de saída de 64 bits):

uint64_t output(__uint128_t internal)
{
    uint64_t hi = internal >> 64;
    uint64_t lo = internal;

    lo |= 1;
    hi ^= hi >> 32;
    hi *= 0xda942042e4dd58b5ULL;
    hi ^= hi >> 48;
    hi *= lo;
    return hi;
}

Esta função de saída é inspirada em xorshift-multiply, pelo qual é amplamente utilizado. A escolha de multiplicadores é (a) para manter o número de constantes mágicas baixo, e (b) para evitar que a permutação seja desfeita (se você não tiver acesso a bits de ordem inferior), e também fornecer todo o "randomized- por si só ”qualidade que as funções de saída PCG normalmente têm.

Outras mudanças

Também é o caso que 0xda942042e4dd58b5 é o multiplicador LCG para este PRNG (e todos os geradores PCG de estado de 128 bits com prefixo cm_ ). Em comparação com 0x2360ed051fc65da44385df649fccf645 usado por pcg64 , esta constante ainda é bastante boa em termos de propriedades de teste espectral, mas é mais barato para multiplicar porque 128 bits x 64 bits é mais fácil do que 128 bits × 128 bits. Eu usei essa constante LCG por vários anos sem problemas. Ao usar a variante do multiplicador barato, executo a função de saída no estado pré-iterado em vez do estado pós-iterado para maior paralelismo no nível de instrução.

Testando

Eu testei completamente (PractRand e TestU01) e estou feliz com isso. Os testes incluíram cenários descritos neste tópico (por exemplo, pegando geradores de gangues em vapores sequenciais ou avançados em 2 ^ 64 e intercalando sua produção - testei uma gangue de quatro e uma gangue de 8192 até 8 TB sem problemas, também como um riacho e sua contraparte de terra oposta).

Rapidez

Eu poderia me alongar sobre testes de velocidade e benchmarks. Existem todos os tipos de fatores que influenciam se um PRNG é executado mais rápido do que outro em um determinado benchmark, mas no geral, essa variante parece frequentemente ser um pouco mais rápida, às vezes muito mais rápida e ocasionalmente um pouco mais lenta. Fatores como o compilador e o aplicativo têm um impacto muito maior na variabilidade do benchmark.

Disponibilidade

Os usuários do cabeçalho C ++ podem acessar este novo membro da família _agora_ como pcg_engines::cm_setseq_dxsm_128_64 ; em algum ponto no futuro, irei mudar pcg64 de pcg_engines::setseq_xsl_rr_128_64 para este novo esquema. Meu plano atual é fazer isso neste verão, como parte de um aumento na versão PCG 2.0.

Anúncios Formais

No geral, estou muito feliz com este novo membro da família e em algum momento no final do verão, haverá postagens no blog com mais detalhes, provavelmente fazendo referência a este tópico.

Suas escolhas...

Claro, você tem que descobrir o que fazer com isso. Independentemente de você usar ou não, eu na verdade ficaria muito curioso para ver se ele se sai melhor ou pior em seus benchmarks de velocidade.

Todos 166 comentários

O que o compilador Intel Windows faz para inteiros de 128 bits? Quanto mais lento é PCG64 compilado com MSVC em comparação com MT1993 no Windows? Suspeito que o recurso jump ahead será amplamente usado, então pode ser bom tê-lo por padrão.

O que o compilador Intel Windows faz para inteiros de 128 bits?

Não tenho certeza absoluta; Não sei se há implicações ABI que o ICC gostaria de ser restringido. Se quisermos apenas ter alguma ideia da montagem gerada que podemos usar, este é um recurso útil: https://godbolt.org/z/kBntXH

Suspeito que o recurso jump ahead será amplamente usado, então pode ser bom tê-lo por padrão.

Você quer dizer fluxos configuráveis? Esse é um bom ponto, mas me pergunto se não seria o contrário. Se nossa escolha de padrão realmente importa muito, então talvez se escolhermos um desses PRNGs com mais recursos, as pessoas usarão esses recursos mais extensivamente no código da biblioteca sem documentar que eles exigem esses recursos "avançados", porque, afinal, eles estão disponíveis "padrão". Mas então, se outro usuário tentar usar essa biblioteca com menos recursos BitGenerator por velocidade ou outros motivos, então eles vão bater em uma parede de tijolos. Em um mundo No default ou MT19937 , as bibliotecas provavelmente pensariam e documentariam os recursos avançados de que precisam.

Por outro lado, essa eventualidade faria com que BitGenerator s sem streams configuráveis ​​parecessem menos desejáveis, e eu _do_ gosto da noção de avançar o que é considerado a melhor prática nessa direção (puramente pessoal; não sinto obrigação de fazer o NumPy-the-project compartilhar essa noção). Pode ajudar a evitar alguns dos abusos que vejo nas pessoas .seed() ing no meio de seu código. Mas, novamente, tudo isso se baseia na noção de que ter um default mudará significativamente o comportamento das pessoas, então todas essas preocupações são provavelmente bastante atenuadas.

Quanto mais lento é PCG64 compilado com MSVC em comparação com MT1993 no Windows?

Em benchmarks postados por @bashtage em # 13163, PCG64 tem quase metade da velocidade do MT19937, o que é um desempenho bastante decepcionante para MSVC e amigos. É 23% mais rápido no Linux.

O que o compilador Intel Windows faz para inteiros de 128 bits?

Outros compiladores como Clang, GCC e o compilador Intel implementam inteiros de 128 bits em sistemas de 64 bits da mesma forma que implementaram inteiros de 64 bits em sistemas de 32 bits. Todas as mesmas técnicas sem necessidade de novas idéias. A Microsoft não se preocupou em fazer isso para o MSVC, portanto, não há inteiros de 128 bits suportados diretamente pelo compilador.

Como resultado, para MSVC, a implementação existente de PCG64 em # 13163 implementa manualmente a matemática de 128 bits chamando intrínsecos da Microsoft como _umul128 em x86_64 (e presumivelmente também poderia usar intrínsecos Intel equivalentes e mais portáteis como _mulx_u64 ), codificando assim o que GCC, Clang e o compilador Intel fariam por si próprios. O maior problema é provavelmente que o compilador da Microsoft não otimiza esses intrínsecos muito bem (com sorte, eles estão pelo menos embutidos?). É possível que o assembler codificado manualmente vá mais rápido, mas a correção adequada seria o compilador não ser tão diabolicamente pobre.

Suspeito que o recurso jump ahead será amplamente usado, então pode ser bom tê-lo por padrão.

Fico feliz que você goste de ir em frente, mas estou curioso para saber por que você acha que seria amplamente usado. (Pessoalmente, gosto muito de distance , que informa a distância entre dois PRNGs. Isso está na versão C ++ do PCG, mas não na versão C. Seria trivial o suficiente adicioná-lo, se houvesse interesse.)

Fico feliz que você goste de ir em frente, mas estou curioso para saber por que você acha que seria amplamente usado.

Provavelmente falta de familiaridade com a terminologia atual. O que quero dizer é fluxos independentes facilmente obtidos que podem ser usados ​​para executar simulações em paralelo. Não sei quantos problemas de simulação podem ser paralelizados, mas suspeito que sejam muitos e, dado o número de núcleos que as pessoas têm em um chip atualmente, isso poderia facilmente compensar uma desvantagem de velocidade.

A Microsoft não se preocupou em fazer isso para o MSVC, portanto, não há inteiros de 128 bits suportados diretamente pelo compilador.

Então isso vai prejudicar nossas rodas, OTOH, muitas pessoas no Windows obtêm seus pacotes do Anaconda ou Enthought, ambos usando Intel, e as pessoas que realmente se preocupam com o desempenho provavelmente estão no Linux, Mac ou talvez AIX.

EDIT: E talvez se a Microsoft estiver preocupada, eles poderiam oferecer uma recompensa para corrigir o problema.

FWIW, aqui está o conjunto que clang geraria para a função crítica, incluindo os bits necessários para desempacotar / reembalar uint128_t na estrutura de uint64_t s: https: // godbolt.org/z/Gtp3os

Muito legal, @rkern. Alguma chance de você fazer o mesmo para ver o que o MSVC está fazendo com o código de 128 bits escrito à mão?

Muito legal, @rkern. Alguma chance de você fazer o mesmo para ver o que o MSVC está fazendo com o código de 128 bits escrito à mão?

Não é, uh, bonito. ~ https://godbolt.org/z/a5L5Gz~

Ops, esqueça de adicionar -O3 , mas ainda é feio: https://godbolt.org/z/yiQRhd

Não é tão ruim assim. Você não ativou a otimização, portanto, não há nada embutido. Eu adicionei /Ox (talvez haja uma opção melhor?). Eu também consertei o código para usar a rotação interna intrínseca ( _rotr64 ), pois aparentemente o MSVC é incapaz de detectar o idioma de rotação C.

Ainda meio que um desastre de trem. Mas acho que é justo dizer que, com um pouco de atenção, o código PCG64 pode ser ajustado para compilar no MSVC em algo que não seja totalmente constrangedor para todos.

Para permitir que todo o resto seja mesclado, por que não escolher "sem padrão" por enquanto? Isso nos deixa livres para tomar a decisão sobre o padrão mais tarde (mesmo após um ou mais releases) sem quebrar a compatibilidade.

A maioria de nossos usuários não é especialista em números aleatórios, devemos fornecer padrões para eles.

Além do prosaico "agora eles precisam digitar mais código", o que acontece quando mudamos algo? No caso em que o BitGenerator é codificado, (porque não fornecemos um padrão), cada usuário não sofisticado agora terá que refatorar seu código e, esperançosamente, compreender as nuances de sua escolha (note que não podemos nem mesmo concordar entre nós é melhor). No entanto, se fornecermos um padrão, podemos interromper ruidosamente seus testes porque o novo padrão ou a nova versão não é compatível com o fluxo de bits.

Entre a suposição de que o fluxo de bits sempre será constante versus a suposição de que os desenvolvedores do NumPy sabem o que estão fazendo e os valores padrão devem ser os melhores da marca, eu erraria ao lado da segunda suposição, mesmo que quebra o primeiro.

Editar: esclarecer quais desenvolvedores devem saber o que estão fazendo

A maioria de nossos usuários não é especialista em números aleatórios, devemos fornecer padrões para eles.

Bem, certamente estaremos documentando recomendações, no mínimo, independentemente de termos ou não um padrão ou de qual é o padrão.

Além do prosaico "agora eles precisam digitar mais código", o que acontece quando mudamos algo?

Em que "algo" você está pensando? Eu não posso seguir seu argumento.

Além do prosaico "agora eles precisam digitar mais código", o que acontece quando mudamos algo?

Em que "algo" você está pensando? Eu não posso seguir seu argumento.

@mattip se refere à alteração do gerador de bits padrão.

Isso deixaria os usuários que o adotaram loucos e poderia exigir algumas mudanças no código.

Por exemplo, se você usou

g = Generator()
g.bit_generator.seed(1234)

e o gerador de bits subjacente foi alterado, então isso estaria errado.

Se você fizesse a coisa mais sensata e usasse

Generator(BitGenerator(1234))

então você não o veria.

IMO, ao considerar a escolha do padrão, devemos pensar que foi corrigido até que uma falha fatal seja encontrada no gerador de bits subjacente ou a Intel adicione um QUANTUM_NI a seus chips, o que produz muitas melhorias OOM no desempenho aleatório.

Sei que sou um pouco estranho aqui, mas não acho que seja razoável esperar que o PRNG que é a escolha padrão seja consertado para sempre e nunca mude. (Em C ++, por exemplo, std::default_random_engine fica a critério da implementação e pode mudar de uma versão para outra.)

Em vez disso, deve haver um mecanismo para reproduzir os resultados anteriores. Assim, uma vez que existe uma implementação específica, é muito desagradável alterá-la (por exemplo, o MT19937 _é_ MT19937, você não pode ajustá-lo para fornecer uma saída diferente). [E também não é legal remover uma implementação que já existe.]

Quando o padrão for alterado, as pessoas que desejam continuar reproduzindo resultados antigos precisarão solicitar o padrão anterior pelo nome. (Você pode fazer isso fornecendo um mecanismo para selecionar o padrão correspondente a uma versão anterior.)

Dito isso, mesmo se você tiver permissão para trocar o gerador padrão por outra coisa, ele realmente precisa ser estritamente melhor - quaisquer recursos presentes no gerador padrão representam um compromisso de oferecer suporte a esse recurso no futuro. Se seu gerador padrão tiver advance eficiente, você realmente não pode retirá-lo mais tarde. (Você poderia bloquear a funcionalidade avançada no gerador padrão para evitar esse problema.)

Em resumo, existem maneiras de garantir que os usos possam ter resultados reproduzíveis sem tentar se prender a um contrato em que o padrão permanece inalterado para sempre. Isso também reduzirá as apostas pela escolha que você fizer.

(FWIW, isso é o que eu fiz no PCG. O PRNG PCG padrão de 32 bits é atualmente a variante XSH-RR [acessada como pcg_setseq_64_xsh_rr_32_random_r na biblioteca C e a classe pcg_engines::setseq_xsh_rr_64_32 no C ++ biblioteca], mas, em princípio, se você realmente deseja reprodutibilidade à prova de futuro, você deve especificar XSH-RR explicitamente, em vez de usar pcg32_random_r ou pcg32 que são apelidos e, em princípio, podem ser atualizados para outra coisa .)

Não é realmente para sempre (todo este projeto é 90% impulsionado por uma promessa real, genuína e honrada para sempre feita cerca de 14 anos atrás), mas como você diz, a mudança precisa de (a) uma razão convincente para mudar e (b) levar pelo menos alguns anos para dar o ciclo de depreciação.

É muito melhor se esforçar hoje para acertar o máximo possível.

Uma coisa que não é proibida, é claro, é melhorar o código PRNG após o lançamento como logn, pois ele produz os mesmos valores. Por exemplo, se fôssemos com um PRNG que usasse uint128, poderíamos deixar o MS adicionar suporte uint128 (grande chance) ou adicionar assembly para Win64 em uma versão futura.

Por exemplo, se você usou

g = Generator()
g.bit_generator.seed(1234)

e o gerador de bits subjacente foi alterado, então isso estaria errado.

Certo, isso parece estar argumentando, com @ eric-wieser, pela opção "Sem padrão", que eu não posso enquadrar com a afirmação inicial "A maioria de nossos usuários não são especialistas em números aleatórios, devemos fornecer padrões para eles . "

Entre nenhum padrão e um padrão amigável, assumindo totalmente o padrão, eu sempre escolheria o último:

Agora:

Generator() # OK
Generator(DefaultBitGenerator(seed)) # OK
Generator(seed) # error

_minha preferência:

Generator(1234) == Generator(DefaultBitGenerator(1234)
Generator(*args**kwargs) == Generator(DefaultBitGenerator(*args, **kwargs))

Agora, eu não acho que isso vai entrar, mas acho que uma forma de prolongar o uso de RandomState é torná-lo disponível apenas para usuários que se sintam experientes o suficiente para escolher um gerador de bits.

Em resumo, existem maneiras de garantir que os usos possam ter resultados reproduzíveis sem tentar se prender a um contrato em que o padrão permanece inalterado para sempre. Isso também reduzirá as apostas pela escolha que você fizer.

Sim, nós temos isso. Os usuários podem pegar BitGenerator s pelo nome (por exemplo, MT19937 , PCG64 , etc.) e instanciá-los com sementes. Objetos BitGenerator implementam o algoritmo PRNG uniforme central com um conjunto limitado de métodos para desenhar [0..1) float64 inteiros e inteiros (bem como quaisquer recursos divertidos de jumpahead / stream que tenham) . A classe Generator qual estamos falando pega um objeto BitGenerator e o envolve para fornecer todas as distribuições não uniformes, as gaussianas, as gamas, os binômios, etc. garantias estritas de compatibilidade de fluxo para BitGenerator s. Não vamos nos livrar de nenhum (que faz para ser lançado), e nem vamos alterá-los.

A questão central sobre o padrão é "O que o código g = Generator() , sem argumentos, faz?" Agora, no PR, ele cria um Xoshiro256 BitGenerator com um estado arbitrário (isto é, extraído de uma boa fonte de entropia como /dev/urandom ). A opção "Sem padrão" tornaria isso um erro; os usuários teriam que nomear explicitamente BitGenerator que desejam. O ponto de @eric-wieser é que "Sem padrão" é uma opção categoricamente _segura_ para a primeira versão. Uma versão posterior fornecendo um padrão não causará problemas da mesma maneira que alterar um padrão existente faz.

@rkern , se você se preocupa apenas com o caso sem argumentos em que a semente é autogerada a partir da entropia disponível, então realmente não importa muito qual é o gerador subjacente - ele pode mudar de hora em hora, uma vez que os resultados nunca seriam reproduzíveis ( corridas diferentes obteriam sementes diferentes).

Em contraste, @bashtage parece se preocupar com um gerador padrão que é fornecido com uma semente.

@rkern , se você se preocupa apenas com o caso _sem argumentos_ onde a semente é autogerada a partir da entropia disponível, então realmente não importa muito qual é o gerador subjacente - ele pode mudar de hora em hora, uma vez que os resultados nunca seriam reproduzíveis ( corridas diferentes obteriam sementes diferentes).

Você pode propagar novamente BitGenerator após sua criação. Portanto, se Generator() funcionar, o que espero que aconteça é que as pessoas que desejam um PRNG propagado apenas o propaguem na próxima linha, como no exemplo de

g = Generator()
g.bit_generator.seed(seed)

Isso é um tanto entediante, e é por isso que sugeri no início que talvez a maioria das pessoas geralmente optaria por Generator(PCG64(<seed>)) qualquer maneira, já que é tão conveniente quanto à digitação. No entanto, @bashtage observa corretamente alguma resistência ao tomar uma decisão extra.

Portanto, acho que _também_ temos uma pergunta mais ampla diante de nós: "Quais são todas as maneiras pelas quais desejamos que os usuários instanciem um desses? E se essas formas tiverem configurações padrão, quais deveriam ser?" Temos um espaço de design aberto e a sugestão de @bashtage para Generator(<seed>) ou Generator(DefaultBitGenerator(<seed>)) ainda são possibilidades.

@bashtage Quanto você acha que a documentação ajudaria? Ou seja, se disséssemos no início " PCG64 é nosso padrão preferido BitGenerator " e usássemos Generator(PCG64(seed)) consistentemente em todos os exemplos (quando não demonstramos especificamente outros algoritmos)?

Eu posso estar mais convencido de ter uma default_generator(<seed>) _function_ sobre Generator(<seed>) ou g=Generator();g.seed(<seed>) . Então, se realmente precisássemos mudá-lo e não quiséssemos quebrar coisas, poderíamos simplesmente adicionar uma nova função e adicionar avisos à antiga. Eu posso recomendar marcá-lo experimental para o primeiro lançamento, dando-nos algum tempo para observar essa infraestrutura antes de fazer um compromisso firme.

Que tal fazer um objeto DefaultBitGenerator que não exponha nenhum detalhe de seu estado interno? Este seria um proxy para um dos outros objetos geradores de bits, mas, em princípio, poderia envolver qualquer um deles - exceto, é claro, para sua sequência específica de números gerados. Esperançosamente, isso desencorajaria os usuários de fazer suposições programáticas sobre o que eles podem fazer com o BitGenerator padrão, ao mesmo tempo que nos permite usar um algoritmo aprimorado.

Eu concordo com @bashtage que seria muito mais amigável apoiar diretamente sementes inteiras como argumentos para Generator , por exemplo, np.random.Generator(1234) . Isso, é claro, faria uso de DefaultBitGenerator .

Na documentação de Generator , poderíamos fornecer um histórico completo de qual era o gerador de bits padrão em cada versão anterior do NumPy. Esta é basicamente uma sugestão de

(Acabei de ver esta edição em um comentário anterior)

Ops, esqueça de adicionar -O3 , mas ainda é feio: https://godbolt.org/z/yiQRhd

Para MSVC, não é -O3 , é /O2 ou /Ox (mas não /O3 !).

Na documentação de Generator , poderíamos fornecer um histórico completo de qual era o gerador de bits padrão em cada versão anterior do NumPy. Esta é basicamente uma sugestão de

Na verdade, ainda melhor seria incluir um argumento version explícito, como o argumento protocol do pickle , em Generator / DefaultBitGenerator . Em seguida, você pode escrever algo como np.random.Generator(123, version=1) para indicar que deseja números aleatórios da "versão 1" (seja lá o que for) ou np.random.Generator(123, version=np.random.HIGHEST_VERSION) (comportamento padrão) para indicar que deseja o gerador de bits mais recente / melhor (seja lá o que é).

Presumivelmente, version=0 seria o MT19937 que NumPy usou até agora, e version=1 poderia ser qualquer novo padrão que escolhermos.

Que tal criar um objeto DefaultBitGenerator que não exponha nenhum detalhe de seu estado interno? Este seria um proxy para um dos outros objetos geradores de bits, mas, em princípio, poderia envolver qualquer um deles - exceto, é claro, para sua sequência específica de números gerados. Esperançosamente, isso desencorajaria os usuários de fazer suposições programáticas sobre o que eles podem fazer com o BitGenerator padrão, ao mesmo tempo que nos permite usar um algoritmo aprimorado.

Hmmm. Isso é atraente. Parece que está complicando as coisas e adicionando outro laço a esse nó górdio (e que _deve_ haver um traço mais parecido com Alexander disponível para nós), mas essa é realmente a única coisa ruim que tenho a dizer sobre isso. Isso _faz_ torna as decisões restantes mais fáceis: podemos nos concentrar na qualidade estatística e no desempenho.

Na verdade, ainda melhor seria incluir um argumento version explícito, como pickle , em Generator / DefaultBitGenerator .

Sou menos fã disso. Ao contrário do caso pickle , essas coisas têm nomes significativos que podemos usar e já temos o mecanismo implementado.

Sou menos fã disso. Ao contrário do caso pickle , essas coisas têm nomes significativos que podemos usar e já temos o mecanismo implementado.

Considere o seguinte da perspectiva de um usuário NumPy típico:

  • np.random.Generator(seed, version=0) vs np.random.Generator(seed, version=1)
  • np.random.Generator(MT19937(seed)) vs np.random.Generator(PCG64(seed))

Acho que é seguro presumir que a maioria de nossos usuários sabe muito pouco sobre os méritos relativos dos algoritmos RNG. Mas mesmo sem ler nenhum documento, eles podem adivinhar com segurança que version=1 (o padrão mais recente) deve ser melhor na maioria dos casos do que version=0 . Para a maioria dos usuários, isso é tudo que eles precisam saber.

Em contraste, nomes como MT19937 e PCG64 são realmente significativos apenas para especialistas ou pessoas que leram nossa documentação :).

No seu caso de uso, ninguém está selecionando version que eles _querem_. Eles apenas selecionam o version que _precisam_ para replicar os resultados de uma versão conhecida. Eles estão sempre procurando por um valor específico que foi usado (implicitamente, porque permitimos que fosse implícito) nos resultados que desejam replicar; eles não precisam raciocinar sobre a relação entre valores múltiplos.

E, em qualquer caso, esse nível de reprodutibilidade de liberação cruzada é algo que rejeitamos na NEP 19 . Os argumentos contra o controle de versão das distribuições também se aplicam aqui.

Algumas reflexões sobre o padrão:

  • 99,9% dos usuários não se importam ou querem saber sobre algoritmos subjacentes, eles querem apenas números aleatórios. Portanto, +1 para fazer uma escolha opinativa para o padrão, por favor, não faça os usuários escolherem.
  • dSFMT parece ser simplesmente uma versão mais rápida do que MT19937 (seria bom afirmar nos documentos quão rápido e remover "SSE2"). Já que não estamos garantindo a reprodutibilidade do fluxo de bits de qualquer maneira, as diferenças de estado interno não são muito interessantes e dSFTM deve ser preferível a MT19937 mesmo se o argumento vencedor aqui for "tornar a vida mais fácil durante a revisão do artigo" .
  • O desempenho é importante para uma fração significativa da base de usuários. As propriedades estatísticas dos geradores são importantes apenas para uma _muito_ pequena fração dos usuários. Todos os geradores incluídos são adequados para casos de uso normais. Portanto, +1 para escolher o mais rápido como padrão.

Lamento dizer - mas 32 bits ainda são importantes no Windows - consulte https://github.com/pypa/manylinux/issues/118#issuecomment -481404761

Acho que devemos nos preocupar muito com as propriedades estatísticas, porque estamos no processo de uma grande mudança em direção a um maior uso de métodos de reamostragem na análise de dados. Se o Python ganhar a reputação de ser um pouco descuidado nesse assunto, mesmo que seja apenas por padrão, isso pode muito bem ser uma barreira a ser adotada por pessoas que consideram o Python para análise de dados. Eu ficaria muito satisfeito se Python fosse o pacote de escolha para pessoas que levam a permutação e simulação a sério.

Acho que não há problema em oferecer algoritmos mais rápidos que não sejam de última geração, mas não por padrão, na medida em que podemos evitá-los e manter a compatibilidade anterior.

Para alguma análise forense e discussão, consulte: https://arxiv.org/pdf/1810.10985.pdf

Os livros didáticos fornecem métodos que implicitamente ou explicitamente assumem que PRNGs podem ser substituídos por variáveis ​​IIDU [0,1) verdadeiras sem introduzir erro material [20, 7, 2, 16, 15]. Mostramos aqui que essa suposição é incorreta para algoritmos em muitos pacotes estatísticos comumente usados, incluindo MATLAB, módulo aleatório do Python, R, SPSS e Stata.

@kellieotto , @pbstark - vocês têm uma opinião sobre qual PRNG devemos escolher aqui, para fornecer a melhor base possível para permutação e bootstrap?

Acho que devemos nos preocupar muito com as propriedades estatísticas, porque estamos no processo de uma grande mudança em direção a um maior uso de métodos de reamostragem na análise de dados

Acordado. Desde que essas propriedades sejam relevantes para alguns casos de uso do mundo real, isso é muito importante. As preocupações normalmente levantadas são sempre extremamente acadêmicas.

Para alguma análise forense e discussão, consulte: https://arxiv.org/pdf/1810.10985.pdf

Artigo muito interessante. Conclui que NumPy é praticamente a única biblioteca que acerta (topo da página 9), ao contrário de R, Python stdlib e co.

Seria muito útil obter exemplos ainda mais concretos do que no papel. Se nosso gerador padrão atual também falhar em algum ponto, quando será isso? Exemplos como R's sample funcionam gerando 40% de números pares e 60% de números ímpares ao desenhar ~ 1,7 bilhões de amostras. Qual é o equivalente de bootstrap / reamostragem aqui?

A última versão do R (3.6) corrige a abordagem de truncamento vs. bits aleatórios
para gerar inteiros aleatórios. O Mersenne Twister continua sendo o padrão
PRNG, no entanto.

@Kellie Ottoboni [email protected] e acho que o PRNG padrão em
linguagens científicas e pacotes de estatísticas devem ser criptograficamente
seguro (um CS-PRNG, por exemplo, SHA256 no modo contador), com a opção de cair
de volta para algo mais rápido, mas de qualidade inferior (por exemplo, o Mersenne Twister)
se a velocidade assim o exigir.

Estamos trabalhando em um CS-PRNG para Python:
https://github.com/statlab/cryptorandom

O desempenho não é ótimo (ainda). O gargalo parece ser a conversão de tipo
dentro do Python para converter strings binárias (saída hash) como inteiros. Estamos
trabalhando em uma implementação que transfere mais do trabalho para C.

Felicidades,
Philip

Na segunda-feira, 27 de maio de 2019 às 6h27 Ralf Gommers [email protected]
escrevi:

Acho que devemos nos preocupar muito com as propriedades estatísticas, porque estamos
no processo de uma grande mudança em direção a um maior uso de métodos de reamostragem em
análise de dados

Acordado. Contanto que essas propriedades sejam relevantes para algum uso no mundo real
casos, isso é muito importante. As preocupações que geralmente são levantadas são
sempre extremamente acadêmico.

Para alguma análise forense e discussão, consulte:
https://arxiv.org/pdf/1810.10985.pdf

Artigo muito interessante. Conclui que o NumPy é praticamente o único
biblioteca que acerta (topo da página 9), ao contrário de R, Python stdlib e co.

Seria muito útil obter exemplos ainda mais concretos do que no
papel. Se nosso gerador padrão atual também quebrar em algum ponto,
quando é isso? Exemplos como função de amostra de R gerando 40% uniforme
números e 60% números ímpares ao desenhar ~ 1,7 bilhões de amostras. Qual é o
bootstrapping / resampling equivalente aqui?

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWJEIA4CTLLHVGZVKBLPXPOUFA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWJZW4Y#issuecomment-496212851 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWJW445QDPGZDGXMPA3PXPOUFANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

O desempenho é importante para uma fração significativa da base de usuários. As propriedades estatísticas dos geradores são importantes apenas para uma _muito_ pequena fração dos usuários. Todos os geradores incluídos são adequados para casos de uso normais. Então, +1 para escolher o mais rápido como padrão

Primeiro, simplesmente não há como escolher “o mais rápido”. @bashtage executou alguns benchmarks no código atual em # 13163 e estava em todo o mapa, com dSFMT vencendo no Windows e sendo derrotado por PCG-64 e Xoroshiro 256 no Linux. E tudo isso na mesma máquina com o mesmo benchmark. Diferentes arquiteturas de hardware (até mesmo revisões no X86) farão a diferença, assim como diferentes benchmarks. (Como já foi discutido neste tópico, o PCG se sai mal nos benchmarks do Windows por causa de problemas com o MSVC, que também é provável que seja temporário, já que o MSVC pode melhorar ou as pessoas podem contornar seus problemas. Provavelmente, problemas semelhantes do MSVC explicam por que o Xoshiro foi espancado.)

Eu também me pergunto o quão grande é a “fração significativa” de usuários que se preocupam com a velocidade. O próprio Python é em média cerca de 50 × mais lento que o C. Qual fração da base de usuários do NumPy o está executando no PyPy (o que daria um aumento de velocidade de 4 ×)? Alguns, certamente, mas suspeito que não sejam um número muito alto.

E para aquela “fração significativa” que se preocupa com a velocidade, dada toda a variabilidade descrita acima, quem irá simplesmente acreditar em sua palavra de que o PRNG padrão será executado mais rápido para seu aplicativo? Uma coisa sensata a fazer (que também é bastante divertida e ao alcance da maioria dos usuários) é comparar os diferentes PRNGs disponíveis e ver qual é o mais rápido _para eles_.

Em contraste, embora eles possam encontrar pistas na documentação, descobrir a qualidade estatística de PRNGs específicos não está, como você observou, no radar da maioria dos usuários (e é um desafio até mesmo para especialistas). A maioria nem sabe quando / se deve se importar ou não. Eu diria que este é um lugar para algum paternalismo - o fato de que a maioria dos usuários não se preocupa com algo não significa que os mantenedores não devam se preocupar com isso.

É verdade que todos os PRNGs incluídos são adequados para a maioria dos casos de uso, mas esse é um padrão bastante baixo. Os sistemas Unix foram enviados com uma miscelânea de PRNGs de biblioteca C que são estatisticamente terríveis e, ainda assim, têm sido amplamente usados ​​por anos sem que o mundo saia de seu eixo.

Além das propriedades estatísticas, há outras propriedades que o usuário pode não querer para si mesmo, mas eu posso querer para elas. Pessoalmente, como provedor de PRNGs, quero evitar previsibilidade trivial - não quero que alguém olhe alguns resultados do PRNG e depois seja capaz de dizer quais serão todos os resultados futuros. Na maioria dos contextos onde o NumPy é usado, a previsibilidade não é um problema - não há adversário que se beneficiará por ser capaz de prever a sequência com facilidade. Mas alguém em algum lugar vai usar os PRNGs do NumPy não porque precisa do NumPy para fazer estatísticas, mas porque é onde eles encontraram PRNGs antes; esse código pode enfrentar um adversário real que se beneficiará por ser capaz de prever o PRNG. Pagar caro (por exemplo, perda significativa de velocidade) para garantir fortemente contra essa situação discrepante não vale a pena, mas um seguro modesto pode valer a pena.

Para alguma análise forense e discussão, consulte: https://arxiv.org/pdf/1810.10985.pdf

Os livros didáticos fornecem métodos que implicitamente ou explicitamente assumem que PRNGs podem ser substituídos por variáveis ​​IIDU [0,1) verdadeiras sem introduzir erro material [20, 7, 2, 16, 15]. Mostramos aqui que essa suposição é incorreta para algoritmos em muitos pacotes estatísticos comumente usados, incluindo MATLAB, módulo aleatório do Python, R, SPSS e Stata.

FWIW, há um bom artigo de @lemire sobre a geração eficiente de um número em um intervalo sem viés. Usei isso como um ponto de partida para explorar e executar alguns benchmarks também em meu próprio artigo . (Ao gerar 64 bits, o método de Lemire usa multiplicação de 128 bits para evitar a divisão lenta de 64 bits, com todos os problemas familiares que podem surgir para os usuários do MSVC.)

@pbstark @kellieotto Li seu artigo com interesse quando apareceu no arXiv. Eu estava visitando alguns amigos no BIDS, e eles mencionaram o seu trabalho. A seção Discussão observa que "até agora, não encontramos uma estatística com viés consistente grande o suficiente para ser detectado em O (10 ^ 5) replicações" para MT19937. Você já encontrou um? Você encontrou um exemplo concreto para um PRNG de estado de 128 bits como o PCG64? Isso me parece ser um limite razoável de relevância prática, onde esta consideração pode começar a pesar sobre outras (IMO), pelo menos para o propósito de selecionar um default de propósito geral.

O bom recurso de nossa nova estrutura PRNG # 13163 é que ela permite que qualquer pessoa forneça seus próprios BitGenerator que podem ser simplesmente plugados. código. Eu encorajo você a olhar para a implementação de cryptorandom como BitGenerator em C para que possamos compará-lo diretamente com as outras opções.

Pessoalmente, espero que aqueles que realmente se preocupam com a velocidade dêem um passo a mais, se necessário (não é muito aqui). Devemos fornecer padrões seguros , e meu melhor palpite atual é que isso significa padrões seguros para todos os propósitos, com exceção da criptografia (provavelmente devemos ter um aviso sobre isso nos documentos). Muitos usuários se preocupam com a velocidade, mas, francamente, é exatamente por isso que evito dar alta prioridade a ela.
Aquele artigo em que Numpy se saiu bem parece interessante (parabéns a Robert provavelmente por acertar!), Mas na verdade é o amostrador, não o gerador de bits.

@pbstark talvez você queira implementar isso como um BitGenerator compatível com numpy / randomgen? Essa é provavelmente a maneira mais fácil de acelerar e disponibilizar para um público amplo de uma forma muito mais útil. Já que parece que você e Kellie Ottoboni estão em Berkeley, podemos nos encontrar um dia para começar? (Apenas uma oferta, eu deveria dar uma olhada no código primeiro).

Em relação ao artigo _Amostragem aleatória: a prática torna imperfeito_, é uma boa leitura, mas vale lembrar que se tivéssemos 1 trilhão de núcleos produzindo um número por nanossegundo por 100 anos, geraríamos menos de 2 ^ 102 números.

Para PRNGs trivialmente previsíveis (mesmo aqueles com grandes espaços de estado como o Mersenne Twister), podemos realmente saber se alguma sequência de saída específica pode algum dia ser produzida (e encontrar a semente que iria gerá-la se ela existir ou sentir saudades se não ), mas para outros PRNGs não trivialmente previsíveis, não podemos (facilmente) saber quais sequências de saída nunca podem ser produzidas e quais estão lá, mas são tão raras que é improvável que jamais os encontremos em um período de pesquisa. (Como você deve saber, tenho um PRNG que sei que cuspirá um arquivo zip com _Twelth Night_ dentro de 2 ^ 80 saídas, mas boa sorte para encontrá-lo.)

Se você realmente quer uma criptografia, a única escolha em hardware moderno é
AES, pois tem uma instrução dedicada. @lemire tem uma implementação
aqui https://github.com/lemire/testingRNG/blob/master/source/aesctr.h que
é tão rápido quanto os geradores não criptografados. Há também ChaCha20 que pode ir
rápido com SIMD. Ambos serão muito lentos em hardware antigo. ThreeFry e
Philox já estão incluídos e são contra-gravações de criptolita.

A criptografia IMO é superestimada em termos de benefícios de custo. Não estou ciente de nenhum
retração importante devido a problemas de PRNG com Mt, que eu acho que foi
usado no pedido de 10e6 artigos publicados. Os únicos aplicativos que vi
onde o PRNG foi realmente problemático, foram os casos em que o período foi tão
pequeno que o gerador completou o ciclo completo. Mesmo aqui o único
efeito foi a redução do tamanho da amostra do estudo, que replicou o principal
resultados uma vez reexecutados em um sistema com um período maior.

Na segunda-feira, 27 de maio de 2019, 19:50, Robert Kern [email protected] escreveu:

@pbstark https://github.com/pbstark @kellieotto
https://github.com/kellieotto Eu li seu artigo com interesse quando
apareceu no arXiv. Eu estava visitando alguns amigos no BIDS, e eles tinham
mencionou seu trabalho. A seção de discussão observa que "até agora, não temos
encontraram uma estatística com tendência consistente grande o suficiente para ser detectada em
O (10 ^ 5) replicações "para MT19937. Você já encontrou um? Você encontrou um
um exemplo concreto para um PRNG de estado de 128 bits como o PCG64? Isso me parece
ser um limite razoável de relevância prática, onde esta consideração
pode começar a superar os outros (IMO), pelo menos com o propósito de selecionar
um padrão de uso geral.

O bom recurso de nossa nova estrutura PRNG # 13163
https://github.com/numpy/numpy/pull/13163 é que permite que qualquer pessoa
fornecem seu próprio BitGenerator que pode simplesmente ser conectado. Ele não
até tem que estar em numpy para as pessoas usarem em seu código numpy. Eu gostaria
encorajam você a olhar para a implementação de cryptorandom como um BitGenerator em C
para que possamos compará-lo diretamente com as outras opções.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRO5PKW4MRFSBGUFUNTPXQUOLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKLICI#issuecomment-496284681 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABKTSRMRIHC4OYDR52HLTHDPXQUOLANCNFSM4HPX3CHA
.

Eu também me pergunto o quão grande é a “fração significativa” de usuários que se preocupam com a velocidade. O próprio Python é em média cerca de 50 × mais lento que C. Qual fração da base de usuários do NumPy o está executando no PyPy (o que daria um aumento de velocidade de 4 ×)? Alguns, certamente, mas suspeito que não sejam um número muito alto.

Eu suspeito que você não seja um usuário regular :) NumPy é principalmente C sob o capô, e é tão rápido quanto fazer suas próprias coisas em C (bem, mais rápido principalmente). Além disso, PyPy não está pronto para a produção para aplicações científicas, e mais lento em qualquer caso (porque é limitado a usar a API CPython que NumPy usa, então não pode obter os benefícios de seu JIT).

De qualquer forma, isso está fora do tópico. Afirmar que a velocidade é importante não é controverso.

@imneme estamos usando o método de Lemires para inteiros limitados. Desde este um
recomeçar sem legado ou depreciação, tentamos arduamente para começar
bons algoritmos.

Na segunda-feira, 27 de maio de 2019, 19:46 imneme [email protected] escreveu:

Para alguma análise forense e discussão, consulte:
https://arxiv.org/pdf/1810.10985.pdf

Os livros didáticos fornecem métodos que implicitamente ou explicitamente assumem que os PRNGs podem
ser substituído por variáveis ​​IIDU [0,1) verdadeiras sem introdução de material
erro [20, 7, 2, 16, 15]. Mostramos aqui que essa suposição é incorreta para
algoritmos em muitos pacotes estatísticos comumente usados, incluindo MATLAB,
Módulo aleatório do Python, R, SPSS e Stata.

FWIW, há um bom artigo https://arxiv.org/abs/1805.10941 de @lemire
https://github.com/lemire sobre a geração eficiente de um número em um intervalo
sem preconceito. Usei isso como um ponto de partida para explorar e executar alguns
benchmarks também em meu próprio artigo
http://www.pcg-random.org/posts/bounded-rands.html . (Ao gerar
64 bits, o método de Lemire usa multiplicação de 128 bits para evitar lentidão
Divisão de 64 bits, com todos os problemas familiares que podem surgir para o MSVC
Comercial.)

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKNAQAK4WIXG5SVLO3PXQUA3A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKLDRI#issuecomment-496284101 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABKTSRKV3KYKRLNMBKNU4JLPXQUA3ANCNFSM4HPX3CHA
.

Devemos fornecer padrões _seguros_, e meu melhor palpite é que isso significa padrões seguros para todos os fins, com exceção da criptografia

É difícil argumentar contra isso. Minha pergunta é - o que é seguro? Existem apenas vários graus de quase aleatoriedade com várias propriedades. Até agora não vi ninguém dar um exemplo concreto, nem aqui nem em outras edições, PRs ou tópicos. Apenas falar sobre propriedades estatísticas abstratas não ajuda.

Minha sensação é que PCG64 seria um bom padrão. A desvantagem de velocidade no Windows não será aparente para quem usa o Anaconda et. al., e é provável que seja corrigido em algum ponto. Com a execução paralela sendo a novidade em Python, também acho que ter fluxos configuráveis ​​é uma propriedade desejável.

Não acredito que a penalidade de velocidade do PCG64 no Visual Studio seja algo que não possa ser eliminado.

Isso foi avaliado cuidadosamente em algum lugar?

Afirmar que a velocidade é importante não é controverso.

Minha pergunta é - o que é seguro?

Aplique a lógica de forma consistente: "o que é rápido"? Não tenho uma grande ideia de quais programas numpy realmente têm o desempenho de BitGenerator como um gargalo significativo. Se eu usar um BitGenerator que é duas vezes mais rápido, terei 5% de aceleração em meu cálculo completo? Provavelmente nem isso. Python-não-ser-tão-rápido-quanto-C não é o problema; só que mesmo os programas pesados ​​de PRNG que são realmente úteis não gastam muito tempo nos BitGenerator . Provavelmente, qualquer uma das opções disponíveis é suficiente.

Não acredito que a penalidade de velocidade do PCG64 no Visual Studio seja algo que não possa ser eliminado.

Up-thread eu mostro como clang compila PCG64 em assembly que podemos roubar para MSVC de 64 bits, então não, não acho que MSVC no Windows de 64 bits seja um problema intransponível.

O que pode ser mais complicado é o PCG64 em sistemas de 32 bits, dos quais apenas o Windows de 32 bits ainda pode ser praticamente importante para nós. Nesse caso, é menos sobre MSVC do que sobre nos restringirmos ao ISA de 32 bits.

O que @Kellie Ottoboni [email protected] e eu apontamos é que
mesmo para problemas de tamanho modesto, o espaço de estado do MT é muito pequeno para se aproximar
permutações uniformes (n <2100) ou amostras aleatórias uniformes (n = 4e8, k = 1000).

Isso afeta tudo, desde o bootstrap até os testes de permutação e o MCMC.
A diferença entre a distribuição pretendida e a real
distribuição pode ser arbitrariamente grande (distância de variação total se aproximando
2). É grande e é sério.

Não nos esforçamos para quebrar a TM em funções "estatísticas" em um
alguns anos. Tenho certeza de que há uma maneira sistemática de quebrá-lo
(já que as distâncias de distribuição são muito grandes).

Felicidades,
Philip

Na segunda-feira, 27 de maio de 2019 às 12h26 Robert Kern [email protected]
escrevi:

Afirmar que a velocidade é importante não é controverso.

Minha pergunta é - o que é seguro?

Aplique a lógica de forma consistente: "o que é rápido"? Não tenho uma grande ideia
quais programas numpy realmente têm o desempenho do BitGenerator como
um gargalo significativo. Se eu usar um BitGenerator que é duas vezes mais rápido,
terei 5% de aceleração em meu cálculo completo? Provavelmente nem isso.
Python-não-ser-tão-rápido-quanto-C não é o problema; é só isso mesmo
Programas pesados ​​de PRNG que são realmente úteis não gastam uma grande quantidade de
tempo no BitGenerator. Provavelmente, qualquer uma das opções disponíveis são
suficiente.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWKSMPAG3GFUCUFRXCDPXQYVRA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKMV3Q#issuecomment-496290542 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWIDCPAJJ6DJ3RO332LPXQYVRANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@pbstark O que eu gostaria de ver é uma implementação concreta do problema (pode ser artificial, mas não muito planejado) em que o MT ou um PRNG de 128 bits falha e cryptorandom funcionaria. Você pode apontar um conjunto de dados onde o método de reamostragem dá inferências erradas com um PRNG de 128 bits e inferências corretas com cryptorandom ?

Mudar para PCG64 torna o limite inferior do tamanho do problema pior,
uma vez que seu espaço de estado é ainda menor do que o de MT. Claro, poderia
ainda produzir aleatoriedade "melhor", pois pode amostrar um subgrupo do
grupo de permutação mais uniformemente do que o MT. Mas tem que quebrar antes
500 escolha 10 e antes de 21 !.

Felicidades,
Philip

Na segunda-feira, 27 de maio de 2019 às 12h30 Robert Kern [email protected]
escrevi:

Não acredito que a penalidade de velocidade do PCG64 no Visual Studio seja
algo que não pode ser apagado.

Up-thread, mostro como o clang compila PCG64 em assembly que podemos roubar
para MSVC de 64 bits, então não, não acho que MSVC no Windows de 64 bits seja um
problema intransponível.

O que pode ser mais complicado é o PCG64 em sistemas de 32 bits, dos quais apenas 32 bits
O Windows ainda pode ser praticamente importante para nós. Nesse caso é menos
sobre MSVC do que sobre nos restringirmos ao ISA de 32 bits.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWJFCINQCYGFCI7ULI3PXQZGLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKM3PI#issuecomment-496291261 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWK6QTB65Z4TJU76XKTPXQZGLANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Eu não sei o suficiente sobre PRNGs para realmente pesar em qualquer caso, eu só quero que o foco seja primeiro nas propriedades estatísticas (se a resposta for que todas elas são muito, muito boas, ótimo). Uma coisa que me pergunto agora é a equidistribuição k-dimensional. Atualmente, usamos variantes de, digamos, PCG que se saem bem aqui em comparação com o MT? (Vindo de dinâmica não linear, isso me deixa um pouco nervoso, mas não tenho visão geral suficiente sobre PRNGs e não vou obtê-lo nos próximos 2 dias ...

Parece improvável que existam muitos usuários do Windows de 32 bits que se preocupam com o desempenho de ponta. Não é preciso muito esforço para mudar para 64 bits.

Eu também gostaria de ver.

Sabemos - com base na matemática - que deve haver muitos problemas grandes,
mas não podemos apontar para um exemplo ainda.

O princípio da precaução diria que, uma vez que sabemos que existem grandes
problemas e sabemos como evitá-los (CS-PRNGs), podemos também
isso por padrão e permite que os usuários sejam menos cautelosos, se assim o desejarem.

Na segunda-feira, 27 de maio de 2019 às 12h39 Robert Kern [email protected]
escrevi:

@pbstark https://github.com/pbstark O que eu gostaria de ver é um concreto
implementação do problema (pode ser artificial, mas não muito inventado) em
em que o MT ou um PRNG de 128 bits falha e o cryptorandom funcionaria. Você pode
apontar um conjunto de dados onde o método de reamostragem dá errado
inferências com um PRNG de 128 bits e inferências corretas com cryptorandom?

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWODTUIPNMVOJB6QP3DPXQ2FPA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKNFCI#issuecomment-496292489 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWITAGQFZDQSIFNEHETPXQ2FPANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

k-equidistribuição é uma propriedade de conjunto de saídas PRNG em todo o
período do PRNG. É uma coisa boa, mas não diz nada sobre outros
tipos de falhas de aleatoriedade, como correlação serial de resultados.
É uma barra relativamente baixa.

Na segunda-feira, 27 de maio de 2019 às 12h48, Sebastian Berg [email protected]
escrevi:

Eu não sei o suficiente sobre PRNGs para realmente pesar em qualquer caso, eu só quero
o foco deve estar nas propriedades estatísticas primeiro (se a resposta for essa
eles são todos muito, muito bons, tudo bem). Uma coisa que me pergunto agora é o
equidistribuição k-dimensional. Atualmente usamos variantes de, digamos, PCG
que se sai bem aqui em comparação com o MT? (Vindo de dinâmica não linear, que
me deixa um pouco nervoso, mas não tenho visão geral suficiente sobre PRNGs e eu
não vou receber nos próximos 2 dias ...

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWPOBTPYHC3XBINQYA3PXQ3HZA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKNPXQ#issuecomment-496293854 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWOEB7KR2YJZWHRRAHLPXQ3HZANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@pbstark MT falha em vários testes estatísticos que o PCG (e outros geradores) passa.

@rkern

Se alguém deseja que o MSVC gere a instrução ror, acho que é necessário usar o intrínseco "_rotr64".

Também pode-se preferir o sinalizador '/ O2' para otimização.

Olhando para ele, pode realmente ser melhor escrevê-lo em conjunto, se alguém quiser usar o PCG64.

Para @pbstark , aqui está uma saída do PCG-64 inicializado com uma semente desconhecida para você (na verdade, direi até o fluxo, é 0x559107ab8002ccda3b8daf9dbe4ed480 ):

  64bit: 0x21fdab3336e3627d 0x593e5ada8c20b97e 0x4c6dce7b21370ffc
     0xe78feafb1a3e4536 0x35a7d7bed633b42f 0x70147a46c2a396a0
  Coins: TTTHHTTTHHTHTTTTHTHHTTTTTHTTHHTTHHTHHTHHHHHHHHTTHHTTHHTHHHHHTHTHH
  Rolls: 5 3 5 2 5 3 1 6 6 5 4 4 5 5 5 6 2 3 5 3 2 3 2 5 6 2 4 6 2 3 4 6 3
  Cards: 5h 3h 3c 8d 9h 7s Kh Ah 5d Kc Tc 6h 7h 8s Ac 5c Ad Td 8c Qd 2h As
     8h 2d 3s 5s 4d 6d 2s Jd 3d 4h Ks 6s Qc Js Th 9d 9c Ts Jh 4c 2c 9s
     6c 4s 7c 7d Jc Qs Kd Qh

Agora, vamos supor que você inicialize outro gerador de pcg com uma semente escolhida aleatoriamente. Vamos escolher, por uma questão de argumento, 0xb124fedbf31ce435ff0151f8a07496d3 . Quantas saídas devemos gerar antes de descobrirmos essa saída conhecida? Como conheço a semente que usei acima, posso responder que (por meio da função de distância do PCG), cerca de 2,5 × 10 ^ 38 (ou cerca de 2 ^ 127,5) saídas. Para referência, 10 ^ 38 nanossegundos é 230 bilhões de vezes a idade do universo.

Portanto, há uma sequência no PCG-64 que está realmente lá, mas, na prática, você nunca a encontrará a menos que eu diga onde procurar. (E haveria ainda mais possibilidades se variarmos o fluxo.)

O PCG normal tem, na verdade, nenhuma chance de produzir uma peça de Shakespeare; o esquema de geração estendida do PCG pode realmente produzir uma peça de Shakespeare, mas a chance de isso acontecer em um cenário não inventado é tão infinitesimal que é essencialmente zero também. A meu ver, há muito pouco valor em uma propriedade que não tenha qualquer consequência prática.

(Além disso, PRNGs criptograficamente seguros não têm garantia de ser k-dimensionalmente equidistribuídos, nem são uma fórmula mágica para pessoas que querem PRNGs que podem gerar todas as sequências possíveis. No momento em que você deseja mais bits de um PRNG do que ele recebe como seu semente e armazena como seu estado, há necessariamente algumas sequências de bits que não podem ser geradas (prova: pelo princípio da classificação). E se você se limitar à mesma quantidade de saída que você colocou como semente, o que você o que realmente está procurando é uma função hash, ou talvez apenas a função de identidade, se sua entrada inicial for realmente aleatória, não um PRNG.)

Por curiosidade, criei um gerador de bits de contador AES usando aesctr.h , tempo em ns por valor aleatório:

+---------------------+--------------+----------+--------------+
|                     |   Xoshiro256 |    PCG64 |   AESCounter |
|---------------------+--------------+----------+--------------+
| 32-bit Unsigned Int |      3.40804 |  3.59984 |      5.2432  |
| Uniform             |      3.71296 |  4.372   |      4.93744 |
| 64-bit Unsigned Int |      3.97516 |  4.55628 |      5.76628 |
| Exponential         |      4.60288 |  5.63736 |      6.72288 |
| Normal              |      8.10372 | 10.1101  |     12.1082  |
+---------------------+--------------+----------+--------------+

Bom trabalho, @bashtage.

Algumas coisas a se ter em mente, uma delas é que as instruções específicas do AES podem variar entre as arquiteturas e não estão presentes em todas as CPUs ativamente usadas, portanto, deve haver um caminho de fallback (lento).

Além disso, é um pouco como uma comparação de maçãs com laranjas. Além de usar instruções especializadas, o código AES obtém uma parte de sua velocidade com o desenrolamento do loop - na verdade, está gerando números em blocos e, em seguida, os lê em voz alta. O desenrolamento pode potencialmente acelerar qualquer PRNG. FWIW, @lemire na verdade tem uma versão vetorizada do PCG que usa instruções AVX para gerar várias saídas de uma vez.

Deixe-me ver se consigo resumir pelo menos um ponto de consenso: Todos nós concordamos que numpy deve ser opinativo sobre qual algoritmo BitGenerator usar e promover um BitGenerator sobre os outros.

Permita-me mais uma tentativa de esboçar uma opção "Sem default" que esteja de acordo com esse consenso e evite alguns dos problemas que algumas das outras opções podem ter. Se não conseguir tração, vou calar a boca sobre isso.

O que eu realmente quis dizer com a opção "Sem padrão" foi "Sem padrão anônimo". Ainda há maneiras de projetar a API de forma que a maneira mais conveniente de obter um Generator propagado seja aquela que nomeia o PRNG que nomearmos. Por exemplo, digamos que _não_ incluamos uma gama completa de BitGenerator algoritmos. Tentamos manter o entorpecimento mínimo e deixar o completismo para scipy e outras bibliotecas de terceiros, em geral, e pode ser uma boa ideia fazer isso aqui. A beleza da arquitetura atual é que ela nos permite mover aqueles BitGenerator s para outras bibliotecas. Então, digamos que fornecemos apenas MT19937 para apoiar o legado RandomState e o BitGenerator que preferimos que as pessoas usem. Para fins de argumentação, digamos que seja Xoshiro256 . Vamos fazer o construtor Generator.__init__() exigir um BitGenerator . Mas também, vamos definir uma função np.random.xoshiro256_gen(seed) que retorna Generator(Xoshiro256(seed)) baixo dos panos. Documentamos essa função de conveniência como a maneira de obter um Generator propagado.

Agora avance alguns lançamentos. Digamos que transferimos PCG64 , ThreeFry , etc. para random-tng ou scipy ou algum outro pacote, e um deles se tornou popular por causa de os recursos extras ou novas falhas estatísticas são encontrados em Xoshiro256 . Decidimos que queremos atualizar a opinião de numpy sobre qual BitGenerator pessoas deveriam usar para PCG64 . Então o que fazemos é adicionar a classe PCG64 BitGenerator e adicionar a função np.random.pcg64_gen(seed) . Adicionamos um aviso de descontinuação a np.random.xoshiro256_gen(seed) para dizer que não é mais o algoritmo preferido: recomendamos que o novo código use np.random.pcg64_gen(seed) , mas continue a usar o algoritmo Xoshiro256 sem avisos, eles devem usar explicitamente Generator(Xoshiro256(seed)) .

Acho que isso evita alguns dos problemas que tenho com uma API "anônima" (ou seja, Generator(seed) , Generator(DefaultBitGenerator(seed)) , np.random.default_gen(seed) ). Podemos apoiar os algoritmos não mais preferidos para sempre. Nunca precisaremos fazer com que nosso construtor "preferido" opinativo faça algo diferente quando mudarmos de opinião. Como usamos nomes reais para distinguir coisas em vez de números de versão, você sempre sabe como atualizar o código para reproduzir resultados antigos (se por algum motivo não suportar os avisos inofensivos). Você pode até mesmo pegar o código sem procedência ou números de lançamento numpy registrados e fazer essa atualização. Ao mesmo tempo, limitando o número de algoritmos a um mínimo absoluto e tornando as melhores práticas a maneira mais _fácil_ de trabalhar com a biblioteca, somos capazes de expressar a opinião de numpy de forma eficaz.

Como isso soa? Ainda devemos nos esforçar muito para fazer uma escolha sólida para aqueles BitGenerator para o primeiro lançamento. Ainda tem consequências.

Parece improvável que existam muitos usuários do Windows de 32 bits que se preocupam com o desempenho de ponta. Não é preciso muito esforço para mudar para 64 bits.

Concordo. Como o problema com PCG64 no Win32 é simplesmente desempenho (e que provavelmente podemos melhorar com algum esforço), você concorda que não é um bloqueador?

Se alguém deseja que o MSVC gere a instrução ror, acho que é necessário usar o intrínseco "_rotr64".

Também pode-se preferir o sinalizador '/ O2' para otimização.

Obrigado! @imneme apontou todos esses erros para mim. :-)

@seberg Você tem alguma citação em sua área que o levou a ficar desconfiado? Por exemplo, um artigo que mostrou que a propriedade de equidistribuição k = 623 de MT19937 corrigiu problemas em simulações de dinâmica não linear que um PRNG menor causou? Posso ser capaz de fornecer alguma garantia mais específica com isso como referência. Em geral, minha opinião sobre a equidistribuição é que geralmente você deseja que a equidistribuição do PRNG seja próxima ao máximo permitido pelo tamanho do estado do PRNG. Na _prática_, se o seu PRNG for grande o suficiente para seus objetivos em outros aspectos (passa PractRand, tem um período maior do que o quadrado do número de amostras que você planeja desenhar, etc.), nunca vi muito motivo para se preocupar com o preciso k . Outros podem ter opiniões diferentes e talvez haja questões específicas em sua área das quais não estou ciente. Se for esse o caso, existem soluções específicas disponíveis!

Por curiosidade, criei um gerador de bits de contador AES usando aesctr.h

Posso estar errado, mas não acredito que isso ajude nas preocupações do k! permutações para considerável k . O contador ainda é um número de 128 bits e, depois de rolar, você atingiu o fim do período. @pbstark está defendendo PRNGs de período muito grande, a maioria dos quais por acaso são CS-RNGs.

Em geral, minha opinião sobre a equidistribuição é que você geralmente deseja que a equidistribuição do PRNG seja próxima ao máximo permitido pelo tamanho do estado do PRNG.

Embora alguns considerem a equidistribuição máxima uma propriedade desejável, ela também pode ser considerada uma falha (e há jornais por aí que dizem isso). Se tivermos um PRNG de _k_ bits e cada sequência de _k_ bits ocorrer exatamente uma vez, isso vai acabar violando o problema de aniversário que diz que esperaríamos ver uma repetição de saída após cerca de 2 ^ (k / 2) saídas. ( Eu escrevi um teste estatístico de problema de aniversário com base nessas ideias . Ele detectou corretamente a ausência estatisticamente implausível de qualquer repetição no SplitMix, um PRNG de estado de 64 bits com saída de 64 bits e Xoroshiro64 +, saída de 64 bits de 32 bits - PRNG bidimensional equidistribuído de estado, entre outros.)

Curiosamente, embora seja muito prático escrever um teste estatístico que irá falhar em um PRNG por falta de repetições de 64 bits (ou muitas repetições - estamos esperando uma distribuição de Poisson), é inversamente _não_ prático escrever um teste que irá detectar a omissão de 36,8% de todos os valores de 64 bits se não soubermos nada sobre quais são omitidos.

Obviamente, o teste para a falha de falta de repetições esperadas começa a ser impraticável para executar conforme _k_ fica maior, mas à medida que aumentamos o tamanho do estado (e período), o tamanho adicionado significa que é impraticável mostrar que um PRNG maximamente equidistribuído é falho por não repetir e igualmente impraticável para mostrar que um PRNG não maximamente equidistribuído é falho por repetir algumas sequências de _k_ bits (de uma forma estatisticamente plausível) e omitir outras inteiramente. Em ambos os casos, o PRNG é muito grande para sermos capazes de distinguir os dois.

Eu também gostaria de ver. Sabemos - com base na matemática - que deve haver muitos problemas grandes, mas ainda não podemos apontar um exemplo. O princípio da precaução diria que, uma vez que sabemos que existem grandes problemas e sabemos como evitá-los (CS-PRNGs), podemos também fazer isso por padrão e deixar os usuários serem menos cautelosos se quiserem.

Devo dizer que não estou persuadido por esta linha de argumento e evidência. Sendo assim, eu não me sentiria confortável em ficar na frente de usuários entorpecidos e dizer a eles que deveriam trocar por esse motivo. Não estou equipado para defender essa afirmação, e é por isso que estou pedindo esses exemplos. Isso seria persuasivo e me prepararia para defender esta posição que você está recomendando que tomemos.

Existem muitas propriedades pelas quais os PRNGs finitos ficam aquém dos verdadeiros RNGs. E há muitos cálculos que queremos fazer que, teoricamente, dependem dessas propriedades de verdadeiros RNGs (ou, pelo menos, não provamos rigorosamente o quanto podemos relaxá-los). Mas muitas dessas deficiências têm apenas um efeito muito pequeno, praticamente imperceptível, nos resultados dos cálculos reais que realizamos. Essas violações não são dispositivos do tipo tudo ou nada, vai / não vai. Eles têm um tamanho de efeito e podemos tolerar mais ou menos o efeito.

Você mostra, de maneira convincente, é claro, que PRNGs de determinado tamanho não podem gerar uniformemente todas as k! permutações para alguns k . A etapa que estou perdendo é como essa falha em ser capaz de gerar todas essas permutações afeta um cálculo concreto que eu estaria interessado em realizar. Estou perdendo o teste que você recomendaria adicionar a PractRand ou TestU01 que demonstraria o problema para as pessoas.

Uma das linhas de análise que achei muito informativa do artigo PCG de @imneme foi derivar várias versões de estado menor de cada PRNG e ver exatamente onde eles começaram a falhar no TestU01. Isso nos dá alguma maneira de comparar ordinalmente as arquiteturas PRNG em vez de apenas dizer tudo ou nada, "X PRNG passa" ou falha. Também nos permite estimar quanto headroom temos nos tamanhos de estado em uso (que passam no TestU01 em um grande número de GiB de amostras). Existem cálculos concretos que você pode fazer para demonstrar um problema com PRNGs de 8 bits? PRNGs de 16 bits? Então, podemos ver se esse novo teste nos forneceria informações sobre o PRNG antes do que TestU01 / PractRand faz atualmente. E isso seria muito útil.

Por outro lado, se são necessários _mais_ dados extraídos do PRNG para mostrar uma falha com base nessas permutações do que o ponto de falha dos pequenos PRNGs para o conjunto atual de testes no PractRand, então eu concluiria que esse problema não é prático preocupação, e poderíamos usar "passa PractRand" como um bom proxy para saber se o problema de permutação teria um efeito prático em meus cálculos.

Mas até que eu tenha um programa em minhas mãos que eu possa executar e mostrar às pessoas o problema, não me sinto confortável pressionando por um CS-PRNG com base nisso. Eu seria incapaz de explicar a escolha de forma convincente.

Para aqueles que exigem isso ao embaralhar 32 itens, todos os 32! shuffles (ou seja, todos os 263130836933693530167218012160000000 deles) devem ser geráveis, em vez de um PRNG que fornece apenas uma amostra aleatória desses 32! embaralha, eu diria que se você vai exigir grandes números, você simplesmente não está

Eu, portanto, (jocosamente) alegaria que a ordem em que esses embaralhamentos saem também não deve ser predeterminada! Obviamente você deve exigir que produza todos os 32! embaralha em todos os pedidos possíveis - (32!)! é o que você precisa! Obviamente, isso exigirá 3,8 × 10 ^ 18 exabytes para o estado e uma quantidade semelhante de entropia para inicializar, mas certamente vale a pena saber que tudo está lá.

... Adicionamos um aviso de descontinuação a np.random.xoshiro256_gen(seed) para dizer que não é mais o algoritmo preferido: recomendamos que o novo código use np.random.pcg64_gen(seed) , mas para continuar a usar o algoritmo Xoshiro256 sem avisos, eles devem usar explicitamente Generator(Xoshiro256(seed))

Isso ainda está incomodando os usuários com a depreciação e com nomes estranhos que eles realmente não querem saber.

O NEP diz: Em segundo lugar, interromper a compatibilidade de fluxo para introduzir novos recursos ou melhorar o desempenho será permitido com cautela.

Se houver uma razão boa o suficiente para atualizar nosso padrão, apenas fazer isso e interromper a reprodutibilidade bit a bit para usuários que não especificaram explicitamente um algoritmo é a melhor opção imho (e aquela que você defendia antes).

O que eu realmente quis dizer com a opção "Sem padrão" foi "Sem padrão anônimo".

Então, você quer _quer_ que os usuários saibam o nome do PRNG que estão usando?

Veja isso do ponto de vista do usuário. É difícil fazê-los passar de np.random.rand & co para np.random.RandomState() e então usar métodos. Agora vamos apresentar um sistema melhor, e o que eles verão é np.random.xoshiro256_gen() ? Isso seria uma grande regressão em termos de usabilidade.

Então, você quer _quer_ que os usuários saibam o nome do PRNG que estão usando?

Não, é para mitigar os problemas de ter um "alvo móvel designado" API como default_generator(seed) que as pessoas estavam trabalhando (por exemplo, o argumento version @shoyer ).

Manter a compatibilidade de fluxo (que a NEP 19 nega) é secundário à quebra de API. Diferentes BitGenerator s têm diferentes APIs eficazes, dependendo de seus conjuntos de recursos (fluxos configuráveis, jumpahead, principalmente, embora possa haver outros, dependendo de quão parametrizável o PRNG é). Portanto, algumas alterações em nossa seleção PRNG padrão realmente quebrariam o código (ou seja, não executariam mais ou não funcionariam mais corretamente), não apenas alterariam os valores que aparecem.

Por exemplo, digamos que primeiro escolhemos PCG64 . Ele tem um estado de 128 bits, 2 ^ 127 fluxos configuráveis ​​e implementa jumpahead; agradável e cheio de recursos. Assim, as pessoas começam a escrever default_generator(seed, stream=whatever) . Agora, digamos que o trabalho futuro encontre alguma falha estatística importante que nos faça querer mudar para outra coisa. O próximo PRNG que promovemos como padrão deve ter> = estado de 128 bits (fácil; eu não recomendaria nada menor como um padrão de uso geral), jumpahead (difícil!),> = 2 ^ 127 fluxos configuráveis ​​(uau, menino!), para não quebrar os usos de default_generator() que já existem no código. Agora talvez possamos viver com essa catraca.

@shoyer sugeriu que talvez pudéssemos tornar o BitGenerator padrão sempre deliberadamente limitado a apenas os recursos de menor denominador comum. Isso funcionaria! Mas também perderia a oportunidade de promover fluxos configuráveis ​​para resolver o problema de fluxos paralelos como @charris gostaria de fazer.

Agora vamos apresentar um sistema melhor, e o que eles verão é np.random.xoshiro256_gen() ? Isso seria uma grande regressão em termos de usabilidade.

Se o nome que soa estranho for o problema, ficarei feliz em usar um nome genérico mais amigável, desde que a política seja a mesma (adicionamos uma nova função e começamos a alertar sobre a antiga). Eu consideraria isso equivalente. Não deveríamos fazer isso com muita frequência.

Eu também estou bem se decidirmos conviver com a catraca e evitar um mecanismo de version .

Minha opinião sobre um "padrão" é que poderíamos deixá-lo como um detalhe de implementação, de modo que Generator() sempre funcionaria. Eu discutiria isso com uma forte nota de cautela de que a única maneira de obter resultados sempre reproduzíveis (até mudanças no Gerador) é usar a sintaxe Generator(BitGenerator(kwarg1=1,kwargs2=b,...))

Não é prático ocultar realmente os detalhes de implementação, pois o acesso ao estado é necessário para decapagem.

A alternativa é apenas tratá-lo como qualquer outra função - e geração aleatória em geral - e passar por um ciclo de depreciação padrão caso haja uma necessidade urgente de mudança. Isso nunca afetará os usuários que fazem as coisas corretamente e, com avisos suficientes nos documentos, pode ser possível obter uma taxa de acertos decente nisso, pelo menos em grandes projetos. O que estou sugerindo aqui é que alguém poderia esquecer a garantia de compatibilidade de fluxo sempre feita quando se pensava na nova API.

@bashtage em # 13650 Eu desabilitei o acesso a Generator().bit_generator de uma forma que ainda permite a decapagem sem acesso direto a state . Ele passa o ligeiramente reescrito test_pickle de uma forma que permitiria o uso em python Thread s

Minha pergunta é - o que é seguro? Existem apenas vários graus de quase aleatoriedade com várias propriedades. Até agora não vi ninguém dar um exemplo concreto, nem aqui nem em outras edições, PRs ou tópicos.

"Passa PractRand em

Se você quiser uma visão mais sofisticada da qualidade estatística que permita a classificação ordinal de algoritmos, recomendo a você a Seção 3 do artigo PCG de BitGenerator 128 bits passar no PractRand, mas sua versão de 120 bits falhar, é provavelmente muito arriscado.

@mattip Isso parece razoável. Embora alguém em algum lugar vá

import gc
state = [o for o in gc.get_objects() if 'Xoshiro256' in str(o)][0].state

se eles quiserem se aprofundar, tudo bem. Eu só quero ajudar o usuário não especialista

Ele passa o ligeiramente reescrito test_pickle de uma forma que permitiria o uso em python Thread s

É importante notar que este é um problema pendente (# 9650) - idealmente Generator() seria propagado novamente em threads filho. IIRC isso só é prático em Python> = 3,7

Minha opinião sobre um "padrão" é que poderíamos deixá-lo como um detalhe de implementação, de modo que Generator() sempre funcionaria. Eu discutiria isso com uma forte nota de cautela de que a única maneira de obter resultados sempre reproduzíveis (até mudanças no Gerador) é usar a sintaxe Generator(BitGenerator(kwarg1=1,kwargs2=b,...))

Existem dois tipos de reprodutibilidade que precisamos distinguir. Uma é que executo meu programa duas vezes com a mesma semente e obtenho os mesmos resultados. É isso que precisamos apoiar. A outra é a reprodutibilidade entre as versões de numpy, que rejeitamos, pelo menos no sentido mais estrito.

Sem argumentos Generator() , ou seja, "dê-me um PRNG semeado arbitrariamente que a Numpy recomenda" não é o caso de uso principal. Não requer muito suporte. "Dê-me o PRNG que a Numpy recomenda com _esta_ semente" é, e é para isso que estamos discutindo opções. Precisamos de uma forma para o numpy expressar uma opinião sobre como obter um PRNG propagado, e essa forma precisa ser fácil e conveniente para os usuários (do contrário, eles não a usarão). Eu _gosto_ de nomear o algoritmo (embora por meio de uma função mais conveniente), mas @rgommers acha que é um passo longe demais, e eu simpatizo com isso.

Gerador sem argumentos (), ou seja, "dê-me um PRNG semeado arbitrariamente que a numpy recomenda" não é o caso de uso principal. Não requer muito suporte. "Dê-me o PRNG que a Numpy recomenda com esta semente" é, e é para isso que estamos discutindo opções. Precisamos de uma forma para o numpy expressar uma opinião sobre como obter um PRNG propagado, e essa forma precisa ser fácil e conveniente para os usuários (do contrário, eles não a usarão). Gosto de nomear o algoritmo (embora por meio de uma função mais conveniente), mas @rgommers acha que é um passo longe demais, e sou simpático a isso.

Eu diria que os usuários realmente estão mal equipados para fornecer boas sementes. Por exemplo, quantos usuários sabem a maneira certa de semear o Mersenne Twister? Não é tão fácil quanto você pensa - se você não está alimentando-o com 624 inteiros de 32 bits aleatórios (para fornecer 19937 bits de estado), você está fazendo errado.

Então, na verdade, eu diria que a maneira certa para o usuário obter resultados reproduzíveis é criar o PRNG (sem fornecer uma semente, permitindo que seja bem semeado automaticamente) e depois fazer a conserva.

Se a discussão for apenas sobre o caminho certo, então eu sou a favor
Generator(BitGenerator(**kwargs)) visto que só será usado por
usuários de semiaware que se preocupam com a reprodução.

Acho que o padrão usado para Generator() importa, pois será
interpretado como uma escolha considerada e, portanto, tomá-lo como um
recomendação ao usar a forma propagada.

Só para jogar mais um, um método de classe Generator.seeded(seed[, bit_generator]) where bit generator is a string. This would allow the pattern of switching from one value to None to warn if the default was going to change, like lstsq. I would also only support a limited pallatte of but generators initially (i.e. 1). Doesn't make it easy to expose advanced features I suppose. In a perfect world it would use kwarg only to allow any keyword argument to be used which avoids most depreciation problems. Of course, this doesn't really need to be a class function, just seeded`.

Na terça-feira, 28 de maio de 2019, 16:38, Robert Kern [email protected] escreveu:

Minha opinião sobre um "padrão" é que poderíamos deixá-lo como uma implementação
detalhe, para que Generator () sempre funcione. Eu iria discutir isso com um
nota forte de que a única maneira de obter resultados sempre reproduzíveis
(até mudanças no Generator) é usar a sintaxe
Generator (BitGenerator (kwarg1 = 1, kwargs2 = b, ...))

Existem dois tipos de reprodutibilidade que precisamos distinguir. Um é
que eu execute meu programa duas vezes com a mesma semente e obtenho os mesmos resultados.
É isso que precisamos apoiar. O outro é reprodutibilidade em
versões de numpy, que rejeitamos, pelo menos no sentido mais estrito.

Gerador sem argumentos (), ou seja, "dê-me um PRNG semeado arbitrariamente que
numpy recomenda "não é o caso de uso principal. Não requer muito
Apoio, suporte. "Dê-me o PRNG que a Numpy recomenda com esta semente" é,
e é para isso que estamos discutindo opções. Precisamos de uma maneira para o numpy
expressar uma opinião sobre como obter um PRNG semeado, e dessa forma precisa ser
fácil e conveniente para os usuários (caso contrário, eles não o usarão). Eu gosto
nomear o algoritmo (embora por meio de uma função mais conveniente), mas
@rgommers https://github.com/rgommers acha que é um passo longe demais e
Eu simpatizo com isso.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKA4SSNW6XZEVFUMCDPXVGW5A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWMRDLI#issuecomment-496570797 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABKTSROCMLHG6E6BLWI6TWDPXVGW5ANCNFSM4HPX3CHA
.

Na terça-feira, 28 de maio de 2019, 16:38, Robert Kern [email protected] escreveu:

Minha opinião sobre um "padrão" é que poderíamos deixá-lo como uma implementação
detalhe, para que Generator () sempre funcione. Eu iria discutir isso com um
nota forte de que a única maneira de obter resultados sempre reproduzíveis
(até mudanças no Generator) é usar a sintaxe
Generator (BitGenerator (kwarg1 = 1, kwargs2 = b, ...))

Existem dois tipos de reprodutibilidade que precisamos distinguir. Um é
que eu execute meu programa duas vezes com a mesma semente e obtenho os mesmos resultados.
É isso que precisamos apoiar. O outro é reprodutibilidade em
versões de numpy, que rejeitamos, pelo menos no sentido mais estrito.

Gerador sem argumentos (), ou seja, "dê-me um PRNG semeado arbitrariamente que
numpy recomenda "não é o caso de uso principal. Não requer muito
Apoio, suporte. "Dê-me o PRNG que a Numpy recomenda com esta semente" é,
e é para isso que estamos discutindo opções. Precisamos de uma maneira para o numpy
expressar uma opinião sobre como obter um PRNG semeado, e dessa forma precisa ser
fácil e conveniente para os usuários (caso contrário, eles não o usarão). Eu gosto
nomear o algoritmo (embora por meio de uma função mais conveniente), mas
@rgommers https://github.com/rgommers acha que é um passo longe demais e
Eu simpatizo com isso.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKA4SSNW6XZEVFUMCDPXVGW5A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWMRDLI#issuecomment-496570797 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABKTSROCMLHG6E6BLWI6TWDPXVGW5ANCNFSM4HPX3CHA
.

De uma perspectiva de usabilidade, acho que realmente precisamos oferecer suporte a Generator(seed) . Caso contrário, diante de uma escolha que não estão preparados para fazer, os usuários ficarão com RandomState .

Para controlar a versão do gerador de bits padrão em Generator , poderíamos usar bit_version=1 vez de version=1 , embora eu também esteja OK em descartar a ideia version . Eu não acho que os usuários precisarão definir geradores de bits explicitamente com muita frequência.

Minha preferência para resolver casos de uso específicos que precisam de recursos específicos do gerador seria projetar APIs BitGenerator novas e genéricas que ocultam detalhes de implementação. Eles podem ser adicionados a DefaultBitGenerator ou colocados em novas classes se seu uso envolver compensações, por exemplo, ParallelBitGenerator .

Definitivamente, gostaria de evitar avisos sobre alterações futuras no fluxo RNG devido à alteração do gerador de bits padrão. Esses avisos seriam apenas ruído para a grande maioria dos usuários que não confiam em tais detalhes, mas que definem um seed no Generator apenas para impedir que seus números aleatórios mudem espontaneamente.

os usuários vão ficar com RandomState.

Tudo bem, eles não são os primeiros a adotar. Estou pressionando muito (talvez muito?) Para obter a API mínima viável possível, pois sempre podemos ampliar a API, mas é muito mais difícil reduzi-la. As nuances entre Generator(Philox()) , Generator(seed(3)) e Generator(bit_version=1) são um pouco difíceis de ver até que cheguem aos usuários finais.

Vamos lançar uma primeira versão sem Generator(seed) e obter alguns comentários.

Vamos lançar uma primeira versão sem Generator(seed) e obter alguns comentários.

OK, não tenho objeções sérias aqui. Nesse caso, podemos também exigir a especificação do BitGenerator completo por enquanto.

Então, na verdade, eu diria que a maneira certa para o usuário obter resultados reproduzíveis é criar o PRNG (sem fornecer uma semente, permitindo que seja bem semeado automaticamente) e depois fazer a conserva.

Eu digo a mesma coisa, mas consigo muito pouca tração com isso. Como você diz: "Bem, só porque algo é uma má ideia não significa que as pessoas não vão querer fazer isso!"

Parte do problema é exacerbado pelo enorme estado do MT, que realmente precisa da serialização para um arquivo. É simplesmente difícil tornar essa dança baseada em arquivo a API mais fácil disponível para que os usuários queiram usá-la. As coisas serão melhores com um PRNG padrão com um estado muito menor. 128 bits é o tamanho de um UUID, que é pequeno o suficiente para imprimir em hexadecimal e copiar e colar. Portanto, um bom padrão pode ser escrever seu programa de forma que ele tenha como padrão uma boa semente de entropia e, em seguida, imprima seu estado de uma forma que você possa copiar e colar na próxima vez que executar o programa.

❯ python secret_prng.py
Seed: 0x977918d0c7da45e5168f72005586500c
...
Result = 0.7223650399276123

❯ python secret_prng.py
Seed: 0xe8962534e5fb585483b86119fcb852ce
...
Result = 0.10640984721018876

❯ python secret_prng.py --seed 0xe8962534e5fb585483b86119fcb852ce
Seed: 0xe8962534e5fb585483b86119fcb852ce
...
Result = 0.10640984721018876

Não tenho certeza se um padrão como este forneceria a simplicidade e a resistência futura.

NumPy 1.next

class Generator:
    def __init__(bitgen_or_seed=None, *, bit_generator='pcg64', inc=0):

NumPy 1.20.x

`` `python
classe Generator:
def __init __ (bitgen_or_seed = None, *, bit_generator = None, inc = None):
se bit_generator não for None ou inc não for None:
warn ('O padrão é mudar de PCG64 para AESCtr. A palavra-chave inc'
'o argumento está obsoleto e será gerado no futuro', FutureWarning)
`` ``

NumPy 1.22

`` `python
classe Generator:
def __init __ (bitgen_or_seed = None, *, bit_generator = 'aesctr', inc = None, counter = 0):
se bit_generator == 'pcg64' ou inc não for nenhum:
levantar exceção ('PCG não é mais suportado e inc foi removido')
`` ``

Não tenho certeza se um padrão como este forneceria a simplicidade e a resistência futura.

Como observei acima em https://github.com/numpy/numpy/issues/13635#issuecomment -496589421, acho que isso seria surpreendente e frustrante para a maioria dos usuários. Eu preferiria exigir o fornecimento de um objeto BitGenerator explícito do que planejar começar a emitir avisos se os usuários não definiram todos os argumentos opcionais. Isso realmente deve ser o último recurso, para os casos em que descobrimos que as APIs estão quebradas de maneiras que não havíamos previsto.

O problema é o período de transição. Todos que usarem os padrões receberão avisos repentinamente, sem uma ótima maneira de alternar durante o período de transição. Ou, pelo menos, você os move de um "estado de baixa energia" de Generator(seed) para um "estado de alta energia" menos conveniente de Generator(seed, bit_generator='aesctr') . Como o objetivo desta API era fornecer um "estado de baixa energia" conveniente, falhamos em nosso propósito durante essa transição. Fizemos isso uma vez com uma de nossas funções de histograma, IIRC, e foi um pesadelo.

Isso é comum a todas as depreciações que tentam alterar os significados dos argumentos no local. Depreciações que _movem_ de uma função para outra são muito mais fáceis de gerenciar, e é isso que eu estava defendendo.

Vamos lançar uma primeira versão sem Generator(seed) e obter alguns comentários.

Por "primeira versão", você quer dizer um lançamento numpy completo? Ou apenas obter a fusão do PR (o que já aconteceu)?

Se for um lançamento completo, ainda temos algumas coisas a determinar, como quantos BitGenerator s incluímos. Se incluirmos o complemento atual completo, divulgamos algumas das opções.

Depreciações que _movem_ de uma função para outra são muito mais fáceis de gerenciar, e é isso que eu estava defendendo.

+1 concordou

Não, é para mitigar os problemas de ter um "alvo móvel designado" API como default_generator (seed) que as pessoas estavam trabalhando (por exemplo, o argumento de versão de @shoyer ).

Manter a compatibilidade de fluxo (que a NEP 19 nega) é secundário à quebra de API. Diferentes BitGenerators têm diferentes APIs eficazes

Tudo bem, agora faz mais sentido para mim.

Se o nome que soa estranho for o problema, ficarei feliz em usar um nome genérico mais amigável, desde que a política seja a mesma (adicionamos uma nova função e começamos a alertar sobre a antiga). Eu consideraria isso equivalente. Não deveríamos fazer isso com muita frequência.

Esta parece ser a melhor solução até agora. Deve ser possível escolher um nome lógico aqui. E há um grande espaço de outros nomes sensatos - que provavelmente nunca precisaremos.

Algo como np.random.generator ou np.random.default_generator .

quantos BitGenerators incluímos

Você poderia abrir um problema separado com uma proposta para descartar aqueles que você acha que devemos remover dos atualmente incluídos (MT19937, DSFMT, PCG32, PCG64, Philox, ThreeFry, Xoshiro256, Xoshiro512)?

Ainda não resolvemos o problema em questão aqui: qual BitGenerator deve ser o padrão (atualmente Xoshiro256 )

Bem, esta questão é mais sobre "qual deve ser promovido como o distinto BitGenerator ", que alimenta a escolha do padrão, mas também quais devem ser incluídos ou eliminados. A mecânica pela qual fornecemos padrões (se fornecermos padrões) adiciona algumas restrições, portanto, todas essas são coisas que mais ou menos precisam ser decididas em conjunto. É uma grande bagunça e depois de todo o trabalho que você fez para acompanhar este PR, tenho certeza de que está exausto para ver outro megatarefa sem ninguém contribuindo com código, então você tem minha solidariedade. :-)

No que diz respeito aos algoritmos em si, já dei minhas recomendações: devemos manter MT19937 para RandomState e para fins de comparação, e eu gosto de PCG64 para fins de recomendação.

Eu mexi um pouco no Compiler Explorer e acho que implementei o PCG64 para MSVC de 64 bits usando intrínsecos de uma forma que força o compilador a gerar um assembly próximo ao uint128_t math do clang: https: // godbolt .org / z / ZnPd7Z

Não tenho um ambiente de desenvolvimento do Windows configurado no momento, então não sei se ele está realmente _correto _... @bashtage , você se importaria de

Sem patch:

Uniforms per second
************************************************************
PCG64            62.77 million

Com patch:

Uniforms per second
************************************************************
PCG64           154.50 million

O patch passa nos testes, incluindo a geração do mesmo conjunto de 1000 valores de uint64 para 2 sementes diferentes.

Para comparação com GCC nativo e no modo de comparação:

Time to produce 1,000,000 Uniforms
************************************************************
Linux-64, GCC 7.4                      PCG64            4.18 ms
Linux-64, GCC 7.4, Forced Emulation    PCG64            5.19 ms
Win64                                  PCG64            6.63 ms
Win32                                  PCG64           45.55 ms

uau, isso parece muito ruim. Talvez devêssemos estender as informações na página de comparações para demonstrar o desempenho msvc2017-on-win {64, 32} vs. gcc7.4-on-linux {64, 32} na mesma máquina (presumo que você esteja usando msvc2017, você provavelmente deve incluir essa informação em algum lugar).

Win32 é inútil aqui. Suspeito que o Linux de 32 bits também seja terrível, mas não tenho um sistema Linux de 32 bits para testar facilmente.

Eu posso definitivamente ver o caso de fazer uma recomendação para pessoas que usam uma máquina de 32 bits (provavelmente Windows devido às políticas corporativas de TI). Este recco é claro: DSFMT para 32 bits (ou MT19937 também é bom). Os benchmarks seriam bons, no entanto.

Pelo que vale a pena, sou bastante cético em relação à afirmação freqüentemente repetida do PCG de múltiplos fluxos aleatórios independentes . Alguém fez alguma análise estatística séria para apoiar a reivindicação de independência? (Na verdade, acho que o artigo de O'Neill se refere apenas a fluxos "distintos", sem qualquer reivindicação de independência.)

Acho que há um bom motivo para ser cético: para um determinado multiplicador LCG, todos esses fluxos distintos são simplesmente relacionados por meio de escala [*]. Assim, dados quaisquer dois fluxos LCG com o mesmo multiplicador, um deles será simplesmente um múltiplo constante (módulo 2**64 ou 2**32 conforme apropriado) do outro, embora com pontos de partida diferentes. A parte de permutação do PCG ajudará a esconder isso um pouco, mas realmente não seria surpreendente se houvesse correlações detectáveis ​​estatisticamente.

Streams tão independentes pelo valor de face sem alguns testes sérios.

[*] Exemplo: suponha que x[0], x[1], x[2], ... seja um fluxo LCG padrão de 64 bits, com x[i+1] := (m*x[i] + a) % 2**64 . Defina y[i] := 3*x[i] % 2**64 para todos os i . Então y[i] é um fluxo LCG com y[i+1] := (m*y[i] + 3*a) % 2**64 , então, simplesmente escalando o fluxo original, você produziu um desses fluxos LCG distintos com o mesmo multiplicador, mas com constante aditiva diferente. Usando outros multiplicadores ímpares no lugar de 3 , e assumindo que estamos interessados ​​apenas em LCGs de período completo (e assim a é ímpar), você obterá todos os valores completos possíveis período LCGs com esse multiplicador.


EDIT: Corrigida declaração errada sobre o número de classes de conjugação.

Acho que a análise pública mais completa dos streams PCG está aqui: http://www.pcg-random.org/posts/critiquing-pcg-streams.html

@imneme Você pode expandir seu conselho final? "correspondência com David Blackman mostra que pode ser mais fácil do que eu pensava fazer fluxos" próximos "com inicializações correlacionadas como uma semente constante e fluxos de 1,2,3,4. Se você for usar vários fluxos em ao mesmo tempo, por enquanto, recomendo que o id do stream e a semente sejam distintos e não tenham correlações óbvias entre si, o que significa não torná-los 1,2,3,4. "

Isso significa que você acha que é normal ter uma única semente boa (por exemplo, derivada de uma fonte de entropia) e, em seguida, IDs 1,2,3,4 de fluxo? Ou deve a semente e a ID do fluxo ser escolhidas aleatoriamente de uma boa fonte de entropia?

Alguns números Linux-32 (Ubuntu 18.04 / GCC 7.4)

Time to produce 1,000,000 Uniforms
***************************************************************
Linux-64, GCC 7.4                      PCG64            4.18 ms
Linux-64, GCC 7.4, Forced Emulation    PCG64            5.19 ms
Win64                                  PCG64            6.63 ms
Win32                                  PCG64           45.55 ms
Linux-32, GCC 7.4                      PCG64           25.45 ms

Portanto, é duas vezes mais rápido que o Win-32, mas lento. Todos os 4 tempos foram feitos na mesma máquina

Other Linux-32/GCC 7.4 Timing Results
-----------------------------------------------------------------
DSFMT            6.99 ms
MT19937         13.09 ms
Xoshiro256      17.28 ms
numpy           15.89 ms

NumPy é o NumPy 1.16.4. DSFMT é o único gerador com bom desempenho em 32 bits (x86). Isso deve ser documentado claramente para qualquer usuário de 32 bits. MT19937 também é uma escolha relativamente boa para um usuário de 32 bits.

Portanto, precisamos ter MT19937 para fins de legado. Se quisermos ser mínimos sobre quais PRNGs incluímos (ou seja, MT19937 mais nossa recomendação única de uso geral), então eu não me sentiria obrigado a usar o desempenho de 32 bits para restringir nossa recomendação única de uso geral, nem sinta-se obrigado a adicionar um terceiro PRNG "recomendado para 32 bits". MT19937 sempre estará disponível e não é pior do que o que eles têm atualmente. E pacotes de terceiros estarão disponíveis para usos mais específicos.

Claro, se quisermos incluir um conjunto mais completo de PRNGs por outros motivos, podemos fazer todos os tipos de recomendações específicas na documentação.

Eu estava curioso para saber o quanto a parte "P" do PCG mitigou possíveis problemas de fluxos correlacionados.

Então aqui está (talvez) o pior caso possível para os LCGs: onde a constante aditiva de um fluxo LCG é a negação exata da constante aditiva do outro. Então, com escolhas apropriadamente terríveis de semente, terminamos com um dos fluxos LCG sendo a negação exata do outro.

Mas agora, se estivermos usando ambos os fluxos para gerar uma série de flutuações, tanto a parte de permutação do PCG quanto a conversão para float64 devem nos ajudar um pouco.

Aqui está um gráfico que mostra o quanto a permutação ajuda:

streams

Esse é um gráfico de dispersão de 10.000 flutuadores de um desses fluxos, contra 10.000 de seu gêmeo negado. Não é terrível, mas também não é ótimo: há artefatos claros.

Não tenho certeza do que concluir disso: é um exemplo absolutamente artificial, que você dificilmente (espero) encontrará por acidente. Por outro lado, demonstra que algum pensamento e cuidado são necessários se você realmente precisar de vários fluxos não correlacionados.

Para registro, aqui está a fonte:

import matplotlib.pyplot as plt
import numpy as np

from pcgrandom import PCG64

gen1, gen2 = PCG64(), PCG64()
multiplier, increment, state = gen1._get_core_state()
new_increment, new_state = -increment % 2**128, -state % 2**128
gen2._set_core_state((multiplier, new_increment, new_state))

xs = np.array([gen1.random() for _ in range(10**4)])
ys = np.array([gen2.random() for _ in range(10**4)])
plt.scatter(xs, ys, s=0.1)
plt.show()

PCG64 é o gerador que O'Neill chama de PCG-XSL-RR (seção 6.3.3 do artigo PCG). O pacote pcgrandom é daqui

Achei que a maneira padrão de obter fluxos independentes é usando jumpahead ().
A nova propagação para obter fluxos “independentes” é geralmente perigosa.

Geradores de contador / hash têm um jumpahead () trivial. O PCG?

Também um apelo de um usuário: forneça pelo menos um bitstream que seja
qualidade criptográfica, com um espaço de estado ilimitado.

Felicidades,
Philip

(EDIT por seberg: citação de email removida)

@pbstark : Isso não é apenas uma nova propagação: os dois geradores LCG subjacentes são realmente distintos: x ↦ mx + a (mod 2 ^ 128) e x ↦ mx + b (mod 2 ^ 128) para incrementos diferentes a e b. O papel PCG de O'Neill vende a ideia de ser capaz de criar diferentes fluxos alterando esse incremento LCG (consulte a seção 4.3.2 do papel).

Mas a simplicidade do LCG significa que mudar essa constante aditiva _só_ equivale a um jumpahead por algum valor desconhecido no gerador original, combinado com uma transformação linear simples (multiplicação por uma constante, ou apenas adição de uma constante em alguns casos).

Não é uma razão para não usar o PCG, e nem por um momento estou argumentando que ele não é adequado para o novo PRNG principal do NumPy; Só não quero que as pessoas se deixem enganar pela promessa de fluxos aleatórios "independentes". Na melhor das hipóteses, a ideia de fluxos configuráveis ​​para PCG oferece uma maneira conveniente de fazer algo equivalente a um jumpahead rápido mais um bônus de transformação multiplicativa ou aditiva extra.

Discutimos o criptográfico um pouco na chamada da comunidade. Acho que fomos um pouco cautelosos com isso. Parece uma boa ideia, mas se incluirmos um RNG criptograficamente sólido, também teremos que nos manter atualizados com quaisquer problemas de segurança que surgirem, já que não sabemos se os usuários os usam para propósitos de criptografia reais.

Sobre quantos a incluir: O consenso tendia a ser bom ter mais alguns geradores de bits por perto (alguma pequena documentação seria bom, é claro). A carga de manutenção não parece muito grande. No final, meu palpite é que seguiríamos qualquer sugestão de Kevin e Robert.

Sobre os nomes: eu pessoalmente não me importo de usar os nomes RNG e forçar os usuários a usá-los, a única desvantagem é que talvez tenhamos que pesquisar o nome durante a codificação. Devemos apenas tentar ter o mínimo possível de avisos de depreciação. Eu gosto da API mínima exposta para o RNG padrão sem propagação.

@mdickinson , tentei reproduzir seu gráfico sozinho e falhei. Usei a versão C ++ canônica do código com este programa, que deve ser o equivalente moral do seu.

#include "pcg_random.hpp"
#include <iostream>
#include <random>

int main() {
    std::random_device rdev;
    pcg_detail::pcg128_t seed = 0;
    pcg_detail::pcg128_t stream = 0;
    for (int i = 0; i < 4; ++i) {
        seed   <<= 32;           
        seed   |= rdev();
        stream <<= 32;           
        stream |= rdev();
    }
    pcg64 rng1(seed,stream);
    pcg64 rng2(-seed,-stream);
    std::cerr << "RNG1: " << rng1 << "\n";
    std::cerr << "RNG2: " << rng2 << "\n";
    std::cout.precision(17);
    for (int i = 0; i < 10000; ++i) {
        std::cout << rng1()/18446744073709551616.0 << "\t";
        std::cout << rng2()/18446744073709551616.0 << "\n";
    }
}

Quando eu executo isso, ele produz (para permitir a reprodutibilidade):

RNG1: 47026247687942121848144207491837523525 203756742601991611962280963671468648533 41579532896305845786243518008404876432
RNG2: 47026247687942121848144207491837523525 136525624318946851501093643760299562925 52472962479578397910044896975270170620

e pontos de dados que podem traçar o seguinte gráfico:

corr1

Se você conseguir descobrir o que estou fazendo de forma diferente, isso seria útil.

(Não estou dizendo isso para refutar a ideia de que as correlações são possíveis, porque são, e vou escrever um comentário separado sobre o assunto, mas foi escrevendo esse comentário que percebi que não poderia reproduzir seu resultado usando minhas ferramentas usuais.)

@mdickinson perfurado no estado computado e incrementado diretamente nos internos, contornando a rotina de inicialização usual , que possui dois avanços em etapas.

Eu escrevi um pequeno script de driver rápido que intercala vários fluxos PCG32, construídos de várias maneiras, para alimentar o PractRand. Ele usa master de numpy no Python 3. Quando eu soco os estados / incrementos internos adversários diretamente, PractRand falha rapidamente. Não está claro para mim se podemos encontrar maneiras razoáveis ​​de encontrar _sementes_ adversárias (que realmente passam pela rotina do inicializador) para atingir os estados adversários.

Conforme observado em minha postagem de blog mencionada anteriormente, os streams do PCG têm muito em comum com os do SplitMix.

Em relação ao gráfico de @mdickinson, para _cada_ PRNG que permite que você semeie todo o seu estado, incluindo os criptográficos baseados em contador, podemos conceber seedings onde teríamos PRNGs cujas saídas foram correlacionadas de alguma forma (a maneira mais fácil de fazer isso é criar estados PRNG que estão a uma curta distância uns dos outros, mas muitas vezes podemos fazer outras coisas com base na compreensão de como funcionam). E embora os PRNGs que não permitem a propagação de estado completo possam evitar esse problema, fazer isso apenas introduz um novo, fornecendo acesso prático apenas a uma pequena fração de seus estados possíveis.

A maneira certa de pensar em fluxos é apenas mais um estado aleatório que precisa ser semeado. Usar valores pequenos como 1,2,3 geralmente é uma má ideia para qualquer propósito de semeadura para _qualquer_ PRNG (porque se todos favorecerem essas sementes, suas sequências iniciais correspondentes serão super-representadas).

Podemos escolher não chamá-lo de fluxo e apenas chamá-lo de estado. Isso é o que Marsaglia fez em XorWow . Se você olhar para o código, a sequência de Weyl counter não interage com o resto do estado de forma alguma e, como LCGs, e variações no valor inicial realmente representam apenas uma constante adicionada.

Os streams de SplitMix, PCG e XorWow são o que podemos chamar de streams “estúpidos”. Eles constituem uma reparametrização trivial do gerador. Há valor nisso, entretanto. Suponha que sem fluxos, nosso PRNG teria uma interessante repetição próxima de 42, onde 42 surge várias vezes em rápida sucessão e só faz isso para 42 e nenhum outro número. Com streams estúpidos de “apenas um incremento” ou “apenas um xor”, na verdade evitaremos conectar a repetição estranha a 42; todos os números têm um fluxo no qual eles se repetem de forma estranha. (Por esse motivo, a correção que eu aplicaria para reparar os problemas de repetição de fechamento no Xoshiro 256 é misturar em uma sequência de Weyl.)

Não sou um especialista, mas do lado da criptografia, o que se propõe não está disponível em:
https://cryptography.io/en/latest/ da Python Cryptographic Authority ?

Sua página sobre geração de números aleatórios também menciona:

A partir do Python 3.6, a biblioteca padrão inclui o módulo secrets , que pode ser usado para gerar números aleatórios criptograficamente seguros, com ajudantes específicos para formatos baseados em texto.

Acho que talvez adicionando matrizes à geração. Eu tenho que me perguntar se a carga de manutenção potencial de estar associada à robustez criptográfica realmente vale a pena e é apropriada no NumPy vs. digamos se comunicar com o pyca e talvez pensar em um gerador / plugin de terceiros para isso. Acho que Nathaniel mencionou uma preocupação semelhante anteriormente.

Na verdade, parece-me que coisas como o potencial refator / aprimoramento dtype também são projetadas para fornecer infraestrutura de API sem necessariamente assumir o fardo de manter uma grande variedade de novos aplicativos especializados.

BTW, também há mais sobre como criar estados PRNG correlacionados em minha resposta à crítica de Vigna sobre PCG [especificamente, esta seção ]. Algo que observei lá é que com o PCG, por ter uma função de distância, você pode realmente verificar com a função de distância para detectar sementes inventadas. Em PRNGs sem uma função de distância, as pessoas ainda podem inventar pares de semeadura que são escolhas ruins (especialmente se eles ignorarem a API pública para semear), mas não há nenhum mecanismo fornecido que possa detectar até mesmo os artifícios mais flagrantes.

Por um lado, é divertido pensar se é possível pegar seu PRNG favorito (ou menos favorito) e inventar para ele sementes que o tornem capaz de fazer coisas divertidas ou terríveis (por exemplo, esta patologia ) ...

Mas, olhando para o panorama geral, acho que faz sentido examinar os problemas enfrentados pelos usuários na prática. Que conselho damos a eles, etc. A maioria dos usuários não percebe que _para todos os PRNGs (passado e futuro) _, uma semente de 32 bits é uma ideia absolutamente terrível e resulta em um viés trivialmente detectável, não importa qual PRNG esteja em jogo. Claro, podemos ignorar isso e, em vez disso, passar nosso tempo nos preocupando se alguém conseguiu inicializar o Mersenne Twister para um estado de zeros (ou o estado de zeros onde os LFSRs não funcionam!), Ou se alguém poderia inicialize o Xoshiro para perto do ponto onde ele repete a mesma saída sete vezes no espaço de onze saídas, ou crie dois fluxos de PCG semelhantes, ou o que for, mas todos esses dispositivos têm basicamente uma chance infinitesimal (na prática zero) de acontecer se o gerador é semeado com dados aleatórios. Por mais envolventes intelectualmente e academicamente interessantes que sejam essas diversões, pensar nelas e, ao mesmo tempo, ignorar o fato de que os usuários normalmente têm pouca ideia do que estão fazendo quando se trata de semear é mexer enquanto Roma queima.

Se inc=1,2,3,4 é uma má ideia, isso não sugere que deva ser documentado de forma muito clara ou talvez devamos ter uma API ligeiramente diferente? Talvez até new_generator = (Bit)Generator().independent() , podemos colocar um aviso nele se o gerador de bits (subjacente) não fornecer uma ótima maneira de fazer isso.

Além disso, dependendo de quão ruim é a semeadura de 32 bits. Podemos pensar em uma boa API para criar e armazenar uma semente para congelá-la? Eu não sei. Talvez até mesmo "crie um arquivo de cache de seed congelado se ele não existir".

Para PCG, poderia apenas seed -> uint64_t [2] -> splitmix64 (seed_by_array) -> uint128 o que garantiria que as sementes consecutivas e baixas fossem distribuídas.

Para PCG, poderia apenas seed -> uint64_t [2] -> splitmix64 (seed_by_array) -> uint128 o que garantiria que as sementes consecutivas e baixas fossem distribuídas.

Ou apenas use qualquer bom hash inteiro. (Deve ser uma bijeção.). Existem muitos baratos e curtos. Algumas rodadas de Multiply – XorShift são suficientes.

Para o ponto de @mdickinson , acho que ele ainda quer um pouco de convencimento de que a dependência do fluxo é limitada a um pequeno conjunto de configurações planejadas / adversárias. E se for esse o caso, podemos resolvê-lo com práticas adequadas para prevenir tais casos. Com o código atual que temos, existem alguns estados ruins que os usuários podem facilmente atingir com as APIs atuais. Posso confirmar que David Blackman descobriu que definir tanto seed=1 quanto inc=0,1,2,... cria correlações. Meu driver PractRand mais recente para fluxos PCG32 intercalados pode ser usado para demonstrar isso.

❯ ./pcg_streams.py --seed 1 --inc 0 |time ./RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 12728272447693586011,
            "inc": 1
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 7009800821677620407,
            "inc": 3
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0x470537d5
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0x470537d5
length= 128 megabytes (2^27 bytes), time= 4.0 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+0,13-3,T)                  R=  +9.6  p =  2.3e-4   mildly suspicious
  ...and 116 test result(s) without anomalies

rng=RNG_stdin32, seed=0x470537d5
length= 256 megabytes (2^28 bytes), time= 8.7 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+0,13-2,T)                  R= +26.1  p =  6.3e-13    FAIL           
  ...and 123 test result(s) without anomalies

./RNG_test stdin32  8.86s user 0.11s system 93% cpu 9.621 total

Eu ainda não tive uma falha com uma semente aleatória, mas com os mesmos incrementos próximos. Vou checar com você amanhã.

Percebo que não estamos usando o incremento padrão recomendado quando nenhum é especificado no construtor. Provavelmente deveríamos consertar isso. Talvez esse seja um bom número base a partir do qual derivamos o incremento real do ID de fluxo fornecido em vez de 2*inc + 1 .

Podemos tentar construir algumas ferramentas para ajudar as pessoas a usar as sementes de entropia padrão e salvá-las. Uma dúvida que eu tenho é se os incrementos para fluxos múltiplos podem ser gerados simplesmente ou se também precisamos amostrá-los por entropia e salvá-los também. É realmente conveniente ser capaz de codificar o "estado inicial" de uma simulação como um número único que pode ser copiado e colado do e-mail de um colega em vez de um arquivo opaco. Com esses PRNGs menores com apenas 128 ou 256 bits de estado, posso facilmente imprimir isso em hexadecimal em meu arquivo de log e, em seguida, apenas copiá-lo e colá-lo em minha linha de comando quando quiser reproduzi-lo. É maior do que um inteiro de 32 bits, mas é gerenciável. Se eu tiver que fazer uma amostragem de entropia de todos os meus IDs de fluxo também, tenho que renunciar a isso e me certificar de que gravarei tudo em um arquivo de estado em algum lugar. Isso pode excluir alguns casos de uso que discutimos, nos quais queremos gerar novos fluxos dinamicamente. Se eu puder apenas incrementar um contador para obter um bom ID de fluxo (talvez derivando-o por meio de um hash do contador, ou qualquer outro), então só preciso registrar a semente inicial e não os IDs de fluxo.

IIRC, o módulo secrets chama a fonte de entropia do SO, que pode ser bastante
ruim em alguns sistemas e não é replicável / reproduzível independentemente.

Na quarta-feira, 29 de maio de 2019 às 15:19 Tyler Reddy [email protected]
escrevi:

Eu não sou um especialista, mas do lado da criptografia, o que se propõe é
não disponível em:
https://cryptography.io/en/latest/ da Python Cryptographic Authority
https://github.com/pyca ?

Sua página na geração de números aleatórios
https://cryptography.io/en/latest/random-numbers/ também menciona:

A partir do Python 3.6, a biblioteca padrão inclui os segredos
https://docs.python.org/3/library/secrets.html módulo, que pode ser
usado para gerar números aleatórios criptograficamente seguros, com
auxiliares para formatos baseados em texto.

Acho que talvez adicionando matrizes à geração. Eu tenho que me perguntar se o
carga de manutenção potencial de estar associada à criptografia
robustez realmente vale a pena e apropriado em NumPy vs. dizer
comunicar-se com a pyca e talvez pensar em um terceiro
gerador / plugin para isso. Acho que Nathaniel mencionou uma preocupação semelhante
anteriormente.

Na verdade, parece-me que coisas como o potencial refatorador dtype /
aprimoramentos também são projetados para fornecer infraestrutura de API sem
necessariamente assumindo o fardo de manter uma grande variedade de
novos aplicativos especializados.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWKXSSTX6QI7HJ65GYTPX36O3A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWQZP6A#issuecomment-497129464 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWKGZMUB67VPCMFZYGTPX36O3ANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@tylerjereddy Esses são para obter uma pequena quantidade de bits aleatórios de uma fonte de entropia física que são imprevisíveis para um invasor (e você!). Eles são usados ​​em criptografia para coisas como vetores de inicialização, nonces, chaves, que são todos curtos. O ponto principal disso é que não há maneira de reproduzi-los, o que está em desacordo com os objetivos de simulação numérica de np.random . Essa página não está falando sobre PRNGs _reproducíveis_ criptograficamente seguros, que também existem e _podem_ ser construídos a partir dos primitivos disponíveis no pacote cryptography . Na _prática_, no entanto, temos melhores implementações daqueles algoritmos já disponíveis para nós em código C eficiente, pelo menos aqueles que foram formulados e testados para fins de simulação. @bashtage implementou alguns para este framework.

Também quero deixar claro para a equipe entorpecida que o que

A maioria dos PRNGs baseados em criptografia que são comumente considerados para simulação _não_ tem o estado ilimitado que @pbstark deseja. Normalmente, eles se baseiam na criptografia de um contador finito. Depois que o contador rolar, você atingiu o período finito. Tecnicamente, seu cryptorandom também está limitado a 2**(256+64) condições iniciais exclusivas devido ao estado de resumo de 256 bits de tamanho fixo e contador de comprimento de 64 bits de tamanho fixo. Isso provavelmente aponta o caminho para a implementação de um PRNG verdadeiramente ilimitado, tornando o contador de comprimento de tamanho arbitrário, mas nunca vi tal algoritmo publicado ou testado.

Por outro lado, se você quiser apenas um algoritmo PRNG que tenha um estado de tamanho arbitrário, apenas um que seja fixado no início em algo acima do que você precisa, os geradores estendidos do PCG funcionarão bem para essa tarefa. Esses claramente não são CS-PRNGs, mas na verdade satisfariam o desejo do @pbstark de ter grandes espaços de estado sob demanda. No entanto, não recomendo que os incluamos em numpy.

Existem outras propriedades que os CS-PRNGs padrão limitados têm que podemos desejar, mas eles não são um padrão acéfalo, IMO.

O espaço de estado do Cryptorandom não é o hash de 256 bits: ele é ilimitado. o
o estado da semente é uma string de comprimento arbitrário e cada atualização acrescenta um zero
para o estado atual. Incrementar um contador inteiro ilimitado
realizar a mesma coisa. Implementamos inicialmente isso, mas mudamos para
acrescentar em vez de incrementar, porque permite uma atualização mais eficiente para
o resumo do que o hash de cada estado do zero (aceleração perceptível).

Quarta-feira, 29 de maio de 2019 às 19:26 Robert Kern [email protected]
escrevi:

@tylerjereddy https://github.com/tylerjereddy Esses são para obter um
pequena quantidade de bits aleatórios de uma fonte de entropia física que são
imprevisível para um invasor (e você!). Eles são usados ​​em criptografia para
coisas como vetores de inicialização, nonces, chaves, que são todos curtos. o
o ponto principal disso é que não há maneira de reproduzi-los, o que está em
probabilidades com os objetivos de simulação numérica de np.random. Essa página é
não falando sobre PRNGs criptograficamente seguros reproduzíveis , que
também são coisas que existem e podem ser construídas a partir dos primitivos
disponível no pacote de criptografia. Na prática , porém, temos
melhores implementações desses algoritmos já disponíveis para nós em
código C eficiente, pelo menos aqueles que foram formulados e testados
para fins de simulação. @bashtage https://github.com/bashtage implementado
alguns https://github.com/numpy/numpy/issues/13635#issuecomment-496287650
para esta estrutura.

Também quero deixar claro para a equipe entediante que @pbstark
https://github.com/pbstark está propondo não é apenas qualquer baseado em criptografia
PRNG. Em vez disso, ele quer um com estado ilimitado , o que forneceria
a propriedade matemática que ele está procurando.

A maioria dos PRNGs baseados em criptografia que são comumente considerados para simulação
não tem o estado ilimitado que @pbstark
https://github.com/pbstark deseja. Eles são normalmente baseados em
criptografar um contador finito. Uma vez que o contador gira, você atingiu o
período finito. Tecnicamente, seu criptorrandom
https://statlab.github.io/cryptorandom/ também está limitado a 2 ** (256 + 64)
condições iniciais únicas devido ao estado de resumo de 256 bits de tamanho fixo e
contador de comprimento de 64 bits de tamanho fixo. Isso provavelmente aponta o caminho para
implementar um PRNG verdadeiramente ilimitado, tornando o contador de comprimento
de tamanho arbitrário, mas nunca vi tal algoritmo publicado ou
testado.

Por outro lado, se você quiser apenas um algoritmo PRNG que tenha um
estado de tamanho arbitrário, apenas um que foi fixado no início para
algo acima de tudo o que você precisa, então os geradores estendidos da PCG
http://www.pcg-random.org/party-tricks.html funcionaria bem para isso
tarefa. Estes não são claramente CS-PRNGs, mas eles realmente satisfariam
@pbstark https://github.com/pbstark desejo de ter grandes espaços estaduais
sob demanda. No entanto, não recomendo que os incluamos em numpy.

Existem outras propriedades que os CS-PRNGs padrão, limitados, têm que
podemos querer, mas eles não são um padrão acéfalo, IMO.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWLGAF6YVIWXYZ2LTT3PX43ONA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWRFG4Q#issuecomment-497177458 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWLIJD3UCVY3NXCLPKDPX43ONANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Infelizmente, não é assim que a atualização online do SHA-256 funciona. O estado que ele mantém é apenas o resumo de 256 bits e o contador de comprimento de 64 bits de tamanho fixo que ele acrescenta ao calcular a atualização. Não contém todo o texto. Os hashes são compactados. É assim que consegue fazer a atualização eficiente em cada byte. Fundamentalmente, existem muitas inicializações / históricos anteriores que são mapeados para o mesmo estado SHA-256 interno, que é finito. Embora os ciclos sejam certamente longos, talvez mais longos do que 2**(256+64) , eles certamente existem. E, em qualquer caso, você só tem menos de 2**(256+64) condições iniciais possíveis (para cada comprimento de texto de 0 a 2**64-1 , você pode ter no máximo 2**256 estados de hash internos; uma vez que o comprimento do texto é superior a 32 bytes, deve haver colisões à la pigeonhole). Simplesmente não há mais bits na estrutura de dados.

Muito obrigado; Entendido. Eu expressaria de outra forma: o estado
o espaço é ilimitado, mas (por escaninho) muitos estados iniciais distintos devem
produzir sequências de saída indistinguíveis.

Na quarta-feira, 29 de maio de 2019 às 20:21 Robert Kern [email protected]
escrevi:

Infelizmente, não é assim que a atualização online do SHA-256 funciona. O Estado
que ele mantém é apenas o resumo de 256 bits e o tamanho fixo de 64 bits
contador de comprimento que acrescenta quando calcula a atualização. Não segura
todo o texto. Os hashes são compactados. É assim que ele é capaz de fazer o eficiente
atualização em cada byte. Fundamentalmente, existem muitas inicializações / passado
histórias que mapeiam para o mesmo estado SHA-256 interno, que é finito.
Embora os ciclos sejam certamente longos, talvez mais do que 2 (256 + 64), elescertamente existem.
possíveis condições iniciais (para cada comprimento de texto 0 a 2 64-1, você podeter no máximo 2 256 estados de hash internos; uma vez que o comprimento do texto é superior a 32
bytes, deve haver colisões a la pigeonhole). Simplesmente não há nenhum
mais bits na estrutura de dados.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWO6HRJZHTVBF2TLK3LPX5B3TA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWRHTSY#issuecomment-497187275 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AANFDWM56HCQRZXDO3BAQHDPX5B3TANCNFSM4HPX3CHA
.

-
Philip B. Stark | Reitor Associado, Ciências Matemáticas e Físicas |
Professor, Departamento de Estatística |
Universidade da Califórnia
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Também é o caso de haver apenas 2**(256+64) estados pelos quais ele pode passar. Como a atualização sempre assume a mesma forma, você eventualmente atinge um estado que já viu antes e entra em um loop de período desconhecido (para mim), mas finito. Quer seja o número finito de estados iniciais ou um período finito, cryptorandom tem ambos, e eu acho que eles são menores ainda do que MT19937 .

Não que eu ache que isso seja um problema com cryptorandom , por si só. Não estou convencido de que ter um conjunto ilimitado de estados iniciais ou período ilimitado é algo realmente necessário em um sentido prático.

Eu ainda não tive uma falha com uma semente aleatória, mas com os mesmos incrementos próximos. Vou checar com você amanhã.

Ainda está forte em 512GiB:

❯ ./pcg_streams.py -i 0 |time ./RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 10843219355420032665,
            "inc": 1
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 5124747729404067061,
            "inc": 3
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0xb83f7253
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0xb83f7253
length= 128 megabytes (2^27 bytes), time= 4.0 seconds
  no anomalies in 117 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 256 megabytes (2^28 bytes), time= 8.6 seconds
  no anomalies in 124 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 512 megabytes (2^29 bytes), time= 16.9 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+2,13-2,T)                  R=  -8.0  p =1-2.1e-4   mildly suspicious
  ...and 131 test result(s) without anomalies

rng=RNG_stdin32, seed=0xb83f7253
length= 1 gigabyte (2^30 bytes), time= 33.8 seconds
  no anomalies in 141 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 2 gigabytes (2^31 bytes), time= 65.7 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+2,13-1,T)                  R=  -7.8  p =1-3.8e-4   unusual          
  ...and 147 test result(s) without anomalies

rng=RNG_stdin32, seed=0xb83f7253
length= 4 gigabytes (2^32 bytes), time= 136 seconds
  no anomalies in 156 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 8 gigabytes (2^33 bytes), time= 270 seconds
  no anomalies in 165 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 16 gigabytes (2^34 bytes), time= 516 seconds
  no anomalies in 172 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 32 gigabytes (2^35 bytes), time= 1000 seconds
  no anomalies in 180 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 64 gigabytes (2^36 bytes), time= 2036 seconds
  no anomalies in 189 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 128 gigabytes (2^37 bytes), time= 4064 seconds
  no anomalies in 196 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 256 gigabytes (2^38 bytes), time= 8561 seconds
  no anomalies in 204 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 512 gigabytes (2^39 bytes), time= 19249 seconds
  no anomalies in 213 test result(s)

Ah, se fizermos mais de 2 fluxos com incrementos sequenciais, veremos falhas rapidamente. Teremos de ver como derivar o incremento real da entrada do usuário com algum tipo de bijeção para espalhá-lo pelo espaço.

❯ ./pcg_streams.py -n 3  | time ./build/RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 18394490676042343370,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 12676019050026377766,
            "inc": 2891336455
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 6957547424010412162,
            "inc": 2891336457
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0x4a9d21d1
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0x4a9d21d1
length= 128 megabytes (2^27 bytes), time= 3.2 seconds
  Test Name                         Raw       Processed     Evaluation
  DC6-9x1Bytes-1                    R= +19.4  p =  1.6e-11    FAIL           
  [Low8/32]DC6-9x1Bytes-1           R= +13.2  p =  4.6e-8    VERY SUSPICIOUS 
  ...and 115 test result(s) without anomalies

@imneme

Se você conseguir descobrir o que estou fazendo de forma diferente, isso seria útil.

Acho que você precisa substituir a linha pcg64 rng2(-seed,-stream); em seu código por pcg64 rng2(-seed,-1-stream); , para permitir a transformação increment = 2 * stream + 1 . A negação do incremento corresponde à negação bit a bit do índice do fluxo. Se eu fizer essa alteração e executar seu código, vejo algo muito parecido com meu enredo anterior. (E eu confirmo que, se eu não fizer essa alteração, tudo parece bem visualmente.)

@imneme

A maneira certa de pensar em fluxos é apenas mais um estado aleatório que precisa ser semeado.

Acordado. Acho que isso dá uma imagem muito clara para LCGs: para um LCG de 64 bits com multiplicador fixo e bem escolhido a , temos então um espaço de estado de tamanho 2^127 , consistindo de todos os pares (x, c) dos inteiros mod 2 ^ 64, onde c é o incremento ímpar. A função de atualização de estado é next : (x, c) ↦ (ax+c, c) , dividindo o espaço de estado em 2^63 ciclos disjuntos de comprimento 2^64 cada. Semear envolve apenas escolher um ponto de partida neste espaço de estado.

Há então uma ação de grupo óbvia que torna a análise fácil e torna claras as relações entre os diferentes fluxos: o grupo de transformações afins 1-d invertíveis em Z / 2^64Z tem ordem exatamente 2^127 e age transitivamente (e assim também fielmente) no espaço de estado: a transformação afim y ↦ ey + f mapeia o par (x, c) para (ex + f, ec + (1-a)f) . Essa ação de grupo comuta com a função next , de modo que o elemento de grupo único que transforma um ponto (x, c) no espaço de estado em outro, (x2, c2) , também mapeia a sequência gerada por (x, c) à sequência gerada por (x2, c2) .

tl; dr: para um multiplicador fixo, quaisquer duas sequências LCG com o mesmo multiplicador (seja usando o mesmo incremento, como no caso de lookahead ou incrementos diferentes) são relacionadas por uma transformação afim. Nos casos infelizes que queremos evitar, essa transformação afim é algo terrivelmente simples, como adicionar 2 ou multiplicar por -1 . No caso geral, esperamos que a transformação afim seja complicada o suficiente para que os testes estatísticos padrão não consigam detectar a relação entre os dois fluxos.

@mdickinson cobre a situação muito bem. As permutações de PCG vão mudar um pouco as coisas do caso LCG, mas não muito. O ponto principal das permutações PCG é que podemos escolher quanto embaralhamento fazer. Como LCGs truncados de 128 bits já passam por BigCrush, quando escolhi uma permutação para pcg64 , escolhi uma quantidade modesta de embaralhamento para esse tamanho LCG (XSL RR). Em contraste, LCGs de 64 bits falham rapidamente em vários testes estatísticos, então pcg32 usa um pouco mais de embaralhamento, mas ainda não é a permutação mais forte do papel do PCG. Como mencionei no thread pull-request do thread, comecei a me inclinar para uma permutação PCG mais forte (RXS M) para o caso de uso pcg32 . Ainda não é o padrão, você deve solicitar essa versão explicitamente, mas há uma boa chance de mudar o padrão quando fizer um aumento de versão principal para PCG. (RXS M é a metade alta de RXS M XS, que Vigna testou extensivamente neste tamanho e também permutação que David Blackman gosta).

Podemos visualizar a diferença de uma versão atualizada do programa de teste near-streams em ambos os esquemas para pcg32 (XSH RR e RCS M [e o LCG básico bruto, também]):

#include "pcg_random.hpp"
#include <iostream>
#include <random>

// Create a "PCG" variant with a trivial output function, just truncation

template <typename xtype, typename itype>
struct truncate_only_mixin {
    static xtype output(itype internal)
    {
        constexpr size_t bits = sizeof(itype) * 8;
        return internal >> (bits/32);
    }
};

using lcg32 = pcg_detail::setseq_base<uint32_t, uint64_t, truncate_only_mixin>;

int main() {
    std::random_device rdev;
    uint64_t seed = 0;
    uint64_t stream = 0;
    for (int i = 0; i < 2; ++i) {
        seed   <<= 32;           
        seed   |= rdev();
        stream <<= 32;           
        stream |= rdev();
    }
    lcg32 rng1(seed,stream);
    lcg32 rng2(-seed,-1-stream);
    // pcg32 rng1(seed,stream);
    // pcg32 rng2(-seed,-1-stream);
    // pcg_engines::setseq_rxs_m_64_32 rng1(seed,stream);
    // pcg_engines::setseq_rxs_m_64_32 rng2(-seed,-1-stream);
    std::cerr << "RNG1: " << rng1 << "\n";
    std::cerr << "RNG2: " << rng2 << "\n";
    std::cout.precision(17);
    for (int i = 0; i < 10000; ++i) {
        std::cout << rng1()/4294967296.0 << "\t";
        std::cout << rng2()/4294967296.0 << "\n";
    }
}

Antes de começar, vamos olhar para o gráfico que @mdickinson desenhou, mas para apenas um LCG sem permutação, apenas truncamento:

corr-truncated-lcg

Observe que isso é para LCG patológico com estados correlacionados. Se, em vez disso, tivéssemos apenas escolhido dois LCGs com constantes aditivas escolhidas aleatoriamente (mas o mesmo valor inicial), ficaria assim:

corr-truncated-lcg-good

Passando para as funções de saída do PCG, se usarmos é XSH RR no caso patológico, fica assim - é uma grande melhoria no gráfico acima, mas claramente não está obscurecendo totalmente a horribilidade:

corr-pcg32-current

e este é RXS M com o mesmo par LCG subjacente (mal correlacionado):

corr-pcg32-future

Mas isso é apenas algo que estou pensando em pcg32 . A penalidade de desempenho é pequena, e pcg32 é pequena o suficiente para que eu possa imaginar algum usuário pesado preocupado em criar uma tonelada de geradores pcg32 semeados aleatoriamente e pedindo uma tonelada de números deles e tendo uma chance não infinitesimal de correlações. Eu estou, francamente, dividido sobre isso, porque este usuário avançado mítico estaria usando pcg32 em primeiro lugar.

Uma razão pela qual não estou muito preocupado em tornar os fluxos de pcg64 mais independentes é que não tenho certeza se vejo um caso de uso em que seria sensato manter todos os outros estados iguais e mudar o fluxo para um diferente (por exemplo, para um valor aleatório, quanto mais para um próximo). Para praticamente todos os PRNGs, a maneira certa de fazer um segundo é inicializá-lo com uma nova entropia.

Concluindo, para NumPy, acho que pode fazer mais sentido apenas considerar que PCG64 deseja dois 256 bits de estado (tecnicamente é 255, pois o bit alto do fluxo é ignorado) e chamá-lo de concluído. Isso também evitará problemas relacionados à API, porque haverá um recurso a menos que as pessoas terão em um BitGenerator e não em outro.

(Mas você pode querer mudar a variante PCG de 32 bits para a RXS M. Para o código-fonte C, você precisa de uma versão recente, já que eu não me preocupava em fornecer RXS M explicitamente no código C, apenas disponibilizando-o no C ++ encarnação.)

[Desculpe se isso é mais do que você sempre quis saber! Bem, não _isso_ desculpe. ;-)]

Uma razão pela qual não estou muito preocupado em tornar os fluxos de pcg64 mais independentes é que não tenho certeza se vejo um caso de uso em que seria sensato manter todos os outros estados iguais e mudar o fluxo para um diferente (por exemplo, para um valor aleatório, quanto mais para um próximo). Para praticamente todos os PRNGs, a maneira certa de fazer um segundo é inicializá-lo com uma nova entropia.

Descrevi o caso de uso anteriormente. Existem fortes razões de UX para escrever um programa estocástico que aceita uma única entrada "seed" curta (ou seja, algo sobre o tamanho que eles podem copiar e colar de um e-mail em uma linha de comando) que então torna a saída do programa determinística. @stevenjkern observou em uma conversa off-line que esse tipo de interação era essencial para trabalhar com agências regulatórias que tinham que validar seu software. Se você tivesse que usar o arquivo _output_ de uma execução de um programa para replicar o resultado, isso pareceria um pouco suspeito em tais circunstâncias. O regulador teria que fazer um mergulho profundo no código (que pode não estar realmente disponível para eles) para garantir que as informações no arquivo eram realmente kosher.

Agora temos boas ferramentas em Python para girar N processos paralelos dinamicamente para fazer parte do trabalho, coletar os resultados e seguir em frente no processo principal (depois girar processos M mais tarde, etc.). Ao contrário dos esquemas mais antigos e menos flexíveis, como o MPI, não apenas giramos N processos fixos no início. Nesses casos, eu poderia ver a entropia semeando cada um dos N PRNGs e salvando-os em um arquivo porque há apenas um lugar no programa que faz isso. Pelo menos de uma perspectiva de programação, isso não é muito difícil. Temos ferramentas muito mais flexíveis para paralelismo agora. Semear os PRNGs é agora o gargalo que nos impede de usar essa flexibilidade em programas estocásticos. Não há mais um único ponto de responsabilidade onde podemos colocar a contabilidade baseada em arquivos.

A necessidade de derivar N streams de forma reproduzível é forte o suficiente para que as pessoas façam coisas estranhas para obtê-lo com nosso algoritmo MT atual. Tive que derrubar um monte de esquemas arriscados e esperava que os streams do PCG nos ajudassem a chegar lá.

O que você acha de usar um bom hash de bijetivo em 2**63 / 2**127 para derivar incrementos de uma seqüência de contador 0,1,2,3, ... enquanto mantém o mesmo estado? Você prevê problemas com isso? O que você acha de combinar o incremento hash seguido por um grande jumpahead para mover o estado para uma parte distante do novo ciclo? Talvez possamos mover esta sub-discussão para e-mail ou outro problema e relatar de volta.

@rkern , pode haver algo que pareça bom em ser capaz de dar

O problema é agravado quando todos estão, digamos, usando o mesmo PRNG (por exemplo, o Mersenne Twister) e pegando sementes do mesmo pequeno conjunto (por exemplo, números menores que 10000), porque ao invés de um viés particular arbitrário por programa, é um viés _para todos que estão fazendo isso_. Por exemplo, vamos supor que você escolha uma semente de quatro dígitos e, em seguida, pegue um número razoável de números do Mersenne Twister (digamos, menos um milhão). Nessa situação, posso assegurar-lhe que o azarado número 13 _nunca aparecerá_ como qualquer uma das 10 bilhões de saídas (na verdade, cerca de 10% dos inteiros de 32 bits estarão ausentes), e o número 123580738 é super-representado por um fator de 16. Isso é exatamente o que esperaríamos para uma amostra aleatória de dez bilhões de inteiros de 32 bits, mas é um problema real _se todos estiverem usando a mesma amostra_. Teríamos um problema exatamente análogo se todos pegassem sementes de nove dígitos e tirassem apenas 10.000 números.

O fato de muitas pessoas quererem fazer algo não a torna uma boa ideia. (Isso não significa que não há problema em dizer apenas às pessoas que elas estão fazendo algo errado ou querem a coisa errada. Você tem que descobrir o que elas realmente precisam (por exemplo, resultados reproduzíveis de um argumento curto de linha de comando - possivelmente o a coisa certa é permitir a propagação de um UUID e um pequeno inteiro; algumas ideias sobre como embaralhar essas coisas de maneira adequada para criar dados de semente podem ser encontradas nesta postagem do blog e chegar aos randutils .)

(Este é o código para brincar, já que é bastante curto ...)

// mtbias.cpp -- warning, uses 4GB of RAM, runs for a few minutes
// note: this is *not* showing a problem with the Mersenne Twister per se, it is
// showing a problem with simplistic seeding

#include <vector>
#include <iostream>
#include <random>
#include <cstdint>

int main() {
    std::vector<uint8_t> counts(size_t(std::mt19937::max()) + 1);
    for (size_t seed=0; seed < 10000; ++seed) {
        std::mt19937 rng(seed);
        for (uint i = 0; i < 1000000; ++i) {
            ++counts[rng()];
        }
    }
    size_t shown = 0;
    std::cout << "Never occurring: ";
    for (size_t i = 0; i <= std::mt19937::max(); ++i) {
        if (counts[i] == 0) {
            std::cout << i << ", ";
            if (++shown >= 20) {
                std::cout << "...";
                break;
            }
        }
    }
    std::cout << "\nMost overrepresented: ";
    size_t highrep_count = 0;
    size_t highrep_n = 0;
    for (size_t i = 0; i <= std::mt19937::max(); ++i) {
        if (counts[i] > highrep_count) {
            highrep_n = i;
            highrep_count = counts[i];
        }
    }
    std::cout << highrep_n << " -- repeated " << highrep_count << " times\n";
}

Como eu disse antes, acho que as sementes de 128 bits são curtas o suficiente para esse propósito e posso construir as ferramentas para ajudar as pessoas a escrever programas que fazem a coisa certa. Ou seja, entropia-amostrá-los por padrão, imprimindo-os ou registrando-os de outra forma, permitindo que sejam transmitidos posteriormente. Sua recomendação de gerar um UUID para cada programa e misturar uma semente possivelmente menor fornecida pelo usuário por execução também é boa.

Vamos supor que eu possa fazer com que as pessoas usem boas sementes de 128 bits para a parte estadual de PCG64 , de uma forma ou de outra. Você tem algum comentário sobre a derivação de streams do mesmo estado? Não estou procurando desenhar mais números, no geral, do que faríamos de um único fluxo de PCG64 . Só quero ser capaz de desenhar esses números em processos diferentes sem coordenação em cada sorteio. Usar um hash multiply-xorshift ad hoc de 63 bits parece funcionar muito bem até agora (estou com 32 GiB no momento), para 8192 fluxos intercalados.

@imneme

Pode ser útil definir o que queremos dizer com curto.

Acho que @rkern escreveu "algo sobre o tamanho que eles podem copiar e colar de um e-mail para uma linha de comando". Posso representar números muito grandes em alguns caracteres como 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff.

Só quero ser capaz de desenhar esses números em processos diferentes sem coordenação em cada sorteio. Usar um hash multiply-xorshift ad hoc de 63 bits parece funcionar muito bem até agora (estou com 32 GiB no momento), para 8192 fluxos intercalados.

Você já tentou intercalar n streams usando uma semente de qualidade única, avançando o estado em um número grande o suficiente (digamos 2 ** 64, que é o que é usado por PCG64.jumped )? Esta parece ser a maneira mais simples de coordenar livremente grandes n streams em um cluster usando algo como PCG64(seed).jumped(node_id)

onde node_id é 0,1,2, ...

Existe um PRNG que é realmente bom em produzir fluxos independentes simples usando algo como um índice? Eu acredito que um MLFG pode, mas eu não gosto disso porque era um gerador de 63 bits.

@bashtage , essa não é a maneira certa de fazer isso. O jeito certo é pegar uma semente, se você quiser adicionar um pequeno inteiro, use uma função hash para fazer o hash. Como mencionei anteriormente, eu escrevi anteriormente (independente do PCG) uma função de mixagem séria [edit: fix link para o post correto] para misturar vários tipos de entropia grande e pequena. Você não precisa usar o meu, mas recomendo que faça algo nesse sentido.

Idealmente, você deseja um mecanismo que não seja específico do PCG. O PCG pode não ser sua escolha padrão e, mesmo que fosse, você deseja que as pessoas façam coisas semelhantes com todos os geradores. Não acho que você deva querer um esquema para fazer vários PRNGs independentes que dependem de streams ou jump-ahead.

(opa, criei um link para a postagem errada do blog; editei a mensagem anterior, mas caso você esteja lendo por e-mail, pretendia criar um link para esta postagem do blog)

@imneme No momento, todos os geradores que temos suportam um salto (alguns dos quais são realmente chamadas de tipo avançado). Não tenho dúvidas de que semear cuidadosamente é uma boa ideia, suspeito que muitos usuários ficarão tentados a usar a chamada PRNG.jumped() . Isso é algo que deve ser dissuadido?

Quanto à propagação, todos os geradores de MT fazem uso das rotinas de inicialização do autor, o PCG usa as suas e o restante, algo como

seed = np.array(required_size, dtype=np.uint64)
last = 0
for i in range(len(user_seed))
    if i < len(user_seed)
        last = seed[i] = splitmix64(last ^ user_seed[i])
    else:
        last = seed[i] = splitmix64(last)

Eu imagino que isso poderia ser melhorado.

Isso é algo que deve ser dissuadido?

Eu não tinha visto jumped . É horrível para o LCG subjacente.

Suponha que temos um multiplicador, M, de 0x96704a6bb5d2c4fb3aa645df0540268d . Se calcularmos M ^ (2 ^ 64), obteremos 0x6147671fb92252440000000000000001 que é um multiplicador LCG terrível. Portanto, se você pegasse cada 2 ^ 64 item de um LCG de 128 bits, seria terrível (os bits de ordem inferior são apenas um contador). As funções de permutação padrão do PCG são projetadas para embaralhar a saída normal de um LCG, não para embaralhar os contadores.

O PCG64 está atualmente testado com até meio petabyte com Practrand e análises adicionais mostram que você pode ler muitos petabytes sem problemas relacionados a potências de dois. Infelizmente, se você pular adiante para pular enormes potências exatas de dois, as permutações usuais (um tanto modestas) de PCG não podem compensar suficientemente a sequência patológica de pular as enormes distâncias subjacentes de LCG como esta. Você pode aumentar a resistência de permutação para corrigir isso e, de fato, tanto I quanto Vigna compararam independentemente as permutações padrão de PCG com funções hash inteiras prontas para uso que provavelmente fariam isso (afinal, eles são a base do SplitMix que _is_ apenas um contador). Quando olhei para isso em 2014 com Fast Hash, a velocidade não parecia tão boa, mas quando Vigna fez isso mais recentemente com murmurhash, ele afirmou que o desempenho superou o PCG padrão (!).

Se você realmente deseja ter um salto à frente de 2 ^ 64, acho que precisa mudar para uma permutação de função de saída mais forte (que, como vimos, pode ser feita a baixo custo). Mas se você acha que não é mais o PCG realmente “padrão” e deseja manter a permutação de saída usual, então jumped() provavelmente precisa ir.

(Aliás, o salto patológico também se aplica a outros PRNGs. SplitMix é conhecido por ter alguns incrementos ruins e é razoável supor que, embora o incremento normal (a.ka. “gama”) de 0xbd24b73a95fb84d9 seja bom, avançando em 2 ^ 32 lhe dará um incremento de 0x95fb84d900000000, o que não é tão bom. Para LFSRs, o salto ruim provavelmente não é uma potência de dois, mas tenho quase certeza de que haverá saltos onde a matriz subjacente termina patologicamente escasso.)

Posso confirmar que, pelo menos com PCG32 , 4 fluxos intercalados usando .jumped() falham muito rapidamente.

❯ ./pcg_streams.py --jumped -n 4 | time ./RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 10149010587776656704,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 1158608670957446464,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 10614950827847787840,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 1624548911028577600,
            "inc": 2891336453
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0xeedd49a8
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0xeedd49a8
length= 128 megabytes (2^27 bytes), time= 2.1 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+0,13-3,T)                  R= +58.7  p =  1.3e-27    FAIL !!!       
  BCFN(2+1,13-3,T)                  R= +48.0  p =  1.5e-22    FAIL !!        
  BCFN(2+2,13-3,T)                  R= +16.0  p =  2.3e-7   very suspicious  
  DC6-9x1Bytes-1                    R= +53.5  p =  1.8e-32    FAIL !!!       
  [Low8/32]DC6-9x1Bytes-1           R= +27.4  p =  1.1e-17    FAIL !         
  ...and 112 test result(s) without anomalies

Idealmente, você deseja um mecanismo que não seja específico do PCG. O PCG pode não ser sua escolha padrão e, mesmo que fosse, você deseja que as pessoas façam coisas semelhantes com todos os geradores.

Bem, é isso que estamos tentando decidir aqui. :-) Ficamos razoavelmente satisfeitos em simplesmente expor quaisquer recursos fornecidos por cada PRNG, na suposição de que as propriedades expostas por cada algoritmo são bem estudadas. Também estamos razoavelmente satisfeitos em dizer "aqui está o PRNG padrão que recomendamos; ele tem um monte de recursos que são úteis; os outros podem não os ter".

A noção de usar um hash para derivar um novo estado de um determinado estado e um ID de fluxo, para qualquer algoritmo, é interessante. Você sabe o quão bem estudado isso é? Parece um problema de pesquisa para verificar se funciona bem para todos os algoritmos. Eu hesitaria em afirmar "aqui está o procedimento geral para derivar fluxos independentes para todos os nossos PRNGs". Estou mais satisfeito com "aqui está uma API comum para derivar fluxos independentes; cada PRNG a implementa de qualquer maneira que seja apropriada para o algoritmo e pode não implementá-la se o algoritmo não o suportar bem"

Por outro lado, se for apenas uma questão de alocar ciclos de CPU suficientes para testar fluxos intercalados para cada BitGenerator saída para N GiB no PractRand, isso não é muito oneroso.

A noção de usar um hash para derivar um novo estado de um determinado estado e um ID de fluxo, para qualquer algoritmo, é interessante. Você sabe o quão bem estudado isso é? Parece um problema de pesquisa para verificar se funciona bem para todos os algoritmos. Eu hesitaria em afirmar "aqui está o procedimento geral para derivar fluxos independentes para todos os nossos PRNGs". Estou mais satisfeito com "aqui está uma API comum para derivar fluxos independentes; cada PRNG a implementa de qualquer maneira que seja apropriada para o algoritmo e pode não implementá-la se o algoritmo não o suportar bem"

Não sei se você pode chamá-lo de "um problema de pesquisa" (e, portanto, "bem estudado") exatamente, mas C ++ 11 (que era principalmente sobre o uso de técnicas comprovadas e comprovadas há muito tempo) fornece o conceito _SeedSequence_ (e uma implementação específica de std::seed_seq ) cujo trabalho é fornecer dados de propagação para PRNGs completamente arbitrários.

Em geral, quase todos os PRNGs esperam ser inicializados / semeados com bits aleatórios. Não há nada de especialmente mágico em bits aleatórios saindo de (digamos) random.org e bits aleatórios saindo de algo mais algorítmico (CS PRNG, função hash, etc.).

É bastante simples pensar em uma coleção de PRNGs do mesmo esquema, todos semeados com seus próprios bits aleatórios. Você pode pensar no que estamos fazendo como escolher pontos (ou, na verdade, intervalos até um determinado comprimento máximo correspondente à quantidade de números aleatórios que esperamos pedir, por exemplo, 2 ^ 56) em uma linha (por exemplo, um linha com 2 ^ 255 pontos). Podemos calcular a probabilidade de que, se pedirmos _n_ intervalos, um se sobreponha a outro. É uma probabilidade bastante básica - não tenho certeza se você conseguiria publicar um artigo sobre isso porque (pelo que entendi) ninguém fica animado com artigos que contenham matemática elementar. ( @lemire pode discordar!)

[Eu diria que o que você geralmente _não_ deve fazer é semear um PRNG com bits aleatórios saindo de si mesmo. Isso parece muito incestuoso para mim.]

Certo, está claro para mim que usar algo como uma _SeedSequence_ bem projetada seria uma maneira de obter uma semente inicial arbitrária e desenhar vários pontos de partida no ciclo de nosso algoritmo que não deveriam se sobrepor. E se essa é a única maneira real de obter streams independentes, que seja. Será uma questão de design da API para tornar isso conveniente.

O que estou menos claro é como é seguro pegar o estado atual de um PRNG inicializado, mixagem hash no ID do fluxo para pular para um novo estado no ciclo, que é o que pensei que você estava sugerindo me ocorreu mais tarde que eu poderia estar errado sobre isso). Estar bem separado no ciclo não é o único fator, como mostra o fracasso de jumped() . jumped() também garante que você está sendo enviado para uma parte distante da sequência que não se sobrepõe; é apenas uma parte que pode se correlacionar fortemente com a parte inicial se o salto não for bem escolhido. Pode ser necessário algum conhecimento da parte interna de cada algoritmo para saber o que é e o que não é um bom salto. Obviamente, não o fizemos no caso do PCG.

Fundamentalmente, se pensarmos nos PRNGs como funções de transição e funções de saída, este new_state = seed_seq(old_state||streamID) é apenas outra função de transição que estamos introduzindo em uma etapa. Temos que ter certeza de que as operações envolvidas nesses seed_seq são diferentes o suficiente das funções de transição em cada algoritmo PRNG (ou seus inversos) e talvez tenha que ter certeza de outras coisas. Eu não gostaria de usar algo construído a partir de, digamos, wyhash para inicializar wyrand . Como você disse, você não quer usar o próprio PRNG para fornecer os bits para si mesmo. É por isso que acho que é necessário algum estudo para garantir isso para todos os nossos PRNGs (estudo que eu esperava não ter que fazer sozinho).

Por outro lado, new_state = seed_seq(old_state||streamID) provavelmente não está pior do que neste aspecto do que o uso pretendido de _SeedSequence_ para vários fluxos: extraia dois estados em sequência. Se sim, então eu ficaria bem em descansar na experiência do C ++, talvez com sua implementação, e apenas fazer alguns testes empíricos com PractRand para todos os nossos algoritmos para mostrar que eles não estão em pior situação do que seus equivalentes de fluxo único.

Seria muito bom fazer o hash jump funcionar porque isso abre alguns casos de uso para gerar PRNGs de uma maneira livre de coordenação. O uso de IDs de fluxo requer alguma comunicação ou pré-alocação. dask já pediu algo assim no passado.

Se houver boas alternativas que dependem apenas de uma boa semeadura que podemos tornar conveniente para fazer a coisa certa, então provavelmente devemos remover os fluxos configuráveis ​​como critério para a seleção do padrão. Queremos apenas um algoritmo padrão que tenha um espaço de estados suficientemente grande.

Dito isso, parece que usar um hash de 63 bits para derivar o incremento PCG32 de IDs de fluxo sequencial ( range(N) ) parece funcionar. 8192 streams intercalados passam PractRand para 2 TiB. Se expormos os IDs de fluxo para os geradores PCG, podemos usar essa técnica para derivar os incrementos, mesmo se sugerirmos que as pessoas usem outros meios para obter fluxos independentes de forma reproduzível.

O que estou menos claro é como é seguro pegar o estado atual de um PRNG inicializado, mixagem hash no ID do fluxo para pular para um novo estado no ciclo, que é o que pensei que você estava sugerindo me ocorreu mais tarde que eu poderia estar errado sobre isso).

Provavelmente me expressei de forma ambígua, mas não , nunca tive a intenção de sugerir que o estado atual do PRNG deveria ser usado na auto-propagação.

Mas, FWIW, SplitMix faz isso, é o que sua operação split() faz. E, eu não gosto disso.

Isso pode ser muita informação, mas vou compartilhar um pouco sobre por que fiquei horrorizado (talvez mais horrorizado do que deveria) com a função split() do SplitMix. Como uma observação histórica, SplitMix e PCG foram projetados de forma independente na mesma época (SplitMix foi publicado em 20 de outubro de 2014, enquanto pcg-random.org foi ao ar em agosto de 2014 e vinculado ao documento PCG em 5 de setembro de 2014 ) Existem alguns paralelos entre o PCG e o SplitMix (e vários outros PRNGs, incluindo o xor shift * e xorshift + da Vigna - também lançado para o mundo em 2014). Todos têm funções de transição de estado bastante simples, não muito boas o suficiente, corrigidas por uma função de saída de embaralhamento. Quando eu estava escrevendo o artigo PCG, uma coisa que eu sabia que algumas pessoas gostariam era uma função split() , mas não conseguia descobrir uma boa maneira de fazê-la; em vez disso, desenvolvi uma prova rápida de que se você tivesse um PRNG de _k_ bits onde pudesse ir para a esquerda ou direita em cada etapa, dentro de _k_ etapas você deve ser capaz de chegar a um estado em que estava antes, provando assim todo o conceito foi mal concebido. Essa observação não apareceu no jornal. Mas, como resultado de minhas idéias ponderadas antes dessa prova, em uma nota de rodapé em um rascunho quase final do artigo, sugeri, um tanto caprichosamente, porque a saída do PCG era um hash / embaralhamento / permutação de seu estado, se você fosse sentindo-se impertinente, você poderia propagar novamente o gerador com sua própria saída e se safar. Eu o retirei da versão final porque pensei que tal extravagância seria uma bandeira vermelha muito grande para os revisores, visto que a nova propagação de um PRNG com seu próprio estado era amplamente considerado o tipo de uso indevido de um PRNG, e o tipo coisa que vemos de pessoas inexperientes em usá-los.

Lendo o jornal SplitMix, descobri que gosto muito, mas fiquei muito surpreso quando vi split() . Ele fez algo que eu basicamente considerei apenas uma piada e o tornou um recurso de sustentação. Poucos anos depois, comecei a escrever com mais profundidade técnica sobre o que acontece quando você tem esse tipo de operação.

A conclusão geral é que, se você tiver um espaço de estado grande o suficiente (e o de SplitMix é apenas o suficiente), você poderá se safar com a auto-propagação por meio de uma função hash. Ainda sinto que não é uma boa ideia. Como os mapeamentos aleatórios (que é com o que estamos lidando nesta situação) têm propriedades como "com probabilidade assintótica diferente de zero, a árvore mais alta em um gráfico funcional não tem raiz no ciclo mais longo", eu afirmo que é difícil ter total confiança a menos que o designer tenha feito o trabalho necessário para mostrar que tais patologias não estão presentes em seu design.

Para se divertir, aqui está um despejo do espaço de estado de uma versão minúscula do SplitMix explorando apenas três maneiras diferentes (e rigidamente fixas) de combinar next() e split() :

Testing: SplitMix16: void advance() { rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 4, at 000043b0 after 516 steps
- state 00000050 -> new cycle 2, size 41, at 00002103 after 2 steps
- state 000000cd -> new cycle 3, size 4, at 0000681a after 6 steps
- state 00000141 -> new cycle 4, size 23, at 00004001 after 11 steps
- state 00000dee -> new cycle 5, size 7, at 00007436 after 4 steps
- state 00008000 -> new cycle 6, size 90278, at 5e5ce38c after 46472 steps
- state 00030000 -> new cycle 7, size 6572, at 12c65374 after 10187 steps
- state 00030016 -> new cycle 8, size 3286, at 65d0fc0c after 402 steps
- state 00058000 -> new cycle 9, size 17097, at 2a2951fb after 31983 steps
- state 08040000 -> new cycle 10, size 36, at 08040000 after 0 steps
- state 08040001 -> new cycle 11, size 218, at 08040740 after 360 steps
- state 08040004 -> new cycle 12, size 10, at 38c01b3d after 107 steps
- state 08040006 -> new cycle 13, size 62, at 38c013a0 after 39 steps
- state 08040009 -> new cycle 14, size 124, at 08045259 after 24 steps
- state 08040019 -> new cycle 15, size 32, at 38c06c63 after 151 steps
- state 08040059 -> new cycle 16, size 34, at 38c00217 after 17 steps
- state 08040243 -> new cycle 17, size 16, at 38c06e36 after 13 steps
- state 123c8000 -> new cycle 18, size 684, at 77d9595f after 194 steps
- state 123c8002 -> new cycle 19, size 336, at 5de8164d after 141 steps
- state 123c9535 -> new cycle 20, size 12, at 123c9535 after 0 steps
- state 139f0000 -> new cycle 21, size 545, at 743e3a31 after 474 steps
- state 139f0b35 -> new cycle 22, size 5, at 139f0b35 after 0 steps
- state 139f1b35 -> new cycle 23, size 5, at 68d3c943 after 8 steps

Cycle Summary:
- Cycle 1, Period 4, Feeders 32095
- Cycle 2, Period 41, Feeders 188
- Cycle 3, Period 4, Feeders 214
- Cycle 4, Period 23, Feeders 180
- Cycle 5, Period 7, Feeders 12
- Cycle 6, Period 90278, Feeders 1479024474
- Cycle 7, Period 6572, Feeders 102385385
- Cycle 8, Period 3286, Feeders 5280405
- Cycle 9, Period 17097, Feeders 560217399
- Cycle 10, Period 36, Feeders 413
- Cycle 11, Period 218, Feeders 51390
- Cycle 12, Period 10, Feeders 1080
- Cycle 13, Period 62, Feeders 4113
- Cycle 14, Period 124, Feeders 4809
- Cycle 15, Period 32, Feeders 2567
- Cycle 16, Period 34, Feeders 545
- Cycle 17, Period 16, Feeders 87
- Cycle 18, Period 684, Feeders 95306
- Cycle 19, Period 336, Feeders 100263
- Cycle 20, Period 12, Feeders 7
- Cycle 21, Period 545, Feeders 163239
- Cycle 22, Period 5, Feeders 12
- Cycle 23, Period 5, Feeders 34

- Histogram of indegrees of all 2147483648 nodes:
      0  529334272
      1 1089077248
      2  528875520
      3     131072
      4      65536
Testing: SplitMix16: void advance() { rng.next(); rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 36174, at 6b34fe8b after 21045 steps
- state 00000002 -> new cycle 2, size 4300, at 042a7c6b after 51287 steps
- state 0000000f -> new cycle 3, size 11050, at 0b471eb5 after 4832 steps
- state 0000001d -> new cycle 4, size 38804, at 2879c05c after 16280 steps
- state 00000020 -> new cycle 5, size 4606, at 46e0bdf6 after 7379 steps
- state 00046307 -> new cycle 6, size 137, at 0a180f87 after 89 steps
- state 00081c25 -> new cycle 7, size 16, at 177ed4d8 after 27 steps
- state 0044c604 -> new cycle 8, size 140, at 5e1f125b after 44 steps
- state 006e329f -> new cycle 9, size 18, at 006e329f after 0 steps
- state 13ebcefc -> new cycle 10, size 10, at 13ebcefc after 0 steps

Cycle Summary:
- Cycle 1, Period 36174, Feeders 975695553
- Cycle 2, Period 4300, Feeders 766130785
- Cycle 3, Period 11050, Feeders 110698235
- Cycle 4, Period 38804, Feeders 251133911
- Cycle 5, Period 4606, Feeders 43723200
- Cycle 6, Period 137, Feeders 4101
- Cycle 7, Period 16, Feeders 172
- Cycle 8, Period 140, Feeders 2310
- Cycle 9, Period 18, Feeders 124
- Cycle 10, Period 10, Feeders 2

- Histogram of indegrees of all 2147483648 nodes:
      0  529334272
      1 1089077248
      2  528875520
      3     131072
      4      65536
Testing: SplitMix16: void advance() { rng.next(); rng = rng.split(); rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 40959, at 0069b555 after 49520 steps
- state 00000031 -> new cycle 2, size 1436, at 5f619520 after 2229 steps
- state 000003a4 -> new cycle 3, size 878, at 18d1cb99 after 1620 steps
- state 0000046c -> new cycle 4, size 2596, at 46ba79c0 after 1591 steps
- state 0000c6e2 -> new cycle 5, size 24, at 0212f11b after 179 steps
- state 000af7c9 -> new cycle 6, size 61, at 40684560 after 14 steps
- state 00154c16 -> new cycle 7, size 110, at 29e067ce after 12 steps
- state 0986e055 -> new cycle 8, size 4, at 2b701c82 after 7 steps
- state 09e73c93 -> new cycle 9, size 3, at 352aab83 after 1 steps
- state 19dda2c0 -> new cycle 10, size 1, at 78825f1b after 2 steps

Cycle Summary:
- Cycle 1, Period 40959, Feeders 2129209855
- Cycle 2, Period 1436, Feeders 5125630
- Cycle 3, Period 878, Feeders 7077139
- Cycle 4, Period 2596, Feeders 5997555
- Cycle 5, Period 24, Feeders 24221
- Cycle 6, Period 61, Feeders 1774
- Cycle 7, Period 110, Feeders 1372
- Cycle 8, Period 4, Feeders 23
- Cycle 9, Period 3, Feeders 4
- Cycle 10, Period 1, Feeders 3

- Histogram of indegrees of all 2147483648 nodes:
      0  829903716
      1  684575196
      2  468475086
      3  132259769
      4   32192209
      5      58402
      6      17026
      7       1982
      8        261
      9          1
Testing: SplitMix16: void advance() { rng.next(); rng.next(); rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 55038, at 3e57af06 after 30005 steps
- state 00000005 -> new cycle 2, size 376, at 4979e8b5 after 6135 steps
- state 0000001e -> new cycle 3, size 10261, at 0cd55c94 after 1837 steps
- state 0000002d -> new cycle 4, size 3778, at 7f5f6afe after 3781 steps
- state 00000064 -> new cycle 5, size 2596, at 3bc5404b after 5124 steps
- state 0000012b -> new cycle 6, size 4210, at 525cc9f3 after 397 steps
- state 00000277 -> new cycle 7, size 1580, at 410010c8 after 1113 steps
- state 00001394 -> new cycle 8, size 916, at 7b20dfb0 after 193 steps
- state 00063c2d -> new cycle 9, size 51, at 6e92350b after 121 steps
- state 058426a6 -> new cycle 10, size 8, at 058426a6 after 0 steps
- state 0e5d412d -> new cycle 11, size 1, at 0e5d412d after 0 steps
- state 4c2556c2 -> new cycle 12, size 1, at 4c2556c2 after 0 steps

Cycle Summary:
- Cycle 1, Period 55038, Feeders 2027042770
- Cycle 2, Period 376, Feeders 28715945
- Cycle 3, Period 10261, Feeders 49621538
- Cycle 4, Period 3778, Feeders 13709744
- Cycle 5, Period 2596, Feeders 15367156
- Cycle 6, Period 4210, Feeders 10418779
- Cycle 7, Period 1580, Feeders 1782252
- Cycle 8, Period 916, Feeders 744273
- Cycle 9, Period 51, Feeders 2351
- Cycle 10, Period 8, Feeders 24
- Cycle 11, Period 1, Feeders 0
- Cycle 12, Period 1, Feeders 0

- Histogram of indegrees of all 2147483648 nodes:
      0  529334272
      1 1089077248
      2  528875520
      3     131072
      4      65536

etc.

Ah, ótimo. Eu mesmo rejeitei algumas propostas nesse sentido alguns anos atrás, então temi que tivéssemos perdido algo bom. :-)

Alguma reflexão final sobre a abordagem de hash para derivar incrementos para fluxos de PCG? Deixando de lado se esse é o principal mecanismo para obter fluxos independentes. Com exceção de remover completamente o acesso a esse recurso, isso parece algo que gostaríamos de fazer para evitar o uso indevido de IDs de fluxo sequencial.

Por curiosidade, existe alguma maneira (fácil) de saber a que distância dois estados estão no PCG64?

Por curiosidade, existe alguma maneira (fácil) de saber a que distância dois estados estão no PCG64?

Sim, embora não o exponhamos: http://www.pcg-random.org/useful-features.html#distance

Na fonte C ++, a função de distância irá até mesmo dizer a você a distância entre os fluxos, dando seu ponto de aproximação mais próximo (onde a única diferença entre os fluxos é uma constante adicionada).

A propósito, para o LCG subjacente, podemos usar a distância para calcular o quão correlacionado esperamos que as posições sejam. Uma distância curta é obviamente ruim (e é ruim para qualquer PRNG), mas uma distância com apenas um bit definido também não é boa, é por isso que pular para 2 ^ 64 ( 0x10000000000000000 ) com .jumped é uma má ideia. Na minha lista de tarefas PCG está escrevendo uma função “ independence_score ” que olha para a distância entre dois estados e diz a você o quão aleatória é a distância (por meio de peso de hamming, etc - de preferência, queremos cerca de metade do os bits devem ser zeros e meio uns e eles podem ser espalhados livremente).

Uma maneira de _manter_ jumped com PCG64 seria não pular n * 0x10000000000000000 mas sim pular n * 0x9e3779b97f4a7c150000000000000000 (truncado para 128 bits). Isso lhe dará todas as propriedades usuais que você deseja ( .jumped(3).jumped(5) == .jumped(8) ) sem ser patológico para o LCG subjacente.

(Também estou ciente de que dizer "não avance em 0x10000000000000000 " é uma espécie de " bem, não segure assim a resposta " e não estou muito satisfeito com isso. Claro, é legal que independence_score pode existir, mas essa coisa toda (e o problema com fluxos semelhantes), pode argumentar por uma função de saída padrão mais forte, mesmo que as pessoas façam coisas (raras) que são totalmente patológicas para o subjacente LCG, nenhum dano será feito. PCG está chegando aos cinco anos de idade neste momento, e estou considerando um aumento e ajustes de versão neste verão, então este problema pode estar na lista. Claro, pode incomodar vocês se assim que você coloca o PCG, eu faço uma versão principal e a melhoro.)

Alguma reflexão final sobre a abordagem de hash para derivar incrementos para fluxos de PCG? Deixando de lado se esse é o principal mecanismo para obter fluxos independentes. Com exceção de remover completamente o acesso a esse recurso, isso parece algo que gostaríamos de fazer para evitar o uso indevido de IDs de fluxo sequencial.

Eu recomendo que você coloque no mixer Murmur3 . É provável que ninguém faça acidentalmente streams semelhantes sem esforço deliberado. (Editar: acho que você precisa de uma versão de 128 bits, mas você pode simplesmente misturar as metades superior e inferior. Eu também adicionaria uma constante. Todo mundo adora 0x9e3779b97f4a7c15f39cc0605cedc835 (parte fracionária de ϕ), mas 0xb7e151628aed2a6abf7158809cf4f3c7 (parte fracionária de e) também seria adequado, ou _qualquer_ número de aparência aleatória.)

Eu recomendo wyhash (https://github.com/wangyi-fudan/wyhash), pois é o mais rápido e simples que passou BigCrush e PractRand. O código c é tão simples quanto

inline  uint64_t    wyrand(uint64_t *seed){    
    *seed+=0xa0761d6478bd642full;    
    __uint128_t t=(__uint128_t)(*seed^0xe7037ed1a0b428dbull)*(*seed);    
    return  (t>>64)^t;    
}

@ wangyi-fudan, não consigo me convencer de que isso é uma bijeção.

Desculpe pelo meu conhecimento limitado: por que a bijeção é necessária / favorecida para um PRNG?
será apreciado por algumas explicações :-) @imneme

@ wangyi-fudan, se uma função hash de ints de 64 bits para ints de 64 bits não for uma bijeção (ou seja, uma função 1 para 1), alguns resultados são gerados mais de uma vez e outros nem tanto. Isso é uma espécie de preconceito.

Eu entendo o que você quer dizer. entretanto, para um gerador de números aleatórios de 64 bits R, esperaremos uma colisão após 1,2 * 2 ^ 32 números aleatórios (http://mathworld.wolfram.com/BirthdayAttack.html). com 2 ^ 64 números aleatórios, é natural haver muitas colisões. as colisões são naturais, enquanto a bijeção não é naturalmente aleatória. Se eu soubesse que a tabela de jogos de azar (por exemplo, um PRNG de 3 bits) é determinada como tendo um valor 0 dentro de 8 trilhas, arrisco fazer uma grande aposta em zero após observar 5 diferente de zero.

@ wangyi-fudan, Neste contexto, estávamos conversando sobre maneiras de permutar o id de fluxo para que fluxos como 1,2,3 se tornem algo de aparência mais aleatória (também conhecido como mais normal). Não há virtude em colisões neste processo.

Para PRNGs em geral, você deve ler sobre a diferença entre PRNGs com base em mapeamentos aleatórios e aqueles baseados em mapeamentos invertíveis aleatórios (funções 1 para 1). Já escrevi sobre isso, mas outros também. Em tamanhos pequenos, PRNGs baseados em mapeamentos aleatórios mostrarão viés e falharão mais rapidamente do que aqueles baseados em outras técnicas. Em tamanhos grandes, falhas de todos os tipos podem ser mais difíceis de detectar.

Podemos calcular a probabilidade de que, se pedirmos _n_ intervalos, um se sobreponha a outro. É uma probabilidade bastante básica - não tenho certeza se você conseguiria publicar um artigo sobre isso porque (pelo que entendi) ninguém fica animado com artigos que contenham matemática elementar.

Eu acho que você só tem que ser Pierre L'Ecuyer. ;-) página 15

Sim, quando ele explica o básico, considera-se normal!

@rkern @imneme Simplicidade é um recurso, tanto em software quanto em matemática. O fato de alguns não se impressionarem com o trabalho simples não deve ser considerado uma evidência contraditória.

@lemire : Há uma peça de humor que gosto e que acho que contém muita verdade chamada _Como criticar os cientistas da computação_ . A ideia subjacente à peça é que os teóricos favorecem a sofisticação e os experimentalistas favorecem a simplicidade. Portanto, se seu público for experimentalista, eles ficarão encantados com a simplicidade, mas se seu público for teórico, nem tanto.

O BitGenerator padrão é PCG64 . Obrigado a todos por suas contribuições atenciosas. E resistência!

Muito inspirado por este tópico, tenho algumas novidades para relatar ...

fundo

Sob muitos aspectos, pcg64 é muito bom; por exemplo, sob as medidas usuais de qualidade estatística, obtém um atestado de saúde limpo. Ele foi testado de várias maneiras; mais recentemente, executei todo o caminho até meio petabyte com PractRand. Funciona bem em casos de uso normais.

MAS, as patologias que surgiram neste tópico não me agradaram. Claro, eu poderia dizer “ bem, não pense assim ”, mas o ponto principal de um PRNG de propósito geral é que ele deve ser robusto. Eu queria fazer melhor ...

Então, cerca de 25 dias atrás, comecei a pensar em projetar um novo membro da família PCG ...

Objetivo

Meu objetivo era projetar um novo membro da família PCG que pudesse substituir a variante pcg64 atual. Assim sendo:

  • A função de saída deve embaralhar os bits mais do que XSL RR (porque isso evitará os problemas que surgiram neste encadeamento).
  • O desempenho deve ser quase tão rápido (ou mais rápido) que o atual pcg64 .
  • O design deve ser semelhante ao PCG (ou seja, não ser trivialmente previsível e, portanto, não permitir que _qualquer_ do trabalho da função de saída seja facilmente desfeito).

Como sempre, há uma compensação, pois tentamos obter a melhor qualidade possível o mais rápido possível. Se não nos importássemos com a velocidade, poderíamos ter mais etapas na função de saída para produzir uma saída mais fortemente embaralhada, mas o ponto do PCG era que o LCG subjacente era "quase bom o suficiente" e, portanto, não precisávamos para fazer tanto esforço quanto faríamos com algo como um contador incrementando em 1.

Spoiler

Tenho o prazer de relatar o sucesso! Cerca de 25 dias atrás, quando pensei sobre isso pela primeira vez, eu estava de férias. Quando voltei, há cerca de dez dias, experimentei as ideias que tinha e fiquei satisfeito ao descobrir que funcionaram bem. O tempo subsequente foi gasto principalmente em vários tipos de teste. Ontem fiquei satisfeito o suficiente para colocar o código na versão C ++ do PCG. Testes em tamanhos pequenos indicam que é muito melhor do que o XSL RR e competitivo com o RXS M, mas realmente brilha em tamanhos maiores. Ele atende a todos os outros objetivos também.

Detalhes

FWIW, a nova função de saída é (para o caso de saída de 64 bits):

uint64_t output(__uint128_t internal)
{
    uint64_t hi = internal >> 64;
    uint64_t lo = internal;

    lo |= 1;
    hi ^= hi >> 32;
    hi *= 0xda942042e4dd58b5ULL;
    hi ^= hi >> 48;
    hi *= lo;
    return hi;
}

Esta função de saída é inspirada em xorshift-multiply, pelo qual é amplamente utilizado. A escolha de multiplicadores é (a) para manter o número de constantes mágicas baixo, e (b) para evitar que a permutação seja desfeita (se você não tiver acesso a bits de ordem inferior), e também fornecer todo o "randomized- por si só ”qualidade que as funções de saída PCG normalmente têm.

Outras mudanças

Também é o caso que 0xda942042e4dd58b5 é o multiplicador LCG para este PRNG (e todos os geradores PCG de estado de 128 bits com prefixo cm_ ). Em comparação com 0x2360ed051fc65da44385df649fccf645 usado por pcg64 , esta constante ainda é bastante boa em termos de propriedades de teste espectral, mas é mais barato para multiplicar porque 128 bits x 64 bits é mais fácil do que 128 bits × 128 bits. Eu usei essa constante LCG por vários anos sem problemas. Ao usar a variante do multiplicador barato, executo a função de saída no estado pré-iterado em vez do estado pós-iterado para maior paralelismo no nível de instrução.

Testando

Eu testei completamente (PractRand e TestU01) e estou feliz com isso. Os testes incluíram cenários descritos neste tópico (por exemplo, pegando geradores de gangues em vapores sequenciais ou avançados em 2 ^ 64 e intercalando sua produção - testei uma gangue de quatro e uma gangue de 8192 até 8 TB sem problemas, também como um riacho e sua contraparte de terra oposta).

Rapidez

Eu poderia me alongar sobre testes de velocidade e benchmarks. Existem todos os tipos de fatores que influenciam se um PRNG é executado mais rápido do que outro em um determinado benchmark, mas no geral, essa variante parece frequentemente ser um pouco mais rápida, às vezes muito mais rápida e ocasionalmente um pouco mais lenta. Fatores como o compilador e o aplicativo têm um impacto muito maior na variabilidade do benchmark.

Disponibilidade

Os usuários do cabeçalho C ++ podem acessar este novo membro da família _agora_ como pcg_engines::cm_setseq_dxsm_128_64 ; em algum ponto no futuro, irei mudar pcg64 de pcg_engines::setseq_xsl_rr_128_64 para este novo esquema. Meu plano atual é fazer isso neste verão, como parte de um aumento na versão PCG 2.0.

Anúncios Formais

No geral, estou muito feliz com este novo membro da família e em algum momento no final do verão, haverá postagens no blog com mais detalhes, provavelmente fazendo referência a este tópico.

Suas escolhas...

Claro, você tem que descobrir o que fazer com isso. Independentemente de você usar ou não, eu na verdade ficaria muito curioso para ver se ele se sai melhor ou pior em seus benchmarks de velocidade.

@imneme Ele contorna a necessidade de ter um multiplicador de 64 bits completo e rápido? (O que é super rápido no x64, mas um pouco mais lento em algumas arquiteturas mais fracas.)

@lemire :

Ainda é potencialmente melhor do que 128 bits × 128 bits. Embora o quanto melhor dependa de quão bem o agendamento da instrução vai naquele momento.

Você está certo que no ARM, 64 bits × 64 bits → resultado de 128 bits são na verdade duas instruções.

(É totalmente possível, claro, agrupar dois LCGs de 64 bits e misturá-los. O espaço de todos os PRNGs que poderiam existir e funcionariam bem é muito grande.)

Uma implementação rápida e suja em nossa estrutura sugere uma leve melhoria de desempenho, pelo menos no Linux de 64 bits:

Time to produce 1,000,000 64-bit unsigned integers
************************************************************
MT19937      5.42 ms
PCG64        2.59 ms
PCG64DXSM    2.41 ms
Philox       4.37 ms
SFC64        2.07 ms
numpy        5.41 ms
dtype: object

64-bit unsigned integers per second
************************************************************
MT19937      184.39 million
PCG64        386.01 million
PCG64DXSM    415.02 million
Philox       228.88 million
SFC64        483.94 million
numpy        184.79 million
dtype: object

Acho que é a saída do estado pré-iterado que lhe dá o impacto, pelo menos neste contexto. Eu deixei isso de fora no início e obtive essencialmente o mesmo desempenho de PCG64 1,0. A verdadeira vitória seria na emulação de 128 bits, eu suspeito, mas eu não comecei a escrever isso e não tenho uma boa maneira de testá-lo nas plataformas que importam.

Eu acho que a verdadeira questão para você, @imneme , é o quão irritado você ficará com o nome numpy.random.PCG64 implementando o algoritmo 1.0? O lançamento é iminente e já está atrasado, portanto, não _ acho_ que vamos mudar o algoritmo neste momento. Se o desempenho em plataformas de 32 bits for particularmente bom, acho que podemos adicionar PCG64DXSM em uma versão seguinte e talvez reconsiderar o padrão algumas versões mais adiante.

A escolha é sua!

Não tenho problemas com o envio da versão 1.0 do PCG64. Muitas outras pessoas usaram essa variante.

Eu acho que a variante DXSM tem a vantagem de evitar os problemas de uso de casos extremos que surgiram neste tópico (afinal, é basicamente por isso que existe), mas por outro lado, tem a desvantagem de ser tarde para a festa. Pode até parecer um tanto imprudente enviar um PRNG com menos de um mês para os usuários (mesmo que seja baseado nas mesmas ideias das variantes PCG mais testadas pelo tempo).

(Dito isso, se fosse _minha_ escolha, apesar de possíveis acusações de imprudência, provavelmente enviaria o novo; acho que o atraso para colocá-lo em funcionamento no Numpy é mínimo. E o risco é muito baixo - já é completamente testado com BigCrush e testado com PractRand até 16 TB (incluindo cm_mcg_dxsm_64_32 que é um quarto do tamanho [sem streams, saída de 32 bits]) e provavelmente atingirá 32 TB em menos de uma semana .)

[Ainda bem que o desempenho ficou um pouco melhor. Cinco anos atrás, o uso do estado pré-iterado era uma pessimização para tamanhos de 128 bits com multiplicadores de 128 bits. Mas isso foi então, nas máquinas em que estava testando, com os benchmarks que estava usando.]

Eu quis dizer mais sobre como usar o nome PCG64 para a variante 1.0 quando você usará esse nome para se referir à variante 2.0.

@rkern Se for apenas uma questão de nomenclatura, então PCG64DXSM e PCG64 os diferenciam muito bem, não?

Para entorpecido, certamente. Só estou me perguntando se @imneme prefere que não PCG64 quando ela estiver promovendo a variante 2.0 com esse nome na versão C ++. Sou sensível ao fato de que ser solto com os nomes significa que algumas pessoas podem testar PCG64 do numpy e compará-lo com as afirmações que serão feitas no pcg-random.org sobre a versão 2.0. Cf. praticamente qualquer conversa sobre os PRNGs de Bob Jenkin.

Na Seção 6.3 do documento PCG, ele diz:

Observe também que, embora os geradores sejam apresentados com nomes mnemônicos com base nas permutações que realizam, os usuários da biblioteca PCG raramente devem selecionar membros da família por esses mnemônicos. A biblioteca fornece geradores nomeados com base em suas propriedades, não em suas implementações subjacentes (por exemplo, pcg32_unique para um gerador de 32 bits de uso geral com um fluxo exclusivo). Dessa forma, quando futuros membros da família com desempenho ainda melhor são descobertos e adicionados (provavelmente devido às descobertas de outros), os usuários podem alternar facilmente para eles.

E as bibliotecas C e C ++ são estruturadas dessa forma. As bibliotecas fornecem

  • uma interface de baixo nível que permite que você escolha um membro específico da família por meio do nome de sua permutação, os tamanhos de bits em que opera e as características do LCG subjacente
  • uma interface de alto nível que fornece aliases convenientes como pcg64 que se conectam a um membro da família de baixo nível pré-escolhido.

Desta forma, os aliases podem ser atualizados para apontar para novos membros da família, mas os usuários que desejam reproduzir exatamente os resultados mais antigos ainda poderão usar a interface de baixo nível para selecionar o membro da família que era anteriormente acessível por um conveniente alto -level alias.

Se você for enviar um PRNG chamado PCG64 , eu diria que é suficiente dizer em sua documentação qual variante PCG específica é - em outras palavras, diga a qual membro da família ela corresponde no mínimo interface de biblioteca de nível C ou C ++.

O gerador padrão implementado é implementado como np.random.default_gen() em https://github.com/numpy/numpy/pull/13840. ( @rkern para referência futura, provavelmente é bom chamar explicitamente os PRs - eles são fáceis de perder no GitHub se você fornecer apenas um link de retorno, já que não há notificações para isso.)

Um detalhe menor: que tal chamar isso de np.random.default_generator() vez disso? gen parece muito curto / nada óbvio para mim. Eu ficaria curioso para saber o que os outros pensam.

que tal chamar este np.random.default_generator () em vez disso?

Eu pensei o mesmo, mas então, np.random.default_generator() é um fio de cabelo no lado longo, então joguei com default_rng .

👍 Eu gosto mais de default_rng que default_gen . Eu ficaria feliz com qualquer um deles, embora ainda incline para default_generator .

: +1: por default_rng() .

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

marcocaccin picture marcocaccin  ·  4Comentários

astrofrog picture astrofrog  ·  4Comentários

keithbriggs picture keithbriggs  ·  3Comentários

Foadsf picture Foadsf  ·  3Comentários

toddrjen picture toddrjen  ·  4Comentários