Понимание форм распределений TensorFlow

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот
import collections

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

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

Основы

Есть три важных концепции, связанных с фигурами TensorFlow Distributions:

  • Форма события описывает форму одного розыгрыша от распределения; это может зависеть от размеров. Для скалярных распределений, форма события [] . Для 5-мерного MultivariateNormal, форма события [5] .
  • Пакетная форма описывает независимой, не одинаково распределены розыгрышами, иначе «партия» распределения.
  • Образец формы описывает независимые одинаково распределенные розыгрыши партий из семейства распределения.

Форма событий и партия форма являются свойствами Distribution объекта, в то время как образец форма связана с определенным вызовом sample или log_prob .

Цель этой записной книжки - проиллюстрировать эти концепции на примерах, поэтому, если это не сразу очевидно, не волнуйтесь!

Другой концептуальный обзор этих понятий см этот блог .

Заметка о TensorFlow Eager.

Весь этот ноутбук написан с использованием TensorFlow Нетерпеливый . Ни одна из концепций не представлены полагаются на Нетерпеливый, хотя и с Нетерпеливые, распределение партии и события формы оцениваются (и , следовательно , они известны) , когда Distribution объект создается в Python, тогда как в графике (режиме без Eager), то можно определить распределение чьи формы событий и пакетов не определены до тех пор, пока график не будет запущен.

Скалярные распределения

Как мы уже отмечали выше, Distribution объекта определенного события и командные формы. Начнем с утилиты для описания дистрибутивов:

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

В этом разделе мы рассмотрим скалярные распределения: распределение с формой событий [] . Типичным примером является распределение Пуассона, задается 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)

Распределение Пуассона является скаляром распределения, поэтому его форма события всегда [] . Если мы укажем больше ставок, они появятся в форме партии. Последняя пара примеров интересна: есть только одна ставка, но поскольку эта скорость встроена в массив numpy с непустой формой, эта форма становится формой пакета.

Стандартное нормальное распределение также является скаляром. Это форма события [] , так же , как для Пуассона, но мы будем играть с ним , чтобы увидеть наш первый пример вещания. Нормальный задается с помощью loc и scale параметров:

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)

Интересный пример выше является Broadcasting Scale распределения. loc параметр имеет форму [4] , а scale параметр имеет форму [2, 1] . Используя Numpy правила радиовещательных , партия форма [2, 4] . Эквивалент (но менее элегантно и не рекомендуемый) способ определения "Broadcasting Scale" распределение будет:

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)

Мы можем понять, почему нотация вещания полезна, хотя она также является источником головной боли и ошибок.

Выборка скалярных распределений

Есть две основные вещи , которые мы можем сделать с распределениями: мы можем sample от них , и мы можем вычислить log_prob с. Давайте сначала рассмотрим выборку. Основное правило заключается в том , что , когда мы выборку из распределения, полученный тензор имеет форму [sample_shape, batch_shape, event_shape] , где batch_shape и event_shape обеспечиваются Distribution объекта и sample_shape обеспечивается вызовом sample . Для скалярных распределений, event_shape = [] , поэтому Тензорная вернулся из образца будет иметь форму [sample_shape, batch_shape] . Давай попробуем:

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)

Вот и все , что можно сказать об sample : возвращаемые образцы тензоры имеют форму [sample_shape, batch_shape, event_shape] .

Вычисление log_prob Для скалярных распределений

Теперь давайте взглянем на log_prob , что несколько сложнее. log_prob принимает в качестве входных данных (непустого) тензор , представляющего местоположение (ы) , при котором для вычисления log_prob для распределения. В наиболее простом случае, этот тензор будет иметь форму вида [sample_shape, batch_shape, event_shape] , где batch_shape и event_shape матча в партии и событие форме распределения. Напомним еще раз , что для скалярных распределений, event_shape = [] , поэтому тензор ввода имеет форму [sample_shape, batch_shape] В этом случае мы получаем обратно тензор формы [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)>

Обратите внимание , как в первом примере, вход и выход имеет форму [2, 3] , а во втором примере они имеют форму [1, 1, 2, 3] .

Это было бы все, что можно было бы сказать, если бы не радиовещание. Вот правила, если принять во внимание трансляцию. Опишем его в полной общности и отметим упрощения для скалярных распределений:

  1. Определение n = len(batch_shape) + len(event_shape) . (Для скалярных распределений, len(event_shape)=0 .)
  2. Если входной тензор t имеет меньше , чем n размеры, колодки свою форму, добавляя размеры размер 1 слева , пока она не имеет ровно n размеры. Вызвать результирующий тензор t' .
  3. Broadcast в n размеры правых из t' против [batch_shape, event_shape] распределения вы вычисления log_prob для. Более подробно: для измерений , где t' уже спичек распределения, ничего не делать, а для измерений , где t' имеет синглтон, тиражировать , что синглтон соответствующего числа раз. Любая другая ситуация - ошибка. (Для скалярных распределений, мы только транслировать против batch_shape , так как event_shape = [] ) .
  4. Теперь мы , наконец , удалось вычислить log_prob . В результате тензор будет иметь форму [sample_shape, batch_shape] , где sample_shape определяется как любые размеры t или t' к левой из n -rightmost размеров: sample_shape = shape(t)[:-n] .

Это может вызвать беспорядок, если вы не знаете, что это означает, поэтому давайте поработаем несколько примеров:

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

Тензор [10.] (с формой [1] ) транслируются по всему batch_shape 3, поэтому мы оцениваем вероятность лога всех три Poissons' на значении 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)>

В приведенном выше примере, тензор ввода имеет форму [2, 2, 1] , в то время как объект распределения имеет пакетную форму 3. Таким образом , для каждого из [2, 2] размеров образца, одно значение при условии , получает broadcats друг трех Пуассонов.

Возможно , более полезный способ думать об этом: потому что three_poissons имеет batch_shape = [2, 3] , вызов log_prob должен принять тензор, последнее измерение либо 1 или 3; все остальное - ошибка. (Правила Numpy вещания рассматривать частный случай скаляра как абсолютно эквивалентен тензор формы [1] .)

Давайте проверим наши отбивные, играя с более сложным распределением Пуассона с 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)>

В приведенных выше примерах использовалась широковещательная передача по партии, но форма образца была пустой. Предположим, у нас есть набор значений, и мы хотим получить логарифмическую вероятность каждого значения в каждой точке пакета. Мы могли сделать это вручную:

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

Или мы могли бы позволить широковещательной передаче обрабатывать последнее измерение пакета:

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

Мы также можем (возможно, несколько менее естественно) позволить широковещательной передаче обрабатывать только первое измерение пакета:

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

Или мы могли позволить передавать ручки и пакетные размеры:

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

Вышеупомянутое работало нормально, когда у нас было только два значения, которые мы хотели, но предположим, что у нас есть длинный список значений, которые мы хотели бы оценивать в каждой точке пакета. Для этого чрезвычайно полезны следующие обозначения, которые добавляют дополнительные размеры размера 1 к правой стороне фигуры:

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

Это пример strided среза нотации , что стоит знать.

Возвращаясь к three_poissons для полноты картины , тот же самый пример выглядит следующим образом :

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

Многомерные распределения

Теперь обратимся к многомерным распределениям, которые имеют непустую форму события. Давайте посмотрим на полиномиальные распределения.

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)

Обратите внимание , как в последних трех примерах batch_shape всегда [2] , но мы можем использовать вещание либо имеет общую total_count , либо совместно probs (или ни), потому что под капотом они транслируются , имеют одинаковую форму.

Отбор проб прост, учитывая то, что мы уже знаем:

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)

Вычислить вероятности журнала также просто. Давайте рассмотрим пример с диагональным многомерным нормальным распределением. (Мультиномы не очень удобны для широковещательной передачи, поскольку ограничения на количество и вероятности означают, что широковещательная передача часто приводит к недопустимым значениям.) Мы будем использовать пакет из 2 трехмерных распределений с одинаковым средним, но разными масштабами (стандартными отклонениями):

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>

(Обратите внимание , что , хотя мы использовали распределения , где масштабы были кратны идентичности, это не является ограничением на, мы могли бы пройти scale вместо scale_identity_multiplier .)

Теперь давайте оценим логарифмическую вероятность каждой точки партии по ее среднему и смещенному среднему значению:

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

Точно то же самое, мы можем использовать https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/strided-slice вставить дополнительную форму = 1 измерение в середине константы:

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

С другой стороны, если мы не делаем вставки дополнительного измерения, мы переходим [1., 2., 3.] к первой партии точки и [3., 4., 5.] в секунду:

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

Методы изменения формы

Изменяющий бижектор

Reshape bijector может быть использован для изменения формы event_shape распределения. Посмотрим на пример:

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>

Мы создали полиномиальные с формой событий [6] . Reshape Bijector позволяет рассматривать это как распределение с формой событий [2, 3] .

Bijector представляет собой дифференцируемую, один-к-одному функции на открытом подмножестве \({\mathbb R}^n\). Bijectors используются в сочетании с TransformedDistribution , который моделирует распределение \(p(y)\) с точки зрения базовой распределения \(p(x)\) и Bijector , который представляет \(Y = g(X)\). Посмотрим на это в действии:

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>

Это единственное , что Reshape bijector может сделать: он не может превратить размеры событий в размерах пакетными или наоборот.

Независимое распространение

Independent распределение используются для лечения коллекции независимого, обязательно идентичных (ака Порции) распределений в виде единое распределения. Более сжато, Independent позволяет преобразовывать размеры в batch_shape размерам в event_shape . Проиллюстрируем на примере:

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>

Мы можем думать об этом как о массиве монет два на пять с соответствующими вероятностями выпадения орла. Давайте оценим вероятность конкретного, произвольного набора нулей и единиц:

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

Мы можем использовать Independent , чтобы превратить это в двух различных «наборов пять Бернулли», что полезно , если мы хотим рассмотреть «ряд» монеты переворачивается придумывают в данной схеме, один исход:

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>

Математически мы вычисляем логарифмическую вероятность каждого «набора» из пяти, суммируя логарифмические вероятности пяти «независимых» подбрасываний монеты в наборе, и именно поэтому распределение получило свое название:

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

Мы можем пойти еще дальше и использовать Independent создать дистрибутив , где отдельные события представляют собой набор из двух по пять Бернулли:

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>

Стоит отметить , что с точки зрения sample , используя Independent ничего не меняет:

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)

В качестве прощального упражнения для читателя, мы предлагаем рассмотреть сходства и различие между вектором партией Normal распределений и MultivariateNormalDiag распределением из выборки и журнала вероятностной точки зрения. Как мы можем использовать Independent построить MultivariateNormalDiag из партии Normal s? (Обратите внимание , что MultivariateNormalDiag не на самом деле реализован таким образом.)