Generowanie liczb losowych

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

TensorFlow udostępnia zestaw generatorów liczb pseudolosowych (RNG), w module tf.random . W tym dokumencie opisano, w jaki sposób można sterować generatorami liczb losowych oraz jak te generatory współdziałają z innymi podsystemami tensorflow.

TensorFlow udostępnia dwa podejścia do kontrolowania procesu generowania liczb losowych:

  1. Poprzez jawne użycie obiektów tf.random.Generator . Każdy taki obiekt zachowuje stan (w tf.Variable ), który będzie się zmieniał po każdym wygenerowaniu liczby.

  2. Poprzez czysto funkcjonalne bezstanowe funkcje losowe, takie jak tf.random.stateless_uniform . Wywołanie tych funkcji z tymi samymi argumentami (które zawierają ziarno) i na tym samym urządzeniu zawsze da te same wyniki.

Ustawiać

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

Klasa tf.random.Generator

Klasa tf.random.Generator jest używana w przypadkach, gdy chcesz, aby każde wywołanie RNG dawało różne wyniki. Utrzymuje stan wewnętrzny (zarządzany przez obiekt tf.Variable ), który będzie aktualizowany za każdym razem, gdy generowane są liczby losowe. Ponieważ stan jest zarządzany przez tf.Variable , korzysta ze wszystkich udogodnień zapewnianych przez tf.Variable , takich jak łatwe wyznaczanie punktów kontrolnych, automatyczna zależność od kontroli i bezpieczeństwo wątków.

Możesz uzyskać tf.random.Generator , ręcznie tworząc obiekt klasy lub wywołując tf.random.get_global_generator() , aby uzyskać domyślny globalny generator:

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)

Istnieje wiele sposobów tworzenia obiektu generatora. Najprostszym jest Generator.from_seed , jak pokazano powyżej, który tworzy generator z nasiona. Ziarno to dowolna nieujemna liczba całkowita. from_seed przyjmuje również opcjonalny argument alg , który jest algorytmem RNG, który będzie używany przez ten generator:

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)

Zobacz sekcję Algorytmy poniżej, aby uzyskać więcej informacji na ten temat.

Innym sposobem tworzenia generatora jest Generator.from_non_deterministic_state . Stworzony w ten sposób generator wystartuje ze stanu niedeterministycznego, zależnego np. od czasu i systemu operacyjnego.

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)

Istnieją jeszcze inne sposoby tworzenia generatorów, na przykład ze stanów jawnych, których nie obejmuje ten przewodnik.

Używając tf.random.get_global_generator , aby uzyskać globalny generator, musisz uważać na rozmieszczenie urządzeń. Generator globalny jest tworzony (ze stanu niedeterministycznego) przy pierwszym tf.random.get_global_generator i umieszczany na urządzeniu domyślnym podczas tego wywołania. Na przykład, jeśli pierwsza witryna, którą wywołasz tf.random.get_global_generator , znajduje się w tf.device("gpu") , generator globalny zostanie umieszczony na GPU, a późniejsze użycie generatora globalnego z procesora spowoduje ponieść kopię GPU-do-CPU.

Istnieje również funkcja tf.random.set_global_generator do zastąpienia globalnego generatora innym obiektem generatora. Ta funkcja powinna być jednak używana z ostrożnością, ponieważ stary globalny generator mógł zostać przechwycony przez tf.function (jako słabe odniesienie), a zastąpienie go spowoduje, że zostanie on odrzucony, co spowoduje uszkodzenie tf.function . Lepszym sposobem na zresetowanie generatora globalnego jest użycie jednej z funkcji „reset”, takich jak Generator.reset_from_seed , która nie tworzy nowych obiektów generatora.

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)

Tworzenie niezależnych strumieni losowych

W wielu aplikacjach potrzeba wielu niezależnych strumieni liczb losowych, niezależnych w tym sensie, że nie będą się pokrywać i nie będą miały żadnych statystycznie wykrywalnych korelacji. Osiąga się to za pomocą Generator.split do tworzenia wielu generatorów, które gwarantują niezależność od siebie (tj. generują niezależne strumienie).

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 zmieni stan generatora, na którym jest wywoływany ( g powyższym przykładzie), podobnie do metody RNG, takiej jak normal . Oprócz tego, że są niezależne od siebie, nowe generatory ( new_gs ) są również niezależne od starego ( g ).

Tworzenie nowych generatorów jest również przydatne, gdy chcesz się upewnić, że generator, którego używasz, znajduje się na tym samym urządzeniu, co inne obliczenia, aby uniknąć obciążenia związanego z kopiowaniem między urządzeniami. Na przykład:

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)

Możesz robić dzielenie rekurencyjnie, wywołując split na podzielonych generatorach. Nie ma ograniczeń (z wyjątkiem przepełnienia liczby całkowitej) głębokości rekurencji.

Interakcja z tf.function

tf.random.Generator przestrzega tych samych zasad, co tf.Variable , gdy jest używany z tf.function . Obejmuje to trzy aspekty.

Tworzenie generatorów poza tf.function

tf.function może używać generatora stworzonego poza nim.

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

Użytkownik musi upewnić się, że obiekt generatora nadal żyje (nie jest zbierany w postaci śmieci), gdy funkcja jest wywoływana.

Tworzenie generatorów wewnątrz tf.function

Tworzenie generatorów wewnątrz funkcji tf.function może nastąpić tylko podczas pierwszego uruchomienia funkcji.

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)

Przekazywanie generatorów jako argumentów do tf.function

Gdy zostanie użyty jako argument do tf.function , różne obiekty generatora spowodują 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

Zauważ, że to zachowanie cofania jest zgodne z 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

Interakcja ze strategiami dystrybucji

Istnieją dwa sposoby interakcji Generator ze strategiami dystrybucji.

Tworzenie generatorów poza strategiami dystrybucji

Jeśli generator zostanie utworzony poza zakresami strategii, dostęp wszystkich replik do generatora zostanie zserializowany, a co za tym idzie repliki otrzymają różne liczby losowe.

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)

Pamiętaj, że to użycie może powodować problemy z wydajnością, ponieważ urządzenie generatora różni się od replik.

Tworzenie generatorów w strategiach dystrybucji

Jeśli generator zostanie utworzony w zakresie strategii, każda replika otrzyma inny i niezależny strumień liczb losowych.

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

Jeśli generator jest inicjowany (np. utworzony przez Generator.from_seed ), liczby losowe są określane przez ziarno, mimo że różne repliki otrzymują różne i nieskorelowane liczby. Można myśleć o losowej liczbie wygenerowanej na replice jako o skrótu identyfikatora repliki i „podstawowej” losowej liczbie, która jest wspólna dla wszystkich replik. Cały system jest więc nadal deterministyczny.

tf.random.Generator można również utworzyć w 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)
}

Nie zalecamy już przekazywania tf.random.Generator jako argumentów do Strategy.run , ponieważ Strategy.run zazwyczaj oczekuje, że argumenty będą tensorami, a nie generatorami.

Oszczędzanie generatorów

Ogólnie rzecz biorąc, do zapisywania lub serializacji można obsługiwać tf.random.Generator w taki sam sposób, jak tf.Variable lub tf.Module (lub ich podklasy). W TF istnieją dwa mechanizmy serializacji: Checkpoint i SavedModel .

Punkt kontrolny

Generatory można dowolnie zapisywać i przywracać za pomocą tf.train.Checkpoint . Strumień liczb losowych z punktu przywracania będzie taki sam, jak z punktu zapisywania.

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)

Możesz także zapisywać i przywracać w ramach strategii dystrybucji:

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

Przed zapisaniem należy upewnić się, że repliki nie różnią się w swojej historii wywołań RNG (np. jedna replika wykonuje jedno wywołanie RNG, a druga dwa wywołania RNG). W przeciwnym razie ich wewnętrzne stany RNG będą się różnić, a tf.train.Checkpoint (który zapisuje tylko stan pierwszej repliki) nie przywróci poprawnie wszystkich replik.

Zapisany punkt kontrolny można również przywrócić do innej strategii dystrybucji z inną liczbą replik. Ponieważ obiekt tf.random.Generator utworzony w strategii może być użyty tylko w tej samej strategii, aby przywrócić inną strategię, należy utworzyć nowy tf.random.Generator w strategii docelowej i nowy tf.train.Checkpoint dla tego, jak pokazano w tym przykładzie:

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

Chociaż g1 i cp1 są różnymi obiektami niż g2 i cp2 , są one połączone przez wspólną filename z punktem kontrolnym i nazwę obiektu my_generator . Nakładające się repliki między strategiami (np cpu:0 i cpu:1 powyżej) będą miały prawidłowo przywrócone strumienie RNG, jak w poprzednich przykładach. Gwarancja ta nie obejmuje przypadku, gdy generator zostanie zapisany w zakresie strategii i przywrócony poza zakresem strategii lub odwrotnie, ponieważ urządzenie poza strategiami jest traktowane jako inne niż dowolna replika w strategii.

Zapisany model

tf.random.Generator można zapisać w SavedModel. Generator można stworzyć w zakresie strategii. Oszczędność może również nastąpić w zakresie strategii.

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)

Ładowanie modelu SavedModel zawierającego tf.random.Generator do strategii dystrybucji nie jest zalecane, ponieważ wszystkie repliki będą generować ten sam strumień liczb losowych (jest to spowodowane tym, że identyfikator repliki jest zamrożony na wykresie SavedModel).

Ładowanie rozproszonego tf.random.Generator (generatora utworzonego w ramach strategii dystrybucji) do środowiska bez strategii, jak w powyższym przykładzie, również ma pewne zastrzeżenie. Stan RNG zostanie prawidłowo przywrócony, ale wygenerowane liczby losowe będą inne niż oryginalne generatory w jego strategii (ponownie, ponieważ urządzenie poza strategiami jest traktowane jako inne niż dowolna replika w strategii).

Bezstanowe RNG

Korzystanie z bezstanowych RNG jest proste. Ponieważ są to tylko czyste funkcje, nie ma w nich żadnego stanu ani efektu ubocznego.

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)

Każdy bezstanowy seed wymaga argumentu inicjującego, który musi być liczbą całkowitą Tensor kształtu [2] . Wyniki operacji są w pełni określone przez to ziarno.

Algorytm RNG używany przez bezstanowe RNG jest zależny od urządzenia, co oznacza, że ​​ta sama operacja działająca na innym urządzeniu może generować różne dane wyjściowe.

Algorytmy

Ogólny

Zarówno klasa tf.random.Generator , jak i funkcje stateless obsługują algorytm Philox (zapisany jako "philox" lub tf.random.Algorithm.PHILOX ) na wszystkich urządzeniach.

Różne urządzenia będą generować te same liczby całkowite, jeśli używają tego samego algorytmu i zaczynają się od tego samego stanu. Będą również generować „prawie takie same” liczby zmiennoprzecinkowe, chociaż mogą wystąpić niewielkie rozbieżności liczbowe spowodowane różnymi sposobami, w jakie urządzenia przeprowadzają obliczenia zmiennoprzecinkowe (np. kolejność redukcji).

Urządzenia XLA

Na urządzeniach opartych na XLA (takich jak TPU, a także CPU/GPU, gdy XLA jest włączony) obsługiwany jest również algorytm ThreeFry (zapisany jako "threefry" lub tf.random.Algorithm.THREEFRY ). Ten algorytm jest szybki na TPU, ale wolny na CPU/GPU w porównaniu do Philoxa.

Zobacz artykuł „Równoległe liczby losowe: tak proste jak 1, 2, 3”, aby uzyskać więcej informacji na temat tych algorytmów.