Comprensione delle forme delle distribuzioni TensorFlow

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino
import collections

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

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

Nozioni di base

Ci sono tre concetti importanti associati alle forme delle distribuzioni TensorFlow:

  • Evento forma descrive la forma di un unico pareggio dalla distribuzione; può dipendere dalle dimensioni. Per le distribuzioni scalari, la forma evento è [] . Per un MultivariateNormal 5-dimensionale, la forma evento è [5] .
  • Forma Batch descrive indipendente, non identicamente distribuite disegna, alias un "batch" di distribuzioni.
  • Esempio forma descrive indipendenti e identicamente distribuite trae lotti della famiglia di distribuzione.

La forma e la forma evento lotto sono proprietà di un Distribution oggetto, mentre la forma campione è associato ad una specifica chiamata a sample o log_prob .

Lo scopo di questo quaderno è illustrare questi concetti attraverso esempi, quindi se questo non è immediatamente ovvio, non preoccuparti!

Per un altro panoramica concettuale di questi concetti, vedere questo post del blog .

Una nota su TensorFlow Eager.

Tutto questo notebook è scritto utilizzando tensorflow Eager . Nessuno dei concetti presentato invocare Eager, anche se con forme di distribuzione lotti e eventi Eager vengono valutati (e quindi conosciuti) quando la Distribution oggetto viene creato in Python, mentre nel grafico (modalità non Desideroso), è possibile definire le distribuzioni i cui eventi e forme batch sono indeterminati fino all'esecuzione del grafico.

Distribuzioni scalari

Come notato sopra, una Distribution oggetto è definita forme di eventi e batch. Inizieremo con un'utilità per descrivere le distribuzioni:

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

In questa sezione esploreremo distribuzioni scalari: le distribuzioni di forma caso di [] . Un esempio tipico è la distribuzione di Poisson, specificata da un 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)

La distribuzione di Poisson è una distribuzione scalare, e la sua forma evento è sempre [] . Se specifichiamo più tariffe, queste vengono visualizzate nella forma batch. L'ultimo paio di esempi è interessante: c'è solo un tasso singolo, ma poiché quel tasso è incorporato in un array numpy con una forma non vuota, quella forma diventa la forma batch.

Anche la distribuzione normale standard è scalare. Forma evento E 'è [] , proprio come per il Poisson, ma staremo a giocare con lui per vedere il nostro primo esempio di radiodiffusione. La normale viene specificato utilizzando loc e scale parametri:

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)

L'esempio precedente è interessante il Broadcasting Scale distribuzione. L' loc parametro ha forma [4] , e la scale parametro ha forma [2, 1] . Usando le regole di radiodiffusione numpy , la forma batch è [2, 4] . Un modo equivalente (ma meno elegante e non-consigliato) per definire il "Broadcasting Scale" di distribuzione potrebbe essere:

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)

Possiamo vedere perché la notazione di trasmissione è utile, sebbene sia anche fonte di mal di testa e bug.

Campionamento di distribuzioni scalari

Ci sono due cose principali che possiamo fare con le distribuzioni: siamo in grado di sample da loro e siamo in grado di calcolare log_prob s. Esaminiamo prima il campionamento. La regola di base è che quando campioniamo da una distribuzione, il risultante Tensor ha forma [sample_shape, batch_shape, event_shape] , dove batch_shape e event_shape sono forniti dal Distribution oggetto, e sample_shape è fornito dalla chiamata a sample . Per le distribuzioni scalari, event_shape = [] , quindi Tensor restituito dal campione avrà forma [sample_shape, batch_shape] . Proviamolo:

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)

Questo è tutto quello che c'è da dire su sample : campione tensori restituiti hanno forma [sample_shape, batch_shape, event_shape] .

Calcolo log_prob Per scalari Distribuzioni

Ora diamo uno sguardo a log_prob , che è un po 'più complicato. log_prob prende in ingresso un tensore (non vuota) rappresenta la posizione (s) in cui per calcolare il log_prob per la distribuzione. Nel caso più semplice, questo tensore avrà una forma della forma [sample_shape, batch_shape, event_shape] , dove batch_shape e event_shape corrispondano alla forma e lotti caso di distribuzione. Richiamo ancora una volta che per distribuzioni scalari, event_shape = [] , quindi il tensore ingresso ha forma [sample_shape, batch_shape] In questo caso, abbiamo tornare un tensore di forma [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)>

Si noti come nel primo esempio, l'ingresso e l'uscita hanno forma [2, 3] e nel secondo esempio hanno forma [1, 1, 2, 3] .

Sarebbe tutto quello che c'era da dire, se non fosse per le trasmissioni. Ecco le regole una volta che prendiamo in considerazione la trasmissione. Lo descriviamo in piena generalità e notiamo le semplificazioni per le distribuzioni scalari:

  1. Definire n = len(batch_shape) + len(event_shape) . (Per le distribuzioni scalari, len(event_shape)=0 .)
  2. Se l'ingresso tensore t ha meno di n dimensioni, la forma del rilievo aggiungendo dimensioni del formato 1 a sinistra fino a che non ha esattamente n dimensioni. Chiama la risultante tensore t' .
  3. Broadcast le n dimensioni più a destra di t' nei confronti del [batch_shape, event_shape] della distribuzione si sta calcolando un log_prob per. In maggior dettaglio: per le dimensioni in cui t' le partite già distribuiti, fare nulla, e per le dimensioni in cui t' ha un singoletto, che replica singoletto il numero appropriato di volte. Qualsiasi altra situazione è un errore. (Per le distribuzioni scalari, abbiamo solo trasmettiamo contro batch_shape , dal momento che event_shape = [] .)
  4. Ora siamo finalmente in grado di calcolare la log_prob . Il tensore risultante avrà forma [sample_shape, batch_shape] , dove sample_shape è definito come qualsiasi dimensione di t o t' a sinistra delle n -rightmost dimensioni: sample_shape = shape(t)[:-n] .

Questo potrebbe essere un pasticcio se non sai cosa significa, quindi lavoriamo con alcuni esempi:

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

Il tensore [10.] (di forma [1] ) viene trasmesso attraverso il batch_shape di 3, quindi valutiamo registro probabilità tutti e tre i Poissons' al valore 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)>

Nell'esempio sopra, il tensore ingresso ha forma [2, 2, 1] , mentre l'oggetto distribuzioni ha una forma lotto di 3. Quindi, per ciascuno dei [2, 2] dimensioni del campione, il valore unico previsto ottiene broadcats a ciascun dei tre Poisson.

Un modo forse utile pensare: perché three_poissons ha batch_shape = [2, 3] , una chiamata a log_prob deve prendere una Tensor cui dimensione ultimo è 1 o 3; qualsiasi altra cosa è un errore. (Le regole di radiodiffusione numpy trattare il caso particolare di uno scalare come totalmente equivalente a un tensore di forma [1] .)

Il test di lasciare che i nostri costolette giocando con le più complessa distribuzione di Poisson con 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)>

Gli esempi precedenti riguardavano la trasmissione sul batch, ma la forma del campione era vuota. Supponiamo di avere una raccolta di valori e di voler ottenere la probabilità logaritmica di ciascun valore in ogni punto del batch. Potremmo farlo manualmente:

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

Oppure potremmo lasciare che la trasmissione gestisca l'ultima dimensione batch:

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

Possiamo anche (forse un po' meno naturalmente) lasciare che la trasmissione gestisca solo la prima dimensione batch:

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

O potremmo far trasmettere gestire sia in batch dimensioni:

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

Quanto sopra ha funzionato bene quando avevamo solo due valori che volevamo, ma supponiamo di avere un lungo elenco di valori che volevamo valutare in ogni punto batch. Per questo, la seguente notazione, che aggiunge dimensioni extra di taglia 1 al lato destro della forma, è estremamente utile:

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

Questo è un esempio di strided notazione fetta , che vale la pena conoscere.

Tornando a three_poissons per completezza, lo stesso esempio appare come segue:

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

Distribuzioni multivariate

Passiamo ora alle distribuzioni multivariate, che hanno forma di evento non vuota. Consideriamo le distribuzioni multinomiali.

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)

Si noti come negli ultimi tre esempi, il batch_shape è sempre [2] , ma possiamo usare la trasmissione di avere sia un condiviso total_count o condiviso probs (o nessuno), perché sotto il cofano sono trasmessi ad avere la stessa forma.

Il campionamento è semplice, dato ciò che sappiamo già:

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)

Il calcolo delle probabilità dei log è altrettanto semplice. Facciamo un esempio con distribuzioni Normali Multivariate diagonali. (I multinomi non sono molto adatti alla trasmissione, poiché i vincoli sui conteggi e sulle probabilità significano che la trasmissione spesso produrrà valori inammissibili.) Utilizzeremo un batch di 2 distribuzioni tridimensionali con la stessa media ma diverse scale (deviazioni standard):

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>

(Si noti che anche se abbiamo usato distribuzioni in cui le scaglie erano multipli della identità, questa non è una limitazione, potremmo passare scale anziché scale_identity_multiplier .)

Ora valutiamo la probabilità logaritmica di ciascun punto batch alla sua media e a una media spostata:

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

Esattamente equivalentemente, possiamo usare https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/strided-slice per inserire una forma in più = 1 dimensione nel bel mezzo di una costante:

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

D'altra parte, se non facciamo inserto la dimensione extra, passiamo [1., 2., 3.] al primo punto batch e [3., 4., 5.] al secondo:

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

Tecniche di manipolazione della forma

Il Biiettore Reshape

Il Reshape bijector può essere utilizzato per rimodellare l'event_shape di una distribuzione. Vediamo un esempio:

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>

Abbiamo creato un multinomiale con una forma caso di [6] . Il Reshape Bijector ci permette di trattare questo come una distribuzione di forma caso di [2, 3] .

Un Bijector rappresenta un differenziabile, uno-a-uno funzione un aperto di \({\mathbb R}^n\). Bijectors sono utilizzati in combinazione con TransformedDistribution , che modella una distribuzione \(p(y)\) in termini di distribuzione di base \(p(x)\) e un Bijector che rappresenta \(Y = g(X)\). Vediamolo in azione:

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>

Questa è l'unica cosa che il Reshape bijector può fare: non può trasformare dimensioni evento in dimensioni batch o viceversa.

La Distribuzione Indipendente

La Independent distribuzione è usato per trattare un insieme di indipendente, non-necessariamente identico (alias un lotto di) distribuzioni come una singola distribuzione. Più sinteticamente, Independent permette di convertire le dimensioni in batch_shape per dimensioni in event_shape . Illustriamo con un esempio:

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>

Possiamo pensare a questo come una serie di monete due per cinque con le probabilità associate di testa. Valutiamo la probabilità di un particolare insieme arbitrario di uno e zero:

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

Possiamo usare Independent di trasformare questo in due diversi "gruppi di cinque Bernoulli di", che è utile se vogliamo considerare una "riga" di lanci della moneta in arrivo in un determinato modello come un unico esito:

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>

Matematicamente, stiamo calcolando la probabilità logaritmica di ogni "insieme" di cinque sommando le probabilità logaritmiche dei cinque lanci di moneta "indipendenti" nell'insieme, da cui prende il nome la distribuzione:

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

Siamo in grado di andare anche oltre e utilizzare Independent per creare una distribuzione in cui i singoli eventi sono un insieme di due a cinque Bernoulli di:

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>

Vale la pena di notare che dal punto di vista sample , utilizzando Independent cambia nulla:

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)

Come un esercizio di separazione per il lettore, si consiglia di considerare le differenze e le somiglianze tra un lotto vettore di Normal distribuzioni e un MultivariateNormalDiag distribuzione da un punto di vista probabilità di campionamento e di registro. Come possiamo usare Independent per costruire un MultivariateNormalDiag da un lotto di Normal s? (Si noti che MultivariateNormalDiag non viene effettivamente implementata in questo modo.)