Comprendre les formes de distribution TensorFlow

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier
import collections

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

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

Notions de base

Il existe trois concepts importants associés aux formes de distributions TensorFlow :

  • Forme de l' événement décrit la forme d'un seul tirage de la distribution; il peut dépendre d'une dimension à l'autre. Pour les distributions scalaires, la forme de l' événement est [] . Pour un MultivariateNormal 5 dimensions, la forme de l' événement est [5] .
  • Forme de lot décrit indépendant, non identiquement distribués dessine, alias un « lot » de distributions.
  • Forme échantillon décrit indépendant, identiquement distribuées tire des lots de la famille de distribution.

La forme de l' événement et la forme de lots sont des propriétés d'une Distribution objet, alors que la forme échantillon est associé à un appel spécifique à l' sample ou log_prob .

Le but de ce cahier est d'illustrer ces concepts par des exemples, donc si ce n'est pas immédiatement évident, ne vous inquiétez pas !

Pour un autre aperçu conceptuel de ces concepts, voir ce billet de blog .

Une note sur TensorFlow Eager.

Ce bloc - notes est entièrement écrit en utilisant tensorflow Eager . Aucun des concepts présentés se fondent sur Eager, mais avec Eager, des formes de traitement par lots de distribution et d' événements sont évalués (et donc connus) lorsque la Distribution objet est créé en Python, alors que dans le graphique (mode non-Désireuse), il est possible de définir des distributions dont les formes d'événement et de lot sont indéterminées jusqu'à ce que le graphique soit exécuté.

Distributions scalaires

Comme nous l' avons indiqué plus haut, une Distribution objet a défini l' événement et des formes lot. Nous allons commencer par un utilitaire pour décrire les distributions :

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

Dans cette section , nous allons explorer les distributions scalaires: distributions avec une forme d'événement de [] . Un exemple typique est la distribution de Poisson, spécifiée par 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 distribution de Poisson est une distribution scalaire, de sorte que sa forme d'événement est toujours [] . Si nous spécifions plus de taux, ceux-ci apparaissent dans la forme du lot. La dernière paire d'exemples est intéressante : il n'y a qu'un seul taux, mais comme ce taux est intégré dans un tableau numpy avec une forme non vide, cette forme devient la forme du lot.

La distribution normale standard est également un scalaire. Il forme de l' événement est [] , tout comme pour le Poisson, mais nous allons jouer avec lui pour voir notre premier exemple de diffusion. La normale est spécifiée à l' aide loc et scale paramètres:

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'exemple ci - dessus est intéressant l' Broadcasting Scale de Broadcasting Scale de la distribution. Le loc paramètre a la forme [4] , et l' scale de paramètre a la forme [2, 1] . En utilisant les règles de [2, 4] diffusion NumPy , la forme de lot est [2, 4] . Un équivalent (mais moins élégante et non recommandée) façon de définir la "Broadcasting Scale" de "Broadcasting Scale" la distribution serait:

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)

On voit pourquoi la notation broadcast est utile, même si c'est aussi une source de maux de tête et de bugs.

Échantillonnage des distributions scalaires

Il y a deux choses principales que nous pouvons faire des distributions: nous pouvons sample d'eux et nous pouvons calculer log_prob s. Explorons d'abord l'échantillonnage. La règle de base est que lorsque nous échantillon d'une distribution, le Tensor résultant a une forme [sample_shape, batch_shape, event_shape] , où batch_shape et event_shape sont fournis par la Distribution objet et sample_shape est fourni par l'appel à l' sample . Pour les distributions scalaires, event_shape = [] , de sorte que le retour de Tensor échantillon aura la forme [sample_shape, batch_shape] . Essayons:

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)

C'est tout ce qu'il ya à dire au sujet de l' sample : l' échantillon tenseurs retournés ont la forme [sample_shape, batch_shape, event_shape] .

Calcul log_prob Pour Scalar Distributions

Maintenant , nous allons jeter un oeil à log_prob , ce qui est un peu plus délicat. log_prob prend en entrée un tenseur (non vide) représentant l'emplacement (s) au cours de laquelle pour calculer la log_prob pour la distribution. Dans le cas le plus simple, ce tenseur aura une forme de la forme [sample_shape, batch_shape, event_shape] , où batch_shape et event_shape correspondent aux formes de traitement par lots et de l' événement de la distribution. Rappelons une fois de plus que pour les distributions scalaires, event_shape = [] , de sorte que le tenseur d'entrée a une forme [sample_shape, batch_shape] Dans ce cas, nous retourner un tenseur de forme [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)>

Notez comment dans le premier exemple, l'entrée et la sortie ont une forme [2, 3] et dans le second exemple , ils ont la forme [1, 1, 2, 3] .

Ce serait tout ce qu'il y aurait à dire, s'il n'y avait pas eu de diffusion. Voici les règles une fois la diffusion prise en compte. Nous le décrivons en toute généralité et notons des simplifications pour les distributions scalaires :

  1. Définir n = len(batch_shape) + len(event_shape) . (Pour les distributions scalaires, len(event_shape)=0 ).
  2. Si le tenseur d'entrée t a moins de n dimensions, sa forme pad en ajoutant les dimensions de la taille 1 à gauche jusqu'à ce qu'il a exactement n dimensions. Appelez le tenseur résultant t' .
  3. Diffusion des n Dimensions de l' extrême droite de t' contre la [batch_shape, event_shape] de la distribution vous calculer un log_prob pour. De façon plus détaillée: pour les dimensions où t' les matches déjà la distribution, ne rien faire, et pour les dimensions où t' a un singleton, répliquée singleton le nombre approprié de fois. Toute autre situation est une erreur. (Pour les distributions scalaires, nous diffusons seulement contre batch_shape , depuis event_shape = [] .)
  4. Maintenant , nous sommes enfin en mesure de calculer le log_prob . Le tenseur résultant aura la forme [sample_shape, batch_shape] , où sample_shape est définie comme étant des dimensions de t ou t' à gauche des n dimensions -rightmost: sample_shape = shape(t)[:-n] .

Cela peut être un gâchis si vous ne savez pas ce que cela signifie, alors prenons quelques exemples :

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

Le tenseur [10.] (avec la forme [1] ) est diffusé à travers le batch_shape de 3, donc nous évaluons tous les trois la probabilité de journal de la valeur 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)>

Dans l'exemple ci - dessus, le tenseur d'entrée a la forme [2, 2, 1] , tandis que l'objet de distributions a une forme de traitement par lots de 3. Ainsi , pour chacun des [2, 2] échantillon dimensions, la seule valeur disponible obtient broadcats à chaque des trois Poissons.

Une façon peut - être utile de penser: parce que three_poissons a batch_shape = [2, 3] , un appel à log_prob doit prendre une Tensor dont la dernière dimension est 1 ou 3; tout le reste est une erreur. (Les règles de radiodiffusion numpy traitent le cas particulier d'un scalaire comme étant totalement équivalent à un tenseur de forme [1] ).

Test Let nos côtelettes en jouant avec la distribution de Poisson plus complexe avec 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)>

Les exemples ci-dessus impliquaient la diffusion sur le lot, mais la forme de l'échantillon était vide. Supposons que nous ayons une collection de valeurs et que nous voulions obtenir la probabilité de log de chaque valeur à chaque point du lot. On pourrait le faire manuellement :

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

Ou nous pourrions laisser la diffusion gérer la dernière dimension du lot :

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

Nous pouvons également (peut-être un peu moins naturellement) laisser la diffusion gérer uniquement la première dimension du lot :

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

Ou nous pourrions laisser gérer à la fois la diffusion des dimensions lot:

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

Ce qui précède a bien fonctionné lorsque nous n'avions que deux valeurs que nous voulions, mais supposons que nous ayons une longue liste de valeurs que nous voulions évaluer à chaque point de lot. Pour cela, la notation suivante, qui ajoute des dimensions supplémentaires de taille 1 sur le côté droit de la forme, est extrêmement 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)>

Ceci est une instance de notation tranche strided , ce qui est bon à savoir.

Pour en revenir à three_poissons pour être complet, les mêmes regards par exemple comme:

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

Distributions multivariées

Passons maintenant aux distributions multivariées, qui ont une forme d'événement non vide. Regardons les distributions multinomiales.

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)

Notez que sur les trois derniers exemples, le batch_shape est toujours [2] , mais nous pouvons utiliser la diffusion soit un partage total_count ou un partage probs (ou aucun des deux), parce que sous le capot , ils sont diffusés à la même forme.

L'échantillonnage est simple, compte tenu de ce que nous savons déjà :

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)

Le calcul des probabilités de log est tout aussi simple. Prenons un exemple avec des distributions normales multivariées diagonales. (Les multinômes ne sont pas très adaptés à la diffusion, car les contraintes sur les nombres et les probabilités signifient que la diffusion produira souvent des valeurs inadmissibles.) Nous utiliserons un lot de 2 distributions tridimensionnelles avec la même moyenne mais des échelles différentes (écarts types) :

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>

(Notez que même si nous avons utilisé les distributions où les échelles étaient multiples de l'identité, ce n'est pas une restriction, nous pourrions passer à l' scale au lieu de scale_identity_multiplier .)

Évaluons maintenant la probabilité de log de chaque point de lot à sa moyenne et à une moyenne décalée :

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

Exactement de manière équivalente, on peut utiliser https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/strided-slice pour insérer une forme supplémentaire = 1 dimension au milieu d'une constante:

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'autre part, si nous ne sommes pas insérer la dimension supplémentaire, nous passons [1., 2., 3.] au premier point de traitement par lots et [3., 4., 5.] à la seconde:

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

Techniques de manipulation de forme

Le Bijecteur Remodeler

Le Reshape bijector peut être utilisé pour remodeler le event_shape d'une distribution. Voyons un exemple :

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>

Nous avons créé un multinomial avec une forme de cas de [6] . Le Reshape Bijector nous permet de traiter cela comme une distribution avec une forme de cas de [2, 3] .

A Bijector représente un dérivable, une à une fonction sur une partie ouverte de \({\mathbb R}^n\). Bijectors sont utilisés conjointement avec TransformedDistribution , qui modélise une distribution \(p(y)\) en termes d'une distribution de base \(p(x)\) et un Bijector qui représente \(Y = g(X)\). Voyons cela en action :

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>

Ceci est la seule chose que le Reshape bijector peut le faire: il ne peut pas transformer les dimensions de l' événement dans des dimensions de traitement par lots ou vice-versa.

La distribution indépendante

La Independent de distribution est utilisé pour traiter une collection de distributions indépendantes, non nécessairement identique (alias un lot de) comme une seule distribution. De façon plus concise, Independent permet de convertir des dimensions en batch_shape aux dimensions en event_shape . Nous allons illustrer par l'exemple :

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>

Nous pouvons considérer cela comme un ensemble de pièces de monnaie de deux par cinq avec les probabilités associées de face. Évaluons la probabilité d'un ensemble particulier et arbitraire de uns et de zéros :

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

Nous pouvons utiliser Independent pour transformer cela en deux « ensembles de cinq Bernoulli de », ce qui est utile si l' on veut envisager une « ligne » de pièce flips venir dans un modèle donné comme seul résultat:

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>

Mathématiquement, nous calculons la probabilité de log de chaque « ensemble » de cinq en additionnant les probabilités de log des cinq lancers de pièce « indépendants » dans l'ensemble, c'est là que la distribution tire son nom :

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

Nous pouvons aller encore plus loin et utiliser Independent pour créer une distribution où les événements individuels sont un ensemble de deux par cinq Bernoulli de:

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>

Il convient de noter que dans la perspective de l' sample , en utilisant Independent ne change rien:

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)

Comme un exercice de séparation pour le lecteur, nous vous suggérons de tenir compte des différences et des similitudes entre un lot de vecteur Normal distributions et MultivariateNormalDiag la distribution à partir d' un échantillonnage et log point de vue des probabilités. Comment peut - on utiliser Independent pour construire un MultivariateNormalDiag à partir d' un lot de Normal s? (Notez que MultivariateNormalDiag n'est pas réellement mis en œuvre de cette façon.)