Google I/O est terminé ! Suivez les sessions TensorFlow Afficher les sessions

Génération de nombres aléatoires

Voir sur TensorFlow.org Exécuter dans Google Colab Voir 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 (dans tf.Variable ) qui sera modifié après chaque génération de numéro.

  2. Grâce aux fonctions aléatoires sans état purement fonctionnelles comme 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 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 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 génération de nombres aléatoires. É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 au 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.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)

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 un 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.43842277 -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 avec Generator.from_non_deterministic_state . Un générateur créé de cette manière démarrera à partir d'un état non déterministe, en fonction par exemple de l'heure et du système d'exploitation.

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)

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 de l'appareil. 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 se trouve dans une tf.device("gpu") , le générateur global sera placé sur le GPU, et l'utilisation ultérieure du générateur global depuis le CPU subir 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 cependant être utilisée avec prudence, car l'ancien générateur global peut avoir été capturé par un tf.function (en tant que référence faible), et le remplacer entraînera un ramasse-miettes, cassant le tf.function . Une meilleure façon de réinitialiser le générateur global consiste à utiliser l'une des fonctions "reset" telles que Generator.reset_from_seed , qui ne créera pas de nouveaux objets générateur.

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)

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

Dans de nombreuses applications, on a besoin de plusieurs flux de nombres aléatoires indépendants, indépendants dans le sens où ils ne se chevaucheront pas et n'auront aucune corrélation statistiquement détectable. 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.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 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 ).

Générer 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 appareil que les autres calculs, afin d'éviter la surcharge de la copie inter-appareils. Par example:

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)

Vous pouvez effectuer un fractionnement de manière récursive, en appelant split sur des générateurs fractionnés. Il n'y a pas de limite (sauf débordement 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 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.43842277, shape=(), dtype=float32)

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

Création de générateurs dans tf.function

La création de générateurs à l'intérieur d'une 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())
tf.Tensor(0.43842277, 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 d'un tf.function , différents objets générateurs entraîneront le retraçage du 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

Notez que ce comportement de retraçage est cohérent avec 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

Interaction avec les stratégies de distribution

Generator interagit avec les stratégies de distribution de deux manières.

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 la stratégie, l'accès de tous les réplicas au générateur sera sérialisé et, par conséquent, les réplicas obtiendront 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

Si un générateur est créé dans une portée de stratégie, chaque réplique recevra un flux différent et indépendant de nombres aléatoires.

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 le générateur est amorcé (par exemple, créé par Generator.from_seed ), les nombres aléatoires sont déterminés par la graine, même si différentes répliques obtiennent des nombres différents et non corrélés. On peut considérer un nombre aléatoire généré sur une réplique comme un hachage de l'ID de réplique et un nombre aléatoire "primaire" commun à toutes les répliques. Par conséquent, l'ensemble du système est encore déterministe.

tf.random.Generator peut également être créé dans 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)
}

Nous ne recommandons plus de transmettre tf.random.Generator comme arguments à Strategy.run , car Strategy.run s'attend généralement à ce que les arguments soient des tenseurs et non des générateurs.

Économie de générateurs

Généralement, pour enregistrer ou sérialiser, vous pouvez gérer un tf.random.Generator de la même manière que vous géreriez un tf.Variable ou un tf.Module (ou ses sous-classes). Dans TF, il existe deux mécanismes de sérialisation : Checkpoint et SavedModel .

Point de contrôle

Les générateurs peuvent être librement sauvegardés et restaurés en utilisant tf.train.Checkpoint . Le flux de nombres aléatoires du point de restauration sera le même que celui du point de sauvegarde.

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)

Vous pouvez également enregistrer et restaurer dans le cadre d'une stratégie de distribution :

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

Vous devez vous assurer que les répliques ne divergent pas dans leur historique d'appels RNG (par exemple, une réplique effectue un appel RNG tandis qu'une autre effectue deux appels RNG) avant d'enregistrer. Sinon, leurs états RNG internes divergeront et tf.train.Checkpoint (qui ne sauvegarde que l'état du premier réplica) ne restaurera pas correctement tous les réplicas.

Vous pouvez également restaurer un point de contrôle enregistré dans une stratégie de distribution différente avec un nombre différent de répliques. Comme un objet tf.random.Generator créé dans une stratégie ne peut être utilisé que dans la même stratégie, pour restaurer vers une stratégie différente, vous devez créer un nouveau tf.random.Generator dans la stratégie cible et un nouveau tf.train.Checkpoint de contrôle pour cela, comme indiqué dans cet exemple :

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

Bien que g1 et cp1 soient des objets différents de g2 et cp2 , ils sont liés via le fichier de point de contrôle commun filename et le nom d'objet my_generator . Les répliques qui se chevauchent entre les stratégies (par exemple cpu:0 et cpu:1 ci-dessus) verront leurs flux RNG correctement restaurés comme dans les exemples précédents. Cette garantie ne couvre pas le cas où un générateur est enregistré dans une portée de stratégie et restauré en dehors de toute portée de stratégie ou vice versa, car un appareil en dehors des stratégies est traité comme différent de toute réplique dans une stratégie.

Modèle enregistré

tf.random.Generator peut être enregistré dans un SavedModel. Le générateur peut être créé dans une portée de stratégie. L'économie peut également se produire dans le cadre d'une stratégie.

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)

Le chargement d'un SavedModel contenant tf.random.Generator dans une stratégie de distribution n'est pas recommandé car les répliques généreront toutes le même flux de nombres aléatoires (ce qui est dû au fait que l'ID de réplique est gelé dans le graphique de SavedModel).

Le chargement d'un tf.random.Generator distribué (un générateur créé dans le cadre d'une stratégie de distribution) dans un environnement sans stratégie, comme dans l'exemple ci-dessus, comporte également une mise en garde. L'état RNG sera correctement restauré, mais les nombres aléatoires générés seront différents du générateur d'origine dans sa stratégie (encore une fois parce qu'un appareil en dehors des stratégies est traité comme différent de toute réplique dans une stratégie).

RNG sans état

L'utilisation des RNG sans état est simple. Comme ce ne sont que des 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.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)

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

L'algorithme RNG utilisé par les RNG sans état dépend de l'appareil, ce qui signifie que la même opération exécutée sur un appareil différent peut produire des sorties différentes.

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, s'ils utilisent le même algorithme et partent 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 manières dont les appareils effectuent le calcul à virgule flottante (par exemple, l'ordre de réduction).

Appareils XLA

Sur les appareils pilotés par XLA (tels que TPU, ainsi que 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.