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:
Através do uso explícito de objetos
tf.random.Generator
. Cada um desses objetos mantém um estado (emtf.Variable
) que será alterado após cada geração de número.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.