Path: blob/master/site/ja/guide/random_numbers.ipynb
25115 views
Copyright 2019 The TensorFlow Authors.
乱数の生成
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 は、まだ使用廃止になっていませんが、使用しないことを強くお勧めします。
セットアップ
tf.random.Generator
クラス
tf.random.Generator
クラスは、それぞれの RNG 呼び出しで異なる結果を得たい場合に使用します。また、乱数が生成されるたびに更新される内部状態(tf.Variable
オブジェクトが管理)を維持します。状態は tf.Variable
によって管理されるため、チェックポイントの簡単な設定、自動制御依存関係、およびスレッドセーフなど、tf.Variable
が提供する便利な機能を利用できます。
クラスのオブジェクトを手動で作成して tf.random.Generator
を得るか、tf.random.get_global_generator()
を呼び出して、デフォルトグローバルジェネレータを得ることができます。
ジェネレータオブジェクトには、さまざまな作成方法があります。最も簡単なのは、上記に示した Generator.from_seed
で、シードからジェネレータを作成します。シードは、負でない整数値です。from_seed
にはオプションの引数 alg
があり、このジェネレータが使用する RNG アルゴリズムを指定します。
この詳細については、以下の「アルゴリズム」のセクションをご覧ください。
ジェネレータを作成するもう 1 つの方法に、Generator.from_non_deterministic_state
を使用する方法があります。この方法で作成されたジェネレータは、時刻や OS に基づいて、非確定的状態から開始します。
ジェネレータの作成方法はほかにもありますが、このガイドでは説明されていません。
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
などの「リセット」関数を使用する方が有効です。この関数は新しいジェネレータオブジェクトを作成しません。
独立乱数ストリームを作成する
多くのアプリケーションでは、複数の独立乱数ストリームが必要です。ここでいう独立とは、これらのストリームがオーバーラップすることがないため、統計的に検出可能な相関を持つことがないということです。これは、Generator.split
を使用して、互いに独立することが保証された複数のジェネレータを作成する(独立ストリームを生成する)ことで実現できます。
split
は、normal
などの RNG メソッドと同様に、それを呼び出したジェネレータ(上記の例の g
)の状態を変更します。互いに独立しているほかに、新しいジェネレータ(new_gs
)は、古いジェネレータ(g
)からの独立も保証されます。
新しいジェネレータをスポーンする方法は、デバイス間コピーのオーバーヘッドを回避するために、使用するジェネレータがほかの計算と同じデバイス上にあることを確実にする上でも役立ちます。次に例を示します。
注意: 理論的には、split
の代わりに from_seed
などのコンストラクタを使用して新しいジェネレータを取得することはできますが、そうした場合、新しいジェネレータがグローバルジェネレータから独立する保証がなくなります。また、偶発的に、同じシードまたは乱数ストリームをオーバーラップさせるシードを伴うジェネレータを 2 つ作成してしまうリスクもあります。
split ジェネレータに split
を呼び出して分割を再帰的に実行することができます。再帰の深度には制限(整数オーバーフローの禁止)はありません。
tf.function
とのインタラクション
tf.random.Generator
は、tf.function
と使用した場合の tf.Variable
と同じルールに従います。これには、3 つの側面が含まれます。
tf.function
の外部でジェネレータを作成する
tf.function
は、その外部で作成されたジェネレータを使用できます。
ユーザーは、関数が呼び出されたときにジェネレータオブジェクトがアライブである(ガベージコレクトされていない)ことを確認する必要があります。
tf.function
内部でジェネレータを作成する
tf.function
内部でのジェネレータの作成は、関数を初めて実行したときにのみ発生します。
ジェネレータを引数として tf.function
に渡す
tf.function
の引数として使用される場合、別のジェネレータオブジェクトによって tf.function
の再トレースが始まります。
この再トレースの動作は tf.Variable
と同じです。
分散ストラテジーとのインタラクション
Generator
が分散ストラテジーとインタラクションする方法には 2 つあります。
分散ストラテジーの外部でジェネレータを作成する
ストラテジーのスコープ外でジェネレータを作成する場合、すべてのレプリカによるジェネレータへのアクセスはシリアライズされるため、レプリカは異なる乱数を取得します。
この使用方法では、ジェネレータのデバイスがレプリカとは異なるため、パフォーマンスの問題が発生する可能性があります。
分散ストラテジーの内部でジェネレータを作成する
ジェネレータがストラテジーの範囲内で作成される場合、レプリカごとに異なる独立した乱数のストリームが取得されます。
注意: 現在、tf.random.Generator
には異なるレプリカに異なるストリームの代わりに同一のストリームを取得させるオプションはありません(厳密には困難なことではありませんが)。この特徴量のユースケースがある場合は、TensorFlow 開発者に知らせてください。
ジェネレータがシードされる場合(Generator.from_seed
で作成されるなど)、レプリカごとに異なる無関係な数字が取得されるにも関わらず、乱数はシードによって決定されます。レプリカで生成される乱数をレプリカ ID のハッシュやすべてのレプリカに共通するランダムな「素」数として考えることができます。そのため、システム全体は依然として確定的なままです。
tf.random.Generator
は、Strategy.run
内にも作成できます。
tf.random.Generator
を Strategy.run
の引数として渡すことは推奨されなくなりました。Strategy.run
が一般的にジェネレータではなくテンソルの引数を期待するためです。
ジェネレータを保存する
一般的に保存またはシリアル化については、tf.random.Generator
を tf.Variable
や tf.Module
(またはそのサブクラス)と同じように扱うことができます。TF には、チェックポイントと SavedModel という 2 つのシリアル化の仕組みがあります。
チェックポイント
ジェネレータは tf.train.Checkpoint
を使って保存と復元を自在に行うことができます。復元ポイントの乱数ストリームは保存ポイントの乱数ストリームと同じになります。
また、分散ストラテジー内でも保存と復元を実行することができます。
保存する前に、レプリカが RNG 呼び出し履歴で分岐しないことを確認する必要があります(1 つのレプリカが 1 つの RNG 呼び出しを行い、もう 1 つが 2 つの RNG 呼び出しを行うなど)。そうでない場合、RNG の内部状態が分岐してしまい、最初のレプリカの状態のみを保存する tf.train.Checkpoint
が、すべてのレプリカを適切に復元できなくなります。
また、保存したチェックポイントをレプリカ数の異なる別の分散ストラテジーに復元することもできます。ストラテジー内で作成された tf.random.Generator
オブジェクトは、同じストラテジーでのみ使用できるため、異なるストラテジーを復元するには以下の例に示すとおり、目的のストラテジーで新しい tf.random.Generator
を作成し、それに使用する新しい tf.train.Checkpoint
を作成する必要があります。
g1
と cp1
は g2
と cp2
からの別々のオブジェクトですが、共通の filename
というチェックポイントファイルと my_generator
というオブジェクト名を介してリンクされています。ストラテジー間で重複するレプリカ(上の cpu:0
To cpu:1
など)は、前の例と同様に、RNG ストリームが適切に復元されます。この動作は、ジェネレータが 1 つのストラテジーの範囲内で保存されており、ストラテジーの範囲外で復元される場合やその逆の場合には保証されません。ストラテジー外のデバイスは、ストラテジー内のレプリカと異なって処理されるためです。
SavedModel
tf.random.Generator
は、SavedModel に保存可能です。ジェネレータはストラテジーの範囲内で作成できます。保存もストラテジーの範囲内で行われます。
tf.random.Generator
を含む SavedModel を分散ストラテジーに読み込むのは、レプリカがすべて同じ乱数ストリームを生成するため、推奨されません。これは、レプリカの ID が SavedModel のグラフで凍結しているために起こります。
上記の例のように、分散された tf.random.Generator
(分散ストラテジーで作成されたジェネレータ)を非ストラテジー環境に読み込む場合にも注意が必要です。RNG の状態は適切に復元されますが、生成される乱数は元のストラテジーのジェネレータとは異なります(やはり、ストラテジー外のデバイスは、ストラテジー内のrプリカとは異なって扱われるためです)。
ステートレス RNG
ステートレス RNG の使用法はシンプルです。純粋関数であるため、ステートや副次的効果はありません。
各ステートレス 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」をご覧ください。