Zrozumienie kształtów rozkładu TensorFlow

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik
import collections

import tensorflow as tf
tf.compat.v2.enable_v2_behavior()

import tensorflow_probability as tfp
tfd = tfp.distributions
tfb = tfp.bijectors

Podstawy

Istnieją trzy ważne koncepcje związane z kształtami TensorFlow Distributions:

  • Kształt zdarzenie opisuje kształt pojedynczego wyciągnąć z rozkładu; może zależeć od wymiarów. Dla rozkładu skalarnych kształt zdarzenie jest [] . Dla 5-wymiarowej MultivariateNormal kształt zdarzenie jest [5] .
  • Kształt partia opisuje niezależny, nie identycznie rozmieszczone rysuje, aka „partia” rozkładów.
  • Kształt przykładzie opisano niezależne identycznie rozmieszczone zwraca partii z rodziny dystrybucji.

Kształt zdarzenia i kształt partii są właściwościami Distribution przedmiotu, podczas gdy próbka kształt jest powiązany z uprzednim specyficznej dla sample lub log_prob .

Celem tego notatnika jest zilustrowanie tych pojęć na przykładach, więc jeśli nie jest to od razu oczywiste, nie martw się!

Dla innego koncepcyjne przegląd tych pojęć, zobaczyć to na blogu .

Uwaga na temat TensorFlow Eager.

Cały ten notebook jest napisane przy użyciu TensorFlow Marzą . Żaden z tych koncepcji przedstawiony polegać Marzą, chociaż z Chętnie, Batch dystrybucji i zdarzeń kształty są oceniane (a zatem znane) kiedy Distribution obiekt jest utworzony w Pythonie, a na wykresie (tryb bez Eager), możliwe jest określenie rozkładu których kształty zdarzeń i partii są nieokreślone do momentu uruchomienia wykresu.

Rozkłady skalarne

Jak już wspomniano powyżej, Distribution obiekt został zdefiniowany kształtów zdarzeń i partii. Zaczniemy od narzędzia do opisywania dystrybucji:

def describe_distributions(distributions):
  print('\n'.join([str(d) for d in distributions]))

W tym rozdziale będziemy badać rozkład skalarnych: wypłaty z kształtu zdarzeń [] . Typowym przykładem jest rozkład Poissona, określony przez rate :

poisson_distributions = [
    tfd.Poisson(rate=1., name='One Poisson Scalar Batch'),
    tfd.Poisson(rate=[1., 10., 100.], name='Three Poissons'),
    tfd.Poisson(rate=[[1., 10., 100.,], [2., 20., 200.]],
                name='Two-by-Three Poissons'),
    tfd.Poisson(rate=[1.], name='One Poisson Vector Batch'),
    tfd.Poisson(rate=[[1.]], name='One Poisson Expanded Batch')
]

describe_distributions(poisson_distributions)
tfp.distributions.Poisson("One_Poisson_Scalar_Batch", batch_shape=[], event_shape=[], dtype=float32)
tfp.distributions.Poisson("Three_Poissons", batch_shape=[3], event_shape=[], dtype=float32)
tfp.distributions.Poisson("Two_by_Three_Poissons", batch_shape=[2, 3], event_shape=[], dtype=float32)
tfp.distributions.Poisson("One_Poisson_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
tfp.distributions.Poisson("One_Poisson_Expanded_Batch", batch_shape=[1, 1], event_shape=[], dtype=float32)

Rozkład Poissona jest rozkład skalarne, to jego kształt zdarzenie zawsze [] . Jeśli określimy więcej stawek, pojawią się one w formie partii. Ostatnia para przykładów jest interesująca: jest tylko jedna stawka, ale ponieważ ta stawka jest osadzona w tablicy numpy z niepustym kształtem, ten kształt staje się kształtem partii.

Standardowy rozkład normalny jest również skalarem. To wydarzenie jest kształt [] , podobnie jak dla Poissona, ale zagramy z nim, aby zobaczyć nasz pierwszy przykład nadawania. Normalny jest określona za pomocą loc i scale parametry:

normal_distributions = [
    tfd.Normal(loc=0., scale=1., name='Standard'),
    tfd.Normal(loc=[0.], scale=1., name='Standard Vector Batch'),
    tfd.Normal(loc=[0., 1., 2., 3.], scale=1., name='Different Locs'),
    tfd.Normal(loc=[0., 1., 2., 3.], scale=[[1.], [5.]],
               name='Broadcasting Scale')
]

describe_distributions(normal_distributions)
tfp.distributions.Normal("Standard", batch_shape=[], event_shape=[], dtype=float32)
tfp.distributions.Normal("Standard_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
tfp.distributions.Normal("Different_Locs", batch_shape=[4], event_shape=[], dtype=float32)
tfp.distributions.Normal("Broadcasting_Scale", batch_shape=[2, 4], event_shape=[], dtype=float32)

Interesujący przykład powyżej jest Broadcasting Scale dystrybucji. loc parametr ma postać [4] , a scale parametr ma postać [2, 1] . Korzystanie NumPy zasady nadającej kształt wsad [2, 4] . Równoważny (ale nie mniej elegancki i zalecany) sposób określenia "Broadcasting Scale" dystrybucji będzie:

describe_distributions(
    [tfd.Normal(loc=[[0., 1., 2., 3], [0., 1., 2., 3.]],
                scale=[[1., 1., 1., 1.], [5., 5., 5., 5.]])])
tfp.distributions.Normal("Normal", batch_shape=[2, 4], event_shape=[], dtype=float32)

Widzimy, dlaczego notacja nadawania jest przydatna, chociaż jest również źródłem bólów głowy i błędów.

Próbkowanie rozkładów skalarnych

Istnieją dwie główne rzeczy, które możemy zrobić z rozkładów: możemy sample z nimi i możemy obliczyć log_prob s. Przyjrzyjmy się najpierw samplowaniu. Podstawową zasadą jest to, że kiedy próbka z rozkładu, otrzymaną tensora ma kształt [sample_shape, batch_shape, event_shape] , gdzie batch_shape i event_shape są dostarczane przez Distribution obiektu i sample_shape jest przez wywołanie sample . Dla rozkładu skalarną event_shape = [] , więc tensora zwrócony z próbki będą miały kształt [sample_shape, batch_shape] . Spróbujmy:

def describe_sample_tensor_shape(sample_shape, distribution):
    print('Sample shape:', sample_shape)
    print('Returned sample tensor shape:',
          distribution.sample(sample_shape).shape)

def describe_sample_tensor_shapes(distributions, sample_shapes):
    started = False
    for distribution in distributions:
      print(distribution)
      for sample_shape in sample_shapes:
        describe_sample_tensor_shape(sample_shape, distribution)
      print()

sample_shapes = [1, 2, [1, 5], [3, 4, 5]]
describe_sample_tensor_shapes(poisson_distributions, sample_shapes)
tfp.distributions.Poisson("One_Poisson_Scalar_Batch", batch_shape=[], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1,)
Sample shape: 2
Returned sample tensor shape: (2,)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5)

tfp.distributions.Poisson("Three_Poissons", batch_shape=[3], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 3)
Sample shape: 2
Returned sample tensor shape: (2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 3)

tfp.distributions.Poisson("Two_by_Three_Poissons", batch_shape=[2, 3], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

tfp.distributions.Poisson("One_Poisson_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 1)
Sample shape: 2
Returned sample tensor shape: (2, 1)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 1)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 1)

tfp.distributions.Poisson("One_Poisson_Expanded_Batch", batch_shape=[1, 1], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 1, 1)
Sample shape: 2
Returned sample tensor shape: (2, 1, 1)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 1, 1)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 1, 1)
describe_sample_tensor_shapes(normal_distributions, sample_shapes)
tfp.distributions.Normal("Standard", batch_shape=[], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1,)
Sample shape: 2
Returned sample tensor shape: (2,)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5)

tfp.distributions.Normal("Standard_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 1)
Sample shape: 2
Returned sample tensor shape: (2, 1)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 1)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 1)

tfp.distributions.Normal("Different_Locs", batch_shape=[4], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 4)
Sample shape: 2
Returned sample tensor shape: (2, 4)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 4)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 4)

tfp.distributions.Normal("Broadcasting_Scale", batch_shape=[2, 4], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 4)
Sample shape: 2
Returned sample tensor shape: (2, 2, 4)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 4)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 4)

To wszystko, co trzeba powiedzieć o sample : próbki zwracane tensory mieć kształt [sample_shape, batch_shape, event_shape] .

Obliczanie log_prob Dla dystrybucje skalarne

Teraz rzućmy okiem na log_prob , która jest nieco trudniejsze. log_prob przyjmuje na wejściu (niepusty) tensor reprezentujący położenie (e), w którym należy obliczyć log_prob do dystrybucji. W najprostszym przypadku, ten napinacz będzie mieć kształt formy [sample_shape, batch_shape, event_shape] , gdzie batch_shape i event_shape mecz pakietu i zdarzeń kształt rozkładu. Przypomnijmy raz jeszcze, że dla rozkładów skalarnych, event_shape = [] , więc tensor wejście ma kształt [sample_shape, batch_shape] W tym przypadku, możemy wrócić tensora kształtu [sample_shape, batch_shape] :

three_poissons = tfd.Poisson(rate=[1., 10., 100.], name='Three Poissons')
three_poissons
<tfp.distributions.Poisson 'Three_Poissons' batch_shape=[3] event_shape=[] dtype=float32>
three_poissons.log_prob([[1., 10., 100.], [100., 10., 1]])  # sample_shape is [2].
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -2.0785608,   -3.2223587],
       [-364.73938  ,   -2.0785608,  -95.39484  ]], dtype=float32)>
three_poissons.log_prob([[[[1., 10., 100.], [100., 10., 1.]]]])  # sample_shape is [1, 1, 2].
<tf.Tensor: shape=(1, 1, 2, 3), dtype=float32, numpy=
array([[[[  -1.       ,   -2.0785608,   -3.2223587],
         [-364.73938  ,   -2.0785608,  -95.39484  ]]]], dtype=float32)>

Uwaga, jak w pierwszym przykładzie, wejścia i wyjścia mają kształt [2, 3] , w drugim przykładzie mają postać [1, 1, 2, 3] .

To by było wszystko, co można by powiedzieć, gdyby nie nadawanie. Oto zasady, gdy weźmiemy pod uwagę nadawanie. Opisujemy to w pełnej ogólności i zwracamy uwagę na uproszczenia dla rozkładów skalarnych:

  1. Określić n = len(batch_shape) + len(event_shape) . (Dystrybucje skalarnych, len(event_shape)=0 ).
  2. Jeśli tensor wejściowy t ma mniej niż n wymiary PAD kształt dodając wymiary o wielkości 1 na lewo, aż ma dokładnie n wymiary. Zadzwoń wynikowy tensorowy t' .
  3. Rozgłaszanie n wymiary skrajnie prawe z t' przeciw [batch_shape, event_shape] dystrybucji jesteś obliczania log_prob dla. Bardziej szczegółowo: dla wymiarów gdzie t' już pasuje rozkład, nic nie robić, a do wymiarów gdzie t' ma Singleton Replicate że Singleton odpowiednią liczbę razy. Każda inna sytuacja jest błędem. (Dystrybucje skalarnych, mamy tylko nadawać na batch_shape , ponieważ event_shape = [] ).
  4. Teraz jesteśmy w końcu w stanie obliczyć log_prob . Otrzymany napinacz będzie miał kształt [sample_shape, batch_shape] , gdzie sample_shape jest zdefiniowany jako dowolny wymiary t i t' w lewo, z n -rightmost wymiarach: sample_shape = shape(t)[:-n] .

To może być bałagan, jeśli nie wiesz, co to znaczy, więc przeanalizujmy kilka przykładów:

three_poissons.log_prob([10.])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-16.104412 ,  -2.0785608, -69.05272  ], dtype=float32)>

Tensor [10.] (z kształtu [1] ) jest nadawany w poprzek batch_shape dnia 3, więc ocenić prawdopodobieństwo rejestrować wszystkie trzy Poissons' o wartości 10.

three_poissons.log_prob([[[1.], [10.]], [[100.], [1000.]]])
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[-1.0000000e+00, -7.6974149e+00, -9.5394836e+01],
        [-1.6104412e+01, -2.0785608e+00, -6.9052719e+01]],

       [[-3.6473938e+02, -1.4348087e+02, -3.2223587e+00],
        [-5.9131279e+03, -3.6195427e+03, -1.4069575e+03]]], dtype=float32)>

W powyższym przykładzie, tensor wejściowy ma postać [2, 2, 1] , przy czym przedmiot ma kształt rozkład wsadu 3. Tak więc dla każdej z [2, 2] wymiarów próbki, pojedyncza wartość podana pobiera broadcats sobie z trzech Poissonów.

A mogą przydatny sposób, że z tego: ponieważ three_poissons ma batch_shape = [2, 3] , wywołanie log_prob musi mieć tensora którego ostatni wymiar 1 lub 3; wszystko inne jest błędem. (The NumPy zasady nadawania leczeniu szczególny przypadek skalara jako całkowicie równoważne tensora kształtu [1] ).

Test Powiedzmy nasze kotlety grając z bardziej złożonego rozkładu Poissona z batch_shape = [2, 3] :

poisson_2_by_3 = tfd.Poisson(
    rate=[[1., 10., 100.,], [2., 20., 200.]],
    name='Two-by-Three Poissons')
poisson_2_by_3.log_prob(1.)
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -17.004269 , -194.70169  ]], dtype=float32)>
poisson_2_by_3.log_prob([1.])  # Exactly equivalent to above, demonstrating the scalar special case.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -17.004269 , -194.70169  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 1., 1.], [1., 1., 1.]])  # Another way to write the same thing. No broadcasting.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -17.004269 , -194.70169  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 10., 100.]])  # Input is [1, 3] broadcast to [2, 3].
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[ -1.       ,  -2.0785608,  -3.2223587],
       [ -1.3068528,  -5.14709  , -33.90767  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 10., 100.], [1., 10., 100.]])  # Equivalent to above. No broadcasting.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[ -1.       ,  -2.0785608,  -3.2223587],
       [ -1.3068528,  -5.14709  , -33.90767  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 1., 1.], [2., 2., 2.]])  # No broadcasting.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -14.701683 , -190.09653  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1.], [2.]])  # Equivalent to above. Input shape [2, 1] broadcast to [2, 3].
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -14.701683 , -190.09653  ]], dtype=float32)>

Powyższe przykłady dotyczyły rozgłaszania w partii, ale kształt próbki był pusty. Załóżmy, że mamy zbiór wartości i chcemy uzyskać prawdopodobieństwo logarytmiczne każdej wartości w każdym punkcie w partii. Moglibyśmy to zrobić ręcznie:

poisson_2_by_3.log_prob([[[1., 1., 1.], [1., 1., 1.]], [[2., 2., 2.], [2., 2., 2.]]])  # Input shape [2, 2, 3].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

Lub możemy pozwolić, aby transmisja obsługiwała ostatni wymiar wsadowy:

poisson_2_by_3.log_prob([[[1.], [1.]], [[2.], [2.]]])  # Input shape [2, 2, 1].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

Możemy również (być może nieco mniej naturalnie) pozwolić, by rozgłaszanie zajmowało się tylko pierwszym wymiarem wsadowym:

poisson_2_by_3.log_prob([[[1., 1., 1.]], [[2., 2., 2.]]])  # Input shape [2, 1, 3].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

Albo możemy pozwolić nadawania obsługiwać zarówno wsadowych wymiary:

poisson_2_by_3.log_prob([[[1.]], [[2.]]])  # Input shape [2, 1, 1].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

Powyższe zadziałało dobrze, gdy mieliśmy tylko dwie wartości, które chcieliśmy, ale załóżmy, że mamy długą listę wartości, które chcieliśmy ocenić w każdym punkcie wsadowym. W tym celu niezwykle przydatna jest następująca notacja, która dodaje dodatkowe wymiary o rozmiarze 1 po prawej stronie kształtu:

poisson_2_by_3.log_prob(tf.constant([1., 2.])[..., tf.newaxis, tf.newaxis])
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

To jest instancją strided notacji slice , co jest warte poznania.

Wracając do three_poissons za kompletność, tym samym przykładzie wygląda następująco:

three_poissons.log_prob([[1.], [10.], [50.], [100.]])
<tf.Tensor: shape=(4, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [ -16.104412 ,   -2.0785608,  -69.05272  ],
       [-149.47777  ,  -43.34851  ,  -18.219261 ],
       [-364.73938  , -143.48087  ,   -3.2223587]], dtype=float32)>
three_poissons.log_prob(tf.constant([1., 10., 50., 100.])[..., tf.newaxis])  # Equivalent to above.
<tf.Tensor: shape=(4, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [ -16.104412 ,   -2.0785608,  -69.05272  ],
       [-149.47777  ,  -43.34851  ,  -18.219261 ],
       [-364.73938  , -143.48087  ,   -3.2223587]], dtype=float32)>

Rozkłady wielowymiarowe

Przejdziemy teraz do rozkładów wielowymiarowych, które mają niepusty kształt zdarzenia. Spójrzmy na rozkłady wielomianowe.

multinomial_distributions = [
    # Multinomial is a vector-valued distribution: if we have k classes,
    # an individual sample from the distribution has k values in it, so the
    # event_shape is `[k]`.
    tfd.Multinomial(total_count=100., probs=[.5, .4, .1],
                    name='One Multinomial'),
    tfd.Multinomial(total_count=[100., 1000.], probs=[.5, .4, .1],
                    name='Two Multinomials Same Probs'),
    tfd.Multinomial(total_count=100., probs=[[.5, .4, .1], [.1, .2, .7]],
                    name='Two Multinomials Same Counts'),
    tfd.Multinomial(total_count=[100., 1000.],
                    probs=[[.5, .4, .1], [.1, .2, .7]],
                    name='Two Multinomials Different Everything')

]

describe_distributions(multinomial_distributions)
tfp.distributions.Multinomial("One_Multinomial", batch_shape=[], event_shape=[3], dtype=float32)
tfp.distributions.Multinomial("Two_Multinomials_Same_Probs", batch_shape=[2], event_shape=[3], dtype=float32)
tfp.distributions.Multinomial("Two_Multinomials_Same_Counts", batch_shape=[2], event_shape=[3], dtype=float32)
tfp.distributions.Multinomial("Two_Multinomials_Different_Everything", batch_shape=[2], event_shape=[3], dtype=float32)

Uwaga, jak w ciągu ostatnich trzech przykładach batch_shape jest zawsze [2] , ale możemy użyć nadawania albo mają wspólną total_count lub dzielonych probs (lub nie), bo pod maską są one nadawane mieć taki sam kształt.

Pobieranie próbek jest proste, biorąc pod uwagę to, co już wiemy:

describe_sample_tensor_shapes(multinomial_distributions, sample_shapes)
tfp.distributions.Multinomial("One_Multinomial", batch_shape=[], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 3)
Sample shape: 2
Returned sample tensor shape: (2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 3)

tfp.distributions.Multinomial("Two_Multinomials_Same_Probs", batch_shape=[2], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

tfp.distributions.Multinomial("Two_Multinomials_Same_Counts", batch_shape=[2], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

tfp.distributions.Multinomial("Two_Multinomials_Different_Everything", batch_shape=[2], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

Obliczanie prawdopodobieństw logarytmicznych jest równie proste. Rozpatrzmy przykład z diagonalnymi wielowymiarowymi rozkładami normalnymi. (Wielomiany nie są zbyt przyjazne dla rozgłaszania, ponieważ ograniczenia dotyczące liczebności i prawdopodobieństw oznaczają, że rozgłaszanie często generuje niedopuszczalne wartości). Użyjemy partii 2 rozkładów trójwymiarowych o tej samej średniej, ale różnych skalach (odchylenia standardowe):

two_multivariate_normals = tfd.MultivariateNormalDiag(loc=[1., 2., 3.], scale_identity_multiplier=[1., 2.])
two_multivariate_normals
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[2] event_shape=[3] dtype=float32>

(Należy pamiętać, że chociaż użyliśmy dystrybucje gdzie łuski były wielokrotnością tożsamości, nie jest to ograniczenie, możemy przejść scale zamiast scale_identity_multiplier ).

Oszacujmy teraz prawdopodobieństwo logarytmiczne każdego punktu partii przy jego średniej i średniej przesuniętej:

two_multivariate_normals.log_prob([[[1., 2., 3.]], [[3., 4., 5.]]])  # Input has shape [2,1,3].
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-2.7568154, -4.836257 ],
       [-8.756816 , -6.336257 ]], dtype=float32)>

Dokładnie równoważnie, możemy użyć https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/strided-slice wstawić dodatkowy kształt = 1 wymiar w środku stałej:

two_multivariate_normals.log_prob(
    tf.constant([[1., 2., 3.], [3., 4., 5.]])[:, tf.newaxis, :])  # Equivalent to above.
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-2.7568154, -4.836257 ],
       [-8.756816 , -6.336257 ]], dtype=float32)>

Z drugiej strony, jeśli nie należy wkładać dodatkowego wymiaru, mijamy [1., 2., 3.] do pierwszego punktu wsadowym i [3., 4., 5.] do drugiego:

two_multivariate_normals.log_prob(tf.constant([[1., 2., 3.], [3., 4., 5.]]))
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-2.7568154, -6.336257 ], dtype=float32)>

Techniki manipulacji kształtem

Przekształć Bijector

Reshape bijector mogą być wykorzystane do przekształcenia event_shape z rozkładem. Zobaczmy przykład:

six_way_multinomial = tfd.Multinomial(total_count=1000., probs=[.3, .25, .2, .15, .08, .02])
six_way_multinomial
<tfp.distributions.Multinomial 'Multinomial' batch_shape=[] event_shape=[6] dtype=float32>

Stworzyliśmy wielomianu o postaci zdarzeń [6] . Reshape Bijector pozwala potraktować jako rozkład z kształtem zdarzeń [2, 3] .

Bijector oznacza różniczkowalnej, jeden do jednej funkcji na otwartej podzbioru \({\mathbb R}^n\). Bijectors są stosowane w połączeniu z TransformedDistribution , który modeluje dystrybucji \(p(y)\) w kategoriach rozkładu podstawy \(p(x)\) i Bijector reprezentującego \(Y = g(X)\). Zobaczmy to w akcji:

transformed_multinomial = tfd.TransformedDistribution(
    distribution=six_way_multinomial,
    bijector=tfb.Reshape(event_shape_out=[2, 3]))
transformed_multinomial
<tfp.distributions.TransformedDistribution 'reshapeMultinomial' batch_shape=[] event_shape=[2, 3] dtype=float32>
six_way_multinomial.log_prob([500., 100., 100., 150., 100., 50.])
<tf.Tensor: shape=(), dtype=float32, numpy=-178.22021>
transformed_multinomial.log_prob([[500., 100., 100.], [150., 100., 50.]])
<tf.Tensor: shape=(), dtype=float32, numpy=-178.22021>

Jest to jedyna rzecz Reshape bijector można zrobić: nie można włączyć wymiary zdarzeń w wymiarach wsadowych lub vice versa.

Niezależna dystrybucja

Independent dystrybucja jest stosowany w leczeniu zbiór niezależnych, nie-koniecznie identyczne (aka partii) rozkładów jako pojedynczy dystrybucji. Bardziej zwięźle, Independent pozwala na konwersję wymiary w batch_shape do wymiarów event_shape . Zilustrujemy na przykładzie:

two_by_five_bernoulli = tfd.Bernoulli(
    probs=[[.05, .1, .15, .2, .25], [.3, .35, .4, .45, .5]],
    name="Two By Five Bernoulli")
two_by_five_bernoulli
<tfp.distributions.Bernoulli 'Two_By_Five_Bernoulli' batch_shape=[2, 5] event_shape=[] dtype=int32>

Możemy myśleć o tym jako o układzie monet dwa na pięć z powiązanym prawdopodobieństwem orła. Oszacujmy prawdopodobieństwo konkretnego, dowolnego zestawu zer i jedynek:

pattern = [[1., 0., 0., 1., 0.], [0., 0., 1., 1., 1.]]
two_by_five_bernoulli.log_prob(pattern)
<tf.Tensor: shape=(2, 5), dtype=float32, numpy=
array([[-2.9957323 , -0.10536052, -0.16251892, -1.609438  , -0.2876821 ],
       [-0.35667497, -0.4307829 , -0.9162907 , -0.7985077 , -0.6931472 ]],
      dtype=float32)>

Możemy użyć Independent się przekształcić dwóch różnych „zestawów pięć Bernoulliego”, co jest przydatne, gdy chcemy rozważyć „wiersz” z monety koziołki zbliża się w danej strukturze jako pojedynczy wynik:

two_sets_of_five = tfd.Independent(
    distribution=two_by_five_bernoulli,
    reinterpreted_batch_ndims=1,
    name="Two Sets Of Five")
two_sets_of_five
<tfp.distributions.Independent 'Two_Sets_Of_Five' batch_shape=[2] event_shape=[5] dtype=int32>

Matematycznie obliczamy prawdopodobieństwo logarytmiczne każdego „zestawu” składającego się z pięciu, sumując prawdopodobieństwa logarytmiczne pięciu „niezależnych” rzutów monetą w zestawie, od którego rozkład otrzymuje swoją nazwę:

two_sets_of_five.log_prob(pattern)
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-5.160732 , -3.1954036], dtype=float32)>

Możemy pójść jeszcze dalej i użyć Independent stworzyć dystrybucję gdzie poszczególne zdarzenia są zbiorem dwa-pięć Bernoulliego:

one_set_of_two_by_five = tfd.Independent(
    distribution=two_by_five_bernoulli, reinterpreted_batch_ndims=2,
    name="One Set Of Two By Five")
one_set_of_two_by_five.log_prob(pattern)
<tf.Tensor: shape=(), dtype=float32, numpy=-8.356134>

Warto zauważyć, że z punktu widzenia sample , stosując Independent niczego nie zmienia:

describe_sample_tensor_shapes(
    [two_by_five_bernoulli,
     two_sets_of_five,
     one_set_of_two_by_five],
    [[3, 5]])
tfp.distributions.Bernoulli("Two_By_Five_Bernoulli", batch_shape=[2, 5], event_shape=[], dtype=int32)
Sample shape: [3, 5]
Returned sample tensor shape: (3, 5, 2, 5)

tfp.distributions.Independent("Two_Sets_Of_Five", batch_shape=[2], event_shape=[5], dtype=int32)
Sample shape: [3, 5]
Returned sample tensor shape: (3, 5, 2, 5)

tfp.distributions.Independent("One_Set_Of_Two_By_Five", batch_shape=[], event_shape=[2, 5], dtype=int32)
Sample shape: [3, 5]
Returned sample tensor shape: (3, 5, 2, 5)

Jako ćwiczenie dla czytelnika podziału proponujemy biorąc pod uwagę różnice i podobieństwa między partii wektora Normal rozkładów i MultivariateNormalDiag dystrybucji z pobierania próbek i dziennika prawdopodobieństwa perspektywy. Jak możemy wykorzystać Independent skonstruować MultivariateNormalDiag z partii Normal s? (Zauważ, że MultivariateNormalDiag nie jest faktycznie realizowany w ten sposób).