Cette page a été traduite par l'API Cloud Translation.
Switch to English

Génération de nombres aléatoires

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher la source sur GitHub Télécharger le cahier

TensorFlow fournit un ensemble de générateurs de nombres pseudo-aléatoires (RNG), dans le module tf.random . Ce document décrit comment vous pouvez contrôler les générateurs de nombres aléatoires, et comment ces générateurs interagissent avec d'autres sous-systèmes tensorflow.

TensorFlow propose deux approches pour contrôler le processus de génération de nombres aléatoires:

  1. Grâce à l'utilisation explicite d'objets tf.random.Generator . Chacun de ces objets conserve un état (en tf.Variable ) qui sera modifié après chaque génération de nombre.

  2. Grâce aux fonctions aléatoires sans état purement fonctionnelles telles que tf.random.stateless_uniform . L'appel de ces fonctions avec les mêmes arguments (qui incluent la graine) et sur le même appareil produira toujours les mêmes résultats.

Installer

 import tensorflow as tf

# Creates 2 virtual devices cpu:0 and cpu:1 for using distribution strategy
physical_devices = tf.config.experimental.list_physical_devices("CPU")
tf.config.experimental.set_virtual_device_configuration(
    physical_devices[0], [
        tf.config.experimental.VirtualDeviceConfiguration(),
        tf.config.experimental.VirtualDeviceConfiguration()
    ])
 

La classe tf.random.Generator

La classe tf.random.Generator est utilisée dans les cas où vous souhaitez que chaque appel RNG produise des résultats différents. Il maintient un état interne (géré par un objet tf.Variable ) qui sera mis à jour à chaque fois que des nombres aléatoires sont générés. Étant donné que l'état est géré par tf.Variable , il bénéficie de toutes les fonctionnalités fournies par tf.Variable telles que le point de contrôle facile, la dépendance de contrôle automatique et la sécurité des threads.

Vous pouvez obtenir un tf.random.Generator en créant manuellement un objet de la classe ou en appelant tf.random.get_global_generator() pour obtenir le générateur global par défaut:

 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.43842274 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[-0.6078218  3.162639  -1.0558378]
 [ 1.2078347  0.6414574  0.4019502]], shape=(2, 3), dtype=float32)

Il existe plusieurs façons de créer un objet générateur. Le plus simple est Generator.from_seed , comme indiqué ci-dessus, qui crée un générateur à partir d'une graine. Une graine est tout entier non négatif. from_seed prend également un argument optionnel alg qui est l'algorithme RNG qui sera utilisé par ce générateur:

 g1 = tf.random.Generator.from_seed(1, alg='philox')
print(g1.normal(shape=[2, 3]))
 
tf.Tensor(
[[ 0.43842274 -0.53439844 -0.07710262]
 [ 1.5658046  -0.1012345  -0.2744976 ]], shape=(2, 3), dtype=float32)

Voir la section Algorithmes ci-dessous pour plus d'informations à ce sujet.

Une autre façon de créer un générateur est d' Generator.from_non_deterministic_state . Un générateur créé de cette manière démarrera à partir d'un état non déterministe, dépendant par exemple du temps et du système d'exploitation.

 g = tf.random.Generator.from_non_deterministic_state()
print(g.normal(shape=[2, 3]))
 
tf.Tensor(
[[ 1.1436943  1.729618   1.0391121]
 [-0.8502223 -1.8823647 -1.4051851]], shape=(2, 3), dtype=float32)

Il existe encore d'autres façons de créer des générateurs, comme à partir d'états explicites, qui ne sont pas couvertes par ce guide.

Lorsque vous utilisez tf.random.get_global_generator pour obtenir le générateur global, vous devez faire attention au placement des périphériques. Le générateur global est créé (à partir d'un état non déterministe) au premier tf.random.get_global_generator , et placé sur le périphérique par défaut lors de cet appel. Ainsi, par exemple, si le premier site que vous appelez tf.random.get_global_generator est dans une tf.device("gpu") , le générateur global sera placé sur le GPU, et l'utilisation du générateur global ultérieurement à partir du CPU sera encourent une copie GPU vers CPU.

Il existe également une fonction tf.random.set_global_generator pour remplacer le générateur global par un autre objet générateur. Cette fonction doit être utilisée avec précaution, car l'ancien générateur global peut avoir été capturé par une fonction tf.function (en tant que référence faible), et le remplacer entraînera un ramassage des ordures, cassant la fonction tf.function . Une meilleure façon de réinitialiser le générateur global est d'utiliser l'une des fonctions "reset" telles que Generator.reset_from_seed , qui ne créera pas de nouveaux objets générateurs.

 g = tf.random.Generator.from_seed(1)
print(g.normal([]))
print(g.normal([]))
g.reset_from_seed(1)
print(g.normal([]))
 
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)
tf.Tensor(0.43842274, shape=(), dtype=float32)

Création de flux de nombres aléatoires indépendants

Dans de nombreuses applications, il faut plusieurs flux de nombres aléatoires indépendants, indépendants dans le sens où ils ne se chevaucheront pas et n'auront pas de corrélations statistiquement détectables. Ceci est réalisé en utilisant Generator.split pour créer plusieurs générateurs qui sont garantis indépendants les uns des autres (c'est-à-dire générant des flux indépendants).

 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.43842274, 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 changera l'état du générateur sur lequel il est appelé ( g dans l'exemple ci-dessus), similaire à une méthode RNG telle que normal . En plus d'être indépendants les uns des autres, les nouveaux générateurs ( new_gs ) sont également garantis indépendants de l'ancien ( g ).

La création de nouveaux générateurs est également utile lorsque vous voulez vous assurer que le générateur que vous utilisez est sur le même périphérique que les autres calculs, pour éviter la surcharge de la copie inter-périphérique. Par exemple:

 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(-1.7580209, shape=(), dtype=float32)

Vous pouvez effectuer un fractionnement de manière récursive, en appelant split sur des générateurs split . Il n'y a pas de limites (sauf dépassement d'entier) sur la profondeur des récursions.

Interaction avec tf.function

tf.random.Generator obéit aux mêmes règles que tf.Variable lorsqu'il est utilisé avec tf.function . Cela comprend trois aspects.

Création de générateurs en dehors de tf.function

tf.function peut utiliser un générateur créé en dehors de celui-ci.

 g = tf.random.Generator.from_seed(1)
@tf.function
def foo():
  return g.normal([])
print(foo())
 
tf.Tensor(0.43842274, shape=(), dtype=float32)

L'utilisateur doit s'assurer que l'objet générateur est toujours actif (non récupéré par la mémoire) lorsque la fonction est appelée.

Créer des générateurs dans tf.function

La création de générateurs à l'intérieur d'une fonction tf.function ne peut avoir lieu que lors de la première exécution de la fonction.

 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())
 
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1817: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
tf.Tensor(0.43842274, shape=(), dtype=float32)
tf.Tensor(1.6272374, shape=(), dtype=float32)

Passer des générateurs comme arguments à tf.function

Lorsqu'ils sont utilisés comme argument pour une fonction tf.function , différents objets générateurs avec la même taille d'état (la taille d'état est déterminée par l'algorithme RNG) ne provoqueront pas de tf.function de la fonction tf.function , alors que ceux avec des tailles d'état différentes le feront.

 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

Interaction avec les stratégies de distribution

Il existe trois façons dont Generator interagit avec les stratégies de distribution.

Créer des générateurs en dehors des stratégies de distribution

Si un générateur est créé en dehors des portées de stratégie, tous les accès des répliques au générateur seront sérialisés, et donc les répliques recevront des nombres aléatoires différents.

 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)

Notez que cette utilisation peut avoir des problèmes de performances car le périphérique du générateur est différent des répliques.

Créer des générateurs dans les stratégies de distribution

La création de générateurs à l'intérieur des portées de stratégie est interdite, car il y a une ambiguïté sur la façon de répliquer un générateur (par exemple, doit-il être copié pour que chaque réplique reçoive les mêmes nombres aléatoires, ou «fractionné» pour que chaque réplica obtienne des nombres aléatoires différents).

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

Notez que Strategy.run exécutera sa fonction d'argument dans une portée de stratégie implicitement:

 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_strategy.py", line 998, 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 282, in wrapper
    return func(*args, **kwargs)
  File "<ipython-input-14-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 444, 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 386, in __init__
    trainable=False)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/stateful_random_ops.py", line 272, 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).

Passer des générateurs comme arguments à Strategy.run

Si vous souhaitez que chaque réplica utilise son propre générateur, vous devez créer n générateurs (soit par copie, soit par fractionnement), où n est le nombre de répliques, puis les transmettre comme arguments à 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.15682742, shape=(), dtype=float32)
tf.Tensor(-0.38042808, shape=(), dtype=float32)

RNG sans état

L'utilisation des RNG sans état est simple. Puisqu'il ne s'agit que de fonctions pures, il n'y a aucun état ou effet secondaire impliqué.

 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.3015898  -0.95385665]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[ 0.5441101   0.20738031  0.07356433]
 [ 0.04643455 -1.3015898  -0.95385665]], shape=(2, 3), dtype=float32)

Chaque RNG sans état nécessite une seed argument, qui doit être un nombre entier Tenseur de forme [2] . Les résultats de l'opération sont entièrement déterminés par cette graine.

Algorithmes

Général

La classe tf.random.Generator et les fonctions stateless prennent en charge l'algorithme Philox (écrit comme "philox" ou tf.random.Algorithm.PHILOX ) sur tous les appareils.

Différents appareils généreront les mêmes nombres entiers, si vous utilisez le même algorithme et en partant du même état. Ils généreront également "presque les mêmes" nombres à virgule flottante, bien qu'il puisse y avoir de petites différences numériques causées par les différentes façons dont les appareils effectuent le calcul en virgule flottante (par exemple, ordre de réduction).

Appareils XLA

Sur les appareils pilotés par XLA (tels que TPU, et également CPU / GPU lorsque XLA est activé), l'algorithme ThreeFry (écrit comme "threefry" ou tf.random.Algorithm.THREEFRY ) est également pris en charge. Cet algorithme est rapide sur TPU mais lent sur CPU / GPU par rapport à Philox.

Voir l'article «Nombres aléatoires parallèles: aussi simple que 1, 2, 3» pour plus de détails sur ces algorithmes.