This example is taken from here.
In the function-space view of gaussian processes (see section 2.2 here) you look at gaussian processes from the perspective of sampling functions in a similar way as you normally sample random numbers from a distribution, e.g.: f(x)∼GP(m(x),k(x,x′))
If you look at it from that perspective then it is interesting to draw a few samples and plot them. The purpose of this notebook is to show how that can be done.
%matplotlib inline
import numpy as np, matplotlib.pyplot as plt, seaborn as sns
# https://stackoverflow.com/questions/37149933/how-to-set-max-output-width-in-numpy
np.set_printoptions(edgeitems=10)
np.core.arrayprint._line_width = 180
sns.set()
SEED = 42
np.random.seed(SEED)
For this example we take the SE (squared exponential) kernel (a.k.a. covariance function; have a look here p. 83). Have a look at the excellent Phd thesis Automatic Model Construction with Gaussian Processes for an overview of different choice of kernels and their properties.
kSE(r)=σ2⋅e−r22ℓ2class GPCovarianceFunctionSquaredExponential:
def __init__(self, l, sigma):
self.l = float(l)
self.sigma = float(sigma)
def covariance(self, x1, x2):
return (self.sigma**2) * np.exp( -0.5 * (np.subtract.outer(x1, x2)**2) / (self.l ** 2))
xpts = np.arange(-3, 3, step=0.01)
k = GPCovarianceFunctionSquaredExponential(l=np.sqrt(0.1), sigma=1)
We will assume a zero function as the mean, so we can plot a band that represents one standard deviation from the mean.
sigma_0 = k.covariance(0, 0)
sigma_0
1.0
fig=plt.figure(figsize=(15, 8), dpi= 80, facecolor='w', edgecolor='k')
ax = plt.subplot(1, 1, 1)
ax.errorbar(xpts, np.zeros(len(xpts)), yerr=sigma_0, capsize=0)
ax.set_ylim(-3.0,3.0)
(-3.0, 3.0)
Let's select an arbitrary starting point to sample, say x=1. Since there are no prevous points, we can sample from an unconditional Gaussian:
x = [1.]
y = [np.random.normal(scale=sigma_0)]
y
[0.4967141530112327]
sigma_1 = k.covariance(x, x)
sigma_1
array([[ 1.]])
In order to create new function points y_new
at locations x_new
after we created a set of initial ones at locations x
with their corresponding y
values we have to use the conditional probability as described here in equation A.6.
Even while I've taken the example from here I will change the notation to fit to the Gaussian Processes for Machine Learning notation. I will use y∗ instead of y_new
, though.
Given a joint distribution: [y∗y]∼N([ˉy∗ˉy],[ACCTB])
We get the conditional distribution analytically as follows:
p(y∗|X,y,X∗)∼N(ˉy∗+CB−1(y−ˉy),A−CB−1CT)And as we set the mean function m(x)=0 this reduces to:
p(y∗|X,y,X∗)∼N(CB−1y,A−CB−1CT)Please also keep in mind that both, y∗ and y can be vectors and do not need to be scalars.
In case that A, B and C are defined by the covariance function and we only look at predicting one new point (x∗,y∗) we have the following:
A=k(x∗,x∗) is a scalarB=k(x∗,x)=k∗ is a vectorC=k(X,X) is a matrixSee also here equations 2.25 and 2.26 for details.
def conditional(x_new, x, y, cov, sigma_n=0):
total_covariance_function = cov
A = total_covariance_function.covariance(x_new, x_new)
C = total_covariance_function.covariance(x_new, x)
B = total_covariance_function.covariance(x, x) + np.power(sigma_n,2)*np.diag(np.ones(len(x)))
mu = [np.linalg.inv(B).dot(C.T).T.dot(y).squeeze()]
sigma = [(A - C.dot(np.linalg.inv(B).dot(C.T))).squeeze()]
return (mu, sigma)
def predict(x_new, x, y, cov, sigma_n=0):
l_y_pred, l_sigmas = conditional(x_new, x, y, cov=cov, sigma_n=sigma_n)
if len(l_sigmas[0].shape) > 1:
return l_y_pred, [np.diagonal(ls) for ls in l_sigmas]
else:
return l_y_pred, l_sigmas
Now we can draw the predicted function value together with the sigmas via the predict method.
y_pred, sigmas = predict(xpts, x, y, cov=k)
fig=plt.figure(figsize=(15, 8), dpi= 80, facecolor='w', edgecolor='k')
ax = plt.subplot(1, 1, 1)
ax.errorbar(xpts, y_pred[0], yerr=sigmas[0], capsize=0)
ax.plot(x, y, "ro")
ax.set_ylim(-3.0,3.0)
(-3.0, 3.0)
Now let's take a second arbitrary location on the x-axis, e.g. −0.7 and let's repeat the process from before:
new_dp = [-0.7]
m, s = predict(new_dp, x, y, cov=k)
print(m)
print(s)
[array(2.633608839077876e-07)] [array(0.9999999999997189)]
Drawing a random number with the mean function and standard deviation (sigma):
y2 = np.random.normal(m[0], s[0])
print(m)
print(s)
y2
[array(2.633608839077876e-07)] [array(0.9999999999997189)]
-0.1382640378102619
Now we add the new x-point to the array of x-values:
# x = [1.0]
x += new_dp
x
[1.0, -0.7]
and the new y-point to the array of y-values:
y.append(y2)
y
[0.4967141530112327, -0.1382640378102619]
And repeat the process from above to predict how the function should look like along the x-axis given the two points we just fixed.
y_pred, sigmas = predict(xpts, x, y, cov=k)
fig=plt.figure(figsize=(15, 8), dpi= 80, facecolor='w', edgecolor='k')
ax = plt.subplot(1, 1, 1)
ax.errorbar(xpts, y_pred[0], yerr=sigmas[0], capsize=0)
ax.plot(x, y, "ro")
ax.set_ylim(-3.0,3.0)
(-3.0, 3.0)
At every location where there is a "known" point the standard deviation goes to zero, because the function at this point is not "unknown" or "random" any longer, but fixed and known. This is the effect of using the conditional probability p(y∗|X,y,X∗) as described above.
Let's draw several values at once rather than going single point by point as we did above:
x_more = [-2.1, -1.5, 0.3, 1.8, 2.5]
m, s = conditional(x_more, x, y, cov=k)
print(m[0])
print(s[0])
[ -7.66697664e-06 -5.63595765e-03 4.19316345e-02 2.02471666e-02 6.46090979e-06] [[ 9.99999997e-01 1.65296628e-01 -3.73627090e-07 1.19843900e-12 3.82424663e-16] [ 1.65296628e-01 9.98338443e-01 -2.74559569e-04 8.80965650e-10 2.81118181e-13] [ -3.73627090e-07 -2.74559569e-04 9.92508018e-01 -3.50450933e-03 -1.12241541e-06] [ 1.19843900e-12 8.80965650e-10 -3.50450933e-03 9.98338443e-01 8.62930563e-02] [ 3.82424663e-16 2.81118181e-13 -1.12241541e-06 8.62930563e-02 1.00000000e+00]]
y_more = np.random.multivariate_normal(m[0], s[0])
y_more
array([-1.5128756 , 0.52371713, -0.13952425, -0.93665367, -1.29343995])
x += x_more
y += y_more.tolist()
y_pred, sigmas = predict(xpts, x, y, cov=k)
fig=plt.figure(figsize=(15, 8), dpi= 80, facecolor='w', edgecolor='k')
ax = plt.subplot(1, 1, 1)
ax.errorbar(xpts, y_pred[0], yerr=sigmas[0], capsize=0)
ax.plot(x, y, "ro")
ax.set_ylim(-3.0,3.0)
(-3.0, 3.0)
You can see how a sample of the function drawn out of the relevant function space (configured via the covariance function) emerges.