Ver en TensorFlow.org | Ejecutar en Google Colab | Ver fuente en GitHub | Descargar libreta |
TensorFlow proporciona un conjunto de generadores de números pseudoaleatorios (RNG), en el módulo tf.random
. Este documento describe cómo puede controlar los generadores de números aleatorios y cómo estos generadores interactúan con otros subsistemas de tensorflow.
TensorFlow proporciona dos enfoques para controlar el proceso de generación de números aleatorios:
Mediante el uso explícito de objetos
tf.random.Generator
. Cada uno de estos objetos mantiene un estado (entf.Variable
) que cambiará después de cada generación de números.A través de las funciones aleatorias sin estado puramente funcionales como
tf.random.stateless_uniform
. Llamar a estas funciones con los mismos argumentos (que incluyen la semilla) y en el mismo dispositivo siempre producirá los mismos resultados.
Configuración
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()
])
La clase tf.random.Generator
La clase tf.random.Generator
se usa en casos en los que desea que cada llamada RNG produzca resultados diferentes. Mantiene un estado interno (gestionado por un objeto tf.Variable
) que se actualizará cada vez que se generen números aleatorios. Debido a que el estado es administrado por tf.Variable
, disfruta de todas las facilidades proporcionadas por tf.Variable
, como puntos de control fáciles, dependencia de control automático y seguridad de subprocesos.
Puede obtener un tf.random.Generator
creando manualmente un objeto de la clase o llamando a tf.random.get_global_generator()
para obtener el generador global predeterminado:
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)
Hay varias formas de crear un objeto generador. El más fácil es Generator.from_seed
, como se muestra arriba, que crea un generador a partir de una semilla. Una semilla es cualquier número entero no negativo. from_seed
también toma un argumento opcional alg
que es el algoritmo RNG que utilizará este generador:
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 la sección Algoritmos a continuación para obtener más información al respecto.
Otra forma de crear un generador es con Generator.from_non_deterministic_state
. Un generador creado de esta manera comenzará desde un estado no determinista, dependiendo, por ejemplo, del tiempo y del sistema operativo.
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)
Hay otras formas de crear generadores, como a partir de estados explícitos, que no se tratan en esta guía.
Cuando use tf.random.get_global_generator
para obtener el generador global, debe tener cuidado con la ubicación del dispositivo. El generador global se crea (a partir de un estado no determinista) la primera vez que se llama a tf.random.get_global_generator
y se coloca en el dispositivo predeterminado en esa llamada. Entonces, por ejemplo, si el primer sitio al que llama tf.random.get_global_generator
está dentro del alcance de tf.device("gpu")
, el generador global se colocará en la GPU, y usar el generador global más adelante desde la CPU incurrir en una copia de GPU a CPU.
También hay una función tf.random.set_global_generator
para reemplazar el generador global con otro objeto generador. Sin embargo, esta función debe usarse con precaución, ya que el antiguo generador global puede haber sido capturado por una tf.function
(como una referencia débil), y reemplazarlo hará que se recolecte basura, rompiendo la tf.function
. Una mejor manera de restablecer el generador global es usar una de las funciones de "restablecimiento" como Generator.reset_from_seed
, que no creará nuevos objetos generadores.
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)
Creación de flujos de números aleatorios independientes
En muchas aplicaciones, se necesitan múltiples flujos de números aleatorios independientes, independientes en el sentido de que no se superpondrán y no tendrán correlaciones detectables estadísticamente. Esto se logra mediante el uso de Generator.split
para crear múltiples generadores que garantizan ser independientes entre sí (es decir, generar flujos independientes).
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
cambiará el estado del generador en el que se llama ( g
en el ejemplo anterior), similar a un método RNG como normal
. Además de ser independientes entre sí, también se garantiza que los nuevos generadores ( new_gs
) sean independientes del anterior ( g
).
La generación de nuevos generadores también es útil cuando desea asegurarse de que el generador que usa esté en el mismo dispositivo que otros cálculos, para evitar la sobrecarga de la copia entre dispositivos. Por ejemplo:
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)
Puede dividir recursivamente, llamando a split
en generadores divididos. No hay límites (salvo el desbordamiento de enteros) en la profundidad de las recursiones.
Interacción con tf.function
tf.random.Generator
obedece las mismas reglas que tf.Variable
cuando se usa con tf.function
. Esto incluye tres aspectos.
Creando generadores fuera tf.function
tf.function
puede usar un generador creado fuera de él.
g = tf.random.Generator.from_seed(1)
@tf.function
def foo():
return g.normal([])
print(foo())
tf.Tensor(0.43842277, shape=(), dtype=float32)
El usuario debe asegurarse de que el objeto generador todavía esté vivo (no recolectado como basura) cuando se llame a la función.
Creando generadores dentro tf.function
La creación de generadores dentro de una tf.function
solo puede ocurrir durante la primera ejecución de la función.
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)
Pasar generadores como argumentos a tf.function
Cuando se usa como argumento para una tf.function
, diferentes objetos generadores provocarán el retroceso de la 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
Tenga en cuenta que este comportamiento de retroceso es coherente con 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
Interacción con las estrategias de distribución
Hay dos formas en que Generator
interactúa con las estrategias de distribución.
Creación de generadores fuera de las estrategias de distribución
Si se crea un generador fuera del alcance de la estrategia, el acceso de todas las réplicas al generador se serializará y, por lo tanto, las réplicas obtendrán números aleatorios 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)
Tenga en cuenta que este uso puede tener problemas de rendimiento porque el dispositivo del generador es diferente de las réplicas.
Creando generadores dentro de las estrategias de distribución
Si se crea un generador dentro del alcance de una estrategia, cada réplica obtendrá un flujo diferente e independiente de números aleatorios.
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) }
Si el generador está sembrado (por ejemplo, creado por Generator.from_seed
), los números aleatorios están determinados por la semilla, aunque diferentes réplicas obtengan números diferentes y no correlacionados. Uno puede pensar en un número aleatorio generado en una réplica como un hash de la ID de la réplica y un número aleatorio "primario" que es común a todas las réplicas. Por lo tanto, todo el sistema sigue siendo determinista.
tf.random.Generator
también se puede crear dentro 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) }
Ya no recomendamos pasar tf.random.Generator
como argumentos a Strategy.run
, porque Strategy.run
generalmente espera que los argumentos sean tensores, no generadores.
Generadores de ahorro
Generalmente, para guardar o serializar, puede manejar un tf.random.Generator
de la misma manera que manejaría un tf.Variable
o un tf.Module
(o sus subclases). En TF hay dos mecanismos para la serialización: Checkpoint y SavedModel .
Control
Los generadores se pueden guardar y restaurar libremente usando tf.train.Checkpoint
. El flujo de números aleatorios del punto de restauración será el mismo que el del punto de guardado.
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)
También puede guardar y restaurar dentro de una estrategia de distribución:
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) }
Debe asegurarse de que las réplicas no diverjan en su historial de llamadas RNG (por ejemplo, una réplica hace una llamada RNG mientras que otra hace dos llamadas RNG) antes de guardar. De lo contrario, sus estados RNG internos divergirán y tf.train.Checkpoint
(que solo guarda el estado de la primera réplica) no restaurará correctamente todas las réplicas.
También puede restaurar un punto de control guardado a una estrategia de distribución diferente con una cantidad diferente de réplicas. Debido a que un objeto tf.random.Generator
creado en una estrategia solo se puede usar en la misma estrategia, para restaurar a una estrategia diferente, debe crear un nuevo tf.random.Generator
en la estrategia de destino y un nuevo tf.train.Checkpoint
Punto de tf.train.Checkpoint
para ello, como se muestra en este ejemplo:
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) }
Aunque g1
y cp1
son objetos diferentes de g2
y cp2
, están vinculados a través del nombre de archivo de filename
de punto de control común y el nombre de objeto my_generator
. Las réplicas superpuestas entre estrategias (por ejemplo, cpu:0
y cpu:1
arriba) tendrán sus flujos de RNG correctamente restaurados como en los ejemplos anteriores. Esta garantía no cubre el caso en que un generador se guarde en el ámbito de una estrategia y se restaure fuera de cualquier ámbito de estrategia o viceversa, porque un dispositivo fuera de las estrategias se trata como diferente de cualquier réplica en una estrategia.
Modelo guardado
tf.random.Generator
se puede guardar en un modelo guardado. El generador se puede crear dentro de un ámbito de estrategia. El ahorro también puede ocurrir dentro del alcance de una estrategia.
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)
No se recomienda cargar un modelo guardado que contenga tf.random.Generator
en una estrategia de distribución porque todas las réplicas generarán el mismo flujo de números aleatorios (que se debe a que el ID de la réplica está congelado en el gráfico del modelo guardado).
Cargar un tf.random.Generator
distribuido (un generador creado dentro de una estrategia de distribución) en un entorno sin estrategia, como el ejemplo anterior, también tiene una advertencia. El estado RNG se restaurará correctamente, pero los números aleatorios generados serán diferentes del generador original en su estrategia (nuevamente porque un dispositivo fuera de las estrategias se trata como diferente de cualquier réplica en una estrategia).
RNG sin estado
El uso de RNG sin estado es simple. Dado que son solo funciones puras, no hay ningún estado o efecto secundario involucrado.
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)
Cada RNG sin estado requiere un argumento seed
, que debe ser un tensor entero de forma [2]
. Los resultados de la operación están completamente determinados por esta semilla.
El algoritmo RNG utilizado por los RNG sin estado depende del dispositivo, lo que significa que la misma operación que se ejecuta en un dispositivo diferente puede producir diferentes resultados.
Algoritmos
General
Tanto la clase tf.random.Generator
como las funciones stateless
admiten el algoritmo Philox (escrito como "philox"
o tf.random.Algorithm.PHILOX
) en todos los dispositivos.
Diferentes dispositivos generarán los mismos números enteros, si usan el mismo algoritmo y comienzan desde el mismo estado. También generarán "casi los mismos" números de punto flotante, aunque puede haber pequeñas discrepancias numéricas causadas por las diferentes formas en que los dispositivos realizan el cálculo del punto flotante (por ejemplo, orden de reducción).
dispositivos XLA
En dispositivos controlados por XLA (como TPU y también CPU/GPU cuando XLA está habilitado), también se admite el algoritmo ThreeFry (escrito como "threefry"
o tf.random.Algorithm.THREEFRY
). Este algoritmo es rápido en TPU pero lento en CPU/GPU en comparación con Philox.
Consulte el documento 'Números aleatorios paralelos: tan fácil como 1, 2, 3' para obtener más detalles sobre estos algoritmos.