1# Copyright (c) 2012-2014, GPy authors (see AUTHORS.txt). 2# Licensed under the BSD 3-clause license (see LICENSE.txt) 3 4import numpy as np 5from .gp import GP 6from .parameterization.param import Param 7from ..inference.latent_function_inference import var_dtc 8from .. import likelihoods 9from GPy.core.parameterization.variational import VariationalPosterior 10 11import logging 12logger = logging.getLogger("sparse gp") 13 14class SparseGP(GP): 15 """ 16 A general purpose Sparse GP model 17 18 This model allows (approximate) inference using variational DTC or FITC 19 (Gaussian likelihoods) as well as non-conjugate sparse methods based on 20 these. 21 22 This is not for missing data, as the implementation for missing data involves 23 some inefficient optimization routine decisions. 24 See missing data SparseGP implementation in py:class:'~GPy.models.sparse_gp_minibatch.SparseGPMiniBatch'. 25 26 :param X: inputs 27 :type X: np.ndarray (num_data x input_dim) 28 :param likelihood: a likelihood instance, containing the observed data 29 :type likelihood: GPy.likelihood.(Gaussian | EP | Laplace) 30 :param kernel: the kernel (covariance function). See link kernels 31 :type kernel: a GPy.kern.kern instance 32 :param X_variance: The uncertainty in the measurements of X (Gaussian variance) 33 :type X_variance: np.ndarray (num_data x input_dim) | None 34 :param Z: inducing inputs 35 :type Z: np.ndarray (num_inducing x input_dim) 36 :param num_inducing: Number of inducing points (optional, default 10. Ignored if Z is not None) 37 :type num_inducing: int 38 39 """ 40 41 def __init__(self, X, Y, Z, kernel, likelihood, mean_function=None, X_variance=None, inference_method=None, 42 name='sparse gp', Y_metadata=None, normalizer=False): 43 44 #pick a sensible inference method 45 if inference_method is None: 46 if isinstance(likelihood, likelihoods.Gaussian): 47 inference_method = var_dtc.VarDTC(limit=3) 48 else: 49 #inference_method = ?? 50 raise NotImplementedError("what to do what to do?") 51 print(("defaulting to ", inference_method, "for latent function inference")) 52 53 self.Z = Param('inducing inputs', Z) 54 self.num_inducing = Z.shape[0] 55 56 super(SparseGP, self).__init__(X, Y, kernel, likelihood, mean_function, inference_method=inference_method, name=name, Y_metadata=Y_metadata, normalizer=normalizer) 57 58 logger.info("Adding Z as parameter") 59 self.link_parameter(self.Z, index=0) 60 self.posterior = None 61 62 @property 63 def _predictive_variable(self): 64 return self.Z 65 66 def has_uncertain_inputs(self): 67 return isinstance(self.X, VariationalPosterior) 68 69 def set_Z(self, Z, trigger_update=True): 70 if trigger_update: self.update_model(False) 71 self.unlink_parameter(self.Z) 72 self.Z = Param('inducing inputs',Z) 73 self.link_parameter(self.Z, index=0) 74 if trigger_update: self.update_model(True) 75 76 def parameters_changed(self): 77 self.posterior, self._log_marginal_likelihood, self.grad_dict = \ 78 self.inference_method.inference(self.kern, self.X, self.Z, self.likelihood, 79 self.Y_normalized, Y_metadata=self.Y_metadata, 80 mean_function=self.mean_function) 81 self._update_gradients() 82 83 def _update_gradients(self): 84 self.likelihood.update_gradients(self.grad_dict['dL_dthetaL']) 85 if self.mean_function is not None: 86 self.mean_function.update_gradients(self.grad_dict['dL_dm'], self.X) 87 88 if isinstance(self.X, VariationalPosterior): 89 #gradients wrt kernel 90 dL_dKmm = self.grad_dict['dL_dKmm'] 91 self.kern.update_gradients_full(dL_dKmm, self.Z, None) 92 kerngrad = self.kern.gradient.copy() 93 self.kern.update_gradients_expectations(variational_posterior=self.X, 94 Z=self.Z, 95 dL_dpsi0=self.grad_dict['dL_dpsi0'], 96 dL_dpsi1=self.grad_dict['dL_dpsi1'], 97 dL_dpsi2=self.grad_dict['dL_dpsi2']) 98 self.kern.gradient += kerngrad 99 100 #gradients wrt Z 101 self.Z.gradient = self.kern.gradients_X(dL_dKmm, self.Z) 102 self.Z.gradient += self.kern.gradients_Z_expectations( 103 self.grad_dict['dL_dpsi0'], 104 self.grad_dict['dL_dpsi1'], 105 self.grad_dict['dL_dpsi2'], 106 Z=self.Z, 107 variational_posterior=self.X) 108 else: 109 #gradients wrt kernel 110 self.kern.update_gradients_diag(self.grad_dict['dL_dKdiag'], self.X) 111 kerngrad = self.kern.gradient.copy() 112 self.kern.update_gradients_full(self.grad_dict['dL_dKnm'], self.X, self.Z) 113 kerngrad += self.kern.gradient 114 self.kern.update_gradients_full(self.grad_dict['dL_dKmm'], self.Z, None) 115 self.kern.gradient += kerngrad 116 #gradients wrt Z 117 self.Z.gradient = self.kern.gradients_X(self.grad_dict['dL_dKmm'], self.Z) 118 self.Z.gradient += self.kern.gradients_X(self.grad_dict['dL_dKnm'].T, self.Z, self.X) 119 self._Zgrad = self.Z.gradient.copy() 120 121 def to_dict(self, save_data=True): 122 """ 123 Convert the object into a json serializable dictionary. 124 125 :param boolean save_data: if true, it adds the training data self.X and self.Y to the dictionary 126 :return dict: json serializable dictionary containing the needed information to instantiate the object 127 """ 128 input_dict = super(SparseGP, self).to_dict(save_data) 129 input_dict["class"] = "GPy.core.SparseGP" 130 input_dict["Z"] = self.Z.tolist() 131 return input_dict 132 133 @staticmethod 134 def _format_input_dict(input_dict, data=None): 135 input_dict = GP._format_input_dict(input_dict, data) 136 input_dict["Z"] = np.array(input_dict["Z"]) 137 return input_dict 138 139 @staticmethod 140 def _build_from_input_dict(input_dict, data=None): 141 input_dict = SparseGP._format_input_dict(input_dict, data) 142 return SparseGP(**input_dict) 143