Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/guide/random_numbers.ipynb
25115 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Generación de números aleatorios

TensorFlow proporciona un conjunto de seudogeneradores de números aleatorios (RNG), en el módulo tf.random. En este documento se describe cómo es posible controlar los generadores de números aleatorios y cómo esos generadores interactúan con otros subsistemas de TensorFlow.

Nota: No es posible garantizar que los números aleatorios serán consistentes entre todas las versiones de TensorFlow. Vea: Compatibilidad de las versiones

TensorFlow brinda dos opciones 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 tales objetos mantiene un estado (en tf.Variable) que se cambiará después de cada generación de número.

  2. A través de funciones aleatorias fuera de su estado puramente funcional, como tf.random.stateless_uniform. Si se llama a estas funciones con los mismos argumentos (que incluyen la semilla) y en el mismo dispositivo, siempre producirán los mismos resultados.

Advertencia: Los RNG anteriores de TF 1.x como tf.random.uniform y tf.random.normal todavía no quedaron obsoletos, pero sí se desalienta enfáticamente su uso.

Preparació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 aquellos casos en los que se quiere que cada llamada de RNG produzca resultados diferentes. Mantiene un estado interno (gestionado por un objeto tf.Variable) que se actualizará cada vez que se generen los números aleatorios. Como el estado está gestionado por tf.Variable, goza de todas las facilidades provistas por tf.Variable, como la determinación sencilla de puntos de verificación y la seguridad.

Es posible obtener un tf.random.Generator si crea manualmente un objeto de clase o una llamada 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]))

Hay muchas maneras de crear un objeto generador. La más sencilla es con Generator.from_seed, tal como se muestra arriba, que crea un generador a partir de una semilla. Una semilla es un integrador no generativo. from_seed también toma un argumento opcional alg que es el algoritmo de RNG que usará este generador:

g1 = tf.random.Generator.from_seed(1, alg='philox') print(g1.normal(shape=[2, 3]))

Para más información al respecto, vea la sección sobre algoritmos que se encuentra más adelante.

Otra forma de crear un generador es con Generator.from_non_deterministic_state. Un generador que se cree de este modo partirá de un estado no determinista, dependiendo de, p.ej., el momento y el sistema operativo.

g = tf.random.Generator.from_non_deterministic_state() print(g.normal(shape=[2, 3]))

Aún hay otras formas de crear generadores, como por ejemplo, a partir de estados explícitos, pero no se incluyen en esta guía.

Cuando use tf.random.get_global_generator para obtener el generador global, deberá prestar atención a 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 de esa llamada. Entonces, por ejemplo, si el primer sitio que se llama con tf.random.get_global_generator se encuentra dentro del alcance de tf.device("gpu"), el generador global se colocará en la GPU, y al usar dicho generador, más adelante desde la CPU, se producirá una copia de GPU a CPU.

También hay una función tf.random.set_global_generator para reemplazar el generador global por otro objeto generador. Sin embargo, esta función debería usarse con cautela, ya que el generador global anterior puede haber sido capturado por una tf.function (como una referencia débil) y reemplazarlo haría que fuera recolectado como basura, lo que rompería la tf.function. Hay una mejor manera de restablecer el generador global y es mediante una de las funciones de "reset" (restablecer) como Generator.reset_from_seed, que no creará objetos generadores nuevos.

g = tf.random.Generator.from_seed(1) print(g.normal([])) print(g.normal([])) g.reset_from_seed(1) print(g.normal([]))

Creación de secuencias (streams) de números aleatorios independientes

Para muchas aplicaciones, uno necesita múltiples streams de números aleatorios independientes. Independientes en el sentido de que no se superpondrán y de que no tendrán ninguna correlación detectable estadísticamente. Todo esto se logra con Generator.split para crear múltiples generadores que sean, con seguridad, independientes entre sí (es decir, que generen streams 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([]))

split cambiará el estado del generador en el que se lo llame (g, en el ejemplo anterior). De un modo similar a lo que sucede con un método de RNG como normal. Además de ser independiente entre sí, los generadores nuevos (new_gs) también tienen la garantía de ser independientes del anterior (g).

La generación (spawning) de generadores nuevos también es útil para cuando desee asegurarse de que el generador que utiliza se encuentra en el mismo dispositivo que otros cálculos, con el objetivo de evitar el sobrecoste de copias en dispositivos cruzados. 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

Nota: En teoría, en este caso, se pueden usar constructores como, por ejemplo, from_seed en vez de split para obtener un generador nuevo, pero al hacerlo se pierde la seguridad de que el generador nuevo será independiente del generador global. También se corre el riesgo de crear accidentalmente dos generadores con la misma semilla o con semillas que conduzcan a la superposición de streams de números aleatorios.

La división se puede hacer recursivamente, invocando split en los generadores de división. No hay límites (excepto por el sobreflujo de enteros) para la profundidad de las recursiones.

Interacción con tf.function

tf.random.Generator obedece a las mismas reglas que tf.Variable cuando se usa con tf.function. Esto incluye tres aspectos.

La creación de generadores fuera de tf.function

tf.function puede usar un generador creado fuera de ella.

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

El usuario debe asegurarse de que el objeto generador aún esté vivo (no recolectado como basura) cuando se llame a la función.

La creación de generadores dentro de tf.function

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

Los generadores de pase como argumentos a tf.function

Cuando se usan como un argumento para una tf.function, los diferentes objetos generadores provocan un nuevo rastreo (retracing) 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)

Tenga en cuenta que este comportamiento es consistente 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)

Interacción con estrategias de distribución

Hay dos maneras en las que interacciona Generator con las estrategias de distribución.

La creación de generadores fuera de las estrategias de distribución

Si un generador se crea fuera de los alcances estratégicos, todos los accesos de las réplicas al generador se serializarán y, entonces, 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)

Tenga en cuenta que pueden haber surgido problemas de desempeño con este uso, ya que el dispositivo del generador es diferente al de las réplicas.

La creación de generadores dentro de las estrategias de distribución

Si un generador se crea dentro del alcance de una estrategia, cada réplica obtendrá una stream de números aleatorios independiente y diferente.

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([])))

Nota: Actualmente, tf.random.Generator no ofrece una opción para permitir que diferentes réplicas obtengan streams idénticas (en vez de diferentes); lo cual resulta, técnicamente, no tan complicado. Si tiene un caso de uso para esta función, por favor, hágaselo saber a los desarrolladores de TensorFlow.

Si el generador tiene semilla (p. ej., creado por Generator.from_seed), los números aleatorios estarán determinados por la semilla, a pesar de que con réplicas diferentes se obtienen distintos números no correlacionados. Uno podría pensar en un número aleatorio generado de una réplica como una función hash del ID de la réplica y un número aleatorio "primario" que es común a todas las réplicas. Por lo tanto, el sistema completo sigue siendo determinista.

tf.random.Generator también se pueden crear dentro de 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))

Ya no recomendamos pasar tf.random.Generator como argumentos a Strategy.run, porque, por lo general, Strategy.run espera que los argumentos sean tensores, no generadores.

Guardado de generadores

Por lo general, para guardar o serializar, un tf.random.Generator se puede manejar del mismo modo que una tf.Variable o un tf.Module (o sus subclases). En TF hay dos mecanismos para la serialización: mediante Puntos de verificación (checkpoint) o con SavedModel.

Punto de verificación (checkpoint)

Los generadores se pueden guardar y restaurar sin problemas con tf.train.Checkpoint. La stream de números aleatorios a partir del número restaurado será igual al del punto de guardado.

filename = "./checkpoint" g = tf.random.Generator.from_seed(1) cp = tf.train.Checkpoint(generator=g) print(g.normal([]))
cp.write(filename) print("RNG stream from saving point:") print(g.normal([])) print(g.normal([]))
cp.restore(filename) print("RNG stream from restoring point:") print(g.normal([])) print(g.normal([]))

También es posible 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([])))
with strat.scope(): cp.write(filename) print("RNG stream from saving point:") print(strat.run(lambda: g.normal([]))) print(strat.run(lambda: g.normal([])))
with strat.scope(): cp.restore(filename) print("RNG stream from restoring point:") print(strat.run(lambda: g.normal([]))) print(strat.run(lambda: g.normal([])))

Deberá controlar que las réplicas no se aparten en sus historias de llamadas de RNG (p. ej., una réplica hace una llamada de RNG mientras otra hace dos) antes de guardar. De lo contrario, sus estados RNG internos divergirán y tf.train.Checkpoint (que solamente guarda el primer estado de réplica) no restaurará las réplicas como corresponde.

También se puede restaurar un punto de verificación (checkpoint) guardado en una estrategia de distribución diferente con un número de réplicas distinto. Dado que un objeto tf.random.Generator creado en una estrategia solamente se puede usar en la misma estrategia, para restaurarlo en una estrategia diferente, deberá crear un tf.random.Generator en la estrategia de destino y un tf.train.Checkpoint nuevo, tal como se muestra en el 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([])))
with strat1.scope(): cp1.write(filename) print("RNG stream from saving point:") print(strat1.run(lambda: g1.normal([]))) print(strat1.run(lambda: g1.normal([])))
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([])))

A pesar de que g1 y cp1 son objetos diferentes de g2 y cp2, están vinculados a través de un archivo de punto de verificación filename y un nombre de objeto my_generator en común. La superposición de las réplicas entre estrategias (p. ej., las cpu:0 y cpu:1 anteriores) tendrán sus streams de generación de números aleatorios debidamente restaurados, tal como en los ejemplos anteriores. Esta garantía no cubre el caso en que un generador se guarda en un alcance de estrategia y se restablece por fuera del alcance de estrategia o viceversa; debido a que un dispositivo fuera de estrategias se trata de manera diferente al de cualquier réplica dentro de una estrategia.

SavedModel

tf.random.Generator se puede guardar en un SavedModel. El generador se puede crear dentro del ámbito de una estrategia. El guardado también se puede producir dentro del ámbito 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())
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())
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())

No se recomienda cargar un SavedModel que contenga tf.random.Generator dentro de una estrategia de distribución, porque todas las réplicas generarán la misma stream de números aleatorios (esto sucede porque el ID de réplica se congela en el grafo del SavedModel).

También hay una salvedad, la carga de un tf.random.Generator distribuido (un generador creado dentro de una estrategia de distribución) dentro de un entorno sin estrategias, tal como el del ejemplo anterior. El estado RNG deberá ser apropiadamente restablecido, pero los números aleatorios generados serán distintos del generador original en su propia estrategia (nuevamente, porque un dispositivo fuera de estrategias se trata diferente de cualquier réplica dentro de una estrategia).

RNG sin estado

El uso de los RNG sin estado es simple. Como son solo funciones simples, no hay estado ni efecto colateral involucrado.

print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2])) print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))

Cada RNG sin estado requiere de un argumento seed, que necesita ser run Tensor entero de forma [2]. Los resultados de la operación están totalmente determinados por esta semilla.

El algoritmo de RNG usado por las RNG sin estado es dependiente del dispositivo; es decir, la misma operación ejecutada en un dispositivo diferente puede producir distintos resultados.

Algoritmos

General

Tanto la función de clase tf.random.Generator como stateless son compatibles con el algoritmos Philox (escrito como "philox" o tf.random.Algorithm.PHILOX) en todos los dispositivos.

Los distintos dispositivos generarán los mismos números enteros si se usa el mismo algoritmo y se empieza a partir del mismo estado. También generarán "casi los mismos" números de puntos flotantes, a pesar de que puede haber algunas discrepancias numéricas menores causadas por las diferentes maneras en que los dispositivos llevan a cabo el cálculo de punto flotante (p. ej., el orden de reducción).

Dispositivos con XLA

En los dispositivos con XLA (como las TPU y también las CPU o GPU cuando XLA está activado) el algoritmo ThreeFry (escrito como "threefry" o como tf.random.Algorithm.THREEFRY) también se admite. Este algoritmo es rápido en TPU pero lento en CPU o GPU si se lo compara con Philox.

Para más detalles sobre estos algoritmos, consulte la publicación 'Parallel Random Numbers: As Easy as 1, 2, 3' (Números aleatorios paralelos: tan sencillo como contar hasta 3).