Посмотреть на TensorFlow.org | Запустить в Google Colab | Посмотреть исходный код на GitHub | Скачать блокнот |
TensorFlow предоставляет набор генераторов псевдослучайных чисел (ГСЧ) в модуле tf.random
. В этом документе описывается, как вы можете управлять генераторами случайных чисел и как эти генераторы взаимодействуют с другими подсистемами тензорного потока.
TensorFlow предоставляет два подхода к управлению процессом генерации случайных чисел:
Путем явного использования объектов
tf.random.Generator
. Каждый такой объект поддерживает состояние (вtf.Variable
), которое будет меняться после каждой генерации числа.Через чисто функциональные случайные функции без сохранения состояния, такие как
tf.random.stateless_uniform
. Вызов этих функций с одними и теми же аргументами (включая начальное значение) и на одном и том же устройстве всегда будет давать одни и те же результаты.
Настраивать
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()
])
Класс tf.random.Generator
Класс tf.random.Generator
используется в тех случаях, когда вы хотите, чтобы каждый вызов ГСЧ давал разные результаты. Он поддерживает внутреннее состояние (управляемое объектом tf.Variable
), которое будет обновляться каждый раз, когда генерируются случайные числа. Поскольку состояние управляется tf.Variable
, оно пользуется всеми возможностями, предоставляемыми tf.Variable
, такими как простое создание контрольных точек, автоматическая зависимость от управления и потокобезопасность.
Вы можете получить tf.random.Generator
, вручную создав объект класса или tf.random.get_global_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)
Существует несколько способов создания объекта-генератора. Самый простой — это Generator.from_seed
, как показано выше, который создает генератор из семени. Семя — это любое неотрицательное целое число. from_seed
также принимает необязательный аргумент alg
, который представляет собой алгоритм ГСЧ, который будет использоваться этим генератором:
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)
Дополнительную информацию об этом см. в разделе « Алгоритмы » ниже.
Другой способ создать генератор — с помощью Generator.from_non_deterministic_state
. Генератор, созданный таким образом, запустится из недетерминированного состояния, в зависимости, например, от времени и операционной системы.
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)
Существуют и другие способы создания генераторов, например, из явных состояний, которые не рассматриваются в этом руководстве.
При использовании tf.random.get_global_generator
для получения глобального генератора вам нужно быть осторожным с размещением устройства. Глобальный генератор создается (из недетерминированного состояния) при первом tf.random.get_global_generator
и помещается на устройство по умолчанию при этом вызове. Так, например, если первый сайт, который вы вызываете tf.random.get_global_generator
, находится в пределах tf.device("gpu")
, глобальный генератор будет размещен на графическом процессоре, а использование глобального генератора позже из ЦП будет нести копию GPU-to-CPU.
Также есть функция tf.random.set_global_generator
для замены глобального генератора другим объектом-генератором. Однако эту функцию следует использовать с осторожностью, потому что старый глобальный генератор мог быть захвачен tf.function
(как слабая ссылка), и его замена приведет к его сборке мусора, что tf.function
. Лучший способ сбросить глобальный генератор — использовать одну из функций «сброса», например Generator.reset_from_seed
, которая не будет создавать новые объекты генератора.
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)
Создание независимых потоков случайных чисел
Во многих приложениях требуется несколько независимых потоков случайных чисел, независимых в том смысле, что они не будут перекрываться и не будут иметь каких-либо статистически обнаруживаемых корреляций. Это достигается с помощью Generator.split
для создания нескольких генераторов, которые гарантированно независимы друг от друга (т. е. генерируют независимые потоки).
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
изменит состояние генератора, для которого он вызывается ( g
в приведенном выше примере), аналогично методу RNG, такому как normal
. В дополнение к тому, что они независимы друг от друга, новые генераторы ( new_gs
) также гарантированно независимы от старого ( g
).
Создание новых генераторов также полезно, когда вы хотите убедиться, что используемый вами генератор находится на том же устройстве, что и другие вычисления, чтобы избежать накладных расходов на копирование между устройствами. Например:
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)
Вы можете выполнять разбиение рекурсивно, вызывая split
на разделенных генераторах. Нет никаких ограничений (за исключением целочисленного переполнения) на глубину рекурсии.
Взаимодействие с tf.function
tf.random.Generator
подчиняется тем же правилам, что и tf.Variable
при использовании с tf.function
. Сюда входят три аспекта.
Создание генераторов вне tf.function
tf.function
может использовать генератор, созданный вне его.
g = tf.random.Generator.from_seed(1)
@tf.function
def foo():
return g.normal([])
print(foo())
tf.Tensor(0.43842277, shape=(), dtype=float32)
Пользователь должен убедиться, что объект генератора все еще жив (не очищен от мусора) при вызове функции.
Создание генераторов внутри tf.function
Создание генераторов внутри tf.function
может произойти только во время первого запуска функции.
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)
Передача генераторов в качестве аргументов в tf.function
При использовании в качестве аргумента для tf.function
другие объекты-генераторы вызовут повторную 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
Обратите внимание, что такое поведение повторной трассировки согласуется с 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
Взаимодействие со стратегиями дистрибуции
Generator
взаимодействует со стратегиями распределения двумя способами.
Создание генераторов вне стратегий распределения
Если генератор создается за рамками стратегии, доступ всех реплик к генератору будет сериализован, и, следовательно, реплики получат разные случайные числа.
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)
Обратите внимание, что это использование может иметь проблемы с производительностью, поскольку устройство генератора отличается от реплик.
Создание генераторов внутри стратегий дистрибуции
Если генератор создается внутри области действия стратегии, каждая реплика получит отдельный и независимый поток случайных чисел.
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) }
Если генератор заполнен (например, создан Generator.from_seed
), случайные числа определяются начальным числом, даже если разные реплики получают разные и некоррелированные числа. Случайное число, сгенерированное на реплике, можно представить как хэш идентификатора реплики и «первичное» случайное число, общее для всех реплик. Следовательно, вся система по-прежнему детерминирована.
tf.random.Generator
также может быть создан внутри 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) }
Мы больше не рекомендуем передавать tf.random.Generator
в качестве аргументов для Strategy.run
, потому что Strategy.run
обычно ожидает, что аргументы будут тензорами, а не генераторами.
Сохранение генераторов
Как правило, для сохранения или сериализации вы можете обрабатывать tf.random.Generator
так же, как вы обрабатываете tf.Variable
или tf.Module
(или его подклассы). В TF есть два механизма сериализации: Checkpoint и SavedModel .
Контрольно-пропускной пункт
Генераторы можно свободно сохранять и восстанавливать с помощью tf.train.Checkpoint
. Поток случайных чисел из точки восстановления будет таким же, как и из точки сохранения.
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)
Вы также можете сохранять и восстанавливать в рамках стратегии распространения:
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) }
Вы должны убедиться, что реплики не расходятся в своей истории вызовов ГСЧ (например, одна реплика делает один вызов ГСЧ, а другая делает два вызова ГСЧ) перед сохранением. В противном случае их внутренние состояния RNG будут расходиться, и tf.train.Checkpoint
(который сохраняет только состояние первой реплики) не сможет должным образом восстановить все реплики.
Вы также можете восстановить сохраненную контрольную точку для другой стратегии распространения с другим количеством реплик. Поскольку объект tf.random.Generator
, созданный в стратегии, может использоваться только в той же стратегии, для восстановления другой стратегии необходимо создать новый tf.random.Generator
в целевой стратегии и новый tf.train.Checkpoint
для него, как показано в этом примере:
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) }
Хотя объекты g1
и cp1
отличаются от объектов g2
и cp2
, они связаны через общее filename
контрольной точки и имя объекта my_generator
. Перекрывающиеся реплики между стратегиями (например, cpu:0
и cpu:1
выше) будут правильно восстанавливать свои потоки RNG, как в предыдущих примерах. Эта гарантия не распространяется на случай, когда генератор сохраняется в области действия стратегии и восстанавливается вне области действия какой-либо стратегии или наоборот, поскольку устройство вне стратегий рассматривается как отличное от любой реплики в стратегии.
Сохраненная модель
tf.random.Generator
можно сохранить в SavedModel. Генератор может быть создан в рамках стратегии. Сохранение также может происходить в рамках стратегии.
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)
Не рекомендуется загружать SavedModel, содержащую tf.random.Generator
, в стратегию распределения, поскольку все реплики будут генерировать один и тот же поток случайных чисел (это связано с тем, что идентификатор реплики зафиксирован в графе SavedModel).
Загрузка распределенного tf.random.Generator
(генератор, созданный в рамках стратегии распространения) в среду, не являющуюся стратегией, как в приведенном выше примере, также имеет одну оговорку. Состояние RNG будет правильно восстановлено, но сгенерированные случайные числа будут отличаться от исходного генератора в его стратегии (опять же, потому что устройство вне стратегий рассматривается как отличное от любой реплики в стратегии).
ГСЧ без гражданства
Использование генераторов случайных чисел без сохранения состояния просто. Так как это просто чистые функции, в них нет состояния или побочных эффектов.
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)
Каждому ГСЧ без сохранения состояния требуется seed
аргумент, который должен быть целочисленным тензором формы [2]
. Результаты операции полностью определяются этим семенем.
Алгоритм ГСЧ, используемый ГСЧ без сохранения состояния, зависит от устройства, то есть одна и та же операция, выполняемая на другом устройстве, может давать разные выходные данные.
Алгоритмы
Общий
И класс tf.random.Generator
, и функции stateless
поддерживают алгоритм Philox (записывается как "philox"
или tf.random.Algorithm.PHILOX
) на всех устройствах.
Различные устройства будут генерировать одни и те же целые числа, если они используют один и тот же алгоритм и начинают с одного и того же состояния. Они также будут генерировать «почти одинаковые» числа с плавающей запятой, хотя могут быть небольшие численные расхождения, вызванные различными способами, которыми устройства выполняют вычисления с плавающей запятой (например, порядок уменьшения).
XLA-устройства
На устройствах с XLA (таких как TPU, а также CPU/GPU, когда XLA включен) также поддерживается алгоритм ThreeFry (записывается как "threefry"
или tf.random.Algorithm.THREEFRY
). Этот алгоритм быстр на TPU, но медленнее на CPU/GPU по сравнению с Philox.
Подробнее об этих алгоритмах см. в статье «Параллельные случайные числа: просто как 1, 2, 3» .