Basic framework for serializing GPy models

This commit is contained in:
Moreno 2017-04-11 11:42:58 +01:00
parent d529da3e6c
commit e572bfb746
26 changed files with 828 additions and 64 deletions

View file

@ -41,6 +41,26 @@ class LatentFunctionInference(object):
"""
pass
def _to_dict(self):
input_dict = {}
return input_dict
def to_dict(self):
raise NotImplementedError
@staticmethod
def from_dict(input_dict):
import copy
input_dict = copy.deepcopy(input_dict)
inference_class = input_dict.pop('class')
import GPy
inference_class = eval(inference_class)
return inference_class._from_dict(inference_class, input_dict)
@staticmethod
def _from_dict(inference_class, input_dict):
return inference_class(**input_dict)
class InferenceMethodList(LatentFunctionInference, list):
def on_optimization_start(self):

View file

@ -21,6 +21,11 @@ class ExactGaussianInference(LatentFunctionInference):
def __init__(self):
pass#self._YYTfactor_cache = caching.cache()
def to_dict(self):
input_dict = super(ExactGaussianInference, self)._to_dict()
input_dict["class"] = "GPy.inference.latent_function_inference.exact_gaussian_inference.ExactGaussianInference"
return input_dict
def inference(self, kern, X, likelihood, Y, mean_function=None, Y_metadata=None, K=None, variance=None, Z_tilde=None):
"""
Returns a Posterior class containing essential quantities of the posterior

View file

@ -7,6 +7,7 @@ from . import ExactGaussianInference, VarDTC
from ...util import diag
from .posterior import PosteriorEP as Posterior
from ...likelihoods import Gaussian
from . import LatentFunctionInference
log_2_pi = np.log(2*np.pi)
@ -26,6 +27,14 @@ class cavityParams(object):
def _update_i(self, eta, ga_approx, post_params, i):
self.tau[i] = 1./post_params.Sigma_diag[i] - eta*ga_approx.tau[i]
self.v[i] = post_params.mu[i]/post_params.Sigma_diag[i] - eta*ga_approx.v[i]
def to_dict(self):
return {"tau": self.tau.tolist(), "v": self.v.tolist()}
@staticmethod
def from_dict(input_dict):
c = cavityParams(len(input_dict["tau"]))
c.tau = np.array(input_dict["tau"])
c.v = np.array(input_dict["v"])
return c
class gaussianApproximation(object):
@ -48,6 +57,11 @@ class gaussianApproximation(object):
self.v[i] += delta_v
return (delta_tau, delta_v)
def to_dict(self):
return {"tau": self.tau.tolist(), "v": self.v.tolist()}
@staticmethod
def from_dict(input_dict):
return gaussianApproximation(np.array(input_dict["v"]), np.array(input_dict["tau"]))
class posteriorParamsBase(object):
@ -71,6 +85,20 @@ class posteriorParams(posteriorParamsBase):
ci = delta_tau/(1.+ delta_tau*self.Sigma_diag[i])
DSYR(self.Sigma, self.Sigma[:,i].copy(), -ci)
self.mu = np.dot(self.Sigma, ga_approx.v)
def to_dict(self):
#TODO: Implement a more memory efficient variant
if self.L is None:
return { "mu": self.mu.tolist(), "Sigma": self.Sigma.tolist()}
else:
return { "mu": self.mu.tolist(), "Sigma": self.Sigma.tolist(), "L": self.L.tolist()}
@staticmethod
def from_dict(input_dict):
if "L" in input_dict:
return posteriorParams(np.array(input_dict["mu"]), np.array(input_dict["Sigma"]), np.array(input_dict["L"]))
else:
return posteriorParams(np.array(input_dict["mu"]), np.array(input_dict["Sigma"]))
@staticmethod
def _recompute(K, ga_approx):
@ -112,7 +140,7 @@ class posteriorParamsDTC(posteriorParamsBase):
return posteriorParamsDTC(mu, Sigma_diag), LLT
class EPBase(object):
def __init__(self, epsilon=1e-6, eta=1., delta=1., always_reset=False, max_iters=np.inf, ep_mode="alternated", parallel_updates=False):
def __init__(self, epsilon=1e-6, eta=1., delta=1., always_reset=False, max_iters=np.inf, ep_mode="alternated", parallel_updates=False, loading=False):
"""
The expectation-propagation algorithm.
For nomenclature see Rasmussen & Williams 2006.
@ -128,6 +156,7 @@ class EPBase(object):
:max_iters: int
:ep_mode: string. It can be "nested" (EP is run every time the Hyperparameters change) or "alternated" (It runs EP at the beginning and then optimize the Hyperparameters).
:parallel_updates: boolean. If true, updates of the parameters of the sites in parallel
:loading: boolean. If True, prevents the EP parameters to change. Hack used when loading a serialized model
"""
super(EPBase, self).__init__()
@ -135,6 +164,8 @@ class EPBase(object):
self.epsilon, self.eta, self.delta, self.max_iters = epsilon, eta, delta, max_iters
self.ep_mode = ep_mode
self.parallel_updates = parallel_updates
#FIXME: Hack for serialiation. If True, prevents the EP parameters to change when loading a serialized model
self.loading = loading
self.reset()
def reset(self):
@ -161,9 +192,21 @@ class EPBase(object):
def __getstate__(self):
return [super(EPBase, self).__getstate__() , [self.epsilon, self.eta, self.delta]]
def _to_dict(self):
input_dict = super(EPBase, self)._to_dict()
input_dict["epsilon"]=self.epsilon
input_dict["eta"]=self.eta
input_dict["delta"]=self.delta
input_dict["always_reset"]=self.always_reset
input_dict["max_iters"]=self.max_iters
input_dict["ep_mode"]=self.ep_mode
input_dict["parallel_updates"]=self.parallel_updates
input_dict["loading"]=True
return input_dict
class EP(EPBase, ExactGaussianInference):
def inference(self, kern, X, likelihood, Y, mean_function=None, Y_metadata=None, precision=None, K=None):
if self.always_reset:
if self.always_reset and not self.loading:
self.reset()
num_data, output_dim = Y.shape
@ -172,11 +215,11 @@ class EP(EPBase, ExactGaussianInference):
if K is None:
K = kern.K(X)
if self.ep_mode=="nested":
if self.ep_mode=="nested" and not self.loading:
#Force EP at each step of the optimization
self._ep_approximation = None
post_params, ga_approx, cav_params, log_Z_tilde = self._ep_approximation = self.expectation_propagation(K, Y, likelihood, Y_metadata)
elif self.ep_mode=="alternated":
elif self.ep_mode=="alternated" or self.loading:
if getattr(self, '_ep_approximation', None) is None:
#if we don't yet have the results of runnign EP, run EP and store the computed factors in self._ep_approximation
post_params, ga_approx, cav_params, log_Z_tilde = self._ep_approximation = self.expectation_propagation(K, Y, likelihood, Y_metadata)
@ -186,6 +229,7 @@ class EP(EPBase, ExactGaussianInference):
else:
raise ValueError("ep_mode value not valid")
self.loading = False
return self._inference(Y, K, ga_approx, cav_params, likelihood, Y_metadata=Y_metadata, Z_tilde=log_Z_tilde)
def expectation_propagation(self, K, Y, likelihood, Y_metadata):
@ -297,6 +341,36 @@ class EP(EPBase, ExactGaussianInference):
dL_dthetaL = likelihood.ep_gradients(Y, cav_params.tau, cav_params.v, np.diag(dL_dK), Y_metadata=Y_metadata, quad_mode='gh')
return Posterior(woodbury_inv=Wi, woodbury_vector=alpha, K=K), log_marginal, {'dL_dK':dL_dK, 'dL_dthetaL':dL_dthetaL, 'dL_dm':alpha}
def to_dict(self):
input_dict = super(EP, self)._to_dict()
input_dict["class"] = "GPy.inference.latent_function_inference.expectation_propagation.EP"
if self.ga_approx_old is not None:
input_dict["ga_approx_old"] = self.ga_approx_old.to_dict()
if self._ep_approximation is not None:
input_dict["_ep_approximation"] = {}
input_dict["_ep_approximation"]["post_params"] = self._ep_approximation[0].to_dict()
input_dict["_ep_approximation"]["ga_approx"] = self._ep_approximation[1].to_dict()
input_dict["_ep_approximation"]["cav_params"] = self._ep_approximation[2].to_dict()
input_dict["_ep_approximation"]["log_Z_tilde"] = self._ep_approximation[3].tolist()
return input_dict
@staticmethod
def _from_dict(inference_class, input_dict):
ga_approx_old = input_dict.pop('ga_approx_old', None)
if ga_approx_old is not None:
ga_approx_old = gaussianApproximation.from_dict(ga_approx_old)
_ep_approximation_dict = input_dict.pop('_ep_approximation', None)
_ep_approximation = []
if _ep_approximation is not None:
_ep_approximation.append(posteriorParams.from_dict(_ep_approximation_dict["post_params"]))
_ep_approximation.append(gaussianApproximation.from_dict(_ep_approximation_dict["ga_approx"]))
_ep_approximation.append(cavityParams.from_dict(_ep_approximation_dict["cav_params"]))
_ep_approximation.append(np.array(_ep_approximation_dict["log_Z_tilde"]))
ee = EP(**input_dict)
ee.ga_approx_old = ga_approx_old
ee._ep_approximation = _ep_approximation
return ee
class EPDTC(EPBase, VarDTC):
def inference(self, kern, X, Z, likelihood, Y, mean_function=None, Y_metadata=None, Lm=None, dL_dKmm=None, psi0=None, psi1=None, psi2=None):