Path: blob/master/site/pt-br/guide/random_numbers.ipynb
25115 views
Copyright 2019 The TensorFlow Authors.
Geração de números aleatórios
O TensorFlow oferece um conjunto de geradores de números pseudo-aleatórios (RNG), no módulo tf.random
. Este documento descreve como você pode controlar os geradores de números aleatórios e como eles interagem com outros subsistemas do TensorFlow.
Observação: a consistência dos números aleatórios nas versões do TensorFlow não é garantida: Compatibilidade com a versão
O TensorFlow oferece duas abordagens para controlar o processo de geração de números aleatórios:
Pelo uso explícito de objetos
tf.random.Generator
. Cada objeto mantém um estado (emtf.Variable
) que será alterado após cada geração de números.Através de funções aleatórias stateless que são puramente funcionais, como
tf.random.stateless_uniform
. Chamar essas funções com os mesmos argumentos (que incluem a semente) e no mesmo dispositivo sempre produzirá os mesmos resultados.
Aviso: os RNGs antigos do TF 1.x, como tf.random.uniform
e tf.random.normal
, ainda não foram descontinuados mas são fortemente desaconselhados.
Configuração
A classe tf.random.Generator
A classe tf.random.Generator
é usada nos casos em que você quer que cada chamada de RNG produza resultados diferentes. Ela mantém um estado interno (gerenciado por um objeto tf.Variable
) que será atualizado sempre que os números aleatórios forem gerados. Como o estado é gerenciado por tf.Variable
, ele aproveita todas as facilidades fornecidas por tf.Variable
, como checkpoint fácil, dependência de controle automática e segurança de thread.
Você pode obter um tf.random.Generator
ao criar manualmente um objeto da classe ou chamar tf.random.get_global_generator()
para obter o gerador global padrão:
Há várias maneiras de criar um objeto gerador. A mais fácil é Generator.from_seed
, conforme mostrado acima, que cria um gerador a partir de uma semente. Uma semente é qualquer número inteiro não negativo. from_seed
também aceita um argumento opcional alg
, que é o algoritmo de RNG que será usado por esse gerador:
Veja a seção Algoritmos abaixo para saber mais.
Outra maneira de criar um gerador é com Generator.from_non_deterministic_state
. Um gerador criado dessa forma começará em um estado não determinístico, dependendo do tempo e do SO, por exemplo.
Ainda há outras maneiras de criar geradores, como a partir de estados explícitos, que não serão abordadas neste guia.
Ao usar tf.random.get_global_generator
para obter o gerador global, você precisa ter cuidado com o posicionamento do dispositivo. O gerador global é criado (a partir de um estado não determinístico) na primeira vez que tf.random.get_global_generator
é chamado, e colocado no dispositivo padrão dessa chamada. Então, por exemplo, se o primeiro local que você chamar tf.random.get_global_generator
for em um escopo tf.device("gpu")
, o gerador global será colocado na GPU, e o uso do gerador global mais tarde na CPU resultará em uma cópia da GPU para a CPU.
Também há uma função tf.random.set_global_generator
para substituir o gerador global por outro objeto gerador. No entanto, essa função deve ser usada com cuidado, porque o gerador global antigo pode ter sido capturado por uma tf.function
(como uma referência fraca), e a substituição fará com que seja coletado como lixo, corrompendo a tf.function
. Uma maneira melhor de redefinir o gerador global é usar uma das funções "reset", como Generator.reset_from_seed
, que não criam novos objetos geradores.
Criando streams de números aleatórios independentes
Em vários aplicativos, é preciso multiplicar streams de números aleatórios independentes (no sentido de que não vão se sobrepor nem ter qualquer correlação detectável estatisticamente). Isso é realizado ao usar Generator.split
para criar vários geradores que têm a garantia de serem independentes uns dos outros (ou seja, gerando streams independentes).
split
mudará o estado do gerador em que é chamado (g
no exemplo acima), semelhante a um método de RNG como normal
. Além de serem independentes, os novos geradores (new_gs
) também são independentes do antigo (g
).
A criação de novos geradores também é útil quando você quer garantir que o gerador usado está no mesmo dispositivo que outras computações, para evitar a sobrecarga de cópia entre dispositivos. Por exemplo:
Observação: em teoria, você pode usar construtores como from_seed
em vez de split
para obter um novo gerador. Porém, com isso, você perde a garantia de que o novo gerador é independente do gerador global. Você também corre o risco de criar acidentalmente dois geradores com a mesma semente ou com sementes que levam à sobreposição de streams de números aleatórios.
Você pode realizar a divisão recursivamente, chamando split
em geradores split. Não há limites (barrando o overflow de números inteiros) de profundidade das recursões.
Interação com tf.function
tf.random.Generator
obedece às mesmas regras que tf.Variable
quando usado com tf.function
. Isso inclui três aspectos.
Criando geradores fora de tf.function
tf.function
pode usar um gerador criado fora dela.
O usuário precisa garantir que o objeto gerador ainda está vivo (e não coletado como lixo) quando a função é chamada.
Criando geradores dentro de tf.function
A criação dos geradores dentro de uma tf.function
só pode ocorrer na primeira execução da função.
Passando geradores como argumentos para tf.function
Quando usados como o argumento de uma tf.function
, os objetos geradores diferentes causarão o retracing da tf.function
.
Esse comportamento de retracing é consistente com tf.Variable
:
Interação com estratégias de distribuição
Há duas maneiras que o Generator
interage com as estratégias de distribuição.
Criando geradores fora das estratégias de distribuição
Se um gerador é criado fora dos escopos das estratégias, o acesso de todas as réplicas ao gerador será serializado. Portanto, as réplicas receberão números aleatórios diferentes.
Observe que esse uso pode apresentar problemas de desempenho, porque o dispositivo do gerador é diferente das réplicas.
Criando geradores dentro das estratégias de distribuição
Se um gerador é criado dentro de um escopo de estratégia, cada réplica receberá um stream diferente e independente de números aleatórios.
Observação: no momento, tf.random.Generator
não oferece a opção de deixar que diferentes réplicas recebam streams idênticos (em vez de diferentes), o que tecnicamente não é difícil. Se você tiver um caso de uso para esse recurso, avise aos desenvolvedores do TensorFlow.
Se o gerador for baseado em sementes (por exemplo, criado por Generator.from_seed
), os números aleatórios serão determinados pela semente, mesmo que réplicas diferentes obtenham números diferentes e não correlacionados. É possível pensar em um número aleatório gerado de uma réplica como um hash do ID da réplica e um número aleatório "primário" comum a todas as réplicas. Portanto, o sistema inteiro ainda é determinístico.
tf.random.Generator
também pode ser criado dentro de Strategy.run
:
Não recomendamos mais passar tf.random.Generator
como argumentos para Strategy.run
, porque Strategy.run
geralmente espera que os argumentos sejam tensores, e não geradores.
Salvando geradores
Geralmente, para salvar ou serializar, você pode tratar o tf.random.Generator
da mesma maneira que um tf.Variable
ou tf.Module
(ou suas subclasses). No TF há dois mecanismos de serialização: Checkpoint e SavedModel.
Checkpoint
Os geradores podem ser salvos e restaurados livremente usando tf.train.Checkpoint
. O stream de números aleatórios do ponto de restauração será o mesmo que o do ponto de salvamento.
Você também pode salvar e restaurar em uma estratégia de distribuição:
Confira se as réplicas não divergem no histórico de chamadas do RNG (por exemplo, uma réplica faz uma chamada de RNG enquanto outra faz duas). Caso contrário, os estados de RNG internos vão divergir e tf.train.Checkpoint
(que só salva o estado da primeira réplica) não restaurará corretamente todas as réplicas.
Você também pode restaurar um checkpoint salvo para outra estratégia de distribuição com um número diferente de réplicas. Como um objeto tf.random.Generator
criado em uma estratégia só pode ser usado nela mesmo, para restaurar para uma estratégia diferente, você precisa criar um novo tf.random.Generator
na estratégia de destino e um novo tf.train.Checkpoint
para ela, conforme mostrado neste exemplo:
Embora g1
e cp1
sejam objetos diferentes de g2
e cp2
, eles são ligados pelo arquivo de checkpoint comum filename
e pelo nome do objeto my_generator
. A sobreposição de réplicas entre estratégias (por exemplo, cpu:0
e cpu:1
acima) fará com que os streams de RNG sejam restaurados da maneira adequada, conforme os exemplos anteriores. Essa garantia não cobre o caso em que um gerador é salvo no escopo de uma estratégia e restaurado fora de qualquer escopo de estratégia ou vice-versa, porque um dispositivo fora das estratégias é tratado como diferente de qualquer réplica em uma estratégia.
SavedModel
tf.random.Generator
pode ser salvo para um SavedModel. O gerador pode ser criado em um escopo de estratégia. O salvamento também pode ocorrer em um escopo de estratégia.
Não é recomendável carregar um SavedModel com tf.random.Generator
em uma estratégia de distribuição, porque as réplicas gerarão o mesmo stream de números aleatórios (porque o ID da réplica está congelado no grafo do SavedModel).
O carregamento de um tf.random.Generator
distribuído (um gerador criado em uma estratégia de distribuição) em um ambiente sem estratégia, como o exemplo acima, também tem uma ressalva. O estado do RNG será restaurado corretamente, mas os números aleatórios gerados serão diferentes do gerador original na sua estratégia (novamente, porque um dispositivo fora das estratégias é tratado como diferente de qualquer réplica em uma estratégia).
RNGs stateless
O uso de RNGs stateless é simples. Como eles são funções puras, não há estado ou efeito colateral envolvido.
Todo RNG stateless exige um argumento seed
, que precisa ser um Tensor de número inteiro no formato [2]
. Os resultados da op são totalmente determinados por essa semente.
O algoritmo de RNG usado por RNGs stateless depende do dispositivo, ou seja, a mesma op executada em um dispositivo diferente pode produzir resultados diferentes.
Algoritmos
Geral
Tanto a classe tf.random.Generator
quanto as funções stateless
são compatíveis com o algoritmo Philox (escrito como "philox"
ou tf.random.Algorithm.PHILOX
) em todos os dispositivos.
Dispositivos diferentes gerarão os mesmos números inteiros se usarem o mesmo algoritmo e começarem no mesmo estado. Eles também gerarão "quase os mesmos" números em ponto flutuante, embora possa haver pequenas discrepâncias numéricas causadas pelas diferentes formas que os dispositivos realizam a computação de ponto flutuante (por exemplo, ordem de redução).
Dispositivos XLA
Em dispositivos baseados em XLA (como a TPU, e também a CPU/GPU quando o XLA está ativado), o algoritmo ThreeFry (escrito como "threefry"
ou tf.random.Algorithm.THREEFRY
) também é compatível. Esse algoritmo é rápido na TPU, mas lento na CPU/GPU quando comparado ao Philox.
Veja o artigo "Parallel Random Numbers: As Easy as 1, 2, 3" (Números aleatórios paralelos: fácil como 1, 2, 3) para mais detalhes sobre esses algoritmos.