贝叶斯框架下, 可以用高斯过程来估计一个函数 f : R→R. 对于每个xi, f(xi)可以用一个均值方差暂未知的高斯分布来建模。因为连续空间的xi可以有无限个,拟合一个函数的高斯过程其实一个无限维的多元高斯。实际中,不管是我们的给定数据{(x, y)},还是测试点{x*}的个数都是有限的。因此无论是高斯过程先验还是还是高斯过程后验都是有限维的。因为多元高斯分布的任意有限自己还是多远高斯分布,所以先验和后验也都可以都用高斯建模。
实践中,高斯过程先验(一个多元高斯)的均值通常统一设为0(因为我们通常对于需要拟合的函数取值并没有太多先验知识),而协方差矩阵则用核函数K(x,x')来建模(i.e., cov[yi, yj] = K(xi, xj) = η exp(-ρ * SED(xi, xj)) )。协方差描述的是,当x变化时,y是如何变化的.而核函数(高斯核)的特点是x,x'越近,返回值越大。用高斯核作协方差可以理解为一个小的x扰动会导致较小的y变化,一个大的x变化会导致较大的y变化。
此外,现实中观测值一定会受到噪声的影响,因此我们对观测值建模需要引入一项高斯白噪声:yi ~ f(xi) + εi. ε ~ N(0, σ)。这样协方差函数就改写成 cov[yi, yj] = K(xi, xj) + σ δij.
注意高斯过程的先验建模中有参数θ = {η, ρ, σ}, 但为了强调高斯过程是一个非参方法,这些参数称为“超参”。这些参数将用贝叶斯推断来估计。
总结一下,我们有各种假设先验(θ的先验分布,f(x) ~ N(0, K(x, x'))),高斯似然 y | θ ~ N(0, K(x, x) + σ I), 然后根据y的观测值可以推断出超参的分布。预测就更简单了,高斯过程的一个优点就是后验分布可以直接求得解析解,要预测的点记为(x*, f(x*)), 后验分布可得 f(x*) | x, x*, y ~ N(μ*, Σ*), 其中 μ* = K(X*, X) (K(x, x) + σ I)^(-1) y, Σ* = K(x*, x*) - K(x*, x) (K(x, x) + σ I)^(-1) K(x, x*).详细推导可见Gaussian Processes for Machine Learning一书的Sec. 2.2, 此外可以记住的一个常见结论如下:
然后是代码实践,用20个带噪的正弦函数观测点来拟合函数:
#%matplotlib inline
import pymc3 as pm
import numpy as np
import theano.tensor as tt
import matplotlib.pyplot as plt
from scipy.spatial.distance import cdist
if __name__ == "__main__":
np.random.seed(1)
squared_distance = lambda x, y: cdist(x.reshape(-1,1), y.reshape(-1,1)) ** 2 #SED function
N = 20 # number of training points.
n = 100 # number of test points.
np.random.seed(1)
f = lambda x: np.sin(x).flatten()
x = np.random.uniform(0, 10, size=N)
y = np.random.normal(np.sin(x), np.sqrt(0.01))
plt.plot(x, y, 'o')
plt.xlabel('$x$', fontsize=16)
plt.ylabel('$f(x)$', fontsize=16, rotation=0)
with pm.Model() as GP:
mu = np.zeros(N)
eta = pm.HalfCauchy('eta', 0.1)
rho = pm.HalfCauchy('rho', 1)
sigma = pm.HalfCauchy('sigma', 1)
D = squared_distance(x, x) #SED(x,x)
K = tt.fill_diagonal(eta * pm.math.exp(-rho * D), eta + sigma) #(K(x, x) + σ I)
obs = pm.MvNormal('obs', mu, cov=K, observed=y)
test_points = np.linspace(0, 10, 100)
D_pred = squared_distance(test_points, test_points) #SED(x*,x*)
D_off_diag = squared_distance(x, test_points) #SED(x,x*) n * N
K_oo = eta * pm.math.exp(-rho * D_pred) #K(x*,x*)
K_o = eta * pm.math.exp(-rho * D_off_diag) #K(x,x*)
inv_K = tt.nlinalg.matrix_inverse(K)
mu_post = pm.Deterministic('mu_post', pm.math.dot(pm.math.dot(K_o.T, inv_K), y))
SIGMA_post = pm.Deterministic('SIGMA_post', K_oo - pm.math.dot(pm.math.dot(K_o.T, inv_K), K_o))
step = pm.Metropolis()
start = pm.find_MAP()
trace = pm.sample(1000, step = step, start=start, nchains = 1)
varnames = ['eta', 'rho', 'sigma']
chain = trace[100:]
pm.traceplot(chain, varnames)
plt.figure()
y_pred = [np.random.multivariate_normal(m, S) for m,S in zip(chain['mu_post'], chain['SIGMA_post'])]
for yp in y_pred:
plt.plot(test_points, yp, 'r-', alpha=0.1)
plt.plot(x, y, 'bo')
plt.xlabel('$x$', fontsize=16)
plt.ylabel('$f(x)$', fontsize=16, rotation=0)
输出: