mirror of
https://github.com/SheffieldML/GPy.git
synced 2026-05-21 14:05:14 +02:00
Various doc string edits and consistency changes in comments.
This commit is contained in:
parent
7bb7c0a08c
commit
c140b2118a
5 changed files with 218 additions and 79 deletions
|
|
@ -24,15 +24,17 @@ class Model(Parameterized):
|
|||
self.preferred_optimizer = 'scg'
|
||||
# self._set_params(self._get_params()) has been taken out as it should only be called on leaf nodes
|
||||
def log_likelihood(self):
|
||||
raise NotImplementedError, "this needs to be implemented to use the Model class"
|
||||
raise NotImplementedError, "this needs to be implemented to use the model class"
|
||||
def _log_likelihood_gradients(self):
|
||||
raise NotImplementedError, "this needs to be implemented to use the Model class"
|
||||
raise NotImplementedError, "this needs to be implemented to use the model class"
|
||||
|
||||
def getstate(self):
|
||||
"""
|
||||
Get the current state of the class.
|
||||
|
||||
Inherited from Parameterized, so add those parameters to the state
|
||||
:return: list of states from the model.
|
||||
|
||||
"""
|
||||
return Parameterized.getstate(self) + \
|
||||
[self.priors, self.optimization_runs,
|
||||
|
|
@ -41,8 +43,10 @@ class Model(Parameterized):
|
|||
def setstate(self, state):
|
||||
"""
|
||||
set state from previous call to getstate
|
||||
|
||||
call Parameterized with the rest of the state
|
||||
|
||||
:param state: the state of the model.
|
||||
:type state: list as returned from getstate.
|
||||
"""
|
||||
self.preferred_optimizer = state.pop()
|
||||
self.sampling_runs = state.pop()
|
||||
|
|
@ -52,21 +56,22 @@ class Model(Parameterized):
|
|||
|
||||
def set_prior(self, regexp, what):
|
||||
"""
|
||||
Sets priors on the Model parameters.
|
||||
|
||||
Arguments
|
||||
---------
|
||||
regexp -- string, regexp, or integer array
|
||||
what -- instance of a Prior class
|
||||
Sets priors on the model parameters.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Asserts that the Prior is suitable for the constraint. If the
|
||||
Asserts that the prior is suitable for the constraint. If the
|
||||
wrong constraint is in place, an error is raised. If no
|
||||
constraint is in place, one is added (warning printed).
|
||||
|
||||
For tied parameters, the Prior will only be "counted" once, thus
|
||||
a Prior object is only inserted on the first tied index
|
||||
For tied parameters, the prior will only be "counted" once, thus
|
||||
a prior object is only inserted on the first tied index
|
||||
|
||||
:param regexp: regular expression of parameters on which priors need to be set.
|
||||
:type param: string, regexp, or integer array
|
||||
:param what: prior to set on parameter.
|
||||
:type what: GPy.core.Prior type
|
||||
|
||||
"""
|
||||
if self.priors is None:
|
||||
self.priors = [None for i in range(self._get_params().size)]
|
||||
|
|
@ -76,12 +81,12 @@ class Model(Parameterized):
|
|||
# check tied situation
|
||||
tie_partial_matches = [tie for tie in self.tied_indices if (not set(tie).isdisjoint(set(which))) & (not set(tie) == set(which))]
|
||||
if len(tie_partial_matches):
|
||||
raise ValueError, "cannot place Prior across partial ties"
|
||||
raise ValueError, "cannot place prior across partial ties"
|
||||
tie_matches = [tie for tie in self.tied_indices if set(which) == set(tie) ]
|
||||
if len(tie_matches) > 1:
|
||||
raise ValueError, "cannot place Prior across multiple ties"
|
||||
raise ValueError, "cannot place prior across multiple ties"
|
||||
elif len(tie_matches) == 1:
|
||||
which = which[:1] # just place a Prior object on the first parameter
|
||||
which = which[:1] # just place a prior object on the first parameter
|
||||
|
||||
|
||||
# check constraints are okay
|
||||
|
|
@ -93,7 +98,7 @@ class Model(Parameterized):
|
|||
else:
|
||||
constrained_positive_indices = np.zeros(shape=(0,))
|
||||
bad_constraints = np.setdiff1d(self.all_constrained_indices(), constrained_positive_indices)
|
||||
assert not np.any(which[:, None] == bad_constraints), "constraint and Prior incompatible"
|
||||
assert not np.any(which[:, None] == bad_constraints), "constraint and prior incompatible"
|
||||
unconst = np.setdiff1d(which, constrained_positive_indices)
|
||||
if len(unconst):
|
||||
print "Warning: constraining parameters to be positive:"
|
||||
|
|
@ -101,17 +106,22 @@ class Model(Parameterized):
|
|||
print '\n'
|
||||
self.constrain_positive(unconst)
|
||||
elif what.domain is REAL:
|
||||
assert not np.any(which[:, None] == self.all_constrained_indices()), "constraint and Prior incompatible"
|
||||
assert not np.any(which[:, None] == self.all_constrained_indices()), "constraint and prior incompatible"
|
||||
else:
|
||||
raise ValueError, "Prior not recognised"
|
||||
raise ValueError, "prior not recognised"
|
||||
|
||||
# store the Prior in a local list
|
||||
# store the prior in a local list
|
||||
for w in which:
|
||||
self.priors[w] = what
|
||||
|
||||
def get_gradient(self, name, return_names=False):
|
||||
"""
|
||||
Get Model gradient(s) by name. The name is applied as a regular expression and all parameters that match that regular expression are returned.
|
||||
Get model gradient(s) by name. The name is applied as a regular expression and all parameters that match that regular expression are returned.
|
||||
|
||||
:param name: the name of parameters required (as a regular expression).
|
||||
:type name: regular expression
|
||||
:param return_names: whether or not to return the names matched (default False)
|
||||
:type return_names: bool
|
||||
"""
|
||||
matches = self.grep_param_names(name)
|
||||
if len(matches):
|
||||
|
|
@ -151,14 +161,14 @@ class Model(Parameterized):
|
|||
|
||||
def randomize(self):
|
||||
"""
|
||||
Randomize the Model.
|
||||
Make this draw from the Prior if one exists, else draw from N(0,1)
|
||||
Randomize the model.
|
||||
Make this draw from the prior if one exists, else draw from N(0,1)
|
||||
"""
|
||||
# first take care of all parameters (from N(0,1))
|
||||
x = self._get_params_transformed()
|
||||
x = np.random.randn(x.size)
|
||||
self._set_params_transformed(x)
|
||||
# now draw from Prior where possible
|
||||
# now draw from prior where possible
|
||||
x = self._get_params()
|
||||
if self.priors is not None:
|
||||
[np.put(x, i, p.rvs(1)) for i, p in enumerate(self.priors) if not p is None]
|
||||
|
|
@ -168,21 +178,30 @@ class Model(Parameterized):
|
|||
|
||||
def optimize_restarts(self, num_restarts=10, robust=False, verbose=True, parallel=False, num_processes=None, **kwargs):
|
||||
"""
|
||||
Perform random restarts of the Model, and set the Model to the best
|
||||
Perform random restarts of the model, and set the model to the best
|
||||
seen solution.
|
||||
|
||||
If the robust flag is set, exceptions raised during optimizations will
|
||||
be handled silently. If _all_ runs fail, the Model is reset to the
|
||||
be handled silently. If _all_ runs fail, the model is reset to the
|
||||
existing parameter values.
|
||||
|
||||
Notes
|
||||
-----
|
||||
:param num_restarts: number of restarts to use (default 10)
|
||||
:type num_restarts: int
|
||||
:param robust: whether to handle exceptions silently or not (default False)
|
||||
:type robust: bool
|
||||
:param parallel: whether to run each restart as a separate process. It relies on the multiprocessing module.
|
||||
:type parallel: bool
|
||||
:param num_processes: number of workers in the multiprocessing pool
|
||||
:type numprocesses: int
|
||||
**kwargs are passed to the optimizer. They can be:
|
||||
:max_f_eval: maximum number of function evaluations
|
||||
:messages: whether to display during optimisation
|
||||
:verbose: whether to show informations about the current restart
|
||||
:parallel: whether to run each restart as a separate process. It relies on the multiprocessing module.
|
||||
:num_processes: number of workers in the multiprocessing pool
|
||||
:param max_f_eval: maximum number of function evaluations
|
||||
:type max_f_eval: int
|
||||
:param max_iters: maximum number of iterations
|
||||
:type max_iters: int
|
||||
:param messages: whether to display during optimisation
|
||||
:type messages: bool
|
||||
|
||||
..Note: If num_processes is None, the number of workes in the multiprocessing pool is automatically
|
||||
set to the number of processors on the current machine.
|
||||
|
|
@ -230,8 +249,13 @@ class Model(Parameterized):
|
|||
self._set_params_transformed(initial_parameters)
|
||||
|
||||
def ensure_default_constraints(self):
|
||||
"""
|
||||
Ensure that any variables which should clearly be positive have been constrained somehow.
|
||||
"""
|
||||
Ensure that any variables which should clearly be positive
|
||||
have been constrained somehow. The method performs a regular
|
||||
expression search on parameter names looking for the terms
|
||||
'variance', 'lengthscale', 'precision' and 'kappa'. If any of
|
||||
these terms are present in the name the parameter is
|
||||
constrained positive.
|
||||
"""
|
||||
positive_strings = ['variance', 'lengthscale', 'precision', 'kappa']
|
||||
# param_names = self._get_param_names()
|
||||
|
|
@ -246,11 +270,15 @@ class Model(Parameterized):
|
|||
|
||||
def objective_function(self, x):
|
||||
"""
|
||||
The objective function passed to the optimizer. It combines the likelihood and the priors.
|
||||
The objective function passed to the optimizer. It combines
|
||||
the likelihood and the priors.
|
||||
|
||||
Failures are handled robustly. The algorithm will try several times to
|
||||
return the objective, and will raise the original exception if it
|
||||
the objective cannot be computed.
|
||||
|
||||
:param x: the parameters of the model.
|
||||
:parameter type: np.array
|
||||
"""
|
||||
try:
|
||||
self._set_params_transformed(x)
|
||||
|
|
@ -267,8 +295,11 @@ class Model(Parameterized):
|
|||
Gets the gradients from the likelihood and the priors.
|
||||
|
||||
Failures are handled robustly. The algorithm will try several times to
|
||||
return the objective, and will raise the original exception if it
|
||||
return the gradients, and will raise the original exception if it
|
||||
the objective cannot be computed.
|
||||
|
||||
:param x: the parameters of the model.
|
||||
:parameter type: np.array
|
||||
"""
|
||||
try:
|
||||
self._set_params_transformed(x)
|
||||
|
|
@ -282,6 +313,13 @@ class Model(Parameterized):
|
|||
return obj_grads
|
||||
|
||||
def objective_and_gradients(self, x):
|
||||
"""
|
||||
Compute the objective function of the model and the gradient of the model at the point given by x.
|
||||
|
||||
:param x: the point at which gradients are to be computed.
|
||||
:type np.array:
|
||||
"""
|
||||
|
||||
try:
|
||||
self._set_params_transformed(x)
|
||||
obj_f = -self.log_likelihood() - self.log_prior()
|
||||
|
|
@ -297,11 +335,13 @@ class Model(Parameterized):
|
|||
|
||||
def optimize(self, optimizer=None, start=None, **kwargs):
|
||||
"""
|
||||
Optimize the Model using self.log_likelihood and self.log_likelihood_gradient, as well as self.priors.
|
||||
Optimize the model using self.log_likelihood and self.log_likelihood_gradient, as well as self.priors.
|
||||
kwargs are passed to the optimizer. They can be:
|
||||
|
||||
:max_f_eval: maximum number of function evaluations
|
||||
:param max_f_eval: maximum number of function evaluations
|
||||
:type max_f_eval: int
|
||||
:messages: whether to display during optimisation
|
||||
:type messages: bool
|
||||
:param optimzer: which optimizer to use (defaults to self.preferred optimizer)
|
||||
:type optimzer: string TODO: valid strings?
|
||||
"""
|
||||
|
|
@ -327,14 +367,14 @@ class Model(Parameterized):
|
|||
self.optimization_runs.append(sgd)
|
||||
|
||||
def Laplace_covariance(self):
|
||||
"""return the covariance matric of a Laplace approximatino at the current (stationary) point"""
|
||||
# TODO add in the Prior contributions for MAP estimation
|
||||
"""return the covariance matrix of a Laplace approximation at the current (stationary) point."""
|
||||
# TODO add in the prior contributions for MAP estimation
|
||||
# TODO fix the hessian for tied, constrained and fixed components
|
||||
if hasattr(self, 'log_likelihood_hessian'):
|
||||
A = -self.log_likelihood_hessian()
|
||||
|
||||
else:
|
||||
print "numerically calculating hessian. please be patient!"
|
||||
print "numerically calculating Hessian. please be patient!"
|
||||
x = self._get_params()
|
||||
def f(x):
|
||||
self._set_params(x)
|
||||
|
|
@ -348,8 +388,8 @@ class Model(Parameterized):
|
|||
return A
|
||||
|
||||
def Laplace_evidence(self):
|
||||
"""Returns an estiamte of the Model evidence based on the Laplace approximation.
|
||||
Uses a numerical estimate of the hessian if none is available analytically"""
|
||||
"""Returns an estiamte of the model evidence based on the Laplace approximation.
|
||||
Uses a numerical estimate of the Hessian if none is available analytically."""
|
||||
A = self.Laplace_covariance()
|
||||
try:
|
||||
hld = np.sum(np.log(np.diag(jitchol(A)[0])))
|
||||
|
|
@ -370,27 +410,28 @@ class Model(Parameterized):
|
|||
log_prior = self.log_prior()
|
||||
obj_funct = '\nLog-likelihood: {0:.3e}'.format(log_like)
|
||||
if len(''.join(strs)) != 0:
|
||||
obj_funct += ', Log Prior: {0:.3e}, LL+Prior = {0:.3e}'.format(log_prior, log_like + log_prior)
|
||||
obj_funct += ', Log prior: {0:.3e}, LL+prior = {0:.3e}'.format(log_prior, log_like + log_prior)
|
||||
obj_funct += '\n\n'
|
||||
s[0] = obj_funct + s[0]
|
||||
s[0] += "|{h:^{col}}".format(h='Prior', col=width)
|
||||
s[0] += "|{h:^{col}}".format(h='prior', col=width)
|
||||
s[1] += '-' * (width + 1)
|
||||
|
||||
for p in range(2, len(strs) + 2):
|
||||
s[p] += '|{Prior:^{width}}'.format(Prior=strs[p - 2], width=width)
|
||||
s[p] += '|{prior:^{width}}'.format(prior=strs[p - 2], width=width)
|
||||
|
||||
return '\n'.join(s)
|
||||
|
||||
|
||||
def checkgrad(self, target_param=None, verbose=False, step=1e-6, tolerance=1e-3):
|
||||
"""
|
||||
Check the gradient of the Model by comparing to a numerical estimate.
|
||||
If the verbose flag is passed, invividual components are tested (and printed)
|
||||
Check the gradient of the ,odel by comparing to a numerical
|
||||
estimate. If the verbose flag is passed, invividual
|
||||
components are tested (and printed)
|
||||
|
||||
:param verbose: If True, print a "full" checking of each parameter
|
||||
:type verbose: bool
|
||||
:param step: The size of the step around which to linearise the objective
|
||||
:type step: float (defaul 1e-6)
|
||||
:type step: float (default 1e-6)
|
||||
:param tolerance: the tolerance allowed (see note)
|
||||
:type tolerance: float (default 1e-3)
|
||||
|
||||
|
|
@ -467,15 +508,15 @@ class Model(Parameterized):
|
|||
|
||||
def input_sensitivity(self):
|
||||
"""
|
||||
return an array describing the sesitivity of the Model to each input
|
||||
return an array describing the sesitivity of the model to each input
|
||||
|
||||
NB. Right now, we're basing this on the lengthscales (or
|
||||
variances) of the kernel. TODO: proper sensitivity analysis
|
||||
where we integrate across the Model inputs and evaluate the
|
||||
effect on the variance of the Model output. """
|
||||
where we integrate across the model inputs and evaluate the
|
||||
effect on the variance of the model output. """
|
||||
|
||||
if not hasattr(self, 'kern'):
|
||||
raise ValueError, "this Model has no kernel"
|
||||
raise ValueError, "this model has no kernel"
|
||||
|
||||
k = [p for p in self.kern.parts if p.name in ['rbf', 'linear', 'rbf_inv']]
|
||||
if (not len(k) == 1) or (not k[0].ARD):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue