TensorFlow Probability 0.10 : ガイド : TensorFlow Distributions : 優しいイントロダクション (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/18/2020 (0.10.0)
* 本ページは、TensorFlow Probability の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
ガイド : TensorFlow Distributions : 優しいイントロダクション
このノートブックでは、TensorFlow 分布 (TFD for short) を探求します。このノートブックの目的は tensor shape の TFD の処理を理解することを含めて、貴方に学習カーブを優しく上げて頂くことです。このノートブックは抽象的な概念の前に例を提示しようとします。最初に物事を進めるための標準的で容易な方法を提示して、最も一般的な抽象的な眺めは最後まで取っておきます。もし貴方がより抽象的でリファレンス・スタイルのチュートリアルを好みタイプであれば、TensorFlow 分布 shape の理解 を調べてください。ここでのマテリアルについてどのような質問でもあれば、TensorFlow Probability メーリングリスト にコンタクト (or 参加) することを躊躇しないでください。We’re happy to help.
始める前に、適切なライブラリをインポートする必要があります。私達の全体的なライブラリは tensorflow_probability です。慣習により、一般的に distributions ライブラリを tfd として参照します。
Tensorflow Eager は TensorFlow のための命令型実行環境です。TensorFlow eager では、総ての TF 演算は直ちに評価されて結果を生成します。これは TensorFlow の標準的な「グラフ」モードとは対称的です、そこでは TF 演算はグラフに (後で実行される) ノードを追加します。ここで表わされる概念のどれもそれに依存はしてませんが、このノートブック全体は TF Eager を使用して書かれます、そして TFP はグラフモードでも利用できます。
import collections import tensorflow as tf import tensorflow_probability as tfp tfd = tfp.distributions try: tf.compat.v1.enable_eager_execution() except ValueError: pass import matplotlib.pyplot as plt
基本的な単変量分布
Let’s dive right in そして正規分布を作成しましょう :
n = tfd.Normal(loc=0., scale=1.) n
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>
それからサンプルをドローできます :
n.sample()
<tf.Tensor: shape=(), dtype=float32, numpy=0.25322816>
複数のサンプルをドローできます :
n.sample(3)
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.4658079, -0.5653636, 0.9314412], dtype=float32)>
log prob を評価できます :
n.log_prob(0.)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>
複数の対数確率を評価できます :
n.log_prob([0., 2., 4.])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-0.9189385, -2.9189386, -8.918939 ], dtype=float32)>
広範囲の分布を持ちます。Bernoulli を試してみましょう:
b = tfd.Bernoulli(probs=0.7) b
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[] event_shape=[] dtype=int32>
b.sample()
<tf.Tensor: shape=(), dtype=int32, numpy=1>
b.sample(8)
<tf.Tensor: shape=(8,), dtype=int32, numpy=array([1, 0, 0, 0, 1, 0, 1, 0], dtype=int32)>
b.log_prob(1)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.35667497>
b.log_prob([1, 0, 1, 0])
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([-0.35667497, -1.2039728 , -0.35667497, -1.2039728 ], dtype=float32)>
多変量分布
対角共分散 (= diagonal covariance) で多変量正規分布を作成します:
nd = tfd.MultivariateNormalDiag(loc=[0., 10.], scale_diag=[1., 4.]) nd
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>
これを先に作成した単変量正規分布と比較します、何が違うのでしょう?
tfd.Normal(loc=0., scale=1.)
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>
単変量正規分布は () の event_shape を持つことを見ます、これはそれがスカラー分布であることを示します。多変量正規分布は 2 の event_shape を持ち、これはこの分布の基本的な事象空間 (= event space) は 2-次元であることを示しています。
サンプリングはちょうど前のように動作します :
nd.sample()
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-1.2489667, 15.025171 ], dtype=float32)>
nd.sample(5)
<tf.Tensor: shape=(5, 2), dtype=float32, numpy= array([[-1.5439653 , 8.9968405 ], [-0.38730723, 12.448896 ], [-0.8697963 , 9.330035 ], [-1.2541095 , 10.268944 ], [ 2.3475595 , 13.184147 ]], dtype=float32)>
nd.log_prob([0., 10])
<tf.Tensor: shape=(), dtype=float32, numpy=-3.2241714>
多変量正規分布は一般には対角共分散を持ちません。TFD は多変量正規分布を作成するための複数の方法を提供します、これはフル共分散仕様を含み、ここでこれを使用します。
nd = tfd.MultivariateNormalFullCovariance( loc = [0., 5], covariance_matrix = [[1., .7], [.7, 1.]]) data = nd.sample(200) plt.scatter(data[:, 0], data[:, 1], color='blue', alpha=0.4) plt.axis([-5, 5, 0, 10]) plt.title("Data set") plt.show()
複数の分布
最初の Bernoulli 分布は一枚の公正なコインのフリップを表しました。独立な Bernoulli 分布のバッチもまた作成できます、単一の Distribution オブジェクトで、それぞれはそれら自身のパラメータを伴います:
b3 = tfd.Bernoulli(probs=[.3, .5, .7]) b3
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>
これが何を意味するかを明らかにすることは重要です。上の呼び出しは 3 つの独立な Bernoulli 分布を定義し、これらは同じ Python Distribution オブジェクトに偶々含まれています。3 つの分布は個別には操作はできません。batch_shape がどのように (3,) であるかに注意してください、これは 3 つの分布のバッチを示します、そして event_shape は () です、これは 個々の分布は単変量事象空間 を持つことを示します。
sample を呼びだせば、3 つ総てからサンプルを得ます :
b3.sample()
<tf.Tensor: shape=(3,), dtype=int32, numpy=array([0, 1, 1], dtype=int32)>
b3.sample(6)
<tf.Tensor: shape=(6, 3), dtype=int32, numpy= array([[1, 0, 1], [0, 1, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 1, 0]], dtype=int32)>
prob を呼び出す場合、(これは log_prob と同じ shape セマンティクスを持ちます ; 明確にするためにこれらの小さな Bernoulli サンプルで prob を使用します、アプリケーションでは通常は log_prob が好まれますが) それにベクトルを渡してその値を生成する各コインの確率を評価できます :
b3.prob([1, 1, 0])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5 , 0.29999998], dtype=float32)>
何故 API はバッチ shape を含むのでしょう?意味的には、分布のリストを作成して for ループでそれらに渡り反復することで同じ計算を遂行できるでしょう (少なくとも Eager モードで、TF graph モードでは tf.while ループが必要でしょう)。けれども、同一にパラメータ化された分布の (潜在的に巨大な) セットを持つことは極めて一般的で、そして可能なときはいつでもベクトル化された計算の使用はハードウェア・アクセラレータを使用して高速な計算を遂行することを可能にする点において主要な構成要素です。
Independent を使用してバッチを事象に集積する (= aggregate)
前のセクションでは、b3 を作成しました、3 つのコイン投げを表わす単一の Distribution オブジェクトです。ベクトル $v$ 上で b3.prob を呼び出した場合、$i$ 番目のエントリは $i$ 番目のコインが値 $v[i]$ を取る確率でした。
代わりに、同じ基礎的な族 (= family) から独立な確率変数に渡る「同時」分布を指定したいと仮定します。これは数学的には異なるオブジェクトで、そこではこの新しい分布に対して、ベクトル $v$ 上の prob は (コインのセット全体がベクトル $v$ に一致する) 確率を表わす 単一の値 を返します。
これをどのように成し遂げるのでしょう?Independent と呼ばれる「高階」分布を使用します、これは分布を取り (batch shape が event shape に移された) 新しい分布を生み出します :
b3_joint = tfd.Independent(b3, reinterpreted_batch_ndims=1) b3_joint
<tfp.distributions.Independent 'IndependentBernoulli' batch_shape=[] event_shape=[3] dtype=int32>
shape を元の b3 のそれと比較します :
b3
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>
約束されたように、その Independent はバッチ shape を事象 shape に移したことを見ます : b3_joint は 3-次元事象空間 (event_shape = (3,)) に渡る単一の分布 (batch_shape = ()) です。
セマンティクスをチェックしましょう :
b3_joint.prob([1, 1, 0])
<tf.Tensor: shape=(), dtype=float32, numpy=0.044999998>
同じ結果を得るための代替の方法は b3 を使用して確率を計算して乗算により手動で reduction を行なうことです (あるいは、対数確率が使用されるより通常のケースでは、合計します) :
tf.reduce_prod(b3.prob([1, 1, 0]))
<tf.Tensor: shape=(), dtype=float32, numpy=0.044999994>
Indpendent は望まれる概念をより明示的に表わすことをユーザに可能にします。これを極めて有用であるとみなします、それは厳密には必要ではありませんが。
Fun facts:
- b3.sample と b3_joint.sample は異なる概念の実装を持ちますが、見分けがつかない出力を持ちます : 独立な分布のバッチと Independent を使用したバッチから作成された単一の分布の間の違いはサンプリングのときではなく、確率を計算するときに現出します。
- MultivariateNormalDiag はスカラー Nomal と Independent 分布を使用して自明に実装できるでしょう (それは実際にはこのように実装されていませんが、可能でしょう)。
多変量分布のバッチ
3 つの full-共分散 2-次元多変量正規分布のバッチを作成します :
nd_batch = tfd.MultivariateNormalFullCovariance( loc = [[0., 0.], [1., 1.], [2., 2.]], covariance_matrix = [[[1., .1], [.1, 1.]], [[1., .3], [.3, 1.]], [[1., .5], [.5, 1.]]]) nd_batch
<tfp.distributions.MultivariateNormalFullCovariance 'MultivariateNormalFullCovariance' batch_shape=[3] event_shape=[2] dtype=float32>
batch_shape = (3,) を見ますので、3 つの独立な多変量正規分布があり、そして event_shape = (2,) ですので、各多変量正規分布は 2-次元です。この例では、個々の分布は独立な要素を持ちません。
サンプリングが動作します :
nd_batch.sample(4)
<tf.Tensor: shape=(4, 3, 2), dtype=float32, numpy= array([[[ 0.7367498 , 2.730996 ], [-0.74080074, -0.36466932], [ 0.6516018 , 0.9391426 ]], [[ 1.038303 , 0.12231752], [-0.94788766, -1.204232 ], [ 4.059758 , 3.035752 ]], [[ 0.56903946, -0.06875849], [-0.35127294, 0.5311631 ], [ 3.4635801 , 4.565582 ]], [[-0.15989424, -0.25715637], [ 0.87479895, 0.97391707], [ 0.5211419 , 2.32108 ]]], dtype=float32)>
batch_shape = (3,) そして event_shape = (2,) ですから、log_prob に shape (3, 2) の tensor を渡します :
nd_batch.log_prob([[0., 0.], [1., 1.], [2., 2.]])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.8328519, -1.7907217, -1.694036 ], dtype=float32)>
ブロードキャスティング, aka 何故これはそれほど混乱させるのでしょう?
ここまで行なったことから要約すると、総ての分布はバッチ shape B と事象 shape E を持ちます。BE を shapes の連結とします :
- 単変量スカラー分布 n と b について、BE = ()..
- 2-次元多変量正規分布 nd について、BE = (2)。
- b3 と b3_joint の両者について、BE = (3)。
- 多変量正規分布 ndb のバッチについて、BE = (3, 2)。
ここまで使用した「評価ルール」は :
- 引数なしの sample は shape BE の tensor を返します; スカラー n を伴うサンプリングは “n by BE” tensor を返します。
- prob と log_prob は shape BE の tensor を取り shape B の結果を返します。
prob と log_prob のための実際の「評価ルール」はより複雑で、潜在的なパワーとスピードを、しかし複雑さと挑戦も提供する方法によります。実際のルールは (本質的に) log_prob への引数は BE に対して ブロードキャスト可能 です ; 任意の「特別な (= extra)」次元は出力で保持されます。
含蓄を調べてみましょう。単変量正規分布 n, BE = () について、log_prob はスカラーを想定します。log_prob に non-empty shape を持つ tensor を渡す場合、それらは出力のバッチ次元として現出します:
n = tfd.Normal(loc=0., scale=1.) n
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>
n.log_prob(0.)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>
n.log_prob([0.])
<tf.Tensor: shape=(1,), dtype=float32, numpy=array([-0.9189385], dtype=float32)>
n.log_prob([[0., 1.], [-1., 2.]])
<tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[-0.9189385, -1.4189385], [-1.4189385, -2.9189386]], dtype=float32)>
2-次元多変量正規分布 nd へ向かいましょう (例示目的でパラメータは変更されています) :
nd = tfd.MultivariateNormalDiag(loc=[0., 1.], scale_diag=[1., 1.]) nd
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>
log_prob は shape (2,) を持つ引数を「期待」しますが、それはこの shape に対してブロードキャストする任意の引数を受け取ります :
nd.log_prob([0., 0.])
<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>
しかし「より多くの」サンプルを渡して、そしてそれらの log_prob 総てを一度に評価することもできます :
nd.log_prob([[0., 0.], [1., 1.], [2., 2.]])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.337877 , -2.337877 , -4.3378773], dtype=float32)>
多分魅力は少ないですが、事象次元に渡りブロードキャストすることもできます :
nd.log_prob([0.])
<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>
nd.log_prob([[0.], [1.], [2.]])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.337877 , -2.337877 , -4.3378773], dtype=float32)>
このようにブロードキャストすることは私達の “enable broadcasting whenever possible” 設計の結果です ; この使用方法は幾分議論の的になりそして TFP の将来バージョンで潜在的に除去されるかもしれません。
さて 3 つのコインの例を再度見てみましょう :
b3 = tfd.Bernoulli(probs=[.3, .5, .7])
ここで、各コインが表が出る確率を表わすためのブロードキャスティングの使用は非常に直感的です :
b3.prob([1])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5 , 0.7 ], dtype=float32)>
(これを b3.prob([1., 1., 1.]) と比較してください、これは b3 が導入されたところで使用されたでしょう。)
今は各コインについて、コインが表になる確率と裏になる確率を知ることを望むとします。次を試すことを想像するかもしれません :
b3.log_prob([0, 1])
不幸なことに、これは 長くて not-very-readable スタックトレースを伴うエラーを生成します。b3 は BE = (3) を持ちますので、b3.prob には (3,) に対してブロードキャスト可能な何かを渡さなければなりません。[0, 1] は shape (2) を持ちますので、それはブロードキャストしないでエラーを作成します。代わりに、次のように言わなければなりません :
b3.prob([[0], [1]])
<tf.Tensor: shape=(2, 3), dtype=float32, numpy= array([[0.7, 0.5, 0.3], [0.3, 0.5, 0.7]], dtype=float32)>
Why? [[0], [1]] は shape (2, 1) を持ちますので、それは (2, 3) のブロードキャスト shape を作成するために shape (3) に対してブロードキャストします。
ブロードキャスティングは非常にパワフルです : それが使用されるメモリの総量において 1 桁削減を可能にするケースがあるでしょう、そしてそれはしばしばユーザ・コードをより短くします。けれども、それでプログラムすることは挑戦的である可能性があります。log_prob を呼び出してエラーを得る場合、ブロードキャストの失敗は殆どいつも問題かもしれません。
Going Farther
このチュートリアルでは、単純な紹介を (願わくば) 提供しました。更に先に進むための幾つかの指針は :
- event_shape, batch_shape と sample_shape は任意のランクであり得ます (このチュートリアルではそれらは常にスカラーかランク 1 です)。これはパワーを増大しますが再度プログラミングの挑戦に繋がるかもしれません、特にブロードキャスティングを伴うときには。shape 操作を更に深く掘り下げて調べるためには、TensorFlow 分布 shape の理解 を見てください。
- TFP は Bijector として知られるパワフルな抽象を含みます、これは TransformedDistribution と連携して (既存の分布の可逆な (= invertible) 変換である) 新しい分布を容易に作成するための柔軟で、合成的な方法を与えます。私達はこれについてのチュートリアルを間もなく書こうとしますが、当面は、この ドキュメント をチェックしてください。
以上