马尔科夫链蒙特卡洛算法
贝叶斯景象图
对于一个含有 NN 个未知元素的贝叶斯推断问题,我们隐式地为其先验分布创建了一个 NN 维空间。先验分布上某一点的概率,将投射到某个高维的面或曲线上,其形状由先验分布决定。
比如,假定有两个未知元素p1、p2 ,其先验分布都是(0,5)上的均匀分布,那么先验分布就存在于一个边长为 5 的正方形空间。而其概率面就是正方形上方的一个平面(由于假定了均匀分布,因此每一点概率相同)。或许,你还是有点懵,现在让我们画出图像,你就可以明白了。
下面代码只是为了画图,你不用自己再写一遍,直接运行即可。
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import scipy.stats as stats
from IPython.core.pylabtools import figsize
import numpy as np
%matplotlib inline
figsize(12.5, 4)
jet = plt.cm.jet
fig = plt.figure()
x = y = np.linspace(0, 5, 100)
X, Y = np.meshgrid(x, y)
plt.subplot(121)
uni_x = stats.uniform.pdf(x, loc=0, scale=5)
uni_y = stats.uniform.pdf(y, loc=0, scale=5)
M = np.dot(uni_x[:, None], uni_y[None, :])
im = plt.imshow(M, interpolation='none', origin='lower',
cmap=jet, vmax=1, vmin=-.15, extent=(0, 5, 0, 5))
plt.xlim(0, 5)
plt.ylim(0, 5)
plt.title("Landscape formed by Uniform priors.")
ax = fig.add_subplot(122, projection='3d')
ax.plot_surface(X, Y, M, cmap=plt.cm.jet, vmax=1, vmin=-.15)
ax.view_init(azim=390)
plt.title("Uniform prior landscape; alternate view")
如上图所示,我们把 2 维空间里的两个参数(p1、p2),映射到了三维空间(概率面,即第三个维度为概率取值)中,形成一个平面。那么,为什么映射到 3 维之后还是一个平面呢?因为这两个参数都遵从相同范围的均匀分布。即选取 (P1,P2)=(m,n) 的概率为0.5,其中 m,n 可以为任意值。
现在,让我们换一个例子。如果(p1、p2)的先验分布分别为Exp(3) 和Exp(10) 的指数分布,那么对应的空间便是二维平面范围,即各维都取正值所确定的范围。而对应的三维概率面的形状则是一个从(0,0) 点向正值方向流淌的瀑布。
同样,让我们画出二维和三维的表示图,代码如下:
figsize(12.5, 5)
fig = plt.figure()
plt.subplot(121)
exp_x = stats.expon.pdf(x, scale=3)
exp_y = stats.expon.pdf(x, scale=10)
M = np.dot(exp_x[:, None], exp_y[None, :])
CS = plt.contour(X, Y, M)
im = plt.imshow(M, interpolation='none', origin='lower',
cmap=jet, extent=(0, 5, 0, 5))
#plt.xlabel("prior on $p_1$")
#plt.ylabel("prior on $p_2$")
plt.title("$Exp(3), Exp(10)$ prior landscape")
ax = fig.add_subplot(122, projection='3d')
ax.plot_surface(X, Y, M, cmap=jet)
ax.view_init(azim=390)
plt.title("$Exp(3), Exp(10)$ prior landscape; \nalternate view")
如上图所示,其中的颜色越是趋向于暗红的位置,其先验概率越高。反过来,颜色越是趋向于深蓝的位置,其先验概率越低。
当然,实际运用中的参数一般不止两个,先验分布所在的空间和其概率面往往具有更高的维度。
如果概率面描述了未知变量的先验分布,那么在得到真实样本以后,先验所在的空间会有什么变化呢?
实际上,真实样本对空间(即三个维度的范围大小)不会有影响,但它会改变概率面的形状。概率面的某些局部区域将会被拉伸或者挤压,以表明参数的真实值所在。更多的数据意味着对概率面更多的拉伸与挤压,使得最初的概率面形状变得不像样子。反之,数据越少,那么最初的形状保留越好。不管如何,最后得到的概率面就是后验概率的分布了。
在二维空间上,这些拉伸、挤压的结果是形成了几座山峰。但当我们在对这个空间施加作用力用以形成局部山峰的同时,我们也会受到先验分布的阻挠,先验概率越小,阻力越大。因此,我们可以从上图中看出,在 (0,0)点的先验较高,阻力较小。因此,比先验较低的 (5,5)点,更容易形成山峰。从后验分布上看,这些山峰的位置,表示的就是各未知量最有可能的取值(因为概率大)。
下图分别展示了,当先验分布为均匀分布和指数分布时,模型得到了一个观测值后的景象:
其中:
左上图:由均匀先验分布p1和p2形成的图形。
右上图:由指数先验分布p1和p2形成的图形。
左下图:均匀先验形成的图形被观测值扭曲后的结果,即均匀分布得到的后验分布。
右下图:指数先验形成的图形被观测值扭曲后的结果,即指数先验得到的后验分布。
四张图中的黑点代表参数的真实取值,即我们放入的那个观测值。如上图,虽然观测值相同,但是两种假设下得到的后验分布却有所不同。我们可以看到,指数先验所对应的后验分布图形中,右上角的区域取值很低。是因为,指数的先验分布在这个区域的取值较小。反之,均匀先验所对应的后验分布图形中,右上图的取值较高,是因为均匀先验在该区域的取值就比较高。(这个“高”是和指数先验在做比较。而均匀先验在每一个点都相同,都为 0.5)。
我们可以很明显的看到,即便只有一个观测值,形成的山峰也试图包含参数值的真实位置。当然,仅仅一个样本做出的推断也无法说服任何人,如此小的样本只是为了方便我们阐述。
模型的训练其实就是去找最佳的参数。换句话说,其实就是去找后验分布上的山峰区域。当然,我们这里找的不是一个点,而是一大片山峰(即上图中的非蓝色区域)。那么如何去寻找这些山峰并得到后验概率的分布情况呢?这就需要使用到 MCMC 了。
使用 MCMC 探索景象图
MCMC 算法简介
或许你会问,找山峰还不简单吗?遍历一遍不就完了吗?为什么还需要使用马尔科夫链蒙特卡洛(MCMC)算法呢?因为,我们实际生活中需要解决的问题,不会像上面一样只是一个 3 维空间。在实际应用中,我们可能有更多需要求解的参数,进而使先验分布的概率面以及观测值结合形成一个很大的 N 维空间。遍历一个 N 维空间的复杂度是非常大的。举个例子,假设我有一个每个维度长为10的二维空间,我只需要遍历 100 次。但是如果现在空间增加到10维,范围长度还是 10,此时我们需要遍历多少次呢?此时我们需要遍历 100亿 次。如果参数数量再增加,那么可能会让计算机卡死机。这个现象,被叫做 维度灾难问题。
这就是我们需要 MCMC 的原因,MCMC 背后的思想就是如何聪明地对空间进行搜索。
回想以下,在之前的实验中,我们利用 MCMC 训练模型得到的总是后验分布上的一些样本点,而非后验分布本身。这是为什么呢?我们可以理解为,MCMC 为了找到山峰区域,它会不断地问各种石头:“你是不是来自我要找的那一座山峰?”。然后。它会试图将数千个回答了“是的,我来自那个山峰”的石头堆砌起来,重塑那一座要找的山峰。在 MCMC 和 PyMC 的术语里,这个返回序列里的“石头”就是样本,堆积起来的动作称之为“迹(trace)”。
那么为什么会选择用数千个样本来描述一个后验分布呢?这是因为:
返回数学公式来描述高维面的山峰和山谷是非常复杂的。
只返回最高点,而不是整个山峰的形状,是无法构成后验概率的分布的。因为如果只返回一个值,那么所有的值都会是固定的,就无法很好的描述后验分布的形状了。
当然,除了上面的原因外,返回上千个样本的做法还有一个很重要的原因,即方便我们后面使用“大数定律”来处理棘手的问题。
MCMC 算法的步骤
MCMC 有很多不同的实现方法。但总体上来讲,该算法的主要步骤如下:
从当前位置开始。
尝试移动一个位置(即前面所说的捡起一块石头)。
根据新的位置是否服从观测数据和先验分布,来决定采纳或者拒绝这次移动(即询问石头是否来自要找的山峰)。
a.如果采纳,那就留在新的位置,重复第一步。
b.如果不采纳,那就返回上一个位置,并重复第一步。
在大量迭代后,返回所有采纳的点。
这些采纳的点就可以很好的描述后验分布的情况了。
这样做为什么会比直接遍历好呢?
MCMC 起始于一个随机的位置,这个所在位置所对应的概率可能很低。而通过 MCMC 算法,我们会谨慎的收集样本,选择附近最好的一个方向进行移动,这个过程其实是缓慢的。但是,一旦我们到达了后验分布所在区域,我们就可以轻松地收集到大量可用样本。因为,当我们走到山腰时,周围的石头都是属于山峰的,都可以用于描述后验分布。
当然,这也造成了算法移动的最初几步并不能很好的反映后面的情况,对于这个问题,我们后面会专门讨论。
我想,现在你应该了解了 MCMC 的求解过程,以及它会返回一大堆样本的原因。它返回的这一大堆样本,其实可以理解为描述后验分布的数据点。
接下来,让我们利用学到 MCMC 算法来解决一个实际问题。
实例:使用混合模型进行无监督聚类
问题描述
在描述实验的具体目的之前,让我们先来下载数据:
!wget -nc "https://labfile.oss.aliyuncs.com/courses/1520/mixture_data.csv"
现在,先让我们看一下这个数据的分布情况:
figsize(12.5, 4)
# 加载数据
data = np.loadtxt("mixture_data.csv", delimiter=",")
# 根据数据画出频率分布图
plt.hist(data, bins=20, color="k", histtype="stepfilled", alpha=0.8)
plt.title("Histogram of the dataset")
plt.ylim([0, None])
print(data[:10], "...")
从图中可以很好的看出,该数据有两个峰值,一个在 120 附近,另一个在 200 附近。那么该数据则有可能是通过两个分布函数叠加产生的,这里我们可以把它叫做两个聚类簇。也就是说上面的数据是由两个模型产生并混合起来的。因此,本实验的任务就是: 重新将这些数据分成两份。使一个聚类簇中的所有数据都由同一个模型产生。
因此,在已知数据集合的情况下,想把这个数据集合精确的分为两个簇,我们就必须先找到具体的生成模型。为了得到具体的生成模型,我们就必须估计概率p和两个正态分布参数的具体取值。
模型的建立
假设,某条数据由第 一 个模型产生的概率为 p1,由第二个模型产生的概率为 1 - p1。代码如下
import pymc3 as pm
with pm.Model() as model:
# p1 服从均匀分布
p1 = pm.Uniform('p', 0, 1)
p2 = 1 - p1
print(" p1 的先验概率 = %.2f:" % p1.tag.test_value)
因此,针对于本实验,我们需要传入 p = T.stack([p1, p2]) ,代码如下:
import theano.tensor as T
with model:
# 将p1,p2 拼起来
p = T.stack([p1, p2])
# 参数 shape 表示我们定义的变量是一个列表,它一共存了 data.shape[0] 个数据
assignment = pm.Categorical("assignment", p,
shape=data.shape[0], # 产生数据的个数
testval=np.random.randint(0, 2, data.shape[0])) # testval 随机变量的初始值
# 根据概率,随机 10 个变量,观察初始化结果
# p1 的概率产生数字 0
# 1-p1 的概率产生数字 1
print(assignment.tag.test_value[:10])
好了,现在我们已经把每个数据应该属于那个聚类簇给定义好了,接下来,我们需要为每个聚类簇定义分布函数所需要的参数。
由于我们无法知道σ 的具体倾向,因此可以设置σ服从范围为 0 - 100 的均匀分布。
至于另一个参数μ。在正态分布中,μ 其实就是概率最高点对应的x的数据,也可以理解为每个聚类簇的中心点。
我们通过肉眼可以估计μ0大概在 120 附近,μ1大概在 190 附近。但是为了更加科学合理,我们还是将 μ0设置为中心点在 120 的正态分布,而μ1设置为中心点在 190 的正态分布。
定义代码如下:
with model:
# 采用变量数组的方式,sagma0 和 sagma1 都存入变量 sds 中
sds = pm.Uniform("sds", 0, 100, shape=2)
# 这里其实是这是数据产生模型所需要的 mu1 mu2
# 而mu1 mu2 又是有一个新的正态分布函数产生
# 因为直接设置为120 或者190 的话,太过武断
# 并且这两个参数又存在一定的取值倾向,一个倾向于120,一个倾向于190
# 因此,选择他们服从正态分布
centers = pm.Normal("centers",
mu=np.array([120, 190]),
sd=np.array([10, 10]),
shape=2) # 还是一次性定义两个变量
model
得到了每个聚类簇的分布函数所需的参数之后。接下来,我们就需要定义这两个分布函数了,代码如下:
with model:
# 根据选择的结果,得到每条数据应该具有的分布函数参数
# 即选择参数是 (mu1,sagma1) 还是 (mu2,sigam2)
center_i = pm.Deterministic('center_i', centers[assignment])
sd_i = pm.Deterministic('sd_i', sds[assignment])
# 将参数传入最终模型,所有的数据都是通过该模型产生的
# observed=data:将真实数据传入我们定义的模型中
observations = pm.Normal("obs", mu=center_i, sd=sd_i, observed=data)
print("Random assignments: ", assignment.tag.test_value[:4], "...")
print("Assigned center: ", center_i.tag.test_value[:4], "...")
print("Assigned standard deviation: ", sd_i.tag.test_value[:4])
至此,模型的所有参数定义完毕,真实数据也已经传入模型。接下来,我们就需要利用 MCMC 算法训练,找到参数后验分布的山峰,并得到上千个描述它的“小石子”。
模型的训练
但是在模型真正进行迭代,展开搜索之前。我们还需要定义模型模型的搜索空间。也就是说,我需要告诉 MCMC 算法,哪些参数属于连续型变量,哪些参数属于离散型变量。代码如下
with model:
# 告诉模型这些参数为连续型变量
step1 = pm.Metropolis(vars=[p, sds, centers])
# 告诉模型,类别是一个离散型变量
step2 = pm.ElemwiseCategorical(vars=[assignment])
model
最后,就是不断的询问各种小石块,找到数千个样本来描述这些参数的后验分布。也就是 MCMC 算法的迭代过程,模型的训练过程。代码如下
with model:
# 这里表示我们需要返回 5000 个样本,来描述我们的后验分布
trace = pm.sample(5000, step=[step1, step2])
最后,让我们来将这些样本进行可视化,得到每个样本出现的频次,进而画出每个参数的后验分布,下面代码为画图代码,无需手敲:
figsize(11.0, 4)
# 获得参数的迹,即 5000 个样本
std_trace = trace["sds"][5000:]
center_trace = trace["centers"][5000:]
# 设置画图的颜色
colors = ["#348ABD", "#A60628"] if center_trace[-1, 0] > center_trace[-1, 1] \
else ["#A60628", "#348ABD"]
_i = [1, 2, 3, 4]
for i in range(2):
plt.subplot(2, 2, _i[2 * i])
plt.title("Posterior of center of cluster %d" % i)
plt.hist(center_trace[:, i], color=colors[i], bins=30,
histtype="stepfilled")
plt.subplot(2, 2, _i[2 * i + 1])
plt.title("Posterior of standard deviation of cluster %d" % i)
plt.hist(std_trace[:, i], color=colors[i], bins=30,
histtype="stepfilled")
plt.tight_layout()
从上图可以看出 MCMC 算法已经估计出两个聚类簇最可能的中心点分别位于 120 和 200 附近。我们还得到了每个数据所属类别的后验分布样本,即 trace["assignment"] 的返回值。让我们先来看看数据类别的矩阵大小:
print(data.shape)
print(trace["assignment"].shape)
从结果中可以看出,trace["assignment"] 是的大小为 (10000,300)。行数 10000 表示一共进行 10000 次估计。即我们获得了 10000 个小石子。而每次估计中存的就是,对 data 中的每一条属于第一聚类簇还是第二聚类簇的预测。而 data 中共有 300 条数据,因此得到的后验分布结果中,每行也只有 300 个数据。让我们查看一组估计,如下:
trace["assignment"][1, :]
如上所示,其中 0 代表该数据点属于 第一类聚类簇,1 代表该数据点属于第二类聚类簇。
接下来,让我们每间隔 40 行,取一次估计,并把这些估计展示到图中。由于训练时会将数据大小进行排序,因此,我们这里在取出一次估计后,还需要重新排序,排成 data 中的顺序,才方便对照,这里我们可以使用 np.argsort(data) 来进行排序。
import matplotlib as mpl
figsize(12.5, 4.5)
# 设置颜色组
plt.cmap = mpl.colors.ListedColormap(colors)
# 每间隔40个估计取一次样,且每次取出来之后,把列按照 data 顺序进行重排
plt.imshow(trace["assignment"][::40, np.argsort(data)],
cmap=plt.cmap, aspect=.4, alpha=.9)
# 画出图片
plt.xticks(np.arange(0, data.shape[0], 40),
["%.2f" % s for s in np.sort(data)[::40]])
plt.ylabel("posterior sample")
plt.xlabel("value of $i$th data point")
plt.title("Posterior labels of data points")
当然,下图可以更加直观的看出,每条数据属于第 1 聚类簇的可能性。下面代码其实主要做的就是统计所有的估计,得到每条数据属于第一类的占比,进而估算可能性。
cmap = mpl.colors.LinearSegmentedColormap.from_list("BMH", colors)
assign_trace = trace["assignment"]
plt.scatter(data, 1 - assign_trace.mean(axis=0), cmap=cmap,
c=assign_trace.mean(axis=0), s=50)
plt.ylim(-0.05, 1.05)
plt.xlim(35, 300)
plt.title("Probability of data point belonging to cluster 0")
plt.ylabel("probability")
plt.xlabel("value of data point")
从上图中,我们可以很清楚的看出第 i 条数据应该属于哪一类簇。
最佳参数的选择
一个简单粗暴却极其有效的方法就是取均值。我们取每个参数后验分布的均值作为模型中个参数的具体值,进而得到具体的正态分布模型。
接下来,让我们画出两个预测的正态分布函数图和真实数据分布图,代码如下:
norm = stats.norm
x = np.linspace(20, 300, 500)
# 获得模型参数结果的平均
posterior_center_means = center_trace.mean(axis=0)
posterior_std_means = std_trace.mean(axis=0)
posterior_p_mean = trace["p"].mean()
# 画出真实数据的频率图
plt.hist(data, bins=20, histtype="step", normed=True, color="k",
lw=2, label="histogram of data")
# 画出第一个模型 的分布函数 Nor0
y = posterior_p_mean * norm.pdf(x, loc=posterior_center_means[0],
scale=posterior_std_means[0])
plt.plot(x, y, label="Cluster 0 (using posterior-mean parameters)", lw=3)
plt.fill_between(x, y, color=colors[1], alpha=0.3)
# 画出第二个模型 的分布函数 Nor1
y = (1 - posterior_p_mean) * norm.pdf(x, loc=posterior_center_means[1],
scale=posterior_std_means[1])
plt.plot(x, y, label="Cluster 1 (using posterior-mean parameters)", lw=3)
plt.fill_between(x, y, color=colors[0], alpha=0.3)
plt.legend(loc="upper left")
plt.title("Visualizing Clusters using posterior-mean parameters")
上图中,蓝色线和橙色线为我们预测的分布函数。黑色线为真实数据的分布。从图中可以看到,我们建立的模型良好,并且合理的将所有数据分成了两类。
从聚类到预测
前面我们已经解决了 2 类聚类问题,利用模型将所有数据分成了两类。那么此时如果来了一条新的观测数据 x=175x=175 ,我们又应该将 xx 归到哪一类呢?
当然,你可以选择距离 xx 最近的中心点所在的类。但是,你可以从上图中看到,数据并不是完全根据中心点来分的。有些数据明明距离 Nor0 的中心点更近却被分到了 Nor1。这是因为除了考虑中心点外,我们还必须考虑标准差等因素。
让我们用一个更加正式的方式阐述上面这个问题:
我们需要得到观测数据(x=175)所属类别为 1 的概率值。设Lx表示x所属类别,它的取值为 0 或 1。那么,我们接下来需要求的就是 P(Lx=1∣x=175) 的值。
解决这个预测问题有两种思路。最朴素的方法就是把新的数据放入数据集合中,然后重新执行整个 MCMC 过程。并通过结果,判断新的观测数据所属类别。但是这样做有一个很大的缺点就是耗时。每当来一个新的数据,我们就需要重新运行整个训练过程,这显然不是一个好的想法。
# 定义正态函数
norm_pdf = stats.norm.pdf
# 获得样本中的 p 后验概率
# 这个p其实就是式子中的 P( L_x = 0 )
p_trace_0 = trace["p"][25000:]
x = 175
# 计算 P( x=175 | L_x = 0 )P( L_x = 0 ):当 x 是第一类时,x =175的概率。
# 因此需要带入参数 center_0 和 sigam_0
p_x_l_0 = norm_pdf(x, loc=center_trace[:, 0], scale=std_trace[:, 0])
# P( x=175 | L_x = 1 )
p_trace_1 = 1 - p_trace_0
# 计算 P( x=175 | L_x = 0 )P( L_x = 0 ):当 x 是第一类时,x =175的概率。
# 因此需要带入参数 center_1 和 sigam_1
p_x_l_1 = norm_pdf(x, loc=center_trace[:, 1], scale=std_trace[:, 1])
# 取所有样本结果的平均。当结果大于0.5表示,超过一半的样本显示 x 属于类别 0
# 若小于0.4,表示,超过一半的样本显示 x 属于 类别 1
v = p_x_l_0 > p_x_l_1
print("Probability of belonging to cluster 0:", v.mean())
从上面结果过可以看出 x 属于类别 0 的概率很低。那么说明,x 类应该属于类别 1。
这种以概率确定类别的思想后面还会经常使用,所以请熟悉这种方法。
使用 MAP 来改进收敛性
如果重复运行上面的例子,你会发现每次的结果都不一致。有时候这两个类离很近,有时候这两个类又离得很远。出现这种现象的原因是每次训练时, MCMC 算法 的起始位置都不一样。
从统计学的角度分析,只要 MCMC 能够训练足够长的时间,就可以忽略起始位置,我们把这种特性叫做 MCMC 的收敛性。因而,如果我们看到不同的后验分析结果,那就可能是因为 MCMC 还没有充分收敛。
实际上。正确的起始位置不仅可以提升收敛速度,还可以提高准确性。理想情况下,我们希望起始位置就在分布图形的山峰处,其实就是后验分布所在的区域。如果我们以这个区域中的某一点为起始点,我们就可以避免很长的预热期以及错误的估计结果。通常,我们将山峰所在位置称为 最大后验,简称为 MAP。
当然 MAP 的真实位置时位置的,但是 PyMC 为我们提供了一个用于估计该位置的对象:PyMC 主命名空间下的 MAP 对象。该对象由 pm.find_MAP() 函数定义。我们只需要把 初始化的位置 加入训练函数中即可。代码如下:
with model:
# 找到较好的山峰
start = pm.find_MAP()
# 利用 MCMC 进行模型训练且指定初始位置
trace = pm.sample(5000, step=[step1, step2], start=start)
# 输出分类矩阵的大小
trace["assignment"].shape
训练完后的分析步骤还是不变,但是我们需要记住,用 find_MAP() 函数可以为 MCMC 算法找到一个较好的起始点。
预热样本的舍去
最后一点,就是之前我们谈到的预热问题。在 MCMC 算法迭代开始时,前面的迭代会四处寻找最佳的方向。因此,前面几代的样本和我们想找的样本会有一定差距,那么此时我们采取的措施就是让 PyMC 自动丢弃前 n 个样本。这个操作很简单,我们只需要从后面开始取点即可。代码如下:
# 原来是要去 trace["assignment"] 作为估计的
# 现在我们只需要取后一半的样本作为后验分布的估计即可
trace["assignment"][2500:]
利用 PyMC 画图
在之前的实验中,我们总是手动地创建直方图、自相关图和迹图,但每次创建都太过繁琐。因此,PyMC 的作者为我们提供了一个工具 pymc3.plots。
该工具库包含了很多不同的画图函数,有直接画出某个变量的迹的分布图和值的散点图,代码如下:
# 第一个参数传入需要画图的变量集合,该集合中可以为多个变量
# var_names 里面指定集合中的具体变量名,这个变量名是在我们定义变量的时候传入的
pm.plots.traceplot(trace, var_names=["centers"])
上图左边为两个 center 的取值分布,一个在 120 周围,一个在 200 周围。右边的是把所有样本的分布画了出来,其中横坐标表示第 i 个样本,纵坐标为 center 的具体值。
该工具还可以直接画出 trace 中的任意变量的后验分布:
# 分别画出这两个模型的分布函数
pm.plots.plot_posterior(trace["centers"][:, 0])
pm.plots.plot_posterior(trace["centers"][:, 1])
还可以自动计算任意序列的自相关性(自相关性的概念,我们会在下一章中学到),并画出自相关性的图:
pm.plots.autocorrplot(trace, var_names=["centers"])
上面会画出 4 幅相关图,你可以双击图像,放大图像观察。这四幅图分别为:序列 center0 和 center0 的相关性、序列 center0 和 center1 的相关性、序列 center1 和 center0 的相关性、序列 center1 和 center1 的相关性。具体的相关性概念和用处,我们会在下一个实验中给出。