def objective(vs, m, x_data, y_data, locs): """NLML objective. Args: vs (:class:`varz.Vars`): Variable container. m (int): Number of latent processes. x_data (tensor): Time stamps of the observations. y_data (tensor): Observations. locs (tensor): Spatial locations of observations. Returns: scalar: Negative log-marginal likelihood. """ y_proj, _, S, noises_obs = project(vs, m, y_data, locs) xs, noise_obs, noises_latent = model(vs, m) # Add contribution of latent processes. lml = 0 for i, (x, y) in enumerate(zip(xs, y_proj)): e_signal = GP((noise_obs / S[i] + noises_latent[i]) * Delta(), graph=x.graph) lml += (x + e_signal)(x_data).logpdf(y) e_noise = GP(noise_obs / S[i] * Delta(), graph=x.graph) lml -= e_noise(x_data).logpdf(y) # Add regularisation contribution. lml += B.sum(Normal(Diagonal(noises_obs)).logpdf(B.transpose(y_data))) # Return negative the evidence, normalised by the number of data points. n, p = B.shape(y_data) return -lml / (n * p)
def predict(vs, m, x_data, y_data, locs, x_pred): """Make predictions. Args: vs (:class:`varz.Vars`): Variable container. m (int): Number of latent processes. x_data (tensor): Time stamps of the observations. y_data (tensor): Observations. locs (tensor): Spatial locations of observations. x_pred (tensor): Time stamps to predict at. Returns: tuple: Tuple containing the predictions for the latent processes and predictions for the observations. """ # Construct model and project data for prediction. xs, noise_obs, noises_latent = model(vs, m) y_proj, H, S, noises_obs = project(vs, m, y_data, locs) L = noise_obs / S + noises_latent # Condition latent processes. xs_posterior = [] for x, noise, y in zip(xs, L, y_proj): e = GP(noise * Delta(), graph=x.graph) xs_posterior.append(x | ((x + e)(x_data), y)) xs = xs_posterior # Extract posterior means and variances of the latent processes. x_means, x_vars = zip(*[(x.mean(x_pred)[:, 0], x.kernel.elwise(x_pred)[:, 0]) for x in xs]) # Construct predictions for latent processes. lat_preds = [ B.to_numpy(mean, mean - 2 * (var + L[i])**.5, mean + 2 * (var + L[i])**.5) for i, (mean, var) in enumerate(zip(x_means, x_vars)) ] # Pull means through mixing matrix. x_means = B.stack(*x_means, axis=0) y_means = B.matmul(H, x_means) # Pull variances through mixing matrix and add noise. x_vars = B.stack(*x_vars, axis=0) y_vars = B.matmul(H**2, x_vars + noises_latent[:, None]) + noise_obs # Construct predictions for observations. obs_preds = [(mean, mean - 2 * var**.5, mean + 2 * var**.5) for mean, var in zip(y_means, y_vars)] return lat_preds, obs_preds
def project(vs, m, y_data, locs): """Project the data. Args: vs (:class:`varz.Vars`): Variable container. m (int): Number of latent processes. y_data (tensor): Observations. locs (tensor): Spatial locations of observations. Returns: tuple: Tuple containing the projected outputs, the mixing matrix, S from the mixing matrix, and the observation noises. """ _, noise_obs, noises_latent = model(vs, m) # Construct mixing matrix and projection. scales = vs.bnd(B.ones(2), name='scales') K = dense(Matern52().stretch(scales)(locs)) U, S, _ = B.svd(K) S = S[:m] H = U[:, :m] * S[None, :]**.5 T = B.transpose(U[:, :m]) / S[:, None]**.5 # Project data and unstack over latent processes. y_proj = B.unstack(B.matmul(T, y_data, tr_b=True)) # Observation noises: noises_obs = noise_obs * B.ones(B.dtype(noise_obs), B.shape(y_data)[1]) return y_proj, H, S, noises_obs
def model(vs, m): """Construct model. Args: vs (:class:`varz.Vars`): Variable container. m (int): Number of latent processes. Returns: tuple: Tuple containing a list of the latent processes, the observation noise, and the noises on the latent processes. """ g = Graph() # Observation noise: noise_obs = vs.bnd(0.1, name='noise_obs') def make_latent_process(i): # Long-term trend: variance = vs.bnd(0.9, name=f'{i}/long_term/var') scale = vs.bnd(2 * 30, name=f'{i}/long_term/scale') kernel = variance * EQ().stretch(scale) # Short-term trend: variance = vs.bnd(0.1, name=f'{i}/short_term/var') scale = vs.bnd(20, name=f'{i}/short_term/scale') kernel += variance * Matern12().stretch(scale) return GP(kernel, graph=g) # Latent processes: xs = [make_latent_process(i) for i in range(m)] # Latent noises: noises_latent = vs.bnd(0.1 * B.ones(m), name='noises_latent') return xs, noise_obs, noises_latent
import matplotlib.pyplot as plt from wbml.plot import tweak from stheno import B, Measure, GP, EQ, Delta # Define points to predict at. x = B.linspace(0, 10, 100) x_obs = B.linspace(0, 10, 20) # Constuct a prior: prior = Measure() w = lambda x: B.exp(-(x**2) / 0.5) # Window b = [(w * GP(EQ(), measure=prior)).shift(xi) for xi in x_obs] # Weighted basis funs f = sum(b) # Latent function e = GP(Delta(), measure=prior) # Noise y = f + 0.2 * e # Observation model # Sample a true, underlying function and observations. f_true, y_obs = prior.sample(f(x), y(x_obs)) # Condition on the observations to make predictions. post = prior | (y(x_obs), y_obs) # Plot result. for i, bi in enumerate(b): mean, lower, upper = post(bi(x)).marginals() kw_args = {"label": "Basis functions"} if i == 0 else {} plt.plot(x, mean, style="pred2", **kw_args) plt.plot(x, f_true, label="True", style="test") plt.scatter(x_obs, y_obs, label="Observations", style="train", s=20)
import matplotlib.pyplot as plt from wbml.plot import tweak from stheno import Measure, GP, EQ, RQ, Linear, Delta, Exp, B B.epsilon = 1e-10 # Define points to predict at. x = B.linspace(0, 10, 200) x_obs = B.linspace(0, 7, 50) # Construct a latent function consisting of four different components. prior = Measure() f_smooth = GP(EQ(), measure=prior) f_wiggly = GP(RQ(1e-1).stretch(0.5), measure=prior) f_periodic = GP(EQ().periodic(1.0), measure=prior) f_linear = GP(Linear(), measure=prior) f = f_smooth + f_wiggly + f_periodic + 0.2 * f_linear # Let the observation noise consist of a bit of exponential noise. e_indep = GP(Delta(), measure=prior) e_exp = GP(Exp(), measure=prior) e = e_indep + 0.3 * e_exp # Sum the latent function and observation noise to get a model for the observations. y = f + 0.5 * e # Sample a true, underlying function and observations. (
import matplotlib.pyplot as plt import wbml.out as out from wbml.plot import tweak from stheno import B, GP, EQ, PseudoObs # Define points to predict at. x = B.linspace(0, 10, 100) x_obs = B.linspace(0, 7, 50_000) x_ind = B.linspace(0, 10, 20) # Construct a prior. f = GP(EQ().periodic(2 * B.pi)) # Sample a true, underlying function and observations. f_true = B.sin(x) y_obs = B.sin(x_obs) + B.sqrt(0.5) * B.randn(*x_obs.shape) # Compute a pseudo-point approximation of the posterior. obs = PseudoObs(f(x_ind), (f(x_obs, 0.5), y_obs)) # Compute the ELBO. out.kv("ELBO", obs.elbo(f.measure)) # Compute the approximate posterior. f_post = f | obs # Make predictions with the approximate posterior. mean, lower, upper = f_post(x).marginal_credible_bounds() # Plot result.
import matplotlib.pyplot as plt from wbml.plot import tweak from stheno import B, Measure, GP, EQ # Define points to predict at. x = B.linspace(0, 10, 100) x_obs = B.linspace(0, 10, 20) with Measure() as prior: w = lambda x: B.exp(-(x**2) / 0.5) # Basis function b = [(w * GP(EQ())).shift(xi) for xi in x_obs] # Weighted basis functions f = sum(b) # Sample a true, underlying function and observations. f_true, y_obs = prior.sample(f(x), f(x_obs, 0.2)) # Condition on the observations to make predictions. post = prior | (f(x_obs, 0.2), y_obs) # Plot result. for i, bi in enumerate(b): mean, lower, upper = post(bi(x)).marginal_credible_bounds() kw_args = {"label": "Basis functions"} if i == 0 else {} plt.plot(x, mean, style="pred2", **kw_args) plt.plot(x, f_true, label="True", style="test") plt.scatter(x_obs, y_obs, label="Observations", style="train", s=20) mean, lower, upper = post(f(x)).marginal_credible_bounds() plt.plot(x, mean, label="Prediction", style="pred") plt.fill_between(x, lower, upper, style="pred") tweak()
self.ps = ps def __add__(self, other): return VGP([f + g for f, g in zip(self.ps, other.ps)]) def lmatmul(self, A): m, n = A.shape ps = [0 for _ in range(m)] for i in range(m): for j in range(n): ps[i] += A[i, j] * self.ps[j] return VGP(ps) # Define points to predict at. x = B.linspace(0, 10, 100) x_obs = B.linspace(0, 10, 10) # Model parameters: m = 2 p = 4 H = B.randn(p, m) # Construct latent functions. prior = Measure() us = VGP([GP(EQ(), measure=prior) for _ in range(m)]) fs = us.lmatmul(H) # Construct noise. e = VGP([GP(0.5 * Delta(), measure=prior) for _ in range(p)])
import matplotlib.pyplot as plt from wbml.plot import tweak from stheno import B, Measure, GP, EQ # Define points to predict at. x = B.linspace(0, 10, 100) # Construct a prior. prior = Measure() f1 = GP(3, EQ(), measure=prior) f2 = GP(3, EQ(), measure=prior) # Compute the approximate product. f_prod = f1 * f2 # Sample two functions. s1, s2 = prior.sample(f1(x), f2(x)) # Predict. post = prior | ((f1(x), s1), (f2(x), s2)) mean, lower, upper = post(f_prod(x)).marginals() # Plot result. plt.plot(x, s1, label="Sample 1", style="train") plt.plot(x, s2, label="Sample 2", style="train", ls="--") plt.plot(x, s1 * s2, label="True product", style="test") plt.plot(x, mean, label="Approximate posterior", style="pred") plt.fill_between(x, lower, upper, style="pred") tweak()
import matplotlib.pyplot as plt from wbml.plot import tweak from stheno import B, GP, EQ # Define points to predict at. x = B.linspace(0, 10, 100) x_obs = B.linspace(0, 7, 20) # Construct a prior. f = GP(EQ().periodic(5.0)) # Sample a true, underlying function and noisy observations. f_true, y_obs = f.measure.sample(f(x), f(x_obs, 0.5)) # Now condition on the observations to make predictions. f_post = f | (f(x_obs, 0.5), y_obs) mean, lower, upper = f_post(x).marginal_credible_bounds() # Plot result. plt.plot(x, f_true, label="True", style="test") plt.scatter(x_obs, y_obs, label="Observations", style="train", s=20) plt.plot(x, mean, label="Prediction", style="pred") plt.fill_between(x, lower, upper, style="pred") tweak() plt.savefig("readme_example1_simple_regression.png") plt.show()