Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/random_numbers.ipynb
25115 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

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:

  1. Pelo uso explícito de objetos tf.random.Generator. Cada objeto mantém um estado (em tf.Variable) que será alterado após cada geração de números.

  2. 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

import tensorflow as tf # Creates some virtual devices (cpu:0, cpu:1, etc.) for using distribution strategy physical_devices = tf.config.list_physical_devices("CPU") tf.config.experimental.set_virtual_device_configuration( physical_devices[0], [ tf.config.experimental.VirtualDeviceConfiguration(), tf.config.experimental.VirtualDeviceConfiguration(), tf.config.experimental.VirtualDeviceConfiguration() ])

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:

g1 = tf.random.Generator.from_seed(1) print(g1.normal(shape=[2, 3])) g2 = tf.random.get_global_generator() print(g2.normal(shape=[2, 3]))

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:

g1 = tf.random.Generator.from_seed(1, alg='philox') print(g1.normal(shape=[2, 3]))

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.

g = tf.random.Generator.from_non_deterministic_state() print(g.normal(shape=[2, 3]))

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.

g = tf.random.Generator.from_seed(1) print(g.normal([])) print(g.normal([])) g.reset_from_seed(1) print(g.normal([]))

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).

g = tf.random.Generator.from_seed(1) print(g.normal([])) new_gs = g.split(3) for new_g in new_gs: print(new_g.normal([])) print(g.normal([]))

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:

with tf.device("cpu"): # change "cpu" to the device you want g = tf.random.get_global_generator().split(1)[0] print(g.normal([])) # use of g won't cause cross-device copy, unlike the global generator

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.

g = tf.random.Generator.from_seed(1) @tf.function def foo(): return g.normal([]) print(foo())

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.

g = None @tf.function def foo(): global g if g is None: g = tf.random.Generator.from_seed(1) return g.normal([]) print(foo()) print(foo())

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.

num_traces = 0 @tf.function def foo(g): global num_traces num_traces += 1 return g.normal([]) foo(tf.random.Generator.from_seed(1)) foo(tf.random.Generator.from_seed(2)) print(num_traces)

Esse comportamento de retracing é consistente com tf.Variable:

num_traces = 0 @tf.function def foo(v): global num_traces num_traces += 1 return v.read_value() foo(tf.Variable(1)) foo(tf.Variable(2)) print(num_traces)

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.

g = tf.random.Generator.from_seed(1) strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"]) with strat.scope(): def f(): print(g.normal([])) results = strat.run(f)

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.

strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"]) with strat.scope(): g = tf.random.Generator.from_seed(1) print(strat.run(lambda: g.normal([]))) print(strat.run(lambda: g.normal([])))

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:

strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"]) with strat.scope(): def f(): g = tf.random.Generator.from_seed(1) a = g.normal([]) b = g.normal([]) return tf.stack([a, b]) print(strat.run(f)) print(strat.run(f))

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.

filename = "./checkpoint" g = tf.random.Generator.from_seed(1) cp = tf.train.Checkpoint(generator=g) print(g.normal([]))
cp.write(filename) print("RNG stream from saving point:") print(g.normal([])) print(g.normal([]))
cp.restore(filename) print("RNG stream from restoring point:") print(g.normal([])) print(g.normal([]))

Você também pode salvar e restaurar em uma estratégia de distribuição:

filename = "./checkpoint" strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"]) with strat.scope(): g = tf.random.Generator.from_seed(1) cp = tf.train.Checkpoint(my_generator=g) print(strat.run(lambda: g.normal([])))
with strat.scope(): cp.write(filename) print("RNG stream from saving point:") print(strat.run(lambda: g.normal([]))) print(strat.run(lambda: g.normal([])))
with strat.scope(): cp.restore(filename) print("RNG stream from restoring point:") print(strat.run(lambda: g.normal([]))) print(strat.run(lambda: g.normal([])))

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:

filename = "./checkpoint" strat1 = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"]) with strat1.scope(): g1 = tf.random.Generator.from_seed(1) cp1 = tf.train.Checkpoint(my_generator=g1) print(strat1.run(lambda: g1.normal([])))
with strat1.scope(): cp1.write(filename) print("RNG stream from saving point:") print(strat1.run(lambda: g1.normal([]))) print(strat1.run(lambda: g1.normal([])))
strat2 = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1", "cpu:2"]) with strat2.scope(): g2 = tf.random.Generator.from_seed(1) cp2 = tf.train.Checkpoint(my_generator=g2) cp2.restore(filename) print("RNG stream from restoring point:") print(strat2.run(lambda: g2.normal([]))) print(strat2.run(lambda: g2.normal([])))

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.

filename = "./saved_model" class MyModule(tf.Module): def __init__(self): super(MyModule, self).__init__() self.g = tf.random.Generator.from_seed(0) @tf.function def __call__(self): return self.g.normal([]) @tf.function def state(self): return self.g.state strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"]) with strat.scope(): m = MyModule() print(strat.run(m)) print("state:", m.state())
with strat.scope(): tf.saved_model.save(m, filename) print("RNG stream from saving point:") print(strat.run(m)) print("state:", m.state()) print(strat.run(m)) print("state:", m.state())
imported = tf.saved_model.load(filename) print("RNG stream from loading point:") print("state:", imported.state()) print(imported()) print("state:", imported.state()) print(imported()) print("state:", imported.state())

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.

print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2])) print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))

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.