Distribusi TensorFlow: Pengantar Lembut

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Di buku catatan ini, kita akan menjelajahi Distribusi TensorFlow (disingkat TFD). Tujuan dari notebook ini adalah untuk membuat Anda mempelajari kurva belajar dengan lembut, termasuk memahami penanganan bentuk tensor TFD. Buku catatan ini mencoba menyajikan contoh-contoh sebelumnya daripada konsep-konsep abstrak. Kami akan menyajikan cara mudah kanonik untuk melakukan sesuatu terlebih dahulu, dan menyimpan tampilan abstrak paling umum hingga akhir. Jika Anda adalah tipe yang lebih suka tutorial lebih abstrak dan referensi gaya, periksa Memahami TensorFlow Distribusi Shapes . Jika Anda memiliki pertanyaan tentang materi di sini, jangan ragu untuk kontak (atau bergabung) dengan TensorFlow Probabilitas milis . Kami senang membantu.

Sebelum kita mulai, kita perlu mengimpor perpustakaan yang sesuai. Perpustakaan kami secara keseluruhan tensorflow_probability . Dengan konvensi, kita biasanya merujuk ke perpustakaan distribusi sebagai tfd .

Tensorflow Bersemangat adalah sebuah lingkungan eksekusi penting untuk TensorFlow. Dalam bersemangat TensorFlow, setiap operasi TF segera dievaluasi dan menghasilkan hasil. Ini berbeda dengan mode "grafik" standar TensorFlow, di mana operasi TF menambahkan node ke grafik yang kemudian dieksekusi. Seluruh notebook ini ditulis menggunakan TF Eager, meskipun tidak ada konsep yang disajikan di sini bergantung pada itu, dan TFP dapat digunakan dalam mode grafik.

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

Distribusi Univariat Dasar

Mari selami dan buat distribusi normal:

n = tfd.Normal(loc=0., scale=1.)
n
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>

Kita dapat mengambil sampel darinya:

n.sample()
<tf.Tensor: shape=(), dtype=float32, numpy=0.25322816>

Kami dapat menggambar beberapa sampel:

n.sample(3)
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.4658079, -0.5653636,  0.9314412], dtype=float32)>

Kami dapat mengevaluasi prob log:

n.log_prob(0.)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>

Kami dapat mengevaluasi beberapa probabilitas log:

n.log_prob([0., 2., 4.])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-0.9189385, -2.9189386, -8.918939 ], dtype=float32)>

Kami memiliki berbagai macam distribusi. Mari kita coba 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)>

Distribusi Multivariat

Kami akan membuat normal multivariat dengan kovarians diagonal:

nd = tfd.MultivariateNormalDiag(loc=[0., 10.], scale_diag=[1., 4.])
nd
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>

Membandingkan ini dengan univariat normal yang kita buat sebelumnya, apa bedanya?

tfd.Normal(loc=0., scale=1.)
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>

Kita melihat bahwa normal univariat memiliki event_shape dari () , menunjukkan itu distribusi skalar. The multivariat yang normal memiliki event_shape dari 2 , yang menunjukkan dasar [ruang acara] (https://en.wikipedia.org/wiki/Event_ (probability_theory)) dari distribusi ini adalah dua dimensi.

Pengambilan sampel bekerja sama seperti sebelumnya:

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>

Normal multivariat pada umumnya tidak memiliki kovarians diagonal. TFD menawarkan beberapa cara untuk membuat normal multivariat, termasuk spesifikasi kovarians penuh, yang kami gunakan di sini.

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

png

Beberapa Distribusi

Distribusi Bernoulli pertama kami mewakili satu koin yang adil. Kami juga dapat membuat batch distribusi Bernoulli independen, masing-masing dengan parameter mereka sendiri, dalam satu Distribution objek:

b3 = tfd.Bernoulli(probs=[.3, .5, .7])
b3
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>

Penting untuk memperjelas apa artinya ini. Panggilan atas mendefinisikan tiga distribusi Bernoulli independen, yang kebetulan terkandung dalam Python yang sama Distribution objek. Ketiga distribusi tidak dapat dimanipulasi satu per satu. Perhatikan bagaimana batch_shape adalah (3,) , menunjukkan batch tiga distribusi, dan event_shape adalah () , menunjukkan distribusi individu memiliki ruang acara univariat.

Jika kita sebut sample , kita mendapatkan sampel dari ketiga:

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

Jika kita sebut prob , (ini memiliki semantik bentuk yang sama seperti log_prob , kami menggunakan prob dengan contoh-contoh Bernoulli kecil untuk kejelasan, meskipun log_prob biasanya disukai dalam aplikasi) kita dapat lulus vektor dan mengevaluasi probabilitas setiap koin menghasilkan nilai yang :

b3.prob([1, 1, 0])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5       , 0.29999998], dtype=float32)>

Mengapa API menyertakan bentuk batch? Semantik, orang bisa melakukan perhitungan yang sama dengan membuat daftar distribusi dan iterasi atas mereka dengan for lingkaran (setidaknya dalam mode Bersemangat, dalam mode grafik TF Anda akan membutuhkan tf.while loop). Namun, memiliki satu set (berpotensi besar) distribusi parameter identik sangat umum, dan penggunaan komputasi vektor bila memungkinkan adalah unsur utama untuk dapat melakukan komputasi cepat menggunakan akselerator perangkat keras.

Menggunakan Independen Untuk Menggabungkan Batch ke Acara

Pada bagian sebelumnya, kami menciptakan b3 , satu Distribution objek yang diwakili tiga koin membalik. Jika kita disebut b3.prob pada vektor \(v\), yang \(i\)'entry th adalah probabilitas bahwa \(i\)th koin mengambil nilai \(v[i]\).

Misalkan kita ingin menentukan distribusi "bersama" atas variabel acak independen dari keluarga dasar yang sama. Ini adalah objek yang berbeda matematis, dalam bahwa untuk distribusi baru ini, prob pada vektor \(v\) akan mengembalikan nilai tunggal yang mewakili probabilitas bahwa seluruh himpunan koin sesuai dengan vektor \(v\).

Bagaimana kita mencapai ini? Kami menggunakan "tingkat tinggi" distribusi disebut Independent , yang mengambil distribusi dan menghasilkan distribusi baru dengan bentuk batch yang pindah ke bentuk acara:

b3_joint = tfd.Independent(b3, reinterpreted_batch_ndims=1)
b3_joint
<tfp.distributions.Independent 'IndependentBernoulli' batch_shape=[] event_shape=[3] dtype=int32>

Bandingkan bentuk dengan yang asli b3 :

b3
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>

Seperti yang dijanjikan, kita melihat bahwa yang Independent telah pindah bentuk batch ke bentuk acara: b3_joint adalah distribusi tunggal ( batch_shape = () ) selama event ruang tiga dimensi ( event_shape = (3,) ).

Mari kita periksa semantiknya:

b3_joint.prob([1, 1, 0])
<tf.Tensor: shape=(), dtype=float32, numpy=0.044999998>

Cara alternatif untuk mendapatkan hasil yang sama akan probabilitas menghitung menggunakan b3 dan melakukan pengurangan secara manual dengan mengalikan (atau, dalam kasus yang lebih biasa di mana probabilitas log yang digunakan, menjumlahkan):

tf.reduce_prod(b3.prob([1, 1, 0]))
<tf.Tensor: shape=(), dtype=float32, numpy=0.044999994>

Indpendent memungkinkan pengguna untuk lebih eksplisit mewakili konsep yang diinginkan. Kami melihat ini sebagai sangat berguna, meskipun tidak sepenuhnya diperlukan.

Fakta menyenangkan:

  • b3.sample dan b3_joint.sample memiliki implementasi konseptual yang berbeda, tetapi output dibedakan: perbedaan antara batch distribusi independen dan distribusi tunggal dibuat dari batch menggunakan Independent menunjukkan ketika komputasi probabilites, bukan ketika sampling.
  • MultivariateNormalDiag dapat sepele diimplementasikan menggunakan skalar Normal dan Independent distribusi (tidak benar-benar diterapkan dengan cara ini, tapi bisa).

Batch Distribusi Multivariat

Mari buat kumpulan tiga normal multivariat dua dimensi kovarians penuh:

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>

Kita melihat batch_shape = (3,) , sehingga ada tiga normals multivariat independen, dan event_shape = (2,) , sehingga setiap multivariat normal adalah dua dimensi. Dalam contoh ini, distribusi individu tidak memiliki elemen independen.

Pekerjaan pengambilan sampel:

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

Sejak batch_shape = (3,) dan event_shape = (2,) , kami melewati sebuah tensor bentuk (3, 2) ke log_prob :

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

Penyiaran, alias Mengapa Ini Sangat Membingungkan?

Abstrak apa yang kami lakukan sejauh ini, setiap distribusi memiliki bentuk batch yang B dan acara bentuk E . Membiarkan BE menjadi gabungan dari bentuk acara:

  • Untuk distribusi skalar univariat n dan b , BE = (). .
  • Untuk normals multivariat dua dimensi nd . BE = (2).
  • Untuk kedua b3 dan b3_joint , BE = (3).
  • Untuk batch normals multivariat ndb , BE = (3, 2).

"Aturan evaluasi" yang kami gunakan sejauh ini adalah:

  • Sampel tanpa argumen mengembalikan tensor dengan bentuk BE ; sampel dengan skalar n kembali sebuah "n oleh BE " tensor.
  • prob dan log_prob mengambil tensor bentuk BE dan mengembalikan hasil dari bentuk B .

Sebenarnya "evaluasi aturan" untuk prob dan log_prob lebih rumit, dengan cara yang menawarkan potensi kekuatan dan kecepatan, tetapi juga kompleksitas dan tantangan. Aturan sebenarnya (dasarnya) bahwa argumen untuk log_prob harus broadcastable terhadap BE ; setiap dimensi "ekstra" dipertahankan dalam output.

Mari kita telusuri implikasinya. Untuk normal univariat n , BE = () , sehingga log_prob mengharapkan skalar. Jika kita melewati log_prob sebuah tensor dengan bentuk non-kosong, mereka muncul sebagai dimensi batch dalam output:

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

Gilirannya Mari ke dua dimensi yang normal multivariat nd (parameter berubah untuk ilustrasi):

nd = tfd.MultivariateNormalDiag(loc=[0., 1.], scale_diag=[1., 1.])
nd
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>

log_prob "mengharapkan" argumen dengan bentuk (2,) , tetapi akan menerima argumen bahwa siaran terhadap bentuk ini:

nd.log_prob([0., 0.])
<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>

Tapi kita bisa lulus dalam "lebih" contoh, dan mengevaluasi semua mereka log_prob Ini sekaligus:

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

Mungkin kurang menarik, kami dapat menyiarkan melalui dimensi acara:

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

Menyiarkan dengan cara ini adalah konsekuensi dari desain "aktifkan penyiaran bila memungkinkan" kami; penggunaan ini agak kontroversial dan berpotensi dihapus dalam versi TFP yang akan datang.

Sekarang mari kita lihat contoh tiga koin lagi:

b3 = tfd.Bernoulli(probs=[.3, .5, .7])

Di sini, menggunakan siaran untuk mewakili probabilitas bahwa setiap koin muncul kepala cukup intuitif:

b3.prob([1])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5       , 0.7       ], dtype=float32)>

(Bandingkan dengan b3.prob([1., 1., 1.]) , yang kita akan memiliki kembali digunakan di mana b3 diperkenalkan.)

Sekarang anggaplah kita ingin tahu, untuk setiap koin, probabilitas koin muncul kepala dan probabilitas itu muncul ekor. Kita bisa membayangkan mencoba:

b3.log_prob([0, 1])

Sayangnya, ini menghasilkan kesalahan dengan jejak tumpukan yang panjang dan tidak terlalu mudah dibaca. b3 memiliki BE = (3) , jadi kami harus melewati b3.prob sesuatu broadcastable terhadap (3,) . [0, 1] memiliki bentuk (2) , sehingga tidak disiarkan dan menciptakan kesalahan. Sebaliknya, kita harus mengatakan:

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

Mengapa? [[0], [1]] memiliki bentuk (2, 1) , sehingga siaran terhadap bentuk (3) untuk membuat bentuk siaran (2, 3) .

Penyiaran cukup kuat: ada kasus di mana memungkinkan pengurangan urutan besaran dalam jumlah memori yang digunakan, dan sering membuat kode pengguna lebih pendek. Namun, ini bisa menjadi tantangan untuk diprogram. Jika Anda menelepon log_prob dan mendapatkan error, kegagalan untuk siaran hampir selalu masalah.

Pergi Lebih Jauh

Dalam tutorial ini, kami (semoga) memberikan pengantar sederhana. Beberapa petunjuk untuk melangkah lebih jauh:

  • event_shape , batch_shape dan sample_shape dapat peringkat sewenang-wenang (dalam tutorial ini mereka selalu baik skalar atau peringkat 1). Ini meningkatkan kekuatan tetapi sekali lagi dapat menyebabkan tantangan pemrograman, terutama ketika penyiaran terlibat. Untuk menyelam dalam tambahan ke manipulasi bentuk, melihat Understanding TensorFlow Distribusi Shapes .
  • TFP termasuk abstraksi kuat yang dikenal sebagai Bijectors , yang bersama dengan TransformedDistribution , menghasilkan fleksibel, cara komposisi untuk dengan mudah membuat distribusi baru yang transformasi dibalik distribusi yang ada. Kami akan mencoba untuk menulis tutorial tentang segera ini, tetapi sementara itu, periksa dokumentasi