<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Copyright 2019 The TensorFlow Authors.
Step1:  乱数の生成
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https
Step2:  tf.random.Generator クラス
tf.random.Generator クラスは、それぞれの RNG 呼び出しで異なる結果を得たい場合に使用します。また、乱数が生成されるたびに更新される内部状態（tf.Variable オブジェクトが管理）を維持します。状態は tf.Variable によって管理されるため、チェックポイントの簡単な設定、自動制御依存関係、およびスレッドセーフなど、tf.Variable が提供する便利な機能を利用できます。
クラスのオブジェクトを手動で作成して tf.random.Generator を得るか、tf.random.get_global_generator() を呼び出して、デフォルトグローバルジェネレータを得ることができます。
Step3:  ジェネレータオブジェクトには、さまざまな作成方法があります。最も簡単なのは、上記に示した Generator.from_seed で、シードからジェネレータを作成します。シードは、負でない整数値です。from_seed にはオプションの引数 alg があり、このジェネレータが使用する RNG アルゴリズムを指定します。
Step4:  この詳細については、以下の「アルゴリズム」のセクションをご覧ください。
ジェネレータを作成するもう 1 つの方法に、Generator.from_non_deterministic_state を使用する方法があります。この方法で作成されたジェネレータは、時刻や OS に基づいて、非決定的状態から開始します。
Step5:  ジェネレータの作成方法はほかにもありますが、このガイドでは説明されていません。
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 などの「リセット」関数を使用する方が有効です。この関数は新しいジェネレータオブジェクトを作成しません。
Step6:  独立乱数ストリームを作成する
多くのアプリケーションでは、複数の独立乱数ストリームが必要です。ここでいう独立とは、これらのストリームがオーバーラップすることがないため、統計的に検出可能な相関を持つことがないということです。これは、Generator.split を使用して、互いに独立することが保証された複数のジェネレータを作成する（独立ストリームを生成する）ことで実現できます。
Step7:  split は、normal などの RNG メソッドと同様に、それを呼び出したジェネレータ（上記の例の g）の状態を変更します。互いに独立しているほかに、新しいジェネレータ（new_gs）は、古いジェネレータ（g）からの独立も保証されます。
新しいジェネレータをスポーンする方法は、デバイス間コピーのオーバーヘッドを回避するために、使用するジェネレータがほかの計算と同じデバイス上にあることを確実にする上でも役立ちます。次に例を示します。
Step8:  注意
Step9:  ユーザーは、関数が呼び出されたときにジェネレータオブジェクトがアライブである（ガベージコレクトされていない）ことを確認する必要があります。
tf.function 内部でジェネレータを作成する
tf.function 内部でのジェネレータの作成は、関数を初めて実行したときにのみ発生します。
Step10:  ジェネレータを引数として tf.function に渡す
tf.function の引数として使用される場合、別のジェネレータオブジェクトによって tf.function の再トレースが始まります。
Step11:  この再トレースの動作は tf.Variable と同じです。
Step12:  分散ストラテジーとのインタラクション
Generator が分散ストラテジーとインタラクションする方法には 2 つあります。
分散ストラテジーの外部でジェネレータを作成する
ストラテジーのスコープ外でジェネレータを作成する場合、すべてのレプリカによるジェネレータへのアクセスはシリアライズされるため、レプリカは異なる乱数を取得します。
Step13:  この使用方法では、ジェネレータのデバイスがレプリカとは異なるため、パフォーマンスの問題が発生する可能性があります。
分散ストラテジーの内部でジェネレータを作成する
ジェネレータがストラテジーの範囲内で作成される場合、レプリカごとに異なる独立した乱数のストリームが取得されます。
Step14:  注意
Step15:  tf.random.Generator を Strategy.run の引数として渡すことは推奨されなくなりました。Strategy.run が一般的にジェネレータではなくテンソルの引数を期待するためです。
ジェネレータを保存する
一般的に保存またはシリアル化については、tf.random.Generator を tf.Variable や tf.Module（またはそのサブクラス）と同じように扱うことができます。TF には、チェックポイントと SavedModel という 2 つのシリアル化の仕組みがあります。
チェックポイント
ジェネレータは tf.train.Checkpoint を使って保存と復元を自在に行うことができます。復元ポイントの乱数ストリームは保存ポイントの乱数ストリームと同じになります。
Step16:  また、分散ストラテジー内でも保存と復元を実行することができます。
Step17:  保存する前に、レプリカが RNG 呼び出し履歴で分岐しないことを確認する必要があります（1 つのレプリカが 1 つの RNG 呼び出しを行い、もう 1 つが 2 つの RNG 呼び出しを行うなど）。そうでない場合、RNG の内部状態が分岐してしまい、最初のレプリカの状態のみを保存する tf.train.Checkpoint が、すべてのレプリカを適切に復元できなくなります。
また、保存したチェックポイントをレプリカ数の異なる別の分散ストラテジーに復元することもできます。ストラテジー内で作成された tf.random.Generator オブジェクトは、同じストラテジーでのみ使用できるため、異なるストラテジーを復元するには以下の例に示すとおり、目的のストラテジーで新しい tf.random.Generator を作成し、それに使用する新しい tf.train.Checkpoint を作成する必要があります。
Step18:  g1 と cp1 は g2 と cp2 からの別々のオブジェクトですが、共通の filename というチェックポイントファイルと my_generator というオブジェクト名を介してリンクされています。ストラテジー間で重複するレプリカ（上の cpu
Step19:  tf.random.Generator を含む SavedModel を分散ストラテジーに読み込むのは、レプリカがすべて同じ乱数ストリームを生成するため、推奨されません。これは、レプリカの ID が SavedModel のグラフで凍結しているために起こります。
上記の例のように、分散された tf.random.Generator（分散ストラテジーで作成されたジェネレータ）を非ストラテジー環境に読み込む場合にも注意が必要です。RNG の状態は適切に復元されますが、生成される乱数は元のストラテジーのジェネレータとは異なります（やはり、ストラテジー外のデバイスは、ストラテジー内のrプリカとは異なって扱われるためです）。
ステートレス RNG
ステートレス RNG の使用法はシンプルです。純粋関数であるため、ステートや副次的効果はありません。
<ASSISTANT_TASK:>
Python Code:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Explanation: Copyright 2019 The TensorFlow Authors.
End of explanation
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()
    ])
Explanation: 乱数の生成
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://www.tensorflow.org/guide/random_numbers"><img src="https://www.tensorflow.org/images/tf_logo_32px.png">TensorFlow.org で実行</a></td>
  <td>     <a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/guide/random_numbers.ipynb"><img src="https://www.tensorflow.org/images/colab_logo_32px.png">Google Colab で実行</a>
</td>
  <td><a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/ja/guide/random_numbers.ipynb">     <img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png">     GitHubでソースを表示</a></td>
  <td>     <a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/guide/random_numbers.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png">ノートブックをダウンロード</a>
</td>
</table>
TensorFlow では、tf.random モジュールに疑似乱数ジェネレータ（RNG）を提供しています。このドキュメントは、乱数ジェネレータをどのように制御し、これらのジェネレータがほかの TensorFlow サブシステムとどのように対話するのかを説明します。
注意: TensorFlow バージョン間での乱数の一貫性は保証されていません。「バージョンの互換性」をご覧ください。
TensorFlow provides two approaches for controlling the random number generation process:
tf.random.Generator オブジェクトを明示的に使用する。各オブジェクトは、数字が生成された後に変更される状態を維持します（tf.Variable）。
tf.random.stateless_uniform などの純粋関数型のステートレスランダム関数を使用する。同一の引数を使って（シードを含む）同じデバイスでこれらの関数を呼び出すと、同じ結果が必ず生成されます。
警告: tf.random.uniform や tf.random.normal といった TF 1.x の古い RNG は、まだ使用廃止になっていませんが、使用しないことを強くお勧めします。
セットアップ
End of explanation
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]))
Explanation: tf.random.Generator クラス
tf.random.Generator クラスは、それぞれの RNG 呼び出しで異なる結果を得たい場合に使用します。また、乱数が生成されるたびに更新される内部状態（tf.Variable オブジェクトが管理）を維持します。状態は tf.Variable によって管理されるため、チェックポイントの簡単な設定、自動制御依存関係、およびスレッドセーフなど、tf.Variable が提供する便利な機能を利用できます。
クラスのオブジェクトを手動で作成して tf.random.Generator を得るか、tf.random.get_global_generator() を呼び出して、デフォルトグローバルジェネレータを得ることができます。
End of explanation
g1 = tf.random.Generator.from_seed(1, alg='philox')
print(g1.normal(shape=[2, 3]))
Explanation: ジェネレータオブジェクトには、さまざまな作成方法があります。最も簡単なのは、上記に示した Generator.from_seed で、シードからジェネレータを作成します。シードは、負でない整数値です。from_seed にはオプションの引数 alg があり、このジェネレータが使用する RNG アルゴリズムを指定します。
End of explanation
g = tf.random.Generator.from_non_deterministic_state()
print(g.normal(shape=[2, 3]))
Explanation: この詳細については、以下の「アルゴリズム」のセクションをご覧ください。
ジェネレータを作成するもう 1 つの方法に、Generator.from_non_deterministic_state を使用する方法があります。この方法で作成されたジェネレータは、時刻や OS に基づいて、非決定的状態から開始します。
End of explanation
g = tf.random.Generator.from_seed(1)
print(g.normal([]))
print(g.normal([]))
g.reset_from_seed(1)
print(g.normal([]))
Explanation: ジェネレータの作成方法はほかにもありますが、このガイドでは説明されていません。
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 などの「リセット」関数を使用する方が有効です。この関数は新しいジェネレータオブジェクトを作成しません。
End of explanation
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([]))
Explanation: 独立乱数ストリームを作成する
多くのアプリケーションでは、複数の独立乱数ストリームが必要です。ここでいう独立とは、これらのストリームがオーバーラップすることがないため、統計的に検出可能な相関を持つことがないということです。これは、Generator.split を使用して、互いに独立することが保証された複数のジェネレータを作成する（独立ストリームを生成する）ことで実現できます。
End of explanation
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
Explanation: split は、normal などの RNG メソッドと同様に、それを呼び出したジェネレータ（上記の例の g）の状態を変更します。互いに独立しているほかに、新しいジェネレータ（new_gs）は、古いジェネレータ（g）からの独立も保証されます。
新しいジェネレータをスポーンする方法は、デバイス間コピーのオーバーヘッドを回避するために、使用するジェネレータがほかの計算と同じデバイス上にあることを確実にする上でも役立ちます。次に例を示します。
End of explanation
g = tf.random.Generator.from_seed(1)
@tf.function
def foo():
  return g.normal([])
print(foo())
Explanation: 注意: 理論的には、split の代わりに from_seed などのコンストラクタを使用して新しいジェネレータを取得することはできますが、そうした場合、新しいジェネレータがグローバルジェネレータから独立する保証がなくなります。また、偶発的に、同じシードまたは乱数ストリームをオーバーラップさせるシードを伴うジェネレータを 2 つ作成してしまうリスクもあります。
分割されたジェネレータに split を呼び出して分割を再帰的に実行することができます。再帰の深度には制限（整数オーバーフローの禁止）はありません。
tf.function とのインタラクション
tf.random.Generator は、tf.function と使用した場合の tf.Variable と同じルールに従います。これには、3 つの側面が含まれます。
tf.function の外部でジェネレータを作成する
tf.function は、その外部で作成されたジェネレータを使用できます。
End of explanation
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())
Explanation: ユーザーは、関数が呼び出されたときにジェネレータオブジェクトがアライブである（ガベージコレクトされていない）ことを確認する必要があります。
tf.function 内部でジェネレータを作成する
tf.function 内部でのジェネレータの作成は、関数を初めて実行したときにのみ発生します。
End of explanation
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)
Explanation: ジェネレータを引数として tf.function に渡す
tf.function の引数として使用される場合、別のジェネレータオブジェクトによって tf.function の再トレースが始まります。
End of explanation
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)
Explanation: この再トレースの動作は tf.Variable と同じです。
End of explanation
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)
Explanation: 分散ストラテジーとのインタラクション
Generator が分散ストラテジーとインタラクションする方法には 2 つあります。
分散ストラテジーの外部でジェネレータを作成する
ストラテジーのスコープ外でジェネレータを作成する場合、すべてのレプリカによるジェネレータへのアクセスはシリアライズされるため、レプリカは異なる乱数を取得します。
End of explanation
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([])))
Explanation: この使用方法では、ジェネレータのデバイスがレプリカとは異なるため、パフォーマンスの問題が発生する可能性があります。
分散ストラテジーの内部でジェネレータを作成する
ジェネレータがストラテジーの範囲内で作成される場合、レプリカごとに異なる独立した乱数のストリームが取得されます。
End of explanation
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))
Explanation: 注意: 現在、tf.random.Generator には異なるレプリカに異なるストリームの代わりに同一のストリームを取得させるオプションはありません（厳密には困難なことではありませんが）。この機能のユースケースがある場合は、TensorFlow 開発者に知らせてください。
ジェネレータがシードされる場合（Generator.from_seed で作成されるなど）、レプリカごとに異なる無関係な数字が取得されるにも関わらず、乱数はシードによって決定されます。レプリカで生成される乱数をレプリカ ID のハッシュやすべてのレプリカに共通するランダムな「素」数として考えることができます。そのため、システム全体は依然として決定的なままです。
tf.random.Generator は、Strategy.run 内にも作成できます。
End of explanation
filename = "./checkpoint"
g = tf.random.Generator.from_seed(1)
cp = tf.train.Checkpoint(generator=g)
print(g.normal([]))
cp.write(filename)
print("RNG stream from saving point:")
print(g.normal([]))
print(g.normal([]))
cp.restore(filename)
print("RNG stream from restoring point:")
print(g.normal([]))
print(g.normal([]))
Explanation: tf.random.Generator を Strategy.run の引数として渡すことは推奨されなくなりました。Strategy.run が一般的にジェネレータではなくテンソルの引数を期待するためです。
ジェネレータを保存する
一般的に保存またはシリアル化については、tf.random.Generator を tf.Variable や tf.Module（またはそのサブクラス）と同じように扱うことができます。TF には、チェックポイントと SavedModel という 2 つのシリアル化の仕組みがあります。
チェックポイント
ジェネレータは tf.train.Checkpoint を使って保存と復元を自在に行うことができます。復元ポイントの乱数ストリームは保存ポイントの乱数ストリームと同じになります。
End of explanation
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([])))
with strat.scope():
  cp.write(filename)
  print("RNG stream from saving point:")
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
with strat.scope():
  cp.restore(filename)
  print("RNG stream from restoring point:")
  print(strat.run(lambda: g.normal([])))
  print(strat.run(lambda: g.normal([])))
Explanation: また、分散ストラテジー内でも保存と復元を実行することができます。
End of explanation
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([])))
with strat1.scope():
  cp1.write(filename)
  print("RNG stream from saving point:")
  print(strat1.run(lambda: g1.normal([])))
  print(strat1.run(lambda: g1.normal([])))
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([])))
Explanation: 保存する前に、レプリカが RNG 呼び出し履歴で分岐しないことを確認する必要があります（1 つのレプリカが 1 つの RNG 呼び出しを行い、もう 1 つが 2 つの RNG 呼び出しを行うなど）。そうでない場合、RNG の内部状態が分岐してしまい、最初のレプリカの状態のみを保存する tf.train.Checkpoint が、すべてのレプリカを適切に復元できなくなります。
また、保存したチェックポイントをレプリカ数の異なる別の分散ストラテジーに復元することもできます。ストラテジー内で作成された tf.random.Generator オブジェクトは、同じストラテジーでのみ使用できるため、異なるストラテジーを復元するには以下の例に示すとおり、目的のストラテジーで新しい tf.random.Generator を作成し、それに使用する新しい tf.train.Checkpoint を作成する必要があります。
End of explanation
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())
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())
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())
Explanation: g1 と cp1 は g2 と cp2 からの別々のオブジェクトですが、共通の filename というチェックポイントファイルと my_generator というオブジェクト名を介してリンクされています。ストラテジー間で重複するレプリカ（上の cpu:0 To cpu:1 など）は、前の例と同様に、RNG ストリームが適切に復元されます。この動作は、ジェネレータが 1 つのストラテジーの範囲内で保存されており、ストラテジーの範囲外で復元される場合やその逆の場合には保証されません。ストラテジー外のデバイスは、ストラテジー内のレプリカと異なって処理されるためです。
SavedModel
tf.random.Generator は、SavedModel に保存可能です。ジェネレータはストラテジーの範囲内で作成できます。保存もストラテジーの範囲内で行われます。
End of explanation
print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))
print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))
Explanation: tf.random.Generator を含む SavedModel を分散ストラテジーに読み込むのは、レプリカがすべて同じ乱数ストリームを生成するため、推奨されません。これは、レプリカの ID が SavedModel のグラフで凍結しているために起こります。
上記の例のように、分散された tf.random.Generator（分散ストラテジーで作成されたジェネレータ）を非ストラテジー環境に読み込む場合にも注意が必要です。RNG の状態は適切に復元されますが、生成される乱数は元のストラテジーのジェネレータとは異なります（やはり、ストラテジー外のデバイスは、ストラテジー内のrプリカとは異なって扱われるためです）。
ステートレス RNG
ステートレス RNG の使用法はシンプルです。純粋関数であるため、ステートや副次的効果はありません。
End of explanation
<END_TASK>