# TensorFlow Distributions 浅析

Tensorflow Eager 是 TensorFlow 的命令式执行环境。在 TensorFlow Eager 中，每个 TF 运算都会立即得到计算并生成结果。这与 TensorFlow 的标准“计算图”模式形成对比，在“计算图”模式下，TF 运算会将节点添加到稍后执行的计算图中。整个笔记本使用 TF Eager 编写，但是本文介绍的任何概念都与其无关，并且 TFP 可以在计算图模式下使用。

import collections

import tensorflow as tf
import tensorflow_probability as tfp
tfd = tfp.distributions

try:
tf.compat.v1.enable_eager_execution()
except ValueError:
pass

import matplotlib.pyplot as plt


## 基本的一元分布

n = tfd.Normal(loc=0., scale=1.)
n

<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>


n.sample()

<tf.Tensor: shape=(), dtype=float32, numpy=0.25322816>


n.sample(3)

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.4658079, -0.5653636,  0.9314412], dtype=float32)>


n.log_prob(0.)

<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>


n.log_prob([0., 2., 4.])

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-0.9189385, -2.9189386, -8.918939 ], dtype=float32)>


b = tfd.Bernoulli(probs=0.7)
b

<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[] event_shape=[] dtype=int32>

b.sample()

<tf.Tensor: shape=(), dtype=int32, numpy=1>

b.sample(8)

<tf.Tensor: shape=(8,), dtype=int32, numpy=array([1, 0, 0, 0, 1, 0, 1, 0], dtype=int32)>

b.log_prob(1)

<tf.Tensor: shape=(), dtype=float32, numpy=-0.35667497>

b.log_prob([1, 0, 1, 0])

<tf.Tensor: shape=(4,), dtype=float32, numpy=array([-0.35667497, -1.2039728 , -0.35667497, -1.2039728 ], dtype=float32)>


## 多元分布

nd = tfd.MultivariateNormalDiag(loc=[0., 10.], scale_diag=[1., 4.])
nd

<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>


tfd.Normal(loc=0., scale=1.)

<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>


nd.sample()

<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-1.2489667, 15.025171 ], dtype=float32)>

nd.sample(5)

<tf.Tensor: shape=(5, 2), dtype=float32, numpy=
array([[-1.5439653 ,  8.9968405 ],
[-0.38730723, 12.448896  ],
[-0.8697963 ,  9.330035  ],
[-1.2541095 , 10.268944  ],
[ 2.3475595 , 13.184147  ]], dtype=float32)>

nd.log_prob([0., 10])

<tf.Tensor: shape=(), dtype=float32, numpy=-3.2241714>


covariance_matrix = [[1., .7], [.7, 1.]]
nd = tfd.MultivariateNormalTriL(
loc = [0., 5], scale_tril = tf.linalg.cholesky(covariance_matrix))
data = nd.sample(200)
plt.scatter(data[:, 0], data[:, 1], color='blue', alpha=0.4)
plt.axis([-5, 5, 0, 10])
plt.title("Data set")
plt.show()


## 多个分布

b3 = tfd.Bernoulli(probs=[.3, .5, .7])
b3

<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>


b3.sample()

<tf.Tensor: shape=(3,), dtype=int32, numpy=array([0, 1, 1], dtype=int32)>

b3.sample(6)

<tf.Tensor: shape=(6, 3), dtype=int32, numpy=
array([[1, 0, 1],
[0, 1, 1],
[0, 0, 1],
[0, 0, 1],
[0, 0, 1],
[0, 1, 0]], dtype=int32)>


b3.prob([1, 1, 0])

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5       , 0.29999998], dtype=float32)>


API 为何会包含批次形状？从语义上来讲，API 可以创建一组分布并使用 for 循环（至少在 Eager 模式下如此；在 TF 计算图模式下，需要使用 tf.while 循环）对这些分布进行迭代，从而执行相同的计算。但是，极为常见的情况是一组（可能较大的）分布采用相同的参数设置；为了能够使用硬件加速器快速进行计算，关键要素是尽可能使用向量化计算。

## 使用 Independent 将批次汇总到事件

b3_joint = tfd.Independent(b3, reinterpreted_batch_ndims=1)
b3_joint

<tfp.distributions.Independent 'IndependentBernoulli' batch_shape=[] event_shape=[3] dtype=int32>


b3

<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>


b3_joint.prob([1, 1, 0])

<tf.Tensor: shape=(), dtype=float32, numpy=0.044999998>


tf.reduce_prod(b3.prob([1, 1, 0]))

<tf.Tensor: shape=(), dtype=float32, numpy=0.044999994>


• b3.sampleb3_joint.sample 采用不同的概念实现，但输出无区别：在计算概率时，使用 Independent 基于批次生成的一批独立分布与单个分布之间存在差异，但在抽样时，这两种分布之间没有差别。
• 可以使用标量 NormalIndependent 分布轻松实现 MultivariateNormalDiag（实际上不会以这种方式来实现它，但可以这么做）。

## 多元分布的批次

covariance_matrix = [[[1., .1], [.1, 1.]],
[[1., .3], [.3, 1.]],
[[1., .5], [.5, 1.]]]
nd_batch = tfd.MultivariateNormalTriL(
loc = [[0., 0.], [1., 1.], [2., 2.]],
scale_tril = tf.linalg.cholesky(covariance_matrix))
nd_batch

<tfp.distributions.MultivariateNormalTriL 'MultivariateNormalTriL' batch_shape=[3] event_shape=[2] dtype=float32>


nd_batch.sample(4)

<tf.Tensor: shape=(4, 3, 2), dtype=float32, numpy=
array([[[ 0.7367498 ,  2.730996  ],
[-0.74080074, -0.36466932],
[ 0.6516018 ,  0.9391426 ]],

[[ 1.038303  ,  0.12231752],
[-0.94788766, -1.204232  ],
[ 4.059758  ,  3.035752  ]],

[[ 0.56903946, -0.06875849],
[-0.35127294,  0.5311631 ],
[ 3.4635801 ,  4.565582  ]],

[[-0.15989424, -0.25715637],
[ 0.87479895,  0.97391707],
[ 0.5211419 ,  2.32108   ]]], dtype=float32)>


nd_batch.log_prob([[0., 0.], [1., 1.], [2., 2.]])

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.8328519, -1.7907217, -1.694036 ], dtype=float32)>


## 广播（也可以说这为何让人如此困惑？）

• 对于一元标量分布 nbBE = ()
• 对于二维多元正态分布 ndBE = (2)
• 对于 b3b3_jointBE = (3)
• 对于多元正态分布批次 ndbBE = (3, 2)

• 没有参数的样本将返回形状为 BE 的张量；标量为 n 的抽样将返回张量“n * BE”。
• problog_prob 使用形状为 BE 的张量，并返回形状为 B 的结果。

problog_prob 的实际“计算规则”更复杂，虽然性能和速度可能不错，但同时也增加了复杂性和挑战。实际规则（本质上）是 {nbsp}log_prob 的参数必须可以根据 BE 进行广播；输出中会预留“额外”维度。

n = tfd.Normal(loc=0., scale=1.)
n

<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>

n.log_prob(0.)

<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>

n.log_prob([0.])

<tf.Tensor: shape=(1,), dtype=float32, numpy=array([-0.9189385], dtype=float32)>

n.log_prob([[0., 1.], [-1., 2.]])

<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-0.9189385, -1.4189385],
[-1.4189385, -2.9189386]], dtype=float32)>


nd = tfd.MultivariateNormalDiag(loc=[0., 1.], scale_diag=[1., 1.])
nd

<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>


log_prob“应该”使用形状为 (2,) 的参数，但它将接受根据此形状广播的任何参数：

nd.log_prob([0., 0.])

<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>


nd.log_prob([[0., 0.],
[1., 1.],
[2., 2.]])

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.337877 , -2.337877 , -4.3378773], dtype=float32)>


nd.log_prob([0.])

<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>

nd.log_prob([[0.], [1.], [2.]])

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.337877 , -2.337877 , -4.3378773], dtype=float32)>


b3 = tfd.Bernoulli(probs=[.3, .5, .7])


b3.prob([1])

<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5       , 0.7       ], dtype=float32)>


（将其与 b3.prob([1., 1., 1.]) 进行比较，我们在前面介绍 b3 时使用过后者。）

b3.log_prob([0, 1])

b3.prob([[0], [1]])

<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[0.7, 0.5, 0.3],
[0.3, 0.5, 0.7]], dtype=float32)>


## 延伸内容

• event_shapebatch_shapesample_shape 可以是任意秩（在本教程中，它们始终为标量或秩 1）。这可以提高性能，但也会为编程带来挑战，特别是在涉及到广播时。要想深入了解形状操作，请参阅 了解 TensorFlow Distributions 形状
• TFP 包含一个强大的抽象概念 Bijectors，将该概念与 TransformedDistribution 结合使用，可以轻松灵活地创建新的分布，这些分布是现有分布的可逆转换。我们很快会尝试编写相关的教程，但目前可以参阅本文档
[{ "type": "thumb-down", "id": "missingTheInformationINeed", "label":"没有我需要的信息" },{ "type": "thumb-down", "id": "tooComplicatedTooManySteps", "label":"太复杂/步骤太多" },{ "type": "thumb-down", "id": "outOfDate", "label":"内容需要更新" },{ "type": "thumb-down", "id": "translationIssue", "label":"翻译问题" },{ "type": "thumb-down", "id": "samplesCodeIssue", "label":"示例/代码问题" },{ "type": "thumb-down", "id": "otherDown", "label":"其他" }]
[{ "type": "thumb-up", "id": "easyToUnderstand", "label":"易于理解" },{ "type": "thumb-up", "id": "solvedMyProblem", "label":"解决了我的问题" },{ "type": "thumb-up", "id": "otherUp", "label":"其他" }]