def _update_inputs(self, x, x_ind, y, f, obs): available = ~B.isnan(y[:, 0]) def estimate(x_): # If observations are available, estimate using the posterior mean; # otherwise, use the prior mean. return ((f | obs) if obs else f).mean(x_) # Update inputs of inducing points. if self.sparse: x_ind = B.concat([x_ind, estimate(x_ind)], axis=1) # Impute missing data and replace available data. if self.impute and self.replace: y = estimate(x) else: # Just impute missing data. if self.impute and B.any(~available): y = merge(y, estimate(x[~available]), ~available) # Just replace available data. if self.replace and B.any(available): y = merge(y, estimate(x[available]), available) # Finally, actually update inputs. x = B.concat([x, y], axis=1) return x, x_ind
def per_output(y, keep=False): """Return observations per output, respecting that the data must be closed downwards. Args: y (tensor): Outputs. keep (bool, optional): Also return missing observations that would make the data closed downwards. Returns: generator: Generator that generates tuples containing the observations per layer and a mask which observations are not missing relative to the previous layer. """ p = B.shape_int(y)[1] # Number of outputs for i in range(p): # Check current and future availability. available = ~B.isnan(y) future = B.any(available[:, i + 1:], axis=1) # Initialise the mask to current availability. mask = available[:, i] # Take into account future observations if necessary. if keep and i < p - 1: # Check whether this is the last output. mask = mask | future # Give stuff back. yield y[mask, i:i + 1], mask # Filter observations. y = y[mask]
def logpdf(self, x, y, w, only_last_layer=False, sample_missing=False, return_inputs=False, x_ind=None, outputs=None): """Compute the logpdf. Args: x (tensor): Inputs. y (tensor): Outputs. w (tensor): Weights. only_last_layer (bool, optional): Compute the logpdf for only the last layer. Defaults to `False`. sample_missing (bool, optional): Sample missing data to compute an unbiased estimate of the pdf, *not* logpdf. Defaults to `False`. return_inputs (bool, optional): Instead return the inputs and inputs for the inducing points with previous outputs concatenated. This can be used to perform precomputation. Defaults to `False`. x_ind (tensor, optional): Inputs for the inducing points. This can be used to resume a computation. Defaults to :attr:`.model.GPAR.x_ind`. outputs (list[int], optional): Only compute the logpdf for a subset of outputs. The list specifies the indices of the outputs. Defaults to computing the logpdf for all outputs. Returns: scalar: Logpdf. If `return_inputs` is set to `True`, instead return a tuple containing the inputs and the inputs for the inducing points with previous outputs concatenated """ logpdf = B.cast(B.dtype(x), 0) x_ind = self.x_ind if x_ind is None else x_ind y_per_output = per_output(y, w, self.impute or sample_missing) for is_last, ((y, w, mask), model) in \ last(zip(y_per_output, self.layers), select=outputs): x = x[mask] # Filter according to mask. f, e = model() # Construct model. obs = self._obs(x, x_ind, y, w, f, e) # Construct observations. # Accumulate logpdf. if not only_last_layer or (is_last and only_last_layer): logpdf += f.graph.logpdf(obs) if not is_last: missing = B.isnan(y[:, 0]) # Sample missing data for an unbiased sample of the pdf. if sample_missing and B.any(missing): y = merge(y, ((f + e) | obs)(x[missing]).sample(), missing) # Update inputs. x, x_ind = self._update_inputs(x, x_ind, y, f, obs) # Return inputs if asked for. return (x, x_ind) if return_inputs else logpdf
def per_output(y, w=None, keep=False): """Return observations per output, respecting that the data must be closed downwards. The function supports caching by feeding it a dictionary where the keys the are values for `keep` and the values lists containing items that the function should yield. Args: y (tensor): Outputs. w (tensor): Weights. keep (bool, optional): Also return missing observations that would make the data closed downwards. Returns: generator: Generator that generates tuples containing the observations per layer and a mask which observations are not missing relative to the previous layer. """ # Handle cache, if that is given. if isinstance(y, dict): for yi in y[keep]: yield yi return p = B.shape(y)[1] # Number of outputs available = ~B.isnan(y) # Availability of outputs. for i in range(p): # Initialise the mask to current availability. mask = available[:, i] # Take into account future observations if necessary. if keep and i < p - 1: # Check whether this is the last output. mask = mask | B.any(available[:, i + 1:], axis=1) # Give stuff back. yield y[mask, i:i + 1], w[mask, i], mask # Filter observations and availability. y = y[mask] w = w[mask] available = available[mask]