Geração de número aleatório

Veja no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

O TensorFlow fornece 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 esses geradores interagem com outros subsistemas do tensorflow.

O TensorFlow oferece 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.

Configurar

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ê 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 facilidades fornecidas pelo tf.Variable , como checkpoints fáceis, dependência de controle automático e segurança de thread.

Você pode obter um tf.random.Generator criando manualmente um objeto da classe ou chamando 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.43842277 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[-0.5496427   0.24263908 -1.1436267 ]
 [ 1.861458   -0.6756685  -0.9900103 ]], shape=(2, 3), dtype=float32)

Existem várias maneiras de criar um objeto gerador. O mais fácil é 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 recebe 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.43842277 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)

Consulte a seção Algoritmos abaixo para obter mais informações sobre isso.

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

g = tf.random.Generator.from_non_deterministic_state()
print(g.normal(shape=[2, 3]))
tf.Tensor(
[[-0.9078738   0.11009752  1.037219  ]
 [ 0.661036    0.4169741   1.4539026 ]], shape=(2, 3), dtype=float32)

Existem ainda outras maneiras de criar geradores, como a partir de estados explícitos, que não são abordados 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 (de um estado não determinístico) na primeira vez que tf.random.get_global_generator é chamado e colocado no dispositivo padrão nessa chamada. Assim, por exemplo, se o primeiro site que você chamar tf.random.get_global_generator estiver dentro de um tf.device("gpu") , o gerador global será colocado na GPU, e usar o gerador global posteriormente na CPU incorrer 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. Esta função deve ser usada com cautela, porém, porque o antigo gerador global pode ter sido capturado por um tf.function (como uma referência fraca), e substituí-lo fará com que ele seja coletado como lixo, quebrando 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 do 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.43842277, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(0.43842277, shape=(), dtype=float32)

Criando fluxos de números aleatórios independentes

Em muitas aplicações, são necessários vários fluxos de números aleatórios independentes, independentes no sentido de que eles não se sobreponham e não tenham correlações estatisticamente detectáveis. Isso é obtido usando Generator.split para criar vários geradores que são garantidos como independentes uns dos outros (ou seja, 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.43842277, 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 normal . Além de serem independentes uns dos outros, os novos geradores ( new_gs ) também têm a garantia de serem 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(0.4142675, shape=(), dtype=float32)

Você pode fazer a divisão recursivamente, chamando split em geradores divididos. Não há limites (exceto estouro de número inteiro) 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 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.43842277, shape=(), dtype=float32)

O usuário precisa ter certeza de que o objeto gerador ainda está ativo (não coletado como lixo) quando a função é chamada.

Criando geradores dentro tf.function

A criação de geradores dentro de um tf.function só pode acontecer 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())
tf.Tensor(0.43842277, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)

Passando geradores como argumentos para tf.function

Quando usado como um argumento para um tf.function , diferentes objetos geradores causarão o retraçamento do 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)
2

Observe que esse comportamento de retração é 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)
2

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

Há duas maneiras pelas quais o Generator interage com as estratégias de distribuição.

Criação de 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

Se um gerador for criado dentro de um escopo de estratégia, cada réplica receberá um fluxo 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([])))
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.
PerReplica:{
  0: tf.Tensor(-0.87930447, shape=(), dtype=float32),
  1: tf.Tensor(0.020661574, shape=(), dtype=float32)
}
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.
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}

Se o gerador for propagado (por exemplo, criado por Generator.from_seed ), os números aleatórios são determinados pela semente, mesmo que diferentes réplicas obtenham números diferentes e não correlacionados. Pode-se pensar em um número aleatório gerado em uma réplica como um hash do ID da réplica e um número aleatório "primário" que é comum a todas as réplicas. Portanto, todo o sistema ainda é determinístico.

tf.random.Generator também pode ser criado dentro do 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))
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.
PerReplica:{
  0: tf.Tensor([-0.87930447 -1.5822568 ], shape=(2,), dtype=float32),
  1: tf.Tensor([0.02066157 0.77539235], shape=(2,), dtype=float32)
}
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.
PerReplica:{
  0: tf.Tensor([-0.87930447 -1.5822568 ], shape=(2,), dtype=float32),
  1: tf.Tensor([0.02066157 0.77539235], shape=(2,), dtype=float32)
}

Não recomendamos mais passar tf.random.Generator como argumentos para Strategy.run , porque Strategy.run geralmente espera que os argumentos sejam tensores, não geradores.

Geradores de economia

Geralmente, para salvar ou serializar, você pode manipular um tf.random.Generator da mesma forma que lidaria com um tf.Variable ou um tf.Module (ou suas subclasses). No TF existem dois mecanismos para serialização: Checkpoint e SavedModel .

Ponto de verificação

Os geradores podem ser salvos e restaurados livremente usando tf.train.Checkpoint . O fluxo de número aleatório do ponto de restauração será o mesmo do ponto de salvamento.

filename = "./checkpoint"
g = tf.random.Generator.from_seed(1)
cp = tf.train.Checkpoint(generator=g)
print(g.normal([]))
tf.Tensor(0.43842277, shape=(), dtype=float32)
cp.write(filename)
print("RNG stream from saving point:")
print(g.normal([]))
print(g.normal([]))
RNG stream from saving point:
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(1.6307176, shape=(), dtype=float32)
cp.restore(filename)
print("RNG stream from restoring point:")
print(g.normal([]))
print(g.normal([]))
RNG stream from restoring point:
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(1.6307176, shape=(), dtype=float32)

Você também pode salvar e restaurar dentro de 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([])))
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')
PerReplica:{
  0: tf.Tensor(-0.87930447, shape=(), dtype=float32),
  1: tf.Tensor(0.020661574, shape=(), dtype=float32)
}
with strat.scope():
  cp.write(filename)
  print("RNG stream from saving point:")
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
RNG stream from saving point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32)
}
with strat.scope():
  cp.restore(filename)
  print("RNG stream from restoring point:")
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
RNG stream from restoring point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32)
}

Você deve certificar-se de que as réplicas não divergem em seu histórico de chamadas RNG (por exemplo, uma réplica faz uma chamada RNG enquanto outra faz duas chamadas RNG) antes de salvar. Caso contrário, seus estados internos de RNG irão divergir e tf.train.Checkpoint (que salva apenas o estado da primeira réplica) não irá restaurar corretamente todas as réplicas.

Você também pode restaurar um ponto de verificação salvo para uma estratégia de distribuição diferente com um número diferente de réplicas. Como um objeto tf.random.Generator criado em uma estratégia só pode ser usado na mesma estratégia, para restaurar para uma estratégia diferente, você deve criar um novo tf.random.Generator na estratégia de destino e um novo tf.train.Checkpoint para ele, 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([])))
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')
PerReplica:{
  0: tf.Tensor(-0.87930447, shape=(), dtype=float32),
  1: tf.Tensor(0.020661574, shape=(), dtype=float32)
}
with strat1.scope():
  cp1.write(filename)
  print("RNG stream from saving point:")
  print(strat1.run(lambda: g1.normal([])))
  print(strat1.run(lambda: g1.normal([])))
RNG stream from saving point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32)
}
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([])))
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', '/job:localhost/replica:0/task:0/device:CPU:2')
RNG stream from restoring point:
PerReplica:{
  0: tf.Tensor(-1.5822568, shape=(), dtype=float32),
  1: tf.Tensor(0.77539235, shape=(), dtype=float32),
  2: tf.Tensor(0.6851049, shape=(), dtype=float32)
}
PerReplica:{
  0: tf.Tensor(-0.5039703, shape=(), dtype=float32),
  1: tf.Tensor(0.1251838, shape=(), dtype=float32),
  2: tf.Tensor(-0.58519536, shape=(), dtype=float32)
}

Embora g1 e cp1 sejam objetos diferentes de g2 e cp2 , eles são vinculados por meio do arquivo de ponto de verificação comum nome do filename e nome do objeto my_generator . As réplicas sobrepostas entre as estratégias (por exemplo, cpu:0 e cpu:1 acima) terão seus fluxos RNG devidamente restaurados como nos exemplos anteriores. Essa garantia não cobre o caso em que um gerador é salvo em um escopo de estratégia e restaurado fora de qualquer escopo de estratégia ou vice-versa, pois um dispositivo fora de estratégias é tratado como diferente de qualquer réplica em uma estratégia.

Modelo salvo

tf.random.Generator pode ser salvo em um SavedModel. O gerador pode ser criado dentro de um escopo de estratégia. A economia também pode acontecer dentro de 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())
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')
PerReplica:{
  0: tf.Tensor(-1.4154755, shape=(), dtype=float32),
  1: tf.Tensor(-0.113884404, shape=(), dtype=float32)
}
state: tf.Tensor([256   0   0], shape=(3,), dtype=int64)
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())
INFO:tensorflow:Assets written to: ./saved_model/assets
RNG stream from saving point:
PerReplica:{
  0: tf.Tensor(-0.68758255, shape=(), dtype=float32),
  1: tf.Tensor(0.8084062, shape=(), dtype=float32)
}
state: tf.Tensor([512   0   0], shape=(3,), dtype=int64)
PerReplica:{
  0: tf.Tensor(-0.27342677, shape=(), dtype=float32),
  1: tf.Tensor(-0.53093255, shape=(), dtype=float32)
}
state: tf.Tensor([768   0   0], shape=(3,), dtype=int64)
2021-09-22 20:45:46.222281: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
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())
RNG stream from loading point:
state: tf.Tensor([256   0   0], shape=(3,), dtype=int64)
tf.Tensor(-1.0359411, shape=(), dtype=float32)
state: tf.Tensor([512   0   0], shape=(3,), dtype=int64)
tf.Tensor(-0.06425078, shape=(), dtype=float32)
state: tf.Tensor([768   0   0], shape=(3,), dtype=int64)

Carregar um SavedModel contendo tf.random.Generator em uma estratégia de distribuição não é recomendado porque todas as réplicas gerarão o mesmo fluxo de número aleatório (o que ocorre porque o ID da réplica está congelado no gráfico de SavedModel).

Carregar um tf.random.Generator distribuído (um gerador criado dentro de uma estratégia de distribuição) em um ambiente não estratégico, como o exemplo acima, também tem uma ressalva. O estado RNG será devidamente restaurado, mas os números aleatórios gerados serão diferentes do gerador original em sua estratégia (novamente porque um dispositivo fora das estratégias é tratado como diferente de qualquer réplica em uma estratégia).

RNGs sem estado

O uso de RNGs sem estado é simples. Uma vez que 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.30159    -0.95385665]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[ 0.5441101   0.20738031  0.07356433]
 [ 0.04643455 -1.30159    -0.95385665]], shape=(2, 3), dtype=float32)

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

O algoritmo RNG usado por RNGs sem estado é dependente do dispositivo, o que significa que a mesma operação em execução em um dispositivo diferente pode produzir saídas diferentes.

Algoritmos

Em geral

Tanto a classe tf.random.Generator quanto as funções stateless suportam 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 irão gerar "quase os mesmos" números de ponto flutuante, embora possa haver pequenas discrepâncias numéricas causadas pelas diferentes maneiras pelas quais os dispositivos realizam o cálculo de ponto flutuante (por exemplo, ordem de redução).

Dispositivos XLA

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

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