¡Reserva! Google I / O regresa del 18 al 20 de mayo Regístrese ahora
Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Generación de números aleatorios

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

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 se 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 2 virtual devices cpu:0 and cpu:1 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()
    ])

La clase tf.random.Generator

La clase tf.random.Generator se usa en los casos en 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 sencillos, 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(
[[ 1.3604434  -0.3518851   3.5727046 ]
 [ 0.09712801 -1.2254813   1.346795  ]], 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 será utilizado por 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 el sistema operativo.

g = tf.random.Generator.from_non_deterministic_state()
print(g.normal(shape=[2, 3]))
tf.Tensor(
[[ 1.4167604  -2.1248727  -0.39414507]
 [-1.1975709   0.26199496 -0.31982416]], 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 utilice 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 tf.device("gpu") , el generador global se colocará en la GPU y el uso del 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, porque el antiguo generador global puede haber sido capturado por una función tf.function (como una referencia débil), y reemplazarlo hará que se recolecte basura, rompiendo la función 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 de generador.

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)

Crear flujos independientes de números aleatorios

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 estadísticamente detectables. Esto se logra usando Generator.split para crear múltiples generadores que están garantizados para 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 el normal . Además de ser independientes entre sí, los nuevos generadores ( new_gs ) también están garantizados como independientes del antiguo ( 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.59089047, shape=(), dtype=float32)

Puede dividir de forma recursiva, 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 de 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 llama a la función.

Creando generadores dentro de tf.function

La creación de generadores dentro de una función tf.function Solo puede tf.function 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)

Pasando generadores como argumentos a tf.function

Cuando se usa como argumento para una función tf.function , diferentes objetos generadores con el mismo tamaño de estado (el tamaño de estado está determinado por el algoritmo RNG) no causarán el tf.function de la función tf.function , mientras que aquellos con tamaños de estado diferentes lo harán.

num_traces = 0
@tf.function
def foo(g):
  global num_traces
  num_traces += 1
  return g.normal([])
foo(tf.random.Generator.from_seed(1))
foo(tf.random.Generator.from_seed(2))
print(num_traces)
1

Interacción con estrategias de distribución

Hay tres formas en las que Generator interactúa con las estrategias de distribución.

Creando generadores fuera de las estrategias de distribución

Si se crea un generador fuera de los ámbitos estratégicos, el acceso de todas las réplicas al generador se serializará y, por lo tanto, las réplicas obtendrán diferentes números aleatorios.

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.43842277, 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

No se permite la creación de generadores dentro de los ámbitos estratégicos, porque existe ambigüedad sobre cómo replicar un generador (por ejemplo, si se debe copiar para que cada réplica obtenga los mismos números aleatorios, o 'dividir' para que cada réplica obtenga diferentes números aleatorios)

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

Tenga en cuenta que Strategy.run ejecutará su función de argumento en un alcance de estrategia implícitamente:

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

Pasando generadores como argumentos a Strategy.run

Si desea que cada réplica use su propio generador, debe crear n generadores (ya sea copiando o dividiendo), donde n es el número de réplicas, y luego pasarlas como argumentos a Strategy.run .

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

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 de forma entero [2] . Los resultados de la operación están completamente determinados por esta semilla.

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), el algoritmo ThreeFry (escrito como "threefry" o tf.random.Algorithm.THREEFRY ) también es compatible. Este algoritmo es rápido en TPU pero lento en CPU / GPU en comparación con Philox.

Consulte el artículo 'Números aleatorios paralelos: tan fácil como 1, 2, 3' para obtener más detalles sobre estos algoritmos.