난수 생성

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

TensorFlow는 tf.random 모듈에서 유사 난수 생성기(pseudo random number generator, RNG) 세트를 제공합니다. 이 문서에서는 난수 생성기를 제어하는 방법과 이러한 생성기가 다른 tensorflow의 하위 시스템과 상호작용하는 방식을 설명합니다.

텐서플로는 난수 생성 프로세스를 다루기 위한 두 가지 방식을 제공합니다:

  1. tf.random.Generator 객체 사용을 통한 방식. 각 객체는 상태를 (tf.Variable 안에) 유지합니다. 이 상태는 매 숫자 생성때마다 변하게 됩니다.

  2. tf.random.stateless_uniform와 같은 순수-함수형으로 상태가 없는 랜덤 함수를 통한 방식. 같은 디바이스에서 동일한 인수를 (시드값 포함) 통해 해당 함수를 호출 하면 항상 같은 결과를 출력 합니다.

주의: tf.random.uniformtf.random.normal 같은 구버전 TF 1.x의 RNG들은 아직 삭제되지 않았지만 사용을 권장하지 않습니다.

설정

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()
    ])
2022-12-14 20:08:18.885165: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory
2022-12-14 20:08:18.885258: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory
2022-12-14 20:08:18.885267: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.

tf.random.Generator 클래스

각 RNG 호출마다 다른 결과를 생성하려는 경우 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.9509389   2.0295      0.19591045]
 [ 0.00700026  0.3858757   1.1050886 ]], shape=(2, 3), dtype=float32)

객체를 생성하는데에는 여러 방법이 있습니다. 위에서 볼 수 있는것 처럼 가장 쉬운 방법은 Generator.from_seed를 사용하는 것이며 이는 시드로부터 생성기를 생성 합니다. 시드는 0 이상의 정수입니다. from_seedalg를 추가적으로 전달 받을 수 있으며 이는 생성기가 사용할 RNG 알고리즘 입니다:

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(
[[-1.3114011  -0.69787496 -0.8103859 ]
 [-0.67154515  0.2667878   0.59468716]], shape=(2, 3), dtype=float32)

이 가이드에서 다루지 않는 명시적 상태에서와 같이 생성기를 만드는 다른 방법들이 있습니다.

tf.random.get_global_generator를 사용하여 전역 생성기를 얻을 때는 장치 배치에 주의해야 합니다. 전역 생성기는 tf.random.get_global_generator가 처음 호출될 때 (비결정적 상태에서) 생성되고 해당 호출 시 기본 장치에 배치됩니다. 따라서 예를 들어, tf.random.get_global_generator를 호출하는 첫 번째 사이트가 tf.device("gpu") 범위 내에 있는 경우, 전역 생성기는 GPU에 배치되고 나중에 CPU에서 전역 생성기를 사용하면 GPU 대 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)

splitnormal과 같은 RNG 메서드와 유사하게 호출된 생성기의 상태를 변경합니다(위의 예에서 g). 새로운 생성기(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.22215867, shape=(), dtype=float32)

참고: 이론적으로, 여기에서 split 대신 from_seed와 같은 생성자를 사용하여 새 생성기를 얻을 수 있지만 그렇게 하면 새 생성기가 전역 생성기와 독립적이라는 보장을 잃게 됩니다. 또한 동일한 시드 또는 난수 스트림의 중복으로 이어지는 시드를 사용하여 실수로 두 개의 생성기를 생성할 위험이 있습니다.

분할된 생성기에서 split를 호출하여 재귀적으로 분할을 수행할 수 있습니다. 재귀의 깊이에는 제한이 없습니다(정수 오버플로 제외).

tf.function와의 상호 작용

tf.random.Generatortf.function와 사용될 경우 tf.Variable와 동일한 규칙이 적용 됩니다. 이는 3가지 측면을 가집니다.

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

분산 전략(distribution strategies)과의 상호 작용

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)

이 사용법은 생성기의 디바이스가 복제에 따라 다르기 때문에 성능에 대한 이슈가 있습니다.

분산 전략(distribution strategies)안에서 생성기 생성하기

전략 범위 내에서 생성기가 생성되면 각 복제본은 서로 다르고 독립적인 난수 스트림을 얻습니다.

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

참고: 현재 tf.random.Generator는 서로 다른 복제본이 동일한 스트림을 얻을 수 있도록 하는(기술적으로 어렵지 않음) 옵션을 제공하지 않습니다. 이 기능에 대한 사용 사례가 있는 경우 TensorFlow 개발자에게 알려주세요.

생성기가 시드되면(예: Generator.from_seed에 의해 생성됨), 다른 복제본이 상이하고 상호 관련되지 않은 숫자를 얻더라도 난수가 시드에 의해 결정됩니다. 복제본에서 생성된 난수는 복제본 ID의 해시 및 모든 복제본에 공통적인 "기본" 난수라고 생각할 수 있습니다. 따라서 전체 시스템은 여전히 결정성이 있습니다.

tf.random.GeneratorStrategy.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.GeneratorStrategy.run에 인수로 전달하는 것을 더 이상 권장하지 않습니다. Strategy.run은 일반적으로 인수가 생성기가 아니라 텐서이기를 기대하기 때문입니다.

생성기 저장하기

일반적으로, 저장 또는 직렬화를 위해 tf.Variable 또는 tf.Module(또는 해당 하위 클래스)을 처리할 때와 마찬가지 방식으로 tf.random.Generator를 처리할 수 있습니다. TF에는 직렬화를 위한 두 가지 메커니즘이 있는데, 각각 체크포인트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 호출 기록에서 분기되지 않는지 확인해야 합니다(예: 어떤 복제본은 하나의 RNG 호출을 수행하고, 또 다른 복제본은 두 개의 RNG 호출을 수행함). 그렇지 않으면 내부 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)
}

g1cp1g2cp2와 다른 객체이지만 공통 체크포인트 파일 filename 및 객체 이름 my_generator을 통해 연결되어 있습니다. 전략 사이에서 중복되는 복제본(예: 위의 cpu:0cpu:1)은 이전 예와 같이 RNG 스트림이 올바르게 복원됩니다. 이 보장은 생성기가 전략 범위에 저장되고 전략 범위 외부에서 복원되는 경우(또는 그 반대) 적용되지 않습니다. 전략 외부의 장치는 전략의 복제본과 다르게 취급되기 때문입니다.

SavedModel

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

tf.random.Generator가 포함된 SavedModel을 배포 전략에 로드하는 것은 권장되지 않는데, 복제본이 모두 동일한 난수 스트림을 생성하기 때문입니다(복제 ID가 SavedModel의 그래프에서 고정되기 때문).

위의 예와 같이 분산된 tf.random.Generator(분산 전략 내에서 생성된 생성기)를 비전략 환경에 로드하는 데에도 주의할 사항이 있습니다. RNG 상태는 적절하게 복원되지만 생성된 난수는 전략의 원래 생성기와 다릅니다(마찬가지로 전략 외부의 장치는 전략의 복제본과 다르게 취급되기 때문).

상태가 없는 RNG

상태가 없는 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)

상태가 없는 모든 RNG는 seed 파라미터를 필요로 합니다. 이 파라미터는 크기가 [2]인 정수형 텐서입니다. 연산의 결과는 이 시드값에 의해 결정 됩니다.

상태 비저장 RNG에서 사용하는 RNG 알고리즘은 장치에 따라 다릅니다. 즉, 다른 장치에서 실행되는 동일한 연산은 다른 출력을 생성할 수 있습니다.

알고리즘

일반

tf.random.Generator 클래스와 stateless 함수는 모든 디바이스에서 필록스(Philox) 알고리즘을 지원합니다 ("philox" 또는 tf.random.Algorithm.PHILOX로 명시할 수 있습니다).

만약 같은 알고리즘을 쓰고 같은 상태에서 시작할 경우 서로 다른 디바이스는 같은 정수를 생성합니다. 또한 "거의 같은" 부동 소수점 수를 생성합니다. 각 디바이스의 부동 소수점 연산의 방식에 따라 약간의 오차가 발생 할 수 있습니다. (예: reduction order).

XLA 디바이스

XLA 기반 디바이스에서는 (TPU와 XLA가 활성화된 CPU/GPU) ThreeFry 알고리즘을 지원 합니다. ("threefry" 또는 tf.random.Algorithm.THREEFRY) 이 알고리즘은 TPU에서 빠르고 CPU/GPU에서는 Philox에 비해 느립니다.

이 알고리즘들에 대한 상세한 정보는 'Parallel Random Numbers: As Easy as 1, 2, 3' 논문을 참고해 주세요.