Generazione di numeri casuali

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza l'origine su GitHub Scarica quaderno

TensorFlow fornisce una serie di generatori di numeri pseudo-casuali (RNG), nel modulo tf.random . Questo documento descrive come controllare i generatori di numeri casuali e come questi generatori interagiscono con altri sottosistemi di flusso tensoriale.

TensorFlow fornisce due approcci per controllare il processo di generazione dei numeri casuali:

  1. Attraverso l'uso esplicito di oggetti tf.random.Generator . Ciascuno di questi oggetti mantiene uno stato (in tf.Variable ) che verrà modificato dopo ogni generazione di numeri.

  2. Attraverso le funzioni casuali stateless puramente funzionali come tf.random.stateless_uniform . Chiamare queste funzioni con gli stessi argomenti (che includono il seme) e sullo stesso dispositivo produrrà sempre gli stessi risultati.

Impostare

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 viene utilizzata nei casi in cui si desidera che ciascuna chiamata RNG produca risultati diversi. Mantiene uno stato interno (gestito da un oggetto tf.Variable ) che verrà aggiornato ogni volta che vengono generati numeri casuali. Poiché lo stato è gestito da tf.Variable , gode di tutti i servizi forniti da tf.Variable come checkpoint facile, dipendenza dal controllo automatico e thread safety.

Puoi ottenere un tf.random.Generator creando manualmente un oggetto della classe o chiamando tf.random.get_global_generator() per ottenere il generatore globale predefinito:

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)

Esistono diversi modi per creare un oggetto generatore. Il più semplice è Generator.from_seed , come mostrato sopra, che crea un generatore da un seme. Un seme è un numero intero non negativo. from_seed accetta anche un argomento opzionale alg che è l'algoritmo RNG che verrà utilizzato da questo generatore:

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)

Vedere la sezione Algoritmi di seguito per ulteriori informazioni a riguardo.

Un altro modo per creare un generatore è con Generator.from_non_deterministic_state . Un generatore creato in questo modo partirà da uno stato non deterministico, a seconda ad esempio del tempo e 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)

Esistono ancora altri modi per creare generatori, ad esempio da stati espliciti, che non sono trattati in questa guida.

Quando si utilizza tf.random.get_global_generator per ottenere il generatore globale, è necessario prestare attenzione al posizionamento del dispositivo. Il generatore globale viene creato (da uno stato non deterministico) alla prima chiamata di tf.random.get_global_generator e posizionato sul dispositivo predefinito in quella chiamata. Quindi, ad esempio, se il primo sito che chiami tf.random.get_global_generator si trova all'interno di un ambito tf.device("gpu") , il generatore globale verrà posizionato sulla GPU e l'utilizzo del generatore globale in seguito dalla CPU verrà incorrere in una copia da GPU a CPU.

C'è anche una funzione tf.random.set_global_generator per sostituire il generatore globale con un altro oggetto generatore. Questa funzione dovrebbe essere utilizzata con cautela, tuttavia, perché il vecchio generatore globale potrebbe essere stato catturato da una tf.function (come riferimento debole) e la sua sostituzione causerà la raccolta dei rifiuti, interrompendo la tf.function . Un modo migliore per ripristinare il generatore globale è utilizzare una delle funzioni di "reset" come Generator.reset_from_seed , che non creerà nuovi oggetti generatore.

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)

Creazione di flussi di numeri casuali indipendenti

In molte applicazioni sono necessari più flussi di numeri casuali indipendenti, indipendenti nel senso che non si sovrapporranno e non avranno correlazioni statisticamente rilevabili. Ciò si ottiene utilizzando Generator.split per creare più generatori che sono garantiti per essere indipendenti l'uno dall'altro (ovvero generando flussi indipendenti).

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 cambierà lo stato del generatore su cui viene chiamato ( g nell'esempio sopra), in modo simile a un metodo RNG come normal . Oltre ad essere indipendenti l'uno dall'altro, i nuovi generatori ( new_gs ) sono garantiti anche indipendenti dal vecchio ( g ).

La generazione di nuovi generatori è utile anche quando vuoi assicurarti che il generatore che usi sia sullo stesso dispositivo di altri calcoli, per evitare il sovraccarico della copia tra dispositivi. Per esempio:

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)

Puoi eseguire la divisione in modo ricorsivo, chiamando split su generatori divisi. Non ci sono limiti (a parte l'overflow di interi) sulla profondità delle ricorsioni.

Interazione con tf.function

tf.random.Generator obbedisce alle stesse regole di tf.Variable quando utilizzato con tf.function . Ciò include tre aspetti.

Creazione di generatori al di fuori tf.function

tf.function può utilizzare un generatore creato al di fuori di esso.

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

L'utente deve assicurarsi che l'oggetto generatore sia ancora vivo (non sottoposto a garbage collection) quando viene chiamata la funzione.

Creazione di generatori all'interno tf.function

La creazione di generatori all'interno di una tf.function può avvenire solo durante la prima esecuzione della funzione.

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)

Passare i generatori come argomenti a tf.function

Se usati come argomento per una tf.function , oggetti generatori diversi causeranno il ritracciamento della 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

Si noti che questo comportamento di ritracciamento è coerente 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

Interazione con le strategie distributive

Esistono due modi in cui Generator interagisce con le strategie di distribuzione.

Creazione di generatori al di fuori delle strategie distributive

Se un generatore viene creato al di fuori degli ambiti della strategia, l'accesso di tutte le repliche al generatore verrà serializzato e quindi le repliche riceveranno numeri casuali diversi.

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)

Si noti che questo utilizzo potrebbe avere problemi di prestazioni perché il dispositivo del generatore è diverso dalle repliche.

Creare generatori all'interno delle strategie distributive

Se viene creato un generatore all'interno di un ambito di strategia, ogni replica riceverà un flusso diverso e indipendente di numeri casuali.

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

Se il generatore è seminato (ad esempio creato da Generator.from_seed ), i numeri casuali sono determinati dal seme, anche se repliche diverse ottengono numeri diversi e non correlati. Si può pensare a un numero casuale generato su una replica come un hash dell'ID replica e un numero casuale "primario" comune a tutte le repliche. Quindi, l'intero sistema è ancora deterministico.

tf.random.Generator può anche essere creato all'interno di 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)
}

Non è più consigliabile passare tf.random.Generator come argomenti a Strategy.run , perché Strategy.run generalmente si aspetta che gli argomenti siano tensori, non generatori.

Risparmio di generatori

Generalmente per il salvataggio o la serializzazione puoi gestire un tf.random.Generator nello stesso modo in cui gestiresti un tf.Variable o un tf.Module (o le sue sottoclassi). In TF ci sono due meccanismi per la serializzazione: Checkpoint e SavedModel .

Punto di controllo

I generatori possono essere salvati e ripristinati liberamente utilizzando tf.train.Checkpoint . Il flusso di numeri casuali dal punto di ripristino sarà lo stesso di quello dal punto di salvataggio.

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)

Puoi anche salvare e ripristinare all'interno di una strategia di distribuzione:

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

È necessario assicurarsi che le repliche non divergano nella cronologia delle chiamate RNG (ad es. una replica effettua una chiamata RNG mentre un'altra effettua due chiamate RNG) prima di salvare. In caso contrario, i loro stati RNG interni divergeranno e tf.train.Checkpoint (che salva solo lo stato della prima replica) non ripristinerà correttamente tutte le repliche.

Puoi anche ripristinare un checkpoint salvato in una strategia di distribuzione diversa con un numero diverso di repliche. Poiché un oggetto tf.random.Generator creato in una strategia può essere utilizzato solo nella stessa strategia, per ripristinare una strategia diversa, è necessario creare un nuovo tf.random.Generator nella strategia di destinazione e un nuovo tf.train.Checkpoint per esso, come mostrato in questo esempio:

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

Sebbene g1 e cp1 siano oggetti diversi da g2 e cp2 , sono collegati tramite il file di checkpoint comune filename e nome oggetto my_generator . Le repliche sovrapposte tra le strategie (ad esempio cpu:0 e cpu:1 sopra) avranno i loro flussi RNG correttamente ripristinati come negli esempi precedenti. Questa garanzia non copre il caso in cui un generatore viene salvato in un ambito di strategia e ripristinato al di fuori di qualsiasi ambito di strategia o viceversa, perché un dispositivo al di fuori delle strategie viene trattato come diverso da qualsiasi replica in una strategia.

Modello salvato

tf.random.Generator può essere salvato in un SavedModel. Il generatore può essere creato all'interno di un ambito di strategia. Il risparmio può avvenire anche nell'ambito di una strategia.

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)

Non è consigliabile caricare un SavedModel contenente tf.random.Generator in una strategia di distribuzione perché le repliche genereranno tutte lo stesso flusso di numeri casuali (ovvero perché l'ID replica è bloccato nel grafico di SavedModel).

Anche il caricamento di un tf.random.Generator distribuito (un generatore creato all'interno di una strategia di distribuzione) in un ambiente non strategico, come l'esempio sopra, ha un avvertimento. Lo stato RNG verrà ripristinato correttamente, ma i numeri casuali generati saranno diversi dal generatore originale nella sua strategia (di nuovo perché un dispositivo al di fuori delle strategie viene trattato come diverso da qualsiasi replica in una strategia).

RNG apolidi

L'uso di RNG senza stato è semplice. Dal momento che sono solo funzioni pure, non ci sono stati o effetti collaterali coinvolti.

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)

Ogni RNG stateless richiede un argomento seed , che deve essere un intero Tensor of shape [2] . I risultati dell'operazione sono completamente determinati da questo seme.

L'algoritmo RNG utilizzato dagli RNG senza stato dipende dal dispositivo, il che significa che la stessa operazione in esecuzione su un dispositivo diverso può produrre output diversi.

Algoritmi

Generale

Sia la classe tf.random.Generator che le funzioni stateless supportano l'algoritmo Philox (scritto come "philox" o tf.random.Algorithm.PHILOX ) su tutti i dispositivi.

Dispositivi diversi genereranno gli stessi numeri interi, se si utilizza lo stesso algoritmo e a partire dallo stesso stato. Genereranno anche "quasi gli stessi" numeri in virgola mobile, sebbene possano esserci piccole discrepanze numeriche causate dai diversi modi in cui i dispositivi eseguono il calcolo in virgola mobile (es. ordine di riduzione).

dispositivi XLA

Sui dispositivi basati su XLA (come TPU e anche CPU/GPU quando XLA è abilitato) è supportato anche l'algoritmo ThreeFry (scritto come "threefry" o tf.random.Algorithm.THREEFRY ). Questo algoritmo è veloce su TPU ma lento su CPU/GPU rispetto a Philox.

Vedi il documento "Numeri casuali paralleli: facili come 1, 2, 3" per maggiori dettagli su questi algoritmi.