Esta página foi traduzida pela API Cloud Translation.
Switch to English

Geração de números aleatórios

Ver em TensorFlow.org Executar no Google Colab Ver fonte no GitHub Download do caderno

O TensorFlow fornece um conjunto de geradores de números pseudoaleatórios (RNG), no módulo tf.random . Este documento descreve como você pode controlar os geradores de números aleatórios e como esses geradores interagem com outros subsistemas de fluxo tensor.

O TensorFlow fornece duas abordagens para controlar o processo de geração de números aleatórios:

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

  2. Através das funções aleatórias sem estado 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.

Configuração

 import tensorflow as tf

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

A classe tf.random.Generator

A classe tf.random.Generator é usada nos casos em que você deseja que cada chamada RNG produza resultados diferentes. Ele mantém um estado interno (gerenciado por um objeto tf.Variable ) que será atualizado toda vez que números aleatórios forem gerados. Como o estado é gerenciado pelo tf.Variable , ele desfruta de todas as instalações fornecidas pelo tf.Variable , como fácil verificação, dependência automática de controle e segurança do encadeamento.

Você pode obter um tf.random.Generator criando manualmente um objeto da classe ou ligue para 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]))
 
tf.Tensor(
[[ 0.43842274 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[-0.6078218  3.162639  -1.0558378]
 [ 1.2078347  0.6414574  0.4019502]], shape=(2, 3), dtype=float32)

Existem várias maneiras de criar um objeto gerador. O mais fácil é o Generator.from_seed , como mostrado acima, que cria um gerador a partir de uma semente. Uma semente é qualquer número inteiro não negativo. from_seed também usa um argumento opcional alg que é o algoritmo RNG que será usado por este gerador:

 g1 = tf.random.Generator.from_seed(1, alg='philox')
print(g1.normal(shape=[2, 3]))
 
tf.Tensor(
[[ 0.43842274 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)

Veja a seção Algoritmos abaixo para obter mais informações.

Outra maneira de criar um gerador é com Generator.from_non_deterministic_state . Um gerador criado dessa maneira começará a partir de um estado não determinístico, dependendo, por exemplo, de tempo e sistema operacional.

 g = tf.random.Generator.from_non_deterministic_state()
print(g.normal(shape=[2, 3]))
 
tf.Tensor(
[[ 1.1436943  1.729618   1.0391121]
 [-0.8502223 -1.8823647 -1.4051851]], shape=(2, 3), dtype=float32)

Existem ainda outras maneiras de criar geradores, como estados explícitos, que não são cobertos por este 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 em que tf.random.get_global_generator é chamado e colocado no dispositivo padrão nessa chamada. Portanto, por exemplo, se o primeiro site que você chama de tf.random.get_global_generator estiver dentro do tf.device("gpu") , o gerador global será colocado na GPU e o uso do gerador global posteriormente na CPU será incorrem em uma cópia de GPU para CPU.

Há também uma função tf.random.set_global_generator para substituir o gerador global por outro objeto gerador. Essa função deve ser usada com cautela, pois o antigo gerador global pode ter sido capturado por uma função tf.function (como uma referência fraca) e substituí-la fará com que seja coletado como lixo, interrompendo a função tf.function . Uma maneira melhor de redefinir o gerador global é usar uma das funções de "redefinição", como Generator.reset_from_seed , que não criará novos objetos de gerador.

 g = tf.random.Generator.from_seed(1)
print(g.normal([]))
print(g.normal([]))
g.reset_from_seed(1)
print(g.normal([]))
 
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(0.43842274, shape=(), dtype=float32)

Criando fluxos independentes de números aleatórios

Em muitas aplicações, é necessário vários fluxos de números aleatórios independentes, independentes no sentido de que não se sobreporão e não terão correlações estatisticamente detectáveis. Isso é conseguido usando o Generator.split para criar vários geradores que são garantidos como independentes um do outro (por exemplo, gerando fluxos 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([]))
 
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(2.536413, shape=(), dtype=float32)
tf.Tensor(0.33186463, shape=(), dtype=float32)
tf.Tensor(-0.07144657, shape=(), dtype=float32)
tf.Tensor(-0.79253083, shape=(), dtype=float32)

split mudará o estado do gerador no qual é chamado ( g no exemplo acima), semelhante a um método RNG como o normal . Além de independentes, os novos geradores ( new_gs ) também são garantidos como independentes do antigo ( g ).

Gerar novos geradores também é útil quando você deseja garantir que o gerador usado esteja no mesmo dispositivo que outros cálculos, para evitar a sobrecarga da 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
 
tf.Tensor(-1.7580209, shape=(), dtype=float32)

Você pode dividir recursivamente, chamando a split em geradores split . Não há limites (exceto o número inteiro excedente) na 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 do tf.function

tf.function pode usar um gerador criado fora dele.

 g = tf.random.Generator.from_seed(1)
@tf.function
def foo():
  return g.normal([])
print(foo())
 
tf.Tensor(0.43842274, shape=(), dtype=float32)

O usuário precisa garantir que o objeto gerador ainda esteja ativo (não coletado pelo lixo) quando a função for chamada.

Criando geradores dentro do tf.function

A criação de geradores dentro de uma função tf.function só pode tf.function durante a 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())
 
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1817: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)

Passando geradores como argumentos para tf.function

Quando usado como argumento para uma função tf.function , diferentes objetos geradores com o mesmo tamanho de estado (o tamanho do estado é determinado pelo algoritmo RNG) não causam tf.function da função tf.function , enquanto aqueles com tamanhos de estado diferentes causam.

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

Interação com estratégias de distribuição

Existem três maneiras pelas quais o Generator interage com estratégias de distribuição.

Criando geradores fora das estratégias de distribuição

Se um gerador for criado fora dos escopos da estratégia, o acesso de todas as réplicas ao gerador será serializado e, portanto, as réplicas obterã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)
 
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)

Observe que esse uso pode ter problemas de desempenho porque o dispositivo do gerador é diferente das réplicas.

Criando geradores dentro de estratégias de distribuição

A criação de geradores dentro dos escopos da estratégia não é permitida, porque há ambiguidade em como replicar um gerador (por exemplo, se ele for copiado para que cada réplica obtenha os mesmos números aleatórios ou 'dividida' para que cada réplica obtenha números aleatórios diferentes).

 strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
with strat.scope():
  try:
    tf.random.Generator.from_seed(1)
  except ValueError as e:
    print("ValueError:", e)
 
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
ValueError: Creating a generator within a strategy scope is disallowed, because there is ambiguity on how to replicate a generator (e.g. should it be copied so that each replica gets the same random numbers, or 'split' so that each replica gets different random numbers).

Observe que o Strategy.run executará sua função de argumento em um escopo de estratégia implicitamente:

 strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
def f():
  tf.random.Generator.from_seed(1)
try:
  strat.run(f)
except ValueError as e:
  print("ValueError:", e)
 
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
INFO:tensorflow:Error reported to Coordinator: Creating a generator within a strategy scope is disallowed, because there is ambiguity on how to replicate a generator (e.g. should it be copied so that each replica gets the same random numbers, or 'split' so that each replica gets different random numbers).
Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/coordinator.py", line 297, in stop_on_exception
    yield
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/distribute/mirrored_strategy.py", line 998, in run
    self.main_result = self.main_fn(*self.main_args, **self.main_kwargs)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/autograph/impl/api.py", line 282, in wrapper
    return func(*args, **kwargs)
  File "<ipython-input-14-2cd7806456bd>", line 3, in f
    tf.random.Generator.from_seed(1)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/stateful_random_ops.py", line 444, in from_seed
    return cls(state=state, alg=alg)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/stateful_random_ops.py", line 386, in __init__
    trainable=False)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/stateful_random_ops.py", line 272, in _create_variable
    "Creating a generator within a strategy scope is disallowed, because "
ValueError: Creating a generator within a strategy scope is disallowed, because there is ambiguity on how to replicate a generator (e.g. should it be copied so that each replica gets the same random numbers, or 'split' so that each replica gets different random numbers).
ValueError: Creating a generator within a strategy scope is disallowed, because there is ambiguity on how to replicate a generator (e.g. should it be copied so that each replica gets the same random numbers, or 'split' so that each replica gets different random numbers).

Passando geradores como argumentos para Strategy.run

Se você deseja que cada réplica use seu próprio gerador, é necessário criar n geradores (copiando ou dividindo), em que n é o número de réplicas e depois passá-los como argumentos para Strategy.run .

 strat = tf.distribute.MirroredStrategy(devices=["cpu:0", "cpu:1"])
gs = tf.random.get_global_generator().split(2)
# to_args is a workaround for the absence of APIs to create arguments for 
# run. It will be replaced when such APIs are available.
def to_args(gs):  
  with strat.scope():
    def f():
      return [gs[tf.distribute.get_replica_context().replica_id_in_sync_group]]
    return strat.run(f)
args = to_args(gs)
def f(g):
  print(g.normal([]))
results = strat.run(f, args=args)
 
WARNING:tensorflow:There are non-GPU devices in `tf.distribute.Strategy`, not using nccl allreduce.
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:CPU:0', '/job:localhost/replica:0/task:0/device:CPU:1')
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
WARNING:tensorflow:Using MirroredStrategy eagerly has significant overhead currently. We will be working on improving this in the future, but for now please wrap `call_for_each_replica` or `experimental_run` or `run` inside a tf.function to get the best performance.
tf.Tensor(-0.15682742, shape=(), dtype=float32)
tf.Tensor(-0.38042808, shape=(), dtype=float32)

RNG sem estado

O uso de RNGs sem estado é simples. Como são apenas 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]))
 
tf.Tensor(
[[ 0.5441101   0.20738031  0.07356433]
 [ 0.04643455 -1.3015898  -0.95385665]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[ 0.5441101   0.20738031  0.07356433]
 [ 0.04643455 -1.3015898  -0.95385665]], shape=(2, 3), dtype=float32)

Todo RNG sem estado exige um argumento de seed , que precisa ser um Tensor inteiro de forma [2] . Os resultados da operação são totalmente determinados por esta semente.

Algoritmos

Geral

A classe tf.random.Generator e as funções stateless suportam o algoritmo Philox (escrito como "philox" ou tf.random.Algorithm.PHILOX ) em todos os dispositivos.

Dispositivos diferentes geram os mesmos números inteiros, se estiverem usando o mesmo algoritmo e iniciando no mesmo estado. Eles também geram "quase o mesmo" número de ponto flutuante, embora possa haver pequenas discrepâncias numéricas causadas pelas diferentes maneiras como os dispositivos executam o cálculo do ponto flutuante (por exemplo, ordem de redução).

Dispositivos XLA

Em dispositivos controlados por XLA (como TPU e também CPU / GPU quando o XLA está ativado), o algoritmo ThreeFry (escrito como "threefry" ou tf.random.Algorithm.THREEFRY ) também é suportado. Esse algoritmo é rápido em TPU, mas lento em CPU / GPU em comparação com Philox.

Consulte o artigo 'Números aleatórios paralelos: tão fáceis quanto 1, 2, 3' para obter mais detalhes sobre esses algoritmos.