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 :
- Définir
n = len(batch_shape) + len(event_shape)
. (Pour les distributions scalaires,len(event_shape)=0
). - Si le tenseur d'entrée
t
a moins den
dimensions, sa forme pad en ajoutant les dimensions de la taille1
à gauche jusqu'à ce qu'il a exactementn
dimensions. Appelez le tenseur résultantt'
. - Diffusion des
n
Dimensions de l' extrême droite det'
contre la[batch_shape, event_shape]
de la distribution vous calculer unlog_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 contrebatch_shape
, depuis event_shape =[]
.) - 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 det
out'
à gauche desn
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.)