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 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