Gaussian process regression: the posterior via Cholesky, length scales, and periodic kernels
How a Gaussian process turns a kernel into a prior over functions, how to compute the posterior mean and variance stably with a Cholesky factorisation, what the hyperparameters do, and how to build a kernel for seasonal data.
University lab From my coursework in the MSc in Statistics and Machine Learning at Linköping University, rewritten as a note to review later.
The first time I heard “a prior over functions” it sounded like magic. It’s simpler than it sounds. A Gaussian process (GP) lets you put a prior directly on functions, without choosing a form like “quadratic” first. You only say how similar the function’s values at two inputs should be, through a kernel, and Bayes does the rest. In the last Advanced Machine Learning lab I implemented the GP posterior by hand, then used kernlab on six years of daily temperatures. The math is short; the traps were about scales.
A prior over functions
means: for any finite set of inputs , the values are jointly normal with mean 0 and covariance matrix . The kernel is everything. The most common one is the squared exponential:
where is the prior variance of (how far it swings from 0) and is the length scale (how far apart two inputs can be and still have strongly correlated function values). Small gives wiggly functions; large gives smooth ones.
The posterior
With observations , , the posterior of at new inputs is normal with
where is the training covariance, the train-test covariance and . Two things I find helpful to notice: the mean is a weighted sum of kernel functions centred at the data, and the variance doesn’t depend on at all, only on where the data are.
Inverting the matrix directly is slow and numerically fragile. Algorithm 2.1 in Rasmussen and Williams uses the Cholesky factor of instead, and gives the log marginal likelihood almost for free:
se_kernel <- function(x1, x2, sigma_f = 1, ell = 0.3)
sigma_f^2 * exp(-0.5 * outer(x1, x2, "-")^2 / ell^2)
gp_posterior <- function(x, y, x_star, sigma_n, ...) {
K <- se_kernel(x, x, ...); Ks <- se_kernel(x, x_star, ...)
L <- t(chol(K + diag(sigma_n^2, length(x)))) # lower-triangular factor
alpha <- backsolve(t(L), forwardsolve(L, y)) # (K + sigma_n^2 I)^-1 y
v <- forwardsolve(L, Ks)
list(mean = drop(t(Ks) %*% alpha),
var = diag(se_kernel(x_star, x_star, ...)) - colSums(v^2),
log_marg_lik = -0.5 * sum(y * alpha) - sum(log(diag(L))) - length(x) / 2 * log(2 * pi))
}
x <- c(-0.9, -0.5, 0, 0.3, 0.7); y <- c(0.5, -0.2, -0.8, 0.4, -0.5)
grid <- seq(-1, 1, length.out = 101)
for (ell in c(0.3, 1)) {
post <- gp_posterior(x, y, grid, sigma_n = 0.1, sigma_f = 1, ell = ell)
band <- 1.96 * sqrt(post$var) # pointwise 95% band for f
cat("ell =", ell, " max half-width:", round(max(band), 2),
" log marginal likelihood:", round(post$log_marg_lik, 2), "\n")
}
# ell = 0.3 max half-width: 1.5 log marginal likelihood: -5.76
# ell = 1 max half-width: 0.4 log marginal likelihood: -38.2
What the hyperparameters do
When I updated the prior with one point, then two, then five, the band pinched in near each observation (down to about ) and stayed wide far from the data, back to the prior’s . Increasing from 0.3 to 1 made the mean much smoother.
The snippet shows the catch. With the band is narrower, but the model can’t bend enough to follow data that change sign within a short range, so it’s confidently wrong, and the log marginal likelihood is far lower ( vs ). Narrow bands aren’t a sign of a good fit. The marginal likelihood is the standard way to compare or optimise hyperparameters.
Scales: the trap I fell into
Hyperparameters only mean something relative to the units of and . "" is tiny if is a day number from 1 to 2190, and reasonable if has been standardised. kernlab::gausspr standardises both inputs and outputs by default (scaled = TRUE), so its lives on the standardised scale.
In my lab report I computed the posterior variance myself, but I mixed the scales: standardised temperatures, raw day numbers for time, and a noise level taken from residuals in raw degrees. The resulting band wasn’t the band of the model gausspr had fitted. To be consistent, standardise and the same way the package does, compute mean and variance on that scale, then transform back (multiply standard deviations by ).
I also drew the band as mean standard deviation while calling it a 95% band. A pointwise 95% band is mean standard deviations.
Kernels for seasonal data
With temperature as a function of time, an SE kernel on the day number captures slow changes across years, but has no idea that 1 January 2011 is similar to 1 January 2010. Using day-of-year as the input does the opposite: it forces every year to have the same curve. A kernel can express both at once:
where is the period (one year), controls how smooth the function is within a period, and controls how quickly the correlation between the same day in different years fades. The first factor alone says “every year is identical”; multiplying by the second (a “locally periodic” kernel) lets the yearly pattern drift slowly. In the lab this model sat between the other two: it followed the seasonal shape and still allowed year-to-year differences. Building kernels by multiplying and adding simpler ones is one of the nicest things about GPs.
gausspr also does GP classification. In the lab, going from two to all four features of a banknote dataset raised test accuracy from 0.925 to 0.995 with default settings, which mostly showed how easy it is to use rather than anything deep.
Key takeaways
- A GP is a prior over functions defined by a kernel; the SE kernel has a signal variance and a length scale .
- Posterior mean and variance come from one Cholesky factorisation; the variance doesn’t depend on .
- Narrow bands can mean an over-smooth, confidently wrong model; use the marginal likelihood to compare hyperparameters.
- Hyperparameters depend on the scale of and ;
gaussprstandardises both by default. - A pointwise 95% band is sd. Periodic × SE kernels model seasonality that drifts.
Resources
- Carl Edward Rasmussen, Christopher K. I. Williams, Gaussian Processes for Machine Learning, MIT Press, 2006, Section 2.2 and Algorithm 2.1, Chapter 4 (covariance functions), Chapter 5 (model selection): https://gaussianprocess.org/gpml/
- David Duvenaud, “The Kernel Cookbook: Advice on covariance functions”: https://www.cs.toronto.edu/~duvenaud/cookbook/
- kernlab reference manual,
gausspr: https://cran.r-project.org/package=kernlab - 732A96 Advanced Machine Learning, Linköping University (course lab, autumn 2020).