Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/ja/guide/random_numbers.ipynb
25115 views
Kernel: Python 3
#@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.

乱数の生成

TensorFlow では、tf.random モジュールに疑似乱数ジェネレータ(RNG)を提供しています。このドキュメントは、乱数ジェネレータをどのように制御し、これらのジェネレータがほかの TensorFlow サブシステムとどのように対話するのかを説明します。

注意: TensorFlow バージョン間での乱数の一貫性は保証されていません。「バージョンの互換性」をご覧ください。

TensorFlow provides two approaches for controlling the random number generation process:

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

tf.random.Generator クラス

tf.random.Generator クラスは、それぞれの RNG 呼び出しで異なる結果を得たい場合に使用します。また、乱数が生成されるたびに更新される内部状態(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]))

ジェネレータオブジェクトには、さまざまな作成方法があります。最も簡単なのは、上記に示した Generator.from_seed で、シードからジェネレータを作成します。シードは、負でない整数値です。from_seed にはオプションの引数 alg があり、このジェネレータが使用する RNG アルゴリズムを指定します。

g1 = tf.random.Generator.from_seed(1, alg='philox') print(g1.normal(shape=[2, 3]))

この詳細については、以下の「アルゴリズム」のセクションをご覧ください。

ジェネレータを作成するもう 1 つの方法に、Generator.from_non_deterministic_state を使用する方法があります。この方法で作成されたジェネレータは、時刻や OS に基づいて、非確定的状態から開始します。

g = tf.random.Generator.from_non_deterministic_state() print(g.normal(shape=[2, 3]))

ジェネレータの作成方法はほかにもありますが、このガイドでは説明されていません。

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

独立乱数ストリームを作成する

多くのアプリケーションでは、複数の独立乱数ストリームが必要です。ここでいう独立とは、これらのストリームがオーバーラップすることがないため、統計的に検出可能な相関を持つことがないということです。これは、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([]))

split は、normal などの 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

注意: 理論的には、split の代わりに from_seed などのコンストラクタを使用して新しいジェネレータを取得することはできますが、そうした場合、新しいジェネレータがグローバルジェネレータから独立する保証がなくなります。また、偶発的に、同じシードまたは乱数ストリームをオーバーラップさせるシードを伴うジェネレータを 2 つ作成してしまうリスクもあります。

split ジェネレータに split を呼び出して分割を再帰的に実行することができます。再帰の深度には制限(整数オーバーフローの禁止)はありません。

tf.function とのインタラクション

tf.random.Generator は、tf.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.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.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)

この再トレースの動作は 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)

分散ストラテジーとのインタラクション

Generator が分散ストラテジーとインタラクションする方法には 2 つあります。

分散ストラテジーの外部でジェネレータを作成する

ストラテジーのスコープ外でジェネレータを作成する場合、すべてのレプリカによるジェネレータへのアクセスはシリアライズされるため、レプリカは異なる乱数を取得します。

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)

この使用方法では、ジェネレータのデバイスがレプリカとは異なるため、パフォーマンスの問題が発生する可能性があります。

分散ストラテジーの内部でジェネレータを作成する

ジェネレータがストラテジーの範囲内で作成される場合、レプリカごとに異なる独立した乱数のストリームが取得されます。

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

注意: 現在、tf.random.Generator には異なるレプリカに異なるストリームの代わりに同一のストリームを取得させるオプションはありません(厳密には困難なことではありませんが)。この特徴量のユースケースがある場合は、TensorFlow 開発者に知らせてください。

ジェネレータがシードされる場合(Generator.from_seed で作成されるなど)、レプリカごとに異なる無関係な数字が取得されるにも関わらず、乱数はシードによって決定されます。レプリカで生成される乱数をレプリカ ID のハッシュやすべてのレプリカに共通するランダムな「素」数として考えることができます。そのため、システム全体は依然として確定的なままです。

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

tf.random.GeneratorStrategy.run の引数として渡すことは推奨されなくなりました。Strategy.run が一般的にジェネレータではなくテンソルの引数を期待するためです。

ジェネレータを保存する

一般的に保存またはシリアル化については、tf.random.Generatortf.Variabletf.Module(またはそのサブクラス)と同じように扱うことができます。TF には、チェックポイントSavedModel という 2 つのシリアル化の仕組みがあります。

チェックポイント

ジェネレータは tf.train.Checkpoint を使って保存と復元を自在に行うことができます。復元ポイントの乱数ストリームは保存ポイントの乱数ストリームと同じになります。

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

また、分散ストラテジー内でも保存と復元を実行することができます。

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

保存する前に、レプリカが RNG 呼び出し履歴で分岐しないことを確認する必要があります(1 つのレプリカが 1 つの RNG 呼び出しを行い、もう 1 つが 2 つの 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([])))
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([])))

g1cp1g2cp2 からの別々のオブジェクトですが、共通の filename というチェックポイントファイルと my_generator というオブジェクト名を介してリンクされています。ストラテジー間で重複するレプリカ(上の cpu:0 To cpu:1 など)は、前の例と同様に、RNG ストリームが適切に復元されます。この動作は、ジェネレータが 1 つのストラテジーの範囲内で保存されており、ストラテジーの範囲外で復元される場合やその逆の場合には保証されません。ストラテジー外のデバイスは、ストラテジー内のレプリカと異なって処理されるためです。

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

tf.random.Generator を含む SavedModel を分散ストラテジーに読み込むのは、レプリカがすべて同じ乱数ストリームを生成するため、推奨されません。これは、レプリカの ID が SavedModel のグラフで凍結しているために起こります。

上記の例のように、分散された tf.random.Generator(分散ストラテジーで作成されたジェネレータ)を非ストラテジー環境に読み込む場合にも注意が必要です。RNG の状態は適切に復元されますが、生成される乱数は元のストラテジーのジェネレータとは異なります(やはり、ストラテジー外のデバイスは、ストラテジー内のrプリカとは異なって扱われるためです)。

ステートレス RNG

ステートレス RNG の使用法はシンプルです。純粋関数であるため、ステートや副次的効果はありません。

print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2])) print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))

各ステートレス RNG には、seed 引数が必要です。これは、形状 [2] の整数テンソルである必要があります。この演算の結果はこのシードによって完全に決定されます。

ステートレス RNG で使用される RNG アルゴリズムはデバイス依存型であるため、異なるデバイスで実行している同一の演算の出力は異なることがあります。

アルゴリズム

全般

tf.random.Generator クラスと stateless 関数は、すべてのデバイスで Philox アルゴリズム("philox" または tf.random.Algorithm.PHILOX として記述されている)をサポートします。

異なるデバイスで同じアルゴリズムを使用し、同じ状態から開始した場合、同じ整数が生成されます。また、デバイスが浮動小数計算の実行の仕方がデバイスによって異なるため(還元順など)わずかな数値の違いが出るかもしれませんが、「ほぼ同じ」浮動小数点数も生成します。

XLA デバイス

XLA 駆動のデバイス(TPU など、および XLA が有効で和える場合の CPU/GPU)では、ThreeFry アルゴリズム("threefry" また tf.random.Algorithm.THREEFRY)もサポートされています。このアルゴリズムは TPU では高速ですが、Philox と比べると、CPU/GPU では遅くなります。

これらのアルゴリズムに関する詳細は、「Parallel Random Numbers: As Easy as 1, 2, 3」をご覧ください。