在这个万物皆可“Machine Learning”的时代,各个研究领域都想和“ML”沾点边,好像论文里有了“…with machine learning”、“learning based…”便能立即高大上起来。不过确实很多传统领域的问题都被大佬们解决的差不多了,为了毕业,我也不能免俗,也想在研究中结合一点机器学习的东西。
机器学习只是一个宏观的概念,下面还包含很多种不同的方法需要根据各自的研究情况具体确定。我这里用到的是一个相对小众的算法,相比于神经网络、KNN、朴素贝叶斯、SVM等都不那么出名,不过它的能力却是非常巨大的。它就是高斯过程(Gaussian processes)。有感于介绍高斯过程回归的文章写得不够明晰,看完还是一头雾水,亦或是没有结合代码,算法理解始终流于纸面。因此接下来我会用最通俗易懂的方式讲解并配以详细的代码解释,同时知道想学习高斯过程的小伙伴光看我这一篇文章是不够的,因此文中送出多重“福(链)利(接)”,都是我在学习过程中觉得相当不错的文章。
图片转载自https://yugeten.github.io/posts/2019/09/GP/
首先我们从命名开始认识一个事物。我们先来说回归,学过一点机器学习的都知道有监督学习的两大任务就是分类(classification)和回归(regression)。用一句话来简单概括回归就是:回归就是找到一个函数来尽可能表示一组数据输入与输出之间的关系。对于线性模型,常用 Y = X T w Y=X^Tw Y=XTw来描述其输入输出关系。回归就是通过这一等式把输出 Y Y Y与实际观测值进行比较,并用其结果不断地更新权重矩阵 w w w以求输出最大限度的接近实际值。
A Gaussian process is a probability distribution over possible functions that fit a set of points.
如果有人突然问你什么叫高斯过程,回他这句话就好了。详细一点的说明便是对于一组给定的数据点,或许存在无限多个函数可以用来拟合,高斯过程就是给每个函数(权重)分配一个概率值,这个概率分布的均值便代表了这个数据最有可能的表征,同时它通过置信区间将不确定性也表示出来。
通过图能看得更清晰一点。红色的点是给定的,但连接他们的线(函数)并不只有这一根,其中深蓝色的代表可能性最高的一条,淡蓝色的部分代表在这一区域内有无数条线段的组成可能,并且所有这些线(函数)的分布服从高斯分布。
有人或许会问,为什么不放书本里的定义?好,我把原文中的定义放在下面。
A Gaussian process is a collection of random variables, any finite number of which have a joint Gaussian distribution.
你品,你细品,对于初识高斯过程的人这句话读完可能是一脸懵逼,脑海中什么都没留下,完全不如“一群函数的概率分布服从高斯分布”来的简洁明了。但是定义毕竟是定义,这么写自然有他的道理,我们接下来就详细解释为什么高斯过程这么定义。
OK, 到目前为止我们弄清楚了“高斯过程”+“回归”。而高斯过程回归便是GP(高斯过程缩写GP,下同)在回归问题上的应用,当然也可以用于分类问题,不过这就不在本文讨论的范畴了,有兴趣的同学建议阅读"Gaussian Processes for Machine Learning"。
相信学习机器学习的小伙伴一定会有感而发,这个贝叶斯就是概率论和统计学的祖宗,到哪都离不开它。的确,今天的故事也因他而起。
首先我们回到前面讲的输入输出模型,我们将其表示为 Y = X T w Y=X^Tw Y=XTw。一般来说,估计值总是会与实际值有一定的差别,我们将这个差别表示成 ε \varepsilon ε,于是系统模型就变为 Y = X T w + ε Y=X^Tw+\varepsilon Y=XTw+ε。这是一个可加性的噪声(additive noise),同时我们假设其服从正态分布(至于为什么能这么假设,只能说大自然就是这么神奇,很多东西都服从正态分布):
ε ∼ N ( 0 , σ n 2 ) \varepsilon \sim N(0, \sigma_n^2) ε∼N(0,σn2) 知道了噪声的概率分布,又有这个系统模型,于是我们就得到了“似然”(likelihood)。似然就是在给定参数的条件下的观察值所服从的概率分布或满足的概率密度函数,记为 P ( y ∣ X , w ) P(y|X,w) P(y∣X,w),由于这些数据集都是独立的,因此 P ( y ∣ X , w ) = ∏ i = 1 n P ( y i ∣ x i , w ) = ∏ i = 1 n 1 2 π σ n e x p ( − ( y i − x i T w ) 2 2 σ n 2 ) P(y|X,w)=\prod_{i=1}^n P(y_i|x_i,w)=\prod_{i=1}^n \frac{1}{\sqrt{2\pi}\sigma_n}exp(-\frac{(y_i-x_i^Tw)^2}{2 \sigma_n^2}) P(y∣X,w)=i=1∏nP(yi∣xi,w)=i=1∏n2πσn1exp(−2σn2(yi−xiTw)2)
和正态分布的形式一对照便知, P ( y ∣ X , w ) P(y|X,w) P(y∣X,w)也服从正态分布。其实这很好理解,相当于一个常数 X T w X^Tw XTw加上了一个服从均值为0,方差为 σ n 2 \sigma_n^2 σn2的高斯分布的随机变量,结果显然还是高斯分布: y ∣ X , w ∼ N ( X T w , σ n 2 I ) y|X,w \sim N(X^Tw, \sigma_n^2I) y∣X,w∼N(XTw,σn2I)
到这里都还没和贝叶斯推论产生关系,别急,接下来就是重头戏。贝叶斯推论就是后验正比于似然乘先验,既然似然我们已经求得,接下来就是先验了。于是我们再次假设权重w的概率分布也服从高斯分布: w ∼ N ( 0 , Σ p ) w \sim N(0, \Sigma_p) w∼N(0,Σp)
根据贝叶斯公式: p ( w ∣ y , X ) = p ( y ∣ X , w ) p ( w ) p ( y ∣ X ) p(w|y,X)=\frac{p(y|X,w)p(w)}{p(y|X)} p(w∣y,X)=p(y∣X)p(y∣X,w)p(w)
可得 p ( w ∣ y , X ) ∝ e x p ( − 1 2 σ n 2 ( y − X T w ) T ( y − X T w ) ) e x p ( − 1 2 w T Σ p − 1 w ) p(w|y,X)\propto exp(-\frac{1}{2 \sigma_n^2}(y-X^Tw)^T(y-X^Tw))exp(-\frac{1}{2}w^T\Sigma_p^{-1}w) p(w∣y,X)∝exp(−2σn21(y−XTw)T(y−XTw))exp(−21wTΣp−1w)
经过一番有些繁杂的变化后,得出后验分布 p ( w ∣ y , X ) ∼ N ( 1 σ n 2 A − 1 X y , A − 1 ) p(w|y,X) \sim N(\frac{1}{\sigma_n^2}A^{-1}Xy, A^{-1}) p(w∣y,X)∼N(σn21A−1Xy,A−1)其中 A = σ n − 2 X X T + Σ p − 1 A= \sigma_n^{-2}XX^T+\Sigma_p^{-1} A=σn−2XXT+Σp−1。
至此我们得出了权重矩阵的概率分布,那这有什么用呢?好问题!还记得我们之前讲过高斯过程的定义吗,就是一系列权重矩阵(函数)服从高斯分布,那现在不就得到了这个结论嘛,当然还有些后续步骤还没完。但我们已经可以通过这个后验概率来预测其他未知数组 x ∗ x_* x∗了, f ∗ = x ∗ T w f_*=x_*^Tw f∗=x∗Tw
f ∗ ∼ N ( 1 σ n 2 x ∗ T A − 1 X y , x ∗ T A − 1 x ∗ ) f_* \sim N(\frac{1}{\sigma_n^2}x_*^TA^{-1}Xy, x_*^TA^{-1}x_*) f∗∼N(σn21x∗TA−1Xy,x∗TA−1x∗)
很显然这也是一个高斯分布,因为是后验乘一个常数,通过这层关系我们就完成了一次回归任务。
到这里高斯过程回归就结束了吗?并没有,因为之前处理的数据还都只是低维的,我们需要将其映射至高维空间甚至无限维空间中。这是因为很多时候低维的数据并不能表现出数据之间的一些特征关系,而高维数据往往要有价值的多。我们把这个映射过程记为 ϕ ( x ) : R D → R N \phi(x):\mathbb{R}^D\rightarrow \mathbb{R}^N ϕ(x):RD→RN。于是上面的线性系统模型就改写为 f ( x ) = ϕ ( x ) T w f(x)=\phi(x)^Tw f(x)=ϕ(x)Tw,那么加入新的数据之后的预测分布就变为 f ∗ ∣ x ∗ , X , y ∼ N ( 1 σ n 2 ϕ ( x ∗ ) T A − 1 Φ y , ϕ ( x ∗ ) T A − 1 ϕ ( x ∗ ) ) f_*|x_*,X,y\sim N(\frac{1}{\sigma_n^2}\phi(x_*)^TA^{-1}\Phi y, \phi(x_*)^TA^{-1}\phi(x_*)) f∗∣x∗,X,y∼N(σn21ϕ(x∗)TA−1Φy,ϕ(x∗)TA−1ϕ(x∗)),式中 Φ \Phi Φ是训练集中所有 ϕ ( x ) \phi(x) ϕ(x)的汇总。
由于对A求逆很麻烦,尤其是当维度变得很大时,因此我们对上式做了变形:
f ∗ ∣ x ∗ , X , y ∼ N ( ϕ ∗ T Σ p Φ ( K + σ n 2 I ) − 1 y , ϕ ∗ T Σ p ϕ ∗ − ϕ ∗ T Σ p Φ ( K + σ n 2 I ) − 1 Φ T Σ p ϕ ∗ ) f_*|x_*,X,y\sim N(\phi_*^T\Sigma_p\Phi(K+\sigma_n^2I)^{-1}y, \phi_*^T\Sigma_p\phi_*-\phi_*^T\Sigma_p\Phi(K+\sigma_n^2I)^{-1}\Phi^T\Sigma_p\phi_*) f∗∣x∗,X,y∼N(ϕ∗TΣpΦ(K+σn2I)−1y,ϕ∗TΣpϕ∗−ϕ∗TΣpΦ(K+σn2I)−1ΦTΣpϕ∗)
其中 ϕ ( x ∗ ) = ϕ ∗ \phi(x_*)=\phi_* ϕ(x∗)=ϕ∗, K = Φ T Σ p Φ K=\Phi^T\Sigma_p\Phi K=ΦTΣpΦ
这部分推导其实还是有点复杂的,还涉及到Woodbury求逆公式,推荐大家看B站的这个视频,有详细推导过程白板推导高斯过程。
这么一个庞然大物看着挺吓人的,但是仔细一比对就会发现其中有一个形如 Φ T Σ p Φ \Phi^T\Sigma_p\Phi ΦTΣpΦ经常出现,我们令 k ( x , x ′ ) = ϕ ( x ) T Σ p ϕ ( x ′ ) k(x,x')=\phi(x)^T\Sigma_p\phi(x') k(x,x′)=ϕ(x)TΣpϕ(x′), 那其他的就都可以写成 k ( x , x ) k(x,x) k(x,x)、 k ( x ′ , x ′ ) k(x',x') k(x′,x′)之类的,我们给这个式子起个名字叫“covariance function or kernel”,这就是大名鼎鼎的核函数。到这儿,高斯过程回归才算完结,而这种结合了贝叶斯推论+“kernel trick”的推导方法由于是从权重矩阵w的视角出发的,因此也称为 Weight-space View。
更多关于从贝叶斯线性回归到高斯过程的请参考这里
之前的权重视角我们从线性模型出发,利用贝叶斯推论证明了为何高斯过程是“高斯”过程(就是这些权重矩阵为何服从高斯分布),现在我们从已有经验出发,再从function的角度来看待这一问题,看能否得到相同的结果。
高斯分布描述的是一组数据(向量)的信息,用均值和协方差矩阵确定
高斯过程描述的是拟合一组随机变量的函数的分布,用均值函数和协方差函数确定
对于一个随机过程 f ( x ) f(x) f(x)来说,既然它服从高斯分布,那它就有均值和协方差。我们将这一点记为 f ( x ) ∼ G P ( m ( x ) , k ( x , x ′ ) ) f(x)\sim \mathcal{GP}(m(x),k(x,x')) f(x)∼GP(m(x),k(x,x′))
其中 m ( x ) = E [ f ( x ) ] m(x)=\mathbb{E}[f(x)] m(x)=E[f(x)], k ( x , x ′ ) = E [ f ( x ) − m ( x ) ] [ f ( x ′ ) − m ( x ′ ) ] k(x,x')=\mathbb{E}[f(x)-m(x)][f(x')-m(x')] k(x,x′)=E[f(x)−m(x)][f(x′)−m(x′)]。这个协方差矩阵又被称为核函数。
通常我们取 m ( x ) = 0 m(x)=0 m(x)=0,让均值保持在0,也是方便计算。那接下来协方差矩阵的选取就得说道说道了。首先我们来看看协方差矩阵的构成特点——如果两个数据相距越近,则关联性越大;相反,离的越远的两个点,关联性越小,不确定性则越大。这才有了文章最开头的动图,在多元高斯分布中,如果我确定了一点不动,则其他点的概率分布同样服从高斯分布(条件概率的性质),但是离得近的点抖动幅度没那么大,象征着确定性较高,离得远的点抖动就很剧烈,象征不确定性较高。
图中红色点代表已知的点,蓝色的是待预测的数据点,蓝色竖线表示不确定性的范围。
在有了这些认识后,我们将核函数选取为 k ( x , x ′ ) = σ 2 e x p ( − 1 2 l 2 ( x − x ′ ) 2 ) k(x,x')=\sigma^2exp(-\frac{1}{2l^2}(x-x')^2) k(x,x′)=σ2exp(−2l21(x−x′)2)这个式子说明了一个问题,如果我们有了两个数据,便可以确定一个高斯过程。式子中的 σ \sigma σ和 l l l叫做超参数,关于这两者取值对核函数的影响会在后面代码部分单独展现。
现在回到我们的高斯过程函数 f ( x ) f(x) f(x)上面来,显然我们对未知的数据较为感兴趣,我们来对函数做个小动作,将其分为两个部分 f f f, f ∗ f_* f∗,其中前者是训练集的输出,后者是测试集的输出,那根据我们GP的定义有
[ f f ∗ ] ∼ N ( 0 , [ K ( X , X ) K ( X , X ∗ ) K ( X ∗ , X ) K ( X ∗ , X ∗ ) ] ) \begin{bmatrix} f \\ f_* \end{bmatrix}\sim N(0,\begin{bmatrix} K(X,X) &K(X,X_*) \\ K(X_*,X) & K(X_*,X_*)\end{bmatrix}) [ff∗]∼N(0,[K(X,X)K(X∗,X)K(X,X∗)K(X∗,X∗)])
当然,这是无噪声版的,现实中一般会在左上角那个系数后面加上 σ n 2 I \sigma_n^2I σn2I作为观测噪声。紧接着,我们就运用多元高斯分布的另一个重要性质——边缘性(Marginalisation)!啥意思?简单来说就是下面这张图
如果两个随机变量服从多元高斯分布,那么其中任意一个变量自身也服从高斯分布,并且均值方差和多元高斯分布时完全对应,协方差矩阵的主对角线上是两个随机变量的方差,副对角线上是两者的协方差。同样,如果我们把两个随机变量扩展到无限维,同样不影响对我们想要预测的数据的预测,这就是高斯过程回归的强大之处。
于是乎我们就得到了所要预测函数集的均值和协方差函数:
μ ∗ = K ∗ T K − 1 y ( 4 ) \mu_*=K_*^TK^{-1}y \quad(4) μ∗=K∗TK−1y(4) Σ ∗ = K ∗ ∗ − K ∗ T K − 1 K ∗ ( 5 ) \Sigma_*=K_{**}-K_*^TK^{-1}K_* \quad(5) Σ∗=K∗∗−K∗TK−1K∗(5)
相信聪明的你们都能看懂各个符号缩写对应的原式子,我就偷个懒不打了,打公式真是太累了。。。
至此高斯过程回归的推导理解就告一段落了,接下来就该讲讲如何implementation了。
本节参考链接:
Gaussian Process, not quite for dummies
看得见的高斯过程:这是一份直观的入门解读
废话不多说,直接上代码(Numpy 实现)
import numpy as np
import matplotlib.pyplot as plt
#作图函数
def plot_gp(mu, cov, X, X_train=None, Y_train=None, samples=[]):
X=X.ravel()
mu=mu.ravel()
uncertainty = 1.96*np.sqrt(np.diag(cov))#95%的置信区间
plt.fill_between(X, mu+uncertainty, mu-uncertainty, alpha=0.1)
plt.plot(X, mu, label='Mean')
for i, sample in enumerate(samples):
plt.plot(X, sample, lw=2, ls='--',label=f'Sample{i+1}') #lw is the width of curve
if X_train is not None:
plt.plot(X_train, Y_train, 'rx')
plt.legend()
上面这段先编写好作图函数,方便接下来的可视化
def kernel(X1, X2, l=1.0, sigma_f=1.0):
'''
Args:
X1: Array of m points
X2: Array of n points
returns:
cov(m x n).
'''
sqdist = np.sum(X1**2,1).reshape(-1,1)+np.sum(X2**2,1)-2*np.dot(X1,X2.T)
#X1**2就是X1中所有数都平方,sum(x,1)表示每一行看作一个向量,竖着相加。结果是mx1
return sigma_f**2*np.exp(-0.5/l**2*sqdist)
定义好核函数,这里采用的是RBF kernel: k ( x i , x j ) = σ f 2 e x p ( − 1 2 l 2 ( x i − x j ) T ( x i − x j ) ) k(x_i,x_j)=\sigma_f^2exp(-\frac{1}{2l^2}(x_i-x_j)^T(x_i-x_j)) k(xi,xj)=σf2exp(−2l21(xi−xj)T(xi−xj))并取两个超参数 σ f = l = 1 \sigma_f=l=1 σf=l=1
# finite number of points
X=np.arange(-5,5,0.2).reshape(-1,1)
mu = np.zeros(X.shape)
cov = kernel(X,X)
samples = np.random.multivariate_normal(mu.ravel(), cov, 3)
plot_gp(mu, cov, X, samples=samples)
从随机生成的数组中选取三组样本,均值为0,协方差由核函数计算得到,这三组样本构成了先验。
from numpy.linalg import inv
def posterior_predictive(X_s, X_train, Y_train, l=1.0, sigma_f=1.0, sigma_y=1e-8):
K = kernel(X_train, X_train, l, sigma_f)+sigma_y**2*np.eye(len(X_train))
K_s = kernel(X_train, X_s, l, sigma_f)
K_ss = kernel(X_s, X_s, l, sigma_f)+1e-8*np.eye(len(X_s))
K_inv = inv(K)
#公式4
mu_s = K_s.T.dot(K_inv).dot(Y_train)
#公式5
cov_s = K_ss-K_s.T.dot(K_inv).dot(K_s)
return mu_s, cov_s
求后验,主要是利用了上面的公式(4)、(5)。 X s X_s Xs就是 X ∗ X_* X∗,即需要预测的数据点,X_train和Y_train分别是训练集的输入输出。l、sigma_f和sigma_y则是三个超参数,值都给定了。
X_train = np.array([-4, -3, -2, -1, 1]).reshape(-1,1)
Y_train = np.sin(X_train)
mu_s, cov_s = posterior_predictive(X, X_train, Y_train)
samples = np.random.multivariate_normal(mu_s.ravel(), cov_s, 3)
plot_gp(mu_s, cov_s, X, X_train=X_train, Y_train=Y_train, samples=samples)
以下示例从后验预测中提取三个样本,并将它们与均值,置信区间和训练数据一起绘制。 在无噪声模型中,训练点的方差为零,从后验提取的所有随机函数都经过训练点。
最后再来让我们一起看看超参数的取值会给图形带来什么样的变化
import matplotlib.pyplot as plt
params = [
(0.3,1.0,0.2),
(3.0,1.0,0.2),
(1.0,0.3,0.2),
(1.0,3.0,0.2),
(1.0,1.0,0.05),
(1.0,1.0,1.5),
]
plt.figure(figsize=(10, 5))
for i,(l,sigma_f,sigma_y) in enumerate(params):
mu_s, cov_s = posterior_predictive(X,X_train, Y_train, l=l,
sigma_f=sigma_f,
sigma_y=sigma_y)
plt.subplot(3,2,i+1)
plt.subplots_adjust(top=2)
plt.title(f'l={l}, sigma_f = {sigma_f}, sigma_y = {sigma_y}')
plot_gp(mu_s, cov_s, X, X_train=X_train, Y_train=Y_train)
这里每次只变动一个参数的值,观察这个参数对预测曲线的影响,如下图所示
本节参考自Gaussian processes
最后,感谢您能一直阅读(翻到)这里。原创码字不易,只希望这篇文章能够帮到大家,让后来人学习的更容易一些。文章难免有不正之处,望各位不吝赐教。