mirror of
https://github.com/SheffieldML/GPy.git
synced 2026-05-13 05:52:38 +02:00
merge the current devel into psi2
This commit is contained in:
commit
785c580032
49 changed files with 1839 additions and 581 deletions
|
|
@ -12,6 +12,10 @@ from .. import likelihoods
|
|||
from ..likelihoods.gaussian import Gaussian
|
||||
from ..inference.latent_function_inference import exact_gaussian_inference, expectation_propagation, LatentFunctionInference
|
||||
from parameterization.variational import VariationalPosterior
|
||||
from scipy.sparse.base import issparse
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger("GP")
|
||||
|
||||
class GP(Model):
|
||||
"""
|
||||
|
|
@ -33,14 +37,16 @@ class GP(Model):
|
|||
|
||||
assert X.ndim == 2
|
||||
if isinstance(X, (ObsAr, VariationalPosterior)):
|
||||
self.X = X
|
||||
self.X = X.copy()
|
||||
else: self.X = ObsAr(X)
|
||||
|
||||
self.num_data, self.input_dim = self.X.shape
|
||||
|
||||
assert Y.ndim == 2
|
||||
self.Y = ObsAr(Y)
|
||||
# assert Y.shape[0] == self.num_data
|
||||
logger.info("initializing Y")
|
||||
if issparse(Y): self.Y = Y
|
||||
else: self.Y = ObsAr(Y)
|
||||
assert Y.shape[0] == self.num_data
|
||||
_, self.output_dim = self.Y.shape
|
||||
|
||||
#TODO: check the type of this is okay?
|
||||
|
|
@ -54,6 +60,7 @@ class GP(Model):
|
|||
self.likelihood = likelihood
|
||||
|
||||
#find a sensible inference method
|
||||
logger.info("initializing inference method")
|
||||
if inference_method is None:
|
||||
if isinstance(likelihood, likelihoods.Gaussian) or isinstance(likelihood, likelihoods.MixedNoise):
|
||||
inference_method = exact_gaussian_inference.ExactGaussianInference()
|
||||
|
|
@ -62,6 +69,7 @@ class GP(Model):
|
|||
print "defaulting to ", inference_method, "for latent function inference"
|
||||
self.inference_method = inference_method
|
||||
|
||||
logger.info("adding kernel and likelihood as parameters")
|
||||
self.add_parameter(self.kern)
|
||||
self.add_parameter(self.likelihood)
|
||||
|
||||
|
|
@ -199,9 +207,9 @@ class GP(Model):
|
|||
if fillcol is not None:
|
||||
kw['fillcol'] = fillcol
|
||||
return models_plots.plot_fit(self, plot_limits, which_data_rows,
|
||||
which_data_ycols, fixed_inputs,
|
||||
levels, samples, fignum, ax, resolution,
|
||||
plot_raw=plot_raw, Y_metadata=Y_metadata,
|
||||
which_data_ycols, fixed_inputs,
|
||||
levels, samples, fignum, ax, resolution,
|
||||
plot_raw=plot_raw, Y_metadata=Y_metadata,
|
||||
data_symbol=data_symbol, **kw)
|
||||
|
||||
def plot(self, plot_limits=None, which_data_rows='all',
|
||||
|
|
@ -250,9 +258,9 @@ class GP(Model):
|
|||
if fillcol is not None:
|
||||
kw['fillcol'] = fillcol
|
||||
return models_plots.plot_fit(self, plot_limits, which_data_rows,
|
||||
which_data_ycols, fixed_inputs,
|
||||
levels, samples, fignum, ax, resolution,
|
||||
plot_raw=plot_raw, Y_metadata=Y_metadata,
|
||||
which_data_ycols, fixed_inputs,
|
||||
levels, samples, fignum, ax, resolution,
|
||||
plot_raw=plot_raw, Y_metadata=Y_metadata,
|
||||
data_symbol=data_symbol, **kw)
|
||||
|
||||
def input_sensitivity(self):
|
||||
|
|
@ -276,5 +284,9 @@ class GP(Model):
|
|||
TODO: valid args
|
||||
"""
|
||||
self.inference_method.on_optimization_start()
|
||||
super(GP, self).optimize(optimizer, start, **kwargs)
|
||||
self.inference_method.on_optimization_end()
|
||||
try:
|
||||
super(GP, self).optimize(optimizer, start, **kwargs)
|
||||
except KeyboardInterrupt:
|
||||
print "KeyboardInterrupt caught, calling on_optimization_end() to round things up"
|
||||
self.inference_method.on_optimization_end()
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class Model(Parameterized):
|
|||
super(Model, self).__init__(name) # Parameterized.__init__(self)
|
||||
self.optimization_runs = []
|
||||
self.sampling_runs = []
|
||||
self.preferred_optimizer = 'scg'
|
||||
self.preferred_optimizer = 'bfgs'
|
||||
|
||||
def log_likelihood(self):
|
||||
raise NotImplementedError, "this needs to be implemented to use the model class"
|
||||
|
|
@ -61,7 +61,7 @@ class Model(Parameterized):
|
|||
on the current machine.
|
||||
|
||||
"""
|
||||
initial_parameters = self._get_params_transformed()
|
||||
initial_parameters = self.optimizer_array.copy()
|
||||
|
||||
if parallel:
|
||||
try:
|
||||
|
|
@ -97,9 +97,9 @@ class Model(Parameterized):
|
|||
|
||||
if len(self.optimization_runs):
|
||||
i = np.argmin([o.f_opt for o in self.optimization_runs])
|
||||
self._set_params_transformed(self.optimization_runs[i].x_opt)
|
||||
self.optimizer_array = self.optimization_runs[i].x_opt
|
||||
else:
|
||||
self._set_params_transformed(initial_parameters)
|
||||
self.optimizer_array = initial_parameters
|
||||
|
||||
def ensure_default_constraints(self, warning=True):
|
||||
"""
|
||||
|
|
@ -118,30 +118,32 @@ class Model(Parameterized):
|
|||
"""
|
||||
The objective function for the given algorithm.
|
||||
|
||||
This function is the true objective, which wants to be minimized.
|
||||
Note that all parameters are already set and in place, so you just need
|
||||
This function is the true objective, which wants to be minimized.
|
||||
Note that all parameters are already set and in place, so you just need
|
||||
to return the objective function here.
|
||||
|
||||
For probabilistic models this is the negative log_likelihood
|
||||
(including the MAP prior), so we return it here. If your model is not
|
||||
probabilistic, just return your objective here!
|
||||
(including the MAP prior), so we return it here. If your model is not
|
||||
probabilistic, just return your objective to minimize here!
|
||||
"""
|
||||
return -float(self.log_likelihood()) - self.log_prior()
|
||||
|
||||
def objective_function_gradients(self):
|
||||
"""
|
||||
The gradients for the objective function for the given algorithm.
|
||||
The gradients are w.r.t. the *negative* objective function, as
|
||||
this framework works with *negative* log-likelihoods as a default.
|
||||
|
||||
You can find the gradient for the parameters in self.gradient at all times.
|
||||
This is the place, where gradients get stored for parameters.
|
||||
|
||||
This function is the true objective, which wants to be minimized.
|
||||
Note that all parameters are already set and in place, so you just need
|
||||
This function is the true objective, which wants to be minimized.
|
||||
Note that all parameters are already set and in place, so you just need
|
||||
to return the gradient here.
|
||||
|
||||
For probabilistic models this is the gradient of the negative log_likelihood
|
||||
(including the MAP prior), so we return it here. If your model is not
|
||||
probabilistic, just return your gradient here!
|
||||
(including the MAP prior), so we return it here. If your model is not
|
||||
probabilistic, just return your *negative* gradient here!
|
||||
"""
|
||||
return -(self._log_likelihood_gradients() + self._log_prior_gradients())
|
||||
|
||||
|
|
@ -157,7 +159,8 @@ class Model(Parameterized):
|
|||
:type x: np.array
|
||||
"""
|
||||
try:
|
||||
self._set_params_transformed(x)
|
||||
# self._set_params_transformed(x)
|
||||
self.optimizer_array = x
|
||||
obj_grads = self._transform_gradients(self.objective_function_gradients())
|
||||
self._fail_count = 0
|
||||
except (LinAlgError, ZeroDivisionError, ValueError):
|
||||
|
|
@ -180,7 +183,7 @@ class Model(Parameterized):
|
|||
:parameter type: np.array
|
||||
"""
|
||||
try:
|
||||
self._set_params_transformed(x)
|
||||
self.optimizer_array = x
|
||||
obj = self.objective_function()
|
||||
self._fail_count = 0
|
||||
except (LinAlgError, ZeroDivisionError, ValueError):
|
||||
|
|
@ -192,7 +195,7 @@ class Model(Parameterized):
|
|||
|
||||
def _objective_grads(self, x):
|
||||
try:
|
||||
self._set_params_transformed(x)
|
||||
self.optimizer_array = x
|
||||
obj_f, obj_grads = self.objective_function(), self._transform_gradients(self.objective_function_gradients())
|
||||
self._fail_count = 0
|
||||
except (LinAlgError, ZeroDivisionError, ValueError):
|
||||
|
|
@ -222,20 +225,24 @@ class Model(Parameterized):
|
|||
if self.size == 0:
|
||||
raise RuntimeError, "Model without parameters cannot be optimized"
|
||||
|
||||
if start == None:
|
||||
start = self.optimizer_array
|
||||
|
||||
if optimizer is None:
|
||||
optimizer = self.preferred_optimizer
|
||||
|
||||
if start == None:
|
||||
start = self._get_params_transformed()
|
||||
|
||||
optimizer = optimization.get_optimizer(optimizer)
|
||||
opt = optimizer(start, model=self, **kwargs)
|
||||
if isinstance(optimizer, optimization.Optimizer):
|
||||
opt = optimizer
|
||||
opt.model = self
|
||||
else:
|
||||
optimizer = optimization.get_optimizer(optimizer)
|
||||
opt = optimizer(start, model=self, **kwargs)
|
||||
|
||||
opt.run(f_fp=self._objective_grads, f=self._objective, fp=self._grads)
|
||||
|
||||
self.optimization_runs.append(opt)
|
||||
|
||||
self._set_params_transformed(opt.x_opt)
|
||||
self.optimizer_array = opt.x_opt
|
||||
|
||||
def optimize_SGD(self, momentum=0.1, learning_rate=0.01, iterations=20, **kwargs):
|
||||
# assert self.Y.shape[1] > 1, "SGD only works with D > 1"
|
||||
|
|
@ -246,7 +253,7 @@ class Model(Parameterized):
|
|||
def _checkgrad(self, target_param=None, verbose=False, step=1e-6, tolerance=1e-3):
|
||||
"""
|
||||
Check the gradient of the ,odel by comparing to a numerical
|
||||
estimate. If the verbose flag is passed, invividual
|
||||
estimate. If the verbose flag is passed, individual
|
||||
components are tested (and printed)
|
||||
|
||||
:param verbose: If True, print a "full" checking of each parameter
|
||||
|
|
@ -260,7 +267,7 @@ class Model(Parameterized):
|
|||
The gradient is considered correct if the ratio of the analytical
|
||||
and numerical gradients is within <tolerance> of unity.
|
||||
"""
|
||||
x = self._get_params_transformed().copy()
|
||||
x = self.optimizer_array.copy()
|
||||
|
||||
if not verbose:
|
||||
# make sure only to test the selected parameters
|
||||
|
|
@ -270,8 +277,8 @@ class Model(Parameterized):
|
|||
transformed_index = self._raveled_index_for(target_param)
|
||||
if self._has_fixes():
|
||||
indices = np.r_[:self.size]
|
||||
which = (transformed_index[:,None]==indices[self._fixes_][None,:]).nonzero()
|
||||
transformed_index = (indices-(~self._fixes_).cumsum())[transformed_index[which[0]]]
|
||||
which = (transformed_index[:, None] == indices[self._fixes_][None, :]).nonzero()
|
||||
transformed_index = (indices - (~self._fixes_).cumsum())[transformed_index[which[0]]]
|
||||
|
||||
if transformed_index.size == 0:
|
||||
print "No free parameters to check"
|
||||
|
|
@ -290,7 +297,7 @@ class Model(Parameterized):
|
|||
gradient = gradient[transformed_index]
|
||||
|
||||
denominator = (2 * np.dot(dx, gradient))
|
||||
global_ratio = (f1 - f2) / np.where(denominator==0., 1e-32, denominator)
|
||||
global_ratio = (f1 - f2) / np.where(denominator == 0., 1e-32, denominator)
|
||||
global_diff = np.abs(f1 - f2) < tolerance and np.allclose(gradient, 0, atol=tolerance)
|
||||
if global_ratio is np.nan:
|
||||
global_ratio = 0
|
||||
|
|
@ -319,10 +326,10 @@ class Model(Parameterized):
|
|||
param_index = self._raveled_index_for(target_param)
|
||||
if self._has_fixes():
|
||||
indices = np.r_[:self.size]
|
||||
which = (param_index[:,None]==indices[self._fixes_][None,:]).nonzero()
|
||||
which = (param_index[:, None] == indices[self._fixes_][None, :]).nonzero()
|
||||
param_index = param_index[which[0]]
|
||||
transformed_index = (indices-(~self._fixes_).cumsum())[param_index]
|
||||
#print param_index, transformed_index
|
||||
transformed_index = (indices - (~self._fixes_).cumsum())[param_index]
|
||||
# print param_index, transformed_index
|
||||
else:
|
||||
transformed_index = param_index
|
||||
|
||||
|
|
@ -340,9 +347,9 @@ class Model(Parameterized):
|
|||
xx[xind] -= 2.*step
|
||||
f2 = self._objective(xx)
|
||||
numerical_gradient = (f1 - f2) / (2 * step)
|
||||
if np.all(gradient[xind]==0): ratio = (f1-f2) == gradient[xind]
|
||||
if np.all(gradient[xind] == 0): ratio = (f1 - f2) == gradient[xind]
|
||||
else: ratio = (f1 - f2) / (2 * step * gradient[xind])
|
||||
difference = np.abs((f1 - f2) / 2 / step - gradient[xind])
|
||||
difference = np.abs(numerical_gradient - gradient[xind])
|
||||
|
||||
if (np.abs(1. - ratio) < tolerance) or np.abs(difference) < tolerance:
|
||||
formatted_name = "\033[92m {0} \033[0m".format(names[nind])
|
||||
|
|
@ -358,7 +365,7 @@ class Model(Parameterized):
|
|||
grad_string = "{0:<{c0}}|{1:^{c1}}|{2:^{c2}}|{3:^{c3}}|{4:^{c4}}".format(formatted_name, r, d, g, ng, c0=cols[0] + 9, c1=cols[1], c2=cols[2], c3=cols[3], c4=cols[4])
|
||||
print grad_string
|
||||
|
||||
self._set_params_transformed(x)
|
||||
self.optimizer_array = x
|
||||
return ret
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -77,8 +77,18 @@ class ObserverList(object):
|
|||
self._poc.insert(ins, (priority, weakref.ref(observer), callble))
|
||||
|
||||
def __str__(self):
|
||||
from . import ObsAr, Param
|
||||
from parameter_core import Parameterizable
|
||||
ret = []
|
||||
curr_p = None
|
||||
|
||||
def frmt(o):
|
||||
if isinstance(o, ObsAr):
|
||||
return 'ObsArr <{}>'.format(hex(id(o)))
|
||||
elif isinstance(o, (Param,Parameterizable)):
|
||||
return '{}'.format(o.hierarchy_name())
|
||||
else:
|
||||
return repr(o)
|
||||
for p, o, c in self:
|
||||
curr = ''
|
||||
if curr_p != p:
|
||||
|
|
@ -87,8 +97,9 @@ class ObserverList(object):
|
|||
else: curr_pre = " "*len(pre)
|
||||
curr_p = p
|
||||
curr += curr_pre
|
||||
ret.append(curr + ", ".join(map(repr, [o,c])))
|
||||
return '\n'.join(ret)
|
||||
|
||||
ret.append(curr + ", ".join([frmt(o), str(c)]))
|
||||
return '\n'.join(ret)
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -30,16 +30,22 @@ class ObsAr(np.ndarray, Pickleable, Observable):
|
|||
def __array_wrap__(self, out_arr, context=None):
|
||||
return out_arr.view(np.ndarray)
|
||||
|
||||
def _setup_observers(self):
|
||||
# do not setup anything, as observable arrays do not have default observers
|
||||
pass
|
||||
|
||||
def copy(self):
|
||||
from lists_and_dicts import ObserverList
|
||||
memo = {}
|
||||
memo[id(self)] = self
|
||||
memo[id(self.observers)] = ObserverList()
|
||||
return self.__deepcopy__(memo)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
s = self.__new__(self.__class__, input_array=self.view(np.ndarray).copy())
|
||||
memo[id(self)] = s
|
||||
import copy
|
||||
s.__dict__.update(copy.deepcopy(self.__dict__, memo))
|
||||
Pickleable.__setstate__(s, copy.deepcopy(self.__getstate__(), memo))
|
||||
return s
|
||||
|
||||
def __reduce__(self):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import itertools
|
||||
import numpy
|
||||
np = numpy
|
||||
from parameter_core import Parameterizable, adjust_name_for_printing
|
||||
from parameter_core import Parameterizable, adjust_name_for_printing, Pickleable
|
||||
from observable_array import ObsAr
|
||||
|
||||
###### printing
|
||||
|
|
@ -173,36 +173,6 @@ class Param(Parameterizable, ObsAr):
|
|||
def _ensure_fixes(self):
|
||||
if not self._has_fixes(): self._fixes_ = numpy.ones(self._realsize_, dtype=bool)
|
||||
|
||||
#===========================================================================
|
||||
# parameterizable
|
||||
#===========================================================================
|
||||
def traverse(self, visit, *args, **kwargs):
|
||||
"""
|
||||
Traverse the hierarchy performing visit(self, *args, **kwargs) at every node passed by.
|
||||
See "visitor pattern" in literature. This is implemented in pre-order fashion.
|
||||
|
||||
This will function will just call visit on self, as Param are leaf nodes.
|
||||
"""
|
||||
self.__visited = True
|
||||
visit(self, *args, **kwargs)
|
||||
self.__visited = False
|
||||
|
||||
def traverse_parents(self, visit, *args, **kwargs):
|
||||
"""
|
||||
Traverse the hierarchy upwards, visiting all parents and their children, except self.
|
||||
See "visitor pattern" in literature. This is implemented in pre-order fashion.
|
||||
|
||||
Example:
|
||||
|
||||
parents = []
|
||||
self.traverse_parents(parents.append)
|
||||
print parents
|
||||
"""
|
||||
if self.has_parent():
|
||||
self.__visited = True
|
||||
self._parent_._traverse_parents(visit, *args, **kwargs)
|
||||
self.__visited = False
|
||||
|
||||
#===========================================================================
|
||||
# Convenience
|
||||
#===========================================================================
|
||||
|
|
@ -217,14 +187,24 @@ class Param(Parameterizable, ObsAr):
|
|||
#===========================================================================
|
||||
# Pickling and copying
|
||||
#===========================================================================
|
||||
def copy(self):
|
||||
return Parameterizable.copy(self, which=self)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
s = self.__new__(self.__class__, name=self.name, input_array=self.view(numpy.ndarray).copy())
|
||||
memo[id(self)] = s
|
||||
memo[id(self)] = s
|
||||
import copy
|
||||
s.__dict__.update(copy.deepcopy(self.__dict__, memo))
|
||||
Pickleable.__setstate__(s, copy.deepcopy(self.__getstate__(), memo))
|
||||
return s
|
||||
|
||||
|
||||
def _setup_observers(self):
|
||||
"""
|
||||
Setup the default observers
|
||||
|
||||
1: pass through to parent, if present
|
||||
"""
|
||||
if self.has_parent():
|
||||
self.add_observer(self._parent_, self._parent_._pass_through_notify_observers, -np.inf)
|
||||
|
||||
#===========================================================================
|
||||
# Printing -> done
|
||||
#===========================================================================
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ Observable Pattern for patameterization
|
|||
from transformations import Logexp, NegativeLogexp, Logistic, __fixed__, FIXED, UNFIXED
|
||||
import numpy as np
|
||||
import re
|
||||
import logging
|
||||
|
||||
__updated__ = '2014-05-20'
|
||||
__updated__ = '2014-05-21'
|
||||
|
||||
class HierarchyError(Exception):
|
||||
"""
|
||||
|
|
@ -49,7 +50,6 @@ class Observable(object):
|
|||
as an observer. Every time the observable changes, it sends a notification with
|
||||
self as only argument to all its observers.
|
||||
"""
|
||||
_updated = True
|
||||
_updates = True
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Observable, self).__init__()
|
||||
|
|
@ -58,26 +58,32 @@ class Observable(object):
|
|||
|
||||
@property
|
||||
def updates(self):
|
||||
self._updates = self._highest_parent_._updates
|
||||
p = getattr(self, '_highest_parent_', None)
|
||||
if p is not None:
|
||||
self._updates = p._updates
|
||||
return self._updates
|
||||
|
||||
@updates.setter
|
||||
def updates(self, ups):
|
||||
assert isinstance(ups, bool), "updates are either on (True) or off (False)"
|
||||
self._highest_parent_._updates = ups
|
||||
p = getattr(self, '_highest_parent_', None)
|
||||
if p is not None:
|
||||
p._updates = ups
|
||||
else:
|
||||
self._updates = ups
|
||||
if ups:
|
||||
self._trigger_params_changed()
|
||||
|
||||
def add_observer(self, observer, callble, priority=0):
|
||||
"""
|
||||
Add an observer `observer` with the callback `callble`
|
||||
Add an observer `observer` with the callback `callble`
|
||||
and priority `priority` to this observers list.
|
||||
"""
|
||||
self.observers.add(priority, observer, callble)
|
||||
|
||||
def remove_observer(self, observer, callble=None):
|
||||
"""
|
||||
Either (if callble is None) remove all callables,
|
||||
Either (if callble is None) remove all callables,
|
||||
which were added alongside observer,
|
||||
or remove callable `callble` which was added alongside
|
||||
the observer `observer`.
|
||||
|
|
@ -86,7 +92,7 @@ class Observable(object):
|
|||
for poc in self.observers:
|
||||
_, obs, clble = poc
|
||||
if callble is not None:
|
||||
if (obs == observer) and (callble == clble):
|
||||
if (obs is observer) and (callble == clble):
|
||||
to_remove.append(poc)
|
||||
else:
|
||||
if obs is observer:
|
||||
|
|
@ -172,6 +178,7 @@ class Pickleable(object):
|
|||
"""
|
||||
def __init__(self, *a, **kw):
|
||||
super(Pickleable, self).__init__()
|
||||
|
||||
#===========================================================================
|
||||
# Pickling operations
|
||||
#===========================================================================
|
||||
|
|
@ -192,37 +199,46 @@ class Pickleable(object):
|
|||
#===========================================================================
|
||||
# copy and pickling
|
||||
#===========================================================================
|
||||
def copy(self):
|
||||
def copy(self, memo=None, which=None):
|
||||
"""
|
||||
Returns a (deep) copy of the current parameter handle.
|
||||
Returns a (deep) copy of the current parameter handle.
|
||||
|
||||
All connections to parents of the copy will be cut.
|
||||
|
||||
:param dict memo: memo for deepcopy
|
||||
:param Parameterized which: parameterized object which started the copy process [default: self]
|
||||
"""
|
||||
#raise NotImplementedError, "Copy is not yet implemented, TODO: Observable hierarchy"
|
||||
if memo is None:
|
||||
memo = {}
|
||||
import copy
|
||||
memo = {}
|
||||
# the next part makes sure that we do not include parents in any form:
|
||||
parents = []
|
||||
self.traverse_parents(parents.append) # collect parents
|
||||
if which is None:
|
||||
which = self
|
||||
which.traverse_parents(parents.append) # collect parents
|
||||
for p in parents:
|
||||
memo[id(p)] = None # set all parents to be None, so they will not be copied
|
||||
memo[id(self.gradient)] = None # reset the gradient
|
||||
memo[id(self.param_array)] = None # and param_array
|
||||
memo[id(self._fixes_)] = None # fixes have to be reset, as this is now highest parent
|
||||
c = copy.deepcopy(self, memo) # and start the copy
|
||||
c._parent_index_ = None
|
||||
return c
|
||||
if not memo.has_key(id(p)):memo[id(p)] = None # set all parents to be None, so they will not be copied
|
||||
if not memo.has_key(id(self.gradient)):memo[id(self.gradient)] = None # reset the gradient
|
||||
if not memo.has_key(id(self._fixes_)):memo[id(self._fixes_)] = None # fixes have to be reset, as this is now highest parent
|
||||
copy = copy.deepcopy(self, memo) # and start the copy
|
||||
copy._parent_index_ = None
|
||||
copy._trigger_params_changed()
|
||||
return copy
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
s = self.__new__(self.__class__) # fresh instance
|
||||
memo[id(self)] = s # be sure to break all cycles --> self is already done
|
||||
import copy
|
||||
s.__dict__.update(copy.deepcopy(self.__dict__, memo)) # standard copy
|
||||
s.__setstate__(copy.deepcopy(self.__getstate__(), memo)) # standard copy
|
||||
return s
|
||||
|
||||
def __getstate__(self):
|
||||
ignore_list = ['_param_array_', # parameters get set from bottom to top
|
||||
'_gradient_array_', # as well as gradients
|
||||
'_optimizer_copy_',
|
||||
'logger',
|
||||
'observers',
|
||||
'_fixes_', # and fixes
|
||||
'_Cacher_wrap__cachers', # never pickle cachers
|
||||
]
|
||||
|
|
@ -231,10 +247,14 @@ class Pickleable(object):
|
|||
if k not in ignore_list:
|
||||
dc[k] = v
|
||||
return dc
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
return self
|
||||
from lists_and_dicts import ObserverList
|
||||
self.observers = ObserverList()
|
||||
self._setup_observers()
|
||||
self._optimizer_copy_transformed = False
|
||||
|
||||
|
||||
class Gradcheckable(Pickleable, Parentable):
|
||||
"""
|
||||
|
|
@ -261,7 +281,7 @@ class Gradcheckable(Pickleable, Parentable):
|
|||
"""
|
||||
if self.has_parent():
|
||||
return self._highest_parent_._checkgrad(self, verbose=verbose, step=step, tolerance=tolerance)
|
||||
return self._checkgrad(self[''], verbose=verbose, step=step, tolerance=tolerance)
|
||||
return self._checkgrad(self, verbose=verbose, step=step, tolerance=tolerance)
|
||||
|
||||
def _checkgrad(self, param, verbose=0, step=1e-6, tolerance=1e-3):
|
||||
"""
|
||||
|
|
@ -352,8 +372,9 @@ class Indexable(Nameable, Observable):
|
|||
basically just sums up the parameter sizes which come before param.
|
||||
"""
|
||||
if param.has_parent():
|
||||
if param._parent_._get_original(param) in self.parameters:
|
||||
return self._param_slices_[param._parent_._get_original(param)._parent_index_].start
|
||||
p = param._parent_._get_original(param)
|
||||
if p in self.parameters:
|
||||
return reduce(lambda a,b: a + b.size, self.parameters[:p._parent_index_], 0)
|
||||
return self._offset_for(param._parent_) + param._parent_._offset_for(param)
|
||||
return 0
|
||||
|
||||
|
|
@ -387,7 +408,6 @@ class Indexable(Nameable, Observable):
|
|||
if value is not None:
|
||||
self[:] = value
|
||||
|
||||
#index = self._raveled_index()
|
||||
index = self.unconstrain()
|
||||
index = self._add_to_index_operations(self.constraints, index, __fixed__, warning)
|
||||
self._highest_parent_._set_fixed(self, index)
|
||||
|
|
@ -423,12 +443,12 @@ class Indexable(Nameable, Observable):
|
|||
if np.all(self._fixes_): self._fixes_ = None # ==UNFIXED
|
||||
|
||||
def _connect_fixes(self):
|
||||
from ties_and_remappings import Tie
|
||||
self._ensure_fixes()
|
||||
[np.put(self._fixes_, ind, FIXED) for c, ind in self.constraints.iteritems()
|
||||
if c == __fixed__ or isinstance(c,Tie)]
|
||||
if np.all(self._fixes_): self._fixes_ = None
|
||||
if self.constraints[__fixed__]==0:
|
||||
fixed_indices = self.constraints[__fixed__]
|
||||
if fixed_indices.size > 0:
|
||||
self._ensure_fixes()
|
||||
self._fixes_[fixed_indices] = FIXED
|
||||
else:
|
||||
self._fixes_ = None
|
||||
del self.constraints[__fixed__]
|
||||
|
||||
#===========================================================================
|
||||
|
|
@ -495,32 +515,6 @@ class Indexable(Nameable, Observable):
|
|||
#===========================================================================
|
||||
# Constrain operations -> done
|
||||
#===========================================================================
|
||||
|
||||
def tie(self, name):
|
||||
from ties_and_remappings import Tie
|
||||
#remove any constraints
|
||||
old_const = [c for c in self.constraints.properties() if not isinstance(c,Tie)]
|
||||
self.unconstrain()
|
||||
|
||||
#see if a tie exists with that name
|
||||
if name in self._highest_parent_.ties:
|
||||
t = self._highest_parent_.ties[name]
|
||||
else:
|
||||
#create a tie object
|
||||
value = np.atleast_1d(self.param_array)[0]*1
|
||||
t = Tie(value=value, name=name)
|
||||
|
||||
#add the new tie object to the global index
|
||||
self._highest_parent_.ties[name] = t
|
||||
self._highest_parent_.add_parameter(t)
|
||||
|
||||
#constrain the tie as we were constrained
|
||||
if len(old_const)>0:
|
||||
t.constrain(old_const[0])
|
||||
|
||||
self.constraints.add(t, self._raveled_index())
|
||||
t.add_tied_parameter(self)
|
||||
self._highest_parent_._connect_fixes()
|
||||
|
||||
def constrain(self, transform, warning=True, trigger_parent=True):
|
||||
"""
|
||||
|
|
@ -638,48 +632,78 @@ class OptimizationHandlable(Indexable):
|
|||
"""
|
||||
This enables optimization handles on an Object as done in GPy 0.4.
|
||||
|
||||
`..._transformed`: make sure the transformations and constraints etc are handled
|
||||
`..._optimizer_copy_transformed`: make sure the transformations and constraints etc are handled
|
||||
"""
|
||||
def __init__(self, name, default_constraint=None, *a, **kw):
|
||||
super(OptimizationHandlable, self).__init__(name, default_constraint=default_constraint, *a, **kw)
|
||||
self._optimizer_copy_ = None
|
||||
self._optimizer_copy_transformed = False
|
||||
|
||||
def _get_params_transformed(self):
|
||||
# transformed parameters (apply un-transformation rules)
|
||||
p = self.param_array.copy()
|
||||
from ties_and_remappings import Tie
|
||||
[np.put(p, ind, c.finv(p[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__ and not isinstance(c,Tie)]
|
||||
if self.has_parent() and self.constraints[__fixed__].size != 0:
|
||||
fixes = np.ones(self.size).astype(bool)
|
||||
[np.put(fixes,ind,FIXED) for c, ind in self.constraints.iteritems()
|
||||
if c == __fixed__ or isinstance(c,Tie)]
|
||||
return p[fixes]
|
||||
elif self._has_fixes():
|
||||
return p[self._fixes_]
|
||||
return p
|
||||
#===========================================================================
|
||||
# Optimizer copy
|
||||
#===========================================================================
|
||||
@property
|
||||
def optimizer_array(self):
|
||||
"""
|
||||
Array for the optimizer to work on.
|
||||
This array always lives in the space for the optimizer.
|
||||
Thus, it is untransformed, going from Transformations.
|
||||
|
||||
def _set_params_transformed(self, p):
|
||||
Setting this array, will make sure the transformed parameters for this model
|
||||
will be set accordingly. It has to be set with an array, retrieved from
|
||||
this method, as e.g. fixing will resize the array.
|
||||
|
||||
The optimizer should only interfere with this array, such that transofrmations
|
||||
are secured.
|
||||
"""
|
||||
Set parameters p, but make sure they get transformed before setting.
|
||||
This means, the optimizer sees p, whereas the model sees transformed(p),
|
||||
such that, the parameters the model sees are in the right domain.
|
||||
"""
|
||||
from ties_and_remappings import Tie
|
||||
if not(p is self.param_array):
|
||||
if self.__dict__.get('_optimizer_copy_', None) is None or self.size != self._optimizer_copy_.size:
|
||||
self._optimizer_copy_ = np.empty(self.size)
|
||||
|
||||
if not self._optimizer_copy_transformed:
|
||||
self._optimizer_copy_.flat = self.param_array.flat
|
||||
[np.put(self._optimizer_copy_, ind, c.finv(self.param_array[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__]
|
||||
if self.has_parent() and self.constraints[__fixed__].size != 0:
|
||||
fixes = np.ones(self.size).astype(bool)
|
||||
# fixes[self.constraints[__fixed__]] = FIXED
|
||||
for c, ind in self.constraints.iteritems():
|
||||
if c == __fixed__ or isinstance(c,Tie):
|
||||
fixes[ind] = FIXED
|
||||
self.param_array.flat[fixes] = p
|
||||
elif self._has_fixes(): self.param_array.flat[self._fixes_] = p
|
||||
else: self.param_array.flat = p
|
||||
[np.put(self.param_array, ind, c.f(self.param_array.flat[ind]))
|
||||
for c, ind in self.constraints.iteritems() if c != __fixed__ and not isinstance(c,Tie)]
|
||||
[np.put(self.param_array, ind, c.val)
|
||||
for c, ind in self.constraints.iteritems() if isinstance(c,Tie)]
|
||||
fixes[self.constraints[__fixed__]] = FIXED
|
||||
return self._optimizer_copy_[fixes]
|
||||
elif self._has_fixes():
|
||||
return self._optimizer_copy_[self._fixes_]
|
||||
self._optimizer_copy_transformed = True
|
||||
|
||||
return self._optimizer_copy_
|
||||
|
||||
@optimizer_array.setter
|
||||
def optimizer_array(self, p):
|
||||
"""
|
||||
Make sure the optimizer copy does not get touched, thus, we only want to
|
||||
set the values *inside* not the array itself.
|
||||
|
||||
Also we want to update param_array in here.
|
||||
"""
|
||||
f = None
|
||||
if self.has_parent() and self.constraints[__fixed__].size != 0:
|
||||
f = np.ones(self.size).astype(bool)
|
||||
f[self.constraints[__fixed__]] = FIXED
|
||||
elif self._has_fixes():
|
||||
f = self._fixes_
|
||||
if f is None:
|
||||
self.param_array.flat = p
|
||||
[np.put(self.param_array, ind, c.f(self.param_array.flat[ind]))
|
||||
for c, ind in self.constraints.iteritems() if c != __fixed__]
|
||||
else:
|
||||
self.param_array.flat[f] = p
|
||||
[np.put(self.param_array, ind[f[ind]], c.f(self.param_array.flat[ind[f[ind]]]))
|
||||
for c, ind in self.constraints.iteritems() if c != __fixed__]
|
||||
|
||||
self._optimizer_copy_transformed = False
|
||||
self._trigger_params_changed()
|
||||
|
||||
def _get_params_transformed(self):
|
||||
raise DeprecationWarning, "_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!"
|
||||
#
|
||||
def _set_params_transformed(self, p):
|
||||
raise DeprecationWarning, "_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!"
|
||||
|
||||
def _trigger_params_changed(self, trigger_parent=True):
|
||||
"""
|
||||
First tell all children to update,
|
||||
|
|
@ -687,7 +711,7 @@ class OptimizationHandlable(Indexable):
|
|||
|
||||
If trigger_parent is True, we will tell the parent, otherwise not.
|
||||
"""
|
||||
[p._trigger_params_changed(trigger_parent=False) for p in self.parameters]
|
||||
[p._trigger_params_changed(trigger_parent=False) for p in self.parameters if not p.is_fixed]
|
||||
self.notify_observers(None, None if trigger_parent else -np.inf)
|
||||
|
||||
def _size_transformed(self):
|
||||
|
|
@ -702,11 +726,7 @@ class OptimizationHandlable(Indexable):
|
|||
Transform the gradients by multiplying the gradient factor for each
|
||||
constraint to it.
|
||||
"""
|
||||
if self.has_parent():
|
||||
return g
|
||||
from ties_and_remappings import Tie
|
||||
[np.put(g, self._raveled_index_for(c.val), g[i].sum()) for c, i in self.constraints.iteritems() if isinstance(c,Tie)]
|
||||
[np.put(g, i, g[i] * c.gradfactor(self.param_array[i])) for c, i in self.constraints.iteritems() if c != __fixed__ and not isinstance(c,Tie)]
|
||||
[np.put(g, i, g[i] * c.gradfactor(self.param_array[i])) for c, i in self.constraints.iteritems() if c != __fixed__]
|
||||
if self._has_fixes(): return g[self._fixes_]
|
||||
return g
|
||||
|
||||
|
|
@ -746,7 +766,7 @@ class OptimizationHandlable(Indexable):
|
|||
#===========================================================================
|
||||
# Randomizeable
|
||||
#===========================================================================
|
||||
def randomize(self, rand_gen=np.random.normal, loc=0, scale=1, *args, **kwargs):
|
||||
def randomize(self, rand_gen=np.random.normal, *args, **kwargs):
|
||||
"""
|
||||
Randomize the model.
|
||||
Make this draw from the prior if one exists, else draw from given random generator
|
||||
|
|
@ -757,10 +777,10 @@ class OptimizationHandlable(Indexable):
|
|||
:param args, kwargs: will be passed through to random number generator
|
||||
"""
|
||||
# first take care of all parameters (from N(0,1))
|
||||
x = rand_gen(loc=loc, scale=scale, size=self._size_transformed(), *args, **kwargs)
|
||||
x = rand_gen(size=self._size_transformed(), *args, **kwargs)
|
||||
# now draw from prior where possible
|
||||
[np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.iteritems() if not p is None]
|
||||
self._set_params_transformed(x) # makes sure all of the tied parameters get the same init (since there's only one prior object...)
|
||||
self.optimizer_array = x # makes sure all of the tied parameters get the same init (since there's only one prior object...)
|
||||
|
||||
#===========================================================================
|
||||
# For shared memory arrays. This does nothing in Param, but sets the memory
|
||||
|
|
@ -788,6 +808,11 @@ class OptimizationHandlable(Indexable):
|
|||
1.) connect param_array of children to self.param_array
|
||||
2.) tell all children to propagate further
|
||||
"""
|
||||
if self.param_array.size != self.size:
|
||||
self._param_array_ = np.empty(self.size, dtype=np.float64)
|
||||
if self.gradient.size != self.size:
|
||||
self._gradient_array_ = np.empty(self.size, dtype=np.float64)
|
||||
|
||||
pi_old_size = 0
|
||||
for pi in self.parameters:
|
||||
pislice = slice(pi_old_size, pi_old_size + pi.size)
|
||||
|
|
@ -801,6 +826,9 @@ class OptimizationHandlable(Indexable):
|
|||
pi._propagate_param_grad(parray[pislice], garray[pislice])
|
||||
pi_old_size += pi.size
|
||||
|
||||
def _connect_parameters(self):
|
||||
pass
|
||||
|
||||
class Parameterizable(OptimizationHandlable):
|
||||
"""
|
||||
A parameterisable class.
|
||||
|
|
@ -819,26 +847,48 @@ class Parameterizable(OptimizationHandlable):
|
|||
self.parameters = ArrayList()
|
||||
self._param_array_ = None
|
||||
self._added_names_ = set()
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.__visited = False # for traversing in reverse order we need to know if we were here already
|
||||
self.ties = {}
|
||||
|
||||
@property
|
||||
def param_array(self):
|
||||
"""
|
||||
Array representing the parameters of this class.
|
||||
There is only one copy of all parameters in memory, two during optimization.
|
||||
|
||||
!WARNING!: setting the parameter array MUST always be done in memory:
|
||||
m.param_array[:] = m_copy.param_array
|
||||
"""
|
||||
if self.__dict__.get('_param_array_', None) is None:
|
||||
self._param_array_ = np.empty(self.size, dtype=np.float64)
|
||||
return self._param_array_
|
||||
|
||||
@property
|
||||
def unfixed_param_array(self):
|
||||
"""
|
||||
Array representing the parameters of this class.
|
||||
There is only one copy of all parameters in memory, two during optimization.
|
||||
|
||||
!WARNING!: setting the parameter array MUST always be done in memory:
|
||||
m.param_array[:] = m_copy.param_array
|
||||
"""
|
||||
if self.__dict__.get('_param_array_', None) is None:
|
||||
self._param_array_ = np.empty(self.size, dtype=np.float64)
|
||||
|
||||
if self.constraints[__fixed__].size !=0:
|
||||
fixes = np.ones(self.size).astype(bool)
|
||||
fixes[self.constraints[__fixed__]] = FIXED
|
||||
return self._param_array_[fixes]
|
||||
else:
|
||||
return self._param_array_
|
||||
|
||||
@param_array.setter
|
||||
def param_array(self, arr):
|
||||
self._param_array_ = arr
|
||||
|
||||
def traverse(self, visit, *args, **kwargs):
|
||||
"""
|
||||
Traverse the hierarchy performing visit(self, *args, **kwargs)
|
||||
Traverse the hierarchy performing visit(self, *args, **kwargs)
|
||||
at every node passed by downwards. This function includes self!
|
||||
|
||||
See "visitor pattern" in literature. This is implemented in pre-order fashion.
|
||||
|
|
@ -930,14 +980,33 @@ class Parameterizable(OptimizationHandlable):
|
|||
self._remove_parameter_name(None, old_name)
|
||||
self._add_parameter_name(param)
|
||||
|
||||
def __setstate__(self, state):
|
||||
super(Parameterizable, self).__setstate__(state)
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
return self
|
||||
|
||||
#===========================================================================
|
||||
# notification system
|
||||
#===========================================================================
|
||||
def _parameters_changed_notification(self, me, which=None):
|
||||
"""
|
||||
In parameterizable we just need to make sure, that the next call to optimizer_array
|
||||
will update the optimizer_array to the latest parameters
|
||||
"""
|
||||
self._optimizer_copy_transformed = False # tells the optimizer array to update on next request
|
||||
self.parameters_changed()
|
||||
def _pass_through_notify_observers(self, me, which=None):
|
||||
self.notify_observers(which=which)
|
||||
def _setup_observers(self):
|
||||
"""
|
||||
Setup the default observers
|
||||
|
||||
1: parameters_changed_notify
|
||||
2: pass through to parent, if present
|
||||
"""
|
||||
self.add_observer(self, self._parameters_changed_notification, -100)
|
||||
if self.has_parent():
|
||||
self.add_observer(self._parent_, self._parent_._pass_through_notify_observers, -np.inf)
|
||||
#===========================================================================
|
||||
# From being parentable, we have to define the parent_change notification
|
||||
#===========================================================================
|
||||
|
|
@ -956,4 +1025,3 @@ class Parameterizable(OptimizationHandlable):
|
|||
updates get passed through. See :py:function:``GPy.core.param.Observable.add_observer``
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,23 @@ from re import compile, _pattern_type
|
|||
from param import ParamConcatenation
|
||||
from parameter_core import HierarchyError, Parameterizable, adjust_name_for_printing
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger("parameters changed meta")
|
||||
|
||||
class ParametersChangedMeta(type):
|
||||
def __call__(self, *args, **kw):
|
||||
instance = super(ParametersChangedMeta, self).__call__(*args, **kw)
|
||||
instance.parameters_changed()
|
||||
return instance
|
||||
self._in_init_ = True
|
||||
#import ipdb;ipdb.set_trace()
|
||||
self = super(ParametersChangedMeta, self).__call__(*args, **kw)
|
||||
logger.debug("finished init")
|
||||
self._in_init_ = False
|
||||
logger.debug("connecting parameters")
|
||||
self._highest_parent_._connect_parameters()
|
||||
self._highest_parent_._notify_parent_change()
|
||||
self._highest_parent_._connect_fixes()
|
||||
logger.debug("calling parameters changed")
|
||||
self.parameters_changed()
|
||||
return self
|
||||
|
||||
class Parameterized(Parameterizable):
|
||||
"""
|
||||
|
|
@ -57,21 +69,19 @@ class Parameterized(Parameterizable):
|
|||
and concatenate them. Printing m[''] will result in printing of all parameters in detail.
|
||||
"""
|
||||
#===========================================================================
|
||||
# Metaclass for parameters changed after init.
|
||||
# Metaclass for parameters changed after init.
|
||||
# This makes sure, that parameters changed will always be called after __init__
|
||||
# **Never** call parameters_changed() yourself
|
||||
# **Never** call parameters_changed() yourself
|
||||
__metaclass__ = ParametersChangedMeta
|
||||
#===========================================================================
|
||||
def __init__(self, name=None, parameters=[], *a, **kw):
|
||||
super(Parameterized, self).__init__(name=name, *a, **kw)
|
||||
self._in_init_ = True
|
||||
self.size = sum(p.size for p in self.parameters)
|
||||
self.add_observer(self, self._parameters_changed_notification, -100)
|
||||
if not self._has_fixes():
|
||||
self._fixes_ = None
|
||||
self._param_slices_ = []
|
||||
self._connect_parameters()
|
||||
del self._in_init_
|
||||
#self._connect_parameters()
|
||||
self.add_parameters(*parameters)
|
||||
|
||||
def build_pydot(self, G=None):
|
||||
|
|
@ -125,6 +135,9 @@ class Parameterized(Parameterizable):
|
|||
param._parent_.remove_parameter(param)
|
||||
# make sure the size is set
|
||||
if index is None:
|
||||
start = sum(p.size for p in self.parameters)
|
||||
self.constraints.shift_right(start, param.size)
|
||||
self.priors.shift_right(start, param.size)
|
||||
self.constraints.update(param.constraints, self.size)
|
||||
self.priors.update(param.priors, self.size)
|
||||
self.parameters.append(param)
|
||||
|
|
@ -143,14 +156,16 @@ class Parameterized(Parameterizable):
|
|||
parent.size += param.size
|
||||
parent = parent._parent_
|
||||
|
||||
self._connect_parameters()
|
||||
if not self._in_init_:
|
||||
self._connect_parameters()
|
||||
self._notify_parent_change()
|
||||
|
||||
self._highest_parent_._connect_parameters(ignore_added_names=_ignore_added_names)
|
||||
self._highest_parent_._notify_parent_change()
|
||||
self._highest_parent_._connect_fixes()
|
||||
self._highest_parent_._connect_parameters(ignore_added_names=_ignore_added_names)
|
||||
self._highest_parent_._notify_parent_change()
|
||||
self._highest_parent_._connect_fixes()
|
||||
|
||||
else:
|
||||
raise HierarchyError, """Parameter exists already and no copy made"""
|
||||
raise HierarchyError, """Parameter exists already, try making a copy"""
|
||||
|
||||
|
||||
def add_parameters(self, *parameters):
|
||||
|
|
@ -198,26 +213,28 @@ class Parameterized(Parameterizable):
|
|||
# no parameters for this class
|
||||
return
|
||||
if self.param_array.size != self.size:
|
||||
self.param_array = np.empty(self.size, dtype=np.float64)
|
||||
self._param_array_ = np.empty(self.size, dtype=np.float64)
|
||||
if self.gradient.size != self.size:
|
||||
self._gradient_array_ = np.empty(self.size, dtype=np.float64)
|
||||
|
||||
old_size = 0
|
||||
self._param_slices_ = []
|
||||
for i, p in enumerate(self.parameters):
|
||||
if not p.param_array.flags['C_CONTIGUOUS']:
|
||||
raise ValueError, "This should not happen! Please write an email to the developers with the code, which reproduces this error. All parameter arrays must be C_CONTIGUOUS"
|
||||
|
||||
p._parent_ = self
|
||||
p._parent_index_ = i
|
||||
|
||||
pslice = slice(old_size, old_size + p.size)
|
||||
|
||||
# first connect all children
|
||||
p._propagate_param_grad(self.param_array[pslice], self.gradient_full[pslice])
|
||||
|
||||
# then connect children to self
|
||||
self.param_array[pslice] = p.param_array.flat # , requirements=['C', 'W']).ravel(order='C')
|
||||
self.gradient_full[pslice] = p.gradient_full.flat # , requirements=['C', 'W']).ravel(order='C')
|
||||
|
||||
if not p.param_array.flags['C_CONTIGUOUS']:
|
||||
raise ValueError, "This should not happen! Please write an email to the developers with the code, which reproduces this error. All parameter arrays must be C_CONTIGUOUS"
|
||||
|
||||
p.param_array.data = self.param_array[pslice].data
|
||||
p.gradient_full.data = self.gradient_full[pslice].data
|
||||
|
||||
|
|
@ -292,12 +309,16 @@ class Parameterized(Parameterizable):
|
|||
except Exception as e:
|
||||
print "WARNING: caught exception {!s}, trying to continue".format(e)
|
||||
|
||||
def copy(self):
|
||||
c = super(Parameterized, self).copy()
|
||||
c._connect_parameters()
|
||||
c._connect_fixes()
|
||||
c._notify_parent_change()
|
||||
return c
|
||||
def copy(self, memo=None):
|
||||
if memo is None:
|
||||
memo = {}
|
||||
memo[id(self.optimizer_array)] = None # and param_array
|
||||
memo[id(self.param_array)] = None # and param_array
|
||||
copy = super(Parameterized, self).copy(memo)
|
||||
copy._connect_parameters()
|
||||
copy._connect_fixes()
|
||||
copy._notify_parent_change()
|
||||
return copy
|
||||
|
||||
#===========================================================================
|
||||
# Printing:
|
||||
|
|
@ -328,7 +349,7 @@ class Parameterized(Parameterizable):
|
|||
def __str__(self, header=True):
|
||||
|
||||
name = adjust_name_for_printing(self.name) + "."
|
||||
constrs = self._constraints_str;
|
||||
constrs = self._constraints_str;
|
||||
ts = self._ties_str
|
||||
prirs = self._priors_str
|
||||
desc = self._description_str; names = self.parameter_names()
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ class Uniform(Prior):
|
|||
o = super(Prior, cls).__new__(cls, lower, upper)
|
||||
cls._instances.append(weakref.ref(o))
|
||||
return cls._instances[-1]()
|
||||
|
||||
|
||||
def __init__(self, lower, upper):
|
||||
self.lower = float(lower)
|
||||
self.upper = float(upper)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "[" + str(np.round(self.lower)) + ', ' + str(np.round(self.upper)) + ']'
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ class Uniform(Prior):
|
|||
|
||||
def rvs(self, n):
|
||||
return np.random.uniform(self.lower, self.upper, size=n)
|
||||
|
||||
|
||||
class LogGaussian(Prior):
|
||||
"""
|
||||
Implementation of the univariate *log*-Gaussian probability function, coupled with random variables.
|
||||
|
|
@ -246,7 +246,7 @@ class Gamma(Prior):
|
|||
"""
|
||||
Creates an instance of a Gamma Prior by specifying the Expected value(s)
|
||||
and Variance(s) of the distribution.
|
||||
|
||||
|
||||
:param E: expected value
|
||||
:param V: variance
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class SpikeAndSlabPrior(VariationalPrior):
|
|||
super(VariationalPrior, self).__init__(name=name, **kw)
|
||||
self.pi = Param('pi', pi, Logistic(1e-10,1.-1e-10))
|
||||
self.variance = Param('variance',variance)
|
||||
self.learnPi = learnPi
|
||||
if learnPi:
|
||||
self.add_parameters(self.pi)
|
||||
|
||||
|
|
@ -58,12 +59,13 @@ class SpikeAndSlabPrior(VariationalPrior):
|
|||
gamma.gradient -= np.log((1-self.pi)/self.pi*gamma/(1.-gamma))+((np.square(mu)+S)/self.variance-np.log(S)+np.log(self.variance)-1.)/2.
|
||||
mu.gradient -= gamma*mu/self.variance
|
||||
S.gradient -= (1./self.variance - 1./S) * gamma /2.
|
||||
if len(self.pi)==1:
|
||||
self.pi.gradient = (gamma/self.pi - (1.-gamma)/(1.-self.pi)).sum()
|
||||
if len(self.pi.shape)==1:
|
||||
self.pi.gradient = (gamma/self.pi - (1.-gamma)/(1.-self.pi)).sum(axis=0)
|
||||
else:
|
||||
self.pi.gradient = (gamma/self.pi - (1.-gamma)/(1.-self.pi))
|
||||
if self.learnPi:
|
||||
if len(self.pi)==1:
|
||||
self.pi.gradient = (gamma/self.pi - (1.-gamma)/(1.-self.pi)).sum()
|
||||
elif len(self.pi.shape)==1:
|
||||
self.pi.gradient = (gamma/self.pi - (1.-gamma)/(1.-self.pi)).sum(axis=0)
|
||||
else:
|
||||
self.pi.gradient = (gamma/self.pi - (1.-gamma)/(1.-self.pi))
|
||||
|
||||
class VariationalPosterior(Parameterized):
|
||||
def __init__(self, means=None, variances=None, name='latent space', *a, **kw):
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ from ..inference.latent_function_inference import var_dtc
|
|||
from .. import likelihoods
|
||||
from parameterization.variational import VariationalPosterior
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger("sparse gp")
|
||||
|
||||
class SparseGP(GP):
|
||||
"""
|
||||
A general purpose Sparse GP model
|
||||
|
|
@ -46,7 +49,7 @@ class SparseGP(GP):
|
|||
self.num_inducing = Z.shape[0]
|
||||
|
||||
GP.__init__(self, X, Y, kernel, likelihood, inference_method=inference_method, name=name, Y_metadata=Y_metadata)
|
||||
|
||||
logger.info("Adding Z as parameter")
|
||||
self.add_parameter(self.Z, index=0)
|
||||
|
||||
def has_uncertain_inputs(self):
|
||||
|
|
@ -57,19 +60,23 @@ class SparseGP(GP):
|
|||
self.likelihood.update_gradients(self.grad_dict['dL_dthetaL'])
|
||||
if isinstance(self.X, VariationalPosterior):
|
||||
#gradients wrt kernel
|
||||
dL_dKmm = self.grad_dict.pop('dL_dKmm')
|
||||
dL_dKmm = self.grad_dict['dL_dKmm']
|
||||
self.kern.update_gradients_full(dL_dKmm, self.Z, None)
|
||||
target = self.kern.gradient.copy()
|
||||
self.kern.update_gradients_expectations(variational_posterior=self.X, Z=self.Z, dL_dpsi0=self.grad_dict['dL_dpsi0'], dL_dpsi1=self.grad_dict['dL_dpsi1'], dL_dpsi2=self.grad_dict['dL_dpsi2'])
|
||||
self.kern.update_gradients_expectations(variational_posterior=self.X,
|
||||
Z=self.Z,
|
||||
dL_dpsi0=self.grad_dict['dL_dpsi0'],
|
||||
dL_dpsi1=self.grad_dict['dL_dpsi1'],
|
||||
dL_dpsi2=self.grad_dict['dL_dpsi2'])
|
||||
self.kern.gradient += target
|
||||
|
||||
#gradients wrt Z
|
||||
self.Z.gradient = self.kern.gradients_X(dL_dKmm, self.Z)
|
||||
self.Z.gradient += self.kern.gradients_Z_expectations(
|
||||
self.grad_dict['dL_dpsi0'],
|
||||
self.grad_dict['dL_dpsi1'],
|
||||
self.grad_dict['dL_dpsi2'],
|
||||
Z=self.Z,
|
||||
self.grad_dict['dL_dpsi0'],
|
||||
self.grad_dict['dL_dpsi1'],
|
||||
self.grad_dict['dL_dpsi2'],
|
||||
Z=self.Z,
|
||||
variational_posterior=self.X)
|
||||
else:
|
||||
#gradients wrt kernel
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue