For the first time (excuse imprecission / mistakes) I took a look at Gaussian processes, and more specifically, watched this video by Nando de Freitas. The notes are available online here.
At some point he draws $10$ random samples from a multivariate normal generated by constructing a covariance matrix based on a Gaussian kernel (exponential of squared distances in the $x$ axis). These random samples form the prior smooth plots that become less spread as data become available. Ultimately, the objective is to predict by modifying the covariance matrix, and obtaining the conditional Gaussian distribution at the points of interest.
The entire code is available at an excellent summary by Katherine Bailey here, which in turn credits a code repository by Nando de Freitas here. I have posted it the Python code here for convenience.
It starts with $3$ (instead of $10$ above) prior functions, and introduces a "tuning parameter".
I have translated the code into Python and [R], including the plots:
Here is the first chunk of code in [R] and the resulting plot of three random curves generated via a Gaussian kernel based on proximity in the $x$ values in the test set:
The second chunk of R code is hairier, and starts off by simulating four points of training data, which eventually will help narrow down the spread among the possible (prior) curves around the areas where these training data points lie. The simulation of the $y$ value for these data points is as a $\text{sin}()$ function. We can see the "tightening of the curves around the points":
The third chunk of R code deals with plotting the curve of mean estimated values (the equivalent of the regression curve), corresponding to $50$ ${\bf\mu}$ values (see calculation below), and their confidence intervals:
QUESTION: I want to ask for an explanation of the operations that take place when going from the prior GP to the posterior.
Specifically, I'd like to understand this part of the R code (in the second chunk) to get the means and sd:
# Apply the kernel function to our training points (5 points):
K_train = kernel(Xtrain, Xtrain, param) #[5 x 5] matrix
Ch_train = chol(K_train + 0.00005 * diag(length(Xtrain))) #[5 x 5] matrix
# Compute the mean at our test points:
K_trte = kernel(Xtrain, Xtest, param) #[5 x 50] matrix
core = solve(Ch_train) %*% K_trte #[5 x 50] matrix
temp = solve(Ch_train) %*% ytrain #[5 x 1] matrix
mu = t(core) %*% temp #[50 x 1] matrix
There are two kernels (one of train ($\bf a$) v. train ($\bf a$), K_train
, let's call it $\bf \Sigma_{aa}$, with its Cholesky (Ch_train
), $\bf \color{orange}{L_{aa}}$, coloring orange all Cholesky's from here on, and the second one of train ($\bf a$) v test ($\bf e$), K_trte
, let's call it $\bf \Sigma_{ae}$), and to generate the estimated means $\hat \mu$ for the $50$ points in the testing set the operation is:
\begin{align} {\bf \hat \mu}&={\bf \left [ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right ]^T \, \underset{\color{blue}{[5 \times 5]}}{\color{orange}{L_{aa}}^{-1}} \, \underset{\color{blue}{[5 \times 1]}}{y_{tr}}}\bf\tag{Eq.1}\\ &\text{dimensions}=\color{red}{\left[50 \times 1\right]} \end{align}
# Compute the standard deviation:
tempor = colSums(core^2) #[50 x 1] matrix
# Notice that all.equal(diag(t(core) %*% core), colSums(core^2)) TRUE
s2 = diag(K_test) - tempor #[50 x 1] matrix
stdv = sqrt(s2) #[50 x 1] matrix
\begin{align} {\bf \hat{\text{var}}}&=\text{diag}\left({\bf \Sigma_{ee}}\right)-\text{diag} \left[\bf \left[ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right]^T \left[ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right] \right] \bf \tag{ Eq.2} \\ &=\text{d}\small{\begin{bmatrix}1&&\dots&\\&1\\&&\ddots\\&&\dots&1&\\ &&&\dots&1\end{bmatrix}}-\bf \text{d} \left[ \left[ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right]^T \left[ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right] \right]\\ &\text{dimensions} = \color{red}{\left[50 \times 1\right]} \end{align}
How does this work?
Also unclear, is the calculation for the color lines (Posterior GP) in the "Three samples from the GP posterior" plot above, where the Cholesky of the testing and training sets seem to come together to generate multivariate normal values, eventually added to $\hat \mu$:
Ch_post_gener = chol(K_test + 1e-6 * diag(n) - (t(core) %*% core))
m_prime = matrix(rnorm(n * 3), ncol = 3)
sam = Ch_post_gener %*% m_prime
f_post = as.vector(mu) + sam
\begin{align} f_{\text{post}}&=\bf \hat \mu +\small \left[ \underset{\color{blue}{[50 \times 50]}} {\color{orange}{L_{ee}}}\, \, \, - \left[ \left[ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right]^T \left[ \underset{\color{blue}{[5 \times 5]}} {\color{orange}{L_{aa}}^{-1}}\, \, \, \underset{\color{blue}{[5 \times 50]}}{\Sigma_{ae}} \right] \right] \right] \left[\underset{\color{green}{[50 \times 3]}}{\mathscr N(0,1)}\right]\tag{Eq.3} \\ &\text{dimensions}= \color{red}{\left[50 \times 3\right]} \end{align}