Generación de números aleatorios

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:

  1. Mediante el uso explícito de objetos tf.random.Generator . Cada uno de estos objetos mantiene un estado (en tf.Variable ) que cambiará después de cada generación de números.

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