mirror of
https://github.com/SheffieldML/GPy.git
synced 2026-05-13 05:52:38 +02:00
merge chagnes from devel
This commit is contained in:
commit
4ec8f464e2
64 changed files with 2592 additions and 10697 deletions
|
|
@ -10,7 +10,7 @@ from model import Model
|
|||
from parameterization import ObsAr
|
||||
from .. import likelihoods
|
||||
from ..likelihoods.gaussian import Gaussian
|
||||
from ..inference.latent_function_inference import exact_gaussian_inference, expectation_propagation
|
||||
from ..inference.latent_function_inference import exact_gaussian_inference, expectation_propagation, LatentFunctionInference
|
||||
from parameterization.variational import VariationalPosterior
|
||||
|
||||
class GP(Model):
|
||||
|
|
@ -21,6 +21,7 @@ class GP(Model):
|
|||
:param Y: output observations
|
||||
:param kernel: a GPy kernel, defaults to rbf+white
|
||||
:param likelihood: a GPy likelihood
|
||||
:param :class:`~GPy.inference.latent_function_inference.LatentFunctionInference` inference_method: The inference method to use for this GP
|
||||
:rtype: model object
|
||||
|
||||
.. Note:: Multiple independent outputs are allowed using columns of Y
|
||||
|
|
@ -220,3 +221,20 @@ class GP(Model):
|
|||
"""
|
||||
return self.kern.input_sensitivity()
|
||||
|
||||
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.
|
||||
kwargs are passed to the optimizer. They can be:
|
||||
|
||||
:param max_f_eval: maximum number of function evaluations
|
||||
:type max_f_eval: int
|
||||
:messages: whether to display during optimisation
|
||||
:type messages: bool
|
||||
:param optimizer: which optimizer to use (defaults to self.preferred optimizer)
|
||||
:type optimizer: string
|
||||
|
||||
TODO: valid args
|
||||
"""
|
||||
self.inference_method.on_optimization_start()
|
||||
super(GP, self).optimize(optimizer, start, **kwargs)
|
||||
self.inference_method.on_optimization_end()
|
||||
|
|
@ -220,7 +220,7 @@ class Model(Parameterized):
|
|||
if self.is_fixed:
|
||||
raise RuntimeError, "Cannot optimize, when everything is fixed"
|
||||
if self.size == 0:
|
||||
raise RuntimeError, "Model without parameters cannot be minimized"
|
||||
raise RuntimeError, "Model without parameters cannot be optimized"
|
||||
|
||||
if optimizer is None:
|
||||
optimizer = self.preferred_optimizer
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@ class ArrayList(list):
|
|||
raise ValueError, "{} is not in list".format(item)
|
||||
pass
|
||||
|
||||
class ObservablesList(object):
|
||||
class ObserverList(object):
|
||||
"""
|
||||
A list which containts the observables.
|
||||
It only holds weak references to observers, such that unbound
|
||||
observers dont dangle in memory.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._poc = []
|
||||
|
||||
|
|
@ -46,26 +51,31 @@ class ObservablesList(object):
|
|||
p,o,c = self._poc[ind]
|
||||
return p, o(), c
|
||||
|
||||
def remove(self, priority, observable, callble):
|
||||
def remove(self, priority, observer, callble):
|
||||
"""
|
||||
Remove one observer, which had priority and callble.
|
||||
"""
|
||||
self.flush()
|
||||
for i in range(len(self) - 1, -1, -1):
|
||||
p,o,c = self[i]
|
||||
if priority==p and observable==o and callble==c:
|
||||
if priority==p and observer==o and callble==c:
|
||||
del self._poc[i]
|
||||
|
||||
def __repr__(self):
|
||||
return self._poc.__repr__()
|
||||
|
||||
def add(self, priority, observable, callble):
|
||||
ins = 0
|
||||
for pr, _, _ in self:
|
||||
if priority > pr:
|
||||
break
|
||||
ins += 1
|
||||
self._poc.insert(ins, (priority, weakref.ref(observable), callble))
|
||||
|
||||
def add(self, priority, observer, callble):
|
||||
"""
|
||||
Add an observer with priority and callble
|
||||
"""
|
||||
if observer is not None:
|
||||
ins = 0
|
||||
for pr, _, _ in self:
|
||||
if priority > pr:
|
||||
break
|
||||
ins += 1
|
||||
self._poc.insert(ins, (priority, weakref.ref(observer), callble))
|
||||
|
||||
def __str__(self):
|
||||
ret = []
|
||||
curr_p = None
|
||||
|
|
@ -81,23 +91,26 @@ class ObservablesList(object):
|
|||
return '\n'.join(ret)
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
Make sure all weak references, which point to nothing are flushed (deleted)
|
||||
"""
|
||||
self._poc = [(p,o,c) for p,o,c in self._poc if o() is not None]
|
||||
|
||||
def __iter__(self):
|
||||
self.flush()
|
||||
for p, o, c in self._poc:
|
||||
if o() is not None:
|
||||
yield p, o(), c
|
||||
yield p, o(), c
|
||||
|
||||
def __len__(self):
|
||||
self.flush()
|
||||
return self._poc.__len__()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
self.flush()
|
||||
s = ObservablesList()
|
||||
import copy
|
||||
s._poc = copy.deepcopy(self._poc, memo)
|
||||
s = ObserverList()
|
||||
for p,o,c in self:
|
||||
import copy
|
||||
s.add(p, copy.deepcopy(o, memo), copy.deepcopy(c, memo))
|
||||
s.flush()
|
||||
return s
|
||||
|
||||
def __getstate__(self):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2012, GPy authors (see AUTHORS.txt).
|
||||
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
||||
|
||||
__updated__ = '2014-04-15'
|
||||
__updated__ = '2014-05-12'
|
||||
|
||||
import numpy as np
|
||||
from parameter_core import Observable, Pickleable
|
||||
|
|
@ -15,10 +15,10 @@ class ObsAr(np.ndarray, Pickleable, Observable):
|
|||
"""
|
||||
__array_priority__ = -1 # Never give back ObsAr
|
||||
def __new__(cls, input_array, *a, **kw):
|
||||
# allways make a copy of input paramters, as we need it to be in C order:
|
||||
if not isinstance(input_array, ObsAr):
|
||||
obj = np.atleast_1d(np.require(input_array, dtype=np.float64, requirements=['W', 'C'])).view(cls)
|
||||
obj = np.atleast_1d(np.require(np.copy(input_array), dtype=np.float64, requirements=['W', 'C'])).view(cls)
|
||||
else: obj = input_array
|
||||
#cls.__name__ = "ObsAr" # because of fixed printing of `array` in np printing
|
||||
super(ObsAr, obj).__init__(*a, **kw)
|
||||
return obj
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import itertools
|
||||
import numpy
|
||||
np = numpy
|
||||
from parameter_core import OptimizationHandlable, adjust_name_for_printing
|
||||
from parameter_core import Parameterizable, adjust_name_for_printing
|
||||
from observable_array import ObsAr
|
||||
|
||||
###### printing
|
||||
|
|
@ -16,7 +16,7 @@ __precision__ = numpy.get_printoptions()['precision'] # numpy printing precision
|
|||
__print_threshold__ = 5
|
||||
######
|
||||
|
||||
class Param(OptimizationHandlable, ObsAr):
|
||||
class Param(Parameterizable, ObsAr):
|
||||
"""
|
||||
Parameter object for GPy models.
|
||||
|
||||
|
|
@ -42,10 +42,9 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
"""
|
||||
__array_priority__ = -1 # Never give back Param
|
||||
_fixes_ = None
|
||||
_parameters_ = []
|
||||
parameters = []
|
||||
def __new__(cls, name, input_array, default_constraint=None):
|
||||
obj = numpy.atleast_1d(super(Param, cls).__new__(cls, input_array=input_array))
|
||||
cls.__name__ = "Param"
|
||||
obj._current_slice_ = (slice(obj.shape[0]),)
|
||||
obj._realshape_ = obj.shape
|
||||
obj._realsize_ = obj.size
|
||||
|
|
@ -58,9 +57,9 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
|
||||
def build_pydot(self,G):
|
||||
import pydot
|
||||
node = pydot.Node(id(self), shape='record', label=self.name)
|
||||
node = pydot.Node(id(self), shape='trapezium', label=self.name)#, fontcolor='white', color='white')
|
||||
G.add_node(node)
|
||||
for o in self.observers.keys():
|
||||
for _, o, _ in self.observers:
|
||||
label = o.name if hasattr(o, 'name') else str(o)
|
||||
observed_node = pydot.Node(id(o), label=label)
|
||||
G.add_node(observed_node)
|
||||
|
|
@ -88,8 +87,18 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
|
||||
@property
|
||||
def param_array(self):
|
||||
"""
|
||||
As we are a leaf, this just returns self
|
||||
"""
|
||||
return self
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""
|
||||
Return self as numpy array view
|
||||
"""
|
||||
return self.view(np.ndarray)
|
||||
|
||||
@property
|
||||
def gradient(self):
|
||||
"""
|
||||
|
|
@ -100,11 +109,11 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
"""
|
||||
if getattr(self, '_gradient_array_', None) is None:
|
||||
self._gradient_array_ = numpy.empty(self._realshape_, dtype=numpy.float64)
|
||||
return self._gradient_array_[self._current_slice_]
|
||||
return self._gradient_array_#[self._current_slice_]
|
||||
|
||||
@gradient.setter
|
||||
def gradient(self, val):
|
||||
self._gradient_array_[self._current_slice_] = val
|
||||
self._gradient_array_[:] = val
|
||||
|
||||
#===========================================================================
|
||||
# Array operations -> done
|
||||
|
|
@ -112,10 +121,13 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
def __getitem__(self, s, *args, **kwargs):
|
||||
if not isinstance(s, tuple):
|
||||
s = (s,)
|
||||
if not reduce(lambda a, b: a or numpy.any(b is Ellipsis), s, False) and len(s) <= self.ndim:
|
||||
s += (Ellipsis,)
|
||||
#if not reduce(lambda a, b: a or numpy.any(b is Ellipsis), s, False) and len(s) <= self.ndim:
|
||||
# s += (Ellipsis,)
|
||||
new_arr = super(Param, self).__getitem__(s, *args, **kwargs)
|
||||
try: new_arr._current_slice_ = s; new_arr._original_ = self.base is new_arr.base
|
||||
try:
|
||||
new_arr._current_slice_ = s
|
||||
new_arr._gradient_array_ = self.gradient[s]
|
||||
new_arr._original_ = self.base is new_arr.base
|
||||
except AttributeError: pass # returning 0d array or float, double etc
|
||||
return new_arr
|
||||
|
||||
|
|
@ -130,6 +142,9 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
def _raveled_index_for(self, obj):
|
||||
return self._raveled_index()
|
||||
|
||||
#===========================================================================
|
||||
# Index recreation
|
||||
#===========================================================================
|
||||
def _expand_index(self, slice_index=None):
|
||||
# this calculates the full indexing arrays from the slicing objects given by get_item for _real..._ attributes
|
||||
# it basically translates slices to their respective index arrays and turns negative indices around
|
||||
|
|
@ -138,6 +153,8 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
slice_index = self._current_slice_
|
||||
def f(a):
|
||||
a, b = a
|
||||
if isinstance(a, numpy.ndarray) and a.dtype == bool:
|
||||
raise ValueError, "Boolean indexing not implemented, use Param[np.where(index)] to index by boolean arrays!"
|
||||
if a not in (slice(None), Ellipsis):
|
||||
if isinstance(a, slice):
|
||||
start, stop, step = a.indices(b)
|
||||
|
|
@ -156,6 +173,36 @@ class Param(OptimizationHandlable, 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
|
||||
#===========================================================================
|
||||
|
|
@ -228,9 +275,16 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
and len(set(map(len, clean_curr_slice))) <= 1):
|
||||
return numpy.fromiter(itertools.izip(*clean_curr_slice),
|
||||
dtype=[('', int)] * self._realndim_, count=len(clean_curr_slice[0])).view((int, self._realndim_))
|
||||
expanded_index = list(self._expand_index(slice_index))
|
||||
return numpy.fromiter(itertools.product(*expanded_index),
|
||||
try:
|
||||
expanded_index = list(self._expand_index(slice_index))
|
||||
indices = numpy.fromiter(itertools.product(*expanded_index),
|
||||
dtype=[('', int)] * self._realndim_, count=reduce(lambda a, b: a * b.size, expanded_index, 1)).view((int, self._realndim_))
|
||||
except:
|
||||
print "Warning: extended indexing was used"
|
||||
indices = np.indices(self._realshape_, dtype=int)
|
||||
indices = indices[(slice(None),)+slice_index]
|
||||
indices = np.rollaxis(indices, 0, indices.ndim)
|
||||
return indices
|
||||
def _max_len_names(self, gen, header):
|
||||
gen = map(lambda x: " ".join(map(str, x)), gen)
|
||||
return reduce(lambda a, b:max(a, len(b)), gen, len(header))
|
||||
|
|
@ -272,7 +326,7 @@ class Param(OptimizationHandlable, ObsAr):
|
|||
class ParamConcatenation(object):
|
||||
def __init__(self, params):
|
||||
"""
|
||||
Parameter concatenation for convienience of printing regular expression matched arrays
|
||||
Parameter concatenation for convenience of printing regular expression matched arrays
|
||||
you can index this concatenation as if it was the flattened concatenation
|
||||
of all the parameters it contains, same for setting parameters (Broadcasting enabled).
|
||||
|
||||
|
|
@ -316,8 +370,8 @@ class ParamConcatenation(object):
|
|||
val = val.values()
|
||||
ind = numpy.zeros(sum(self._param_sizes), dtype=bool); ind[s] = True;
|
||||
vals = self.values(); vals[s] = val
|
||||
[numpy.copyto(p, vals[ps], where=ind[ps])
|
||||
for p, ps in zip(self.params, self._param_slices_)]
|
||||
for p, ps in zip(self.params, self._param_slices_):
|
||||
p.flat[ind[ps]] = vals[ps]
|
||||
if update:
|
||||
self.update_all_params()
|
||||
def values(self):
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from transformations import Logexp, NegativeLogexp, Logistic, __fixed__, FIXED,
|
|||
import numpy as np
|
||||
import re
|
||||
|
||||
__updated__ = '2014-04-16'
|
||||
__updated__ = '2014-05-15'
|
||||
|
||||
class HierarchyError(Exception):
|
||||
"""
|
||||
|
|
@ -52,13 +52,23 @@ class Observable(object):
|
|||
_updated = True
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Observable, self).__init__()
|
||||
from lists_and_dicts import ObservablesList
|
||||
self.observers = ObservablesList()
|
||||
from lists_and_dicts import ObserverList
|
||||
self.observers = ObserverList()
|
||||
|
||||
def add_observer(self, observer, callble, priority=0):
|
||||
"""
|
||||
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,
|
||||
which were added alongside observer,
|
||||
or remove callable `callble` which was added alongside
|
||||
the observer `observer`.
|
||||
"""
|
||||
to_remove = []
|
||||
for poc in self.observers:
|
||||
_, obs, clble = poc
|
||||
|
|
@ -91,10 +101,6 @@ class Observable(object):
|
|||
break
|
||||
callble(self, which=which)
|
||||
|
||||
#===============================================================================
|
||||
# Foundation framework for parameterized and param objects:
|
||||
#===============================================================================
|
||||
|
||||
class Parentable(object):
|
||||
"""
|
||||
Enable an Object to have a parent.
|
||||
|
|
@ -124,7 +130,7 @@ class Parentable(object):
|
|||
"""
|
||||
Disconnect this object from its parent
|
||||
"""
|
||||
raise NotImplementedError, "Abstaract superclass"
|
||||
raise NotImplementedError, "Abstract superclass"
|
||||
|
||||
@property
|
||||
def _highest_parent_(self):
|
||||
|
|
@ -162,7 +168,6 @@ class Pickleable(object):
|
|||
:param protocol: pickling protocol to use, python-pickle for details.
|
||||
"""
|
||||
import cPickle as pickle
|
||||
import pickle #TODO: cPickle
|
||||
if isinstance(f, str):
|
||||
with open(f, 'w') as f:
|
||||
pickle.dump(self, f, protocol)
|
||||
|
|
@ -173,38 +178,42 @@ class Pickleable(object):
|
|||
# copy and pickling
|
||||
#===========================================================================
|
||||
def copy(self):
|
||||
"""Returns a (deep) copy of the current model"""
|
||||
"""
|
||||
Returns a (deep) copy of the current parameter handle.
|
||||
|
||||
All connections to parents of the copy will be cut.
|
||||
"""
|
||||
#raise NotImplementedError, "Copy is not yet implemented, TODO: Observable hierarchy"
|
||||
import copy
|
||||
memo = {}
|
||||
memo[id(self._parent_)] = None
|
||||
memo[id(self.gradient)] = None
|
||||
memo[id(self.param_array)] = None
|
||||
memo[id(self._fixes_)] = None
|
||||
c = copy.deepcopy(self, memo)
|
||||
# the next part makes sure that we do not include parents in any form:
|
||||
parents = []
|
||||
self.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
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
s = self.__new__(self.__class__)
|
||||
memo[id(self)] = s
|
||||
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))
|
||||
s.__dict__.update(copy.deepcopy(self.__dict__, memo)) # standard copy
|
||||
return s
|
||||
|
||||
def __getstate__(self):
|
||||
ignore_list = ([#'_parent_', '_parent_index_',
|
||||
#'observers',
|
||||
'_param_array_', '_gradient_array_', '_fixes_',
|
||||
'_Cacher_wrap__cachers']
|
||||
#+ self.parameter_names(recursive=False)
|
||||
)
|
||||
ignore_list = ['_param_array_', # parameters get set from bottom to top
|
||||
'_gradient_array_', # as well as gradients
|
||||
'_fixes_', # and fixes
|
||||
'_Cacher_wrap__cachers', # never pickle cachers
|
||||
]
|
||||
dc = dict()
|
||||
for k,v in self.__dict__.iteritems():
|
||||
if k not in ignore_list:
|
||||
#if hasattr(v, "__getstate__"):
|
||||
#dc[k] = v.__getstate__()
|
||||
#else:
|
||||
dc[k] = v
|
||||
return dc
|
||||
|
||||
|
|
@ -212,12 +221,6 @@ class Pickleable(object):
|
|||
self.__dict__.update(state)
|
||||
return self
|
||||
|
||||
#def __getstate__(self, memo):
|
||||
# raise NotImplementedError, "get state must be implemented to be able to pickle objects"
|
||||
|
||||
#def __setstate__(self, memo):
|
||||
# raise NotImplementedError, "set state must be implemented to be able to pickle objects"
|
||||
|
||||
class Gradcheckable(Pickleable, Parentable):
|
||||
"""
|
||||
Adds the functionality for an object to be gradcheckable.
|
||||
|
|
@ -252,7 +255,6 @@ class Gradcheckable(Pickleable, Parentable):
|
|||
"""
|
||||
raise HierarchyError, "This parameter is not in a model with a likelihood, and, therefore, cannot be gradient checked!"
|
||||
|
||||
|
||||
class Nameable(Gradcheckable):
|
||||
"""
|
||||
Make an object nameable inside the hierarchy.
|
||||
|
|
@ -291,41 +293,8 @@ class Nameable(Gradcheckable):
|
|||
return self._parent_.hierarchy_name() + "." + adjust(self.name)
|
||||
return adjust(self.name)
|
||||
|
||||
class Indexable(object):
|
||||
"""
|
||||
Enable enraveled indexes and offsets for this object.
|
||||
The raveled index of an object is the index for its parameters in a flattened int array.
|
||||
"""
|
||||
def __init__(self, *a, **kw):
|
||||
super(Indexable, self).__init__()
|
||||
|
||||
def _raveled_index(self):
|
||||
"""
|
||||
Flattened array of ints, specifying the index of this object.
|
||||
This has to account for shaped parameters!
|
||||
"""
|
||||
raise NotImplementedError, "Need to be able to get the raveled Index"
|
||||
|
||||
def _offset_for(self, param):
|
||||
"""
|
||||
Return the offset of the param inside this parameterized object.
|
||||
This does not need to account for shaped parameters, as it
|
||||
basically just sums up the parameter sizes which come before param.
|
||||
"""
|
||||
return 0
|
||||
#raise NotImplementedError, "shouldnt happen, offset required from non parameterization object?"
|
||||
|
||||
def _raveled_index_for(self, param):
|
||||
"""
|
||||
get the raveled index for a param
|
||||
that is an int array, containing the indexes for the flattened
|
||||
param inside this parameterized logic.
|
||||
"""
|
||||
return param._raveled_index()
|
||||
#raise NotImplementedError, "shouldnt happen, raveld index transformation required from non parameterization object?"
|
||||
|
||||
|
||||
class Constrainable(Nameable, Indexable, Observable):
|
||||
class Indexable(Nameable, Observable):
|
||||
"""
|
||||
Make an object constrainable with Priors and Transformations.
|
||||
TODO: Mappings!!
|
||||
|
|
@ -336,7 +305,7 @@ class Constrainable(Nameable, Indexable, Observable):
|
|||
:func:`constrain()` and :func:`unconstrain()` are main methods here
|
||||
"""
|
||||
def __init__(self, name, default_constraint=None, *a, **kw):
|
||||
super(Constrainable, self).__init__(name=name, *a, **kw)
|
||||
super(Indexable, self).__init__(name=name, *a, **kw)
|
||||
self._default_constraint_ = default_constraint
|
||||
from index_operations import ParameterIndexOperations
|
||||
self.constraints = ParameterIndexOperations()
|
||||
|
|
@ -358,6 +327,39 @@ class Constrainable(Nameable, Indexable, Observable):
|
|||
self._connect_fixes()
|
||||
self._notify_parent_change()
|
||||
|
||||
#===========================================================================
|
||||
# Indexable
|
||||
#===========================================================================
|
||||
def _offset_for(self, param):
|
||||
"""
|
||||
Return the offset of the param inside this parameterized object.
|
||||
This does not need to account for shaped parameters, as it
|
||||
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
|
||||
return self._offset_for(param._parent_) + param._parent_._offset_for(param)
|
||||
return 0
|
||||
|
||||
def _raveled_index_for(self, param):
|
||||
"""
|
||||
get the raveled index for a param
|
||||
that is an int array, containing the indexes for the flattened
|
||||
param inside this parameterized logic.
|
||||
"""
|
||||
from param import ParamConcatenation
|
||||
if isinstance(param, ParamConcatenation):
|
||||
return np.hstack((self._raveled_index_for(p) for p in param.params))
|
||||
return param._raveled_index() + self._offset_for(param)
|
||||
|
||||
def _raveled_index(self):
|
||||
"""
|
||||
Flattened array of ints, specifying the index of this object.
|
||||
This has to account for shaped parameters!
|
||||
"""
|
||||
return np.r_[:self.size]
|
||||
|
||||
#===========================================================================
|
||||
# Fixing Parameters:
|
||||
#===========================================================================
|
||||
|
|
@ -412,9 +414,24 @@ class Constrainable(Nameable, Indexable, Observable):
|
|||
self._fixes_ = None
|
||||
del self.constraints[__fixed__]
|
||||
|
||||
#===========================================================================
|
||||
# Convenience for fixed
|
||||
#===========================================================================
|
||||
def _has_fixes(self):
|
||||
return hasattr(self, "_fixes_") and self._fixes_ is not None and self._fixes_.size == self.size
|
||||
|
||||
@property
|
||||
def is_fixed(self):
|
||||
for p in self.parameters:
|
||||
if not p.is_fixed: return False
|
||||
return True
|
||||
|
||||
def _get_original(self, param):
|
||||
# if advanced indexing is activated it happens that the array is a copy
|
||||
# you can retrieve the original param through this method, by passing
|
||||
# the copy here
|
||||
return self.parameters[param._parent_index_]
|
||||
|
||||
#===========================================================================
|
||||
# Prior Operations
|
||||
#===========================================================================
|
||||
|
|
@ -438,8 +455,7 @@ class Constrainable(Nameable, Indexable, Observable):
|
|||
|
||||
def unset_priors(self, *priors):
|
||||
"""
|
||||
Un-set all priors given from this parameter handle.
|
||||
|
||||
Un-set all priors given (in *priors) from this parameter handle.
|
||||
"""
|
||||
return self._remove_from_index_operations(self.priors, priors)
|
||||
|
||||
|
|
@ -541,13 +557,13 @@ class Constrainable(Nameable, Indexable, Observable):
|
|||
self.constraints = ParameterIndexOperationsView(parent.constraints, parent._offset_for(self), self.size)
|
||||
self.priors = ParameterIndexOperationsView(parent.priors, parent._offset_for(self), self.size)
|
||||
self._fixes_ = None
|
||||
for p in self._parameters_:
|
||||
for p in self.parameters:
|
||||
p._parent_changed(parent)
|
||||
|
||||
def _add_to_index_operations(self, which, reconstrained, what, warning):
|
||||
"""
|
||||
Helper preventing copy code.
|
||||
This addes the given what (transformation, prior etc) to parameter index operations which.
|
||||
This adds the given what (transformation, prior etc) to parameter index operations which.
|
||||
revonstrained are reconstrained indices.
|
||||
warn when reconstraining parameters if warning is True.
|
||||
TODO: find out which parameters have changed specifically
|
||||
|
|
@ -569,14 +585,13 @@ class Constrainable(Nameable, Indexable, Observable):
|
|||
removed = np.empty((0,), dtype=int)
|
||||
for t in transforms:
|
||||
unconstrained = which.remove(t, self._raveled_index())
|
||||
print unconstrained
|
||||
removed = np.union1d(removed, unconstrained)
|
||||
if t is __fixed__:
|
||||
self._highest_parent_._set_unfixed(self, unconstrained)
|
||||
|
||||
return removed
|
||||
|
||||
class OptimizationHandlable(Constrainable):
|
||||
class OptimizationHandlable(Indexable):
|
||||
"""
|
||||
This enables optimization handles on an Object as done in GPy 0.4.
|
||||
|
||||
|
|
@ -585,14 +600,8 @@ class OptimizationHandlable(Constrainable):
|
|||
def __init__(self, name, default_constraint=None, *a, **kw):
|
||||
super(OptimizationHandlable, self).__init__(name, default_constraint=default_constraint, *a, **kw)
|
||||
|
||||
def transform(self):
|
||||
[np.put(self.param_array, ind, c.finv(self.param_array.flat[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__]
|
||||
|
||||
def untransform(self):
|
||||
[np.put(self.param_array, ind, c.f(self.param_array.flat[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__]
|
||||
|
||||
def _get_params_transformed(self):
|
||||
# transformed parameters (apply transformation rules)
|
||||
# transformed parameters (apply un-transformation rules)
|
||||
p = self.param_array.copy()
|
||||
[np.put(p, ind, c.finv(p[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__]
|
||||
if self.has_parent() and self.constraints[__fixed__].size != 0:
|
||||
|
|
@ -604,29 +613,55 @@ class OptimizationHandlable(Constrainable):
|
|||
return p
|
||||
|
||||
def _set_params_transformed(self, p):
|
||||
if p is self.param_array:
|
||||
p = p.copy()
|
||||
if self.has_parent() and self.constraints[__fixed__].size != 0:
|
||||
fixes = np.ones(self.size).astype(bool)
|
||||
fixes[self.constraints[__fixed__]] = FIXED
|
||||
self.param_array.flat[fixes] = p
|
||||
elif self._has_fixes(): self.param_array.flat[self._fixes_] = p
|
||||
else: self.param_array.flat = p
|
||||
self.untransform()
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if not(p is self.param_array):
|
||||
if self.has_parent() and self.constraints[__fixed__].size != 0:
|
||||
fixes = np.ones(self.size).astype(bool)
|
||||
fixes[self.constraints[__fixed__]] = 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__]
|
||||
self._trigger_params_changed()
|
||||
|
||||
def _trigger_params_changed(self, trigger_parent=True):
|
||||
[p._trigger_params_changed(trigger_parent=False) for p in self._parameters_]
|
||||
"""
|
||||
First tell all children to update,
|
||||
then update yourself.
|
||||
|
||||
If trigger_parent is True, we will tell the parent, otherwise not.
|
||||
"""
|
||||
[p._trigger_params_changed(trigger_parent=False) for p in self.parameters]
|
||||
self.notify_observers(None, None if trigger_parent else -np.inf)
|
||||
|
||||
def _size_transformed(self):
|
||||
"""
|
||||
As fixes are not passed to the optimiser, the size of the model for the optimiser
|
||||
is the size of all parameters minus the size of the fixes.
|
||||
"""
|
||||
return self.size - self.constraints[__fixed__].size
|
||||
|
||||
def _transform_gradients(self, g):
|
||||
"""
|
||||
Transform the gradients by multiplying the gradient factor for each
|
||||
constraint to it.
|
||||
"""
|
||||
if self.has_parent():
|
||||
return g
|
||||
[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
|
||||
|
||||
@property
|
||||
def num_params(self):
|
||||
"""
|
||||
Return the number of parameters of this parameter_handle.
|
||||
Param objects will allways return 0.
|
||||
Param objects will always return 0.
|
||||
"""
|
||||
raise NotImplemented, "Abstract, please implement in respective classes"
|
||||
|
||||
|
|
@ -640,10 +675,11 @@ class OptimizationHandlable(Constrainable):
|
|||
"""
|
||||
if adjust_for_printing: adjust = lambda x: adjust_name_for_printing(x)
|
||||
else: adjust = lambda x: x
|
||||
if recursive: names = [xi for x in self._parameters_ for xi in x.parameter_names(add_self=True, adjust_for_printing=adjust_for_printing)]
|
||||
else: names = [adjust(x.name) for x in self._parameters_]
|
||||
if recursive: names = [xi for x in self.parameters for xi in x.parameter_names(add_self=True, adjust_for_printing=adjust_for_printing)]
|
||||
else: names = [adjust(x.name) for x in self.parameters]
|
||||
if add_self: names = map(lambda x: adjust(self.name) + "." + x, names)
|
||||
return names
|
||||
|
||||
def _get_param_names(self):
|
||||
n = np.array([p.hierarchy_name() + '[' + str(i) + ']' for p in self.flattened_parameters for i in p._indices()])
|
||||
return n
|
||||
|
|
@ -662,7 +698,7 @@ class OptimizationHandlable(Constrainable):
|
|||
Randomize the model.
|
||||
Make this draw from the prior if one exists, else draw from given random generator
|
||||
|
||||
:param rand_gen: numpy random number generator which takes args and kwargs
|
||||
:param rand_gen: np random number generator which takes args and kwargs
|
||||
:param flaot loc: loc parameter for random number generator
|
||||
:param float scale: scale parameter for random number generator
|
||||
:param args, kwargs: will be passed through to random number generator
|
||||
|
|
@ -678,7 +714,7 @@ class OptimizationHandlable(Constrainable):
|
|||
# for all parameterized objects
|
||||
#===========================================================================
|
||||
@property
|
||||
def full_gradient(self):
|
||||
def gradient_full(self):
|
||||
"""
|
||||
Note to users:
|
||||
This does not return the gradient in the right shape! Use self.gradient
|
||||
|
|
@ -692,30 +728,53 @@ class OptimizationHandlable(Constrainable):
|
|||
return self._gradient_array_
|
||||
|
||||
def _propagate_param_grad(self, parray, garray):
|
||||
"""
|
||||
For propagating the param_array and gradient_array.
|
||||
This ensures the in memory view of each subsequent array.
|
||||
|
||||
1.) connect param_array of children to self.param_array
|
||||
2.) tell all children to propagate further
|
||||
"""
|
||||
pi_old_size = 0
|
||||
for pi in self._parameters_:
|
||||
for pi in self.parameters:
|
||||
pislice = slice(pi_old_size, pi_old_size + pi.size)
|
||||
|
||||
self.param_array[pislice] = pi.param_array.flat # , requirements=['C', 'W']).flat
|
||||
self.full_gradient[pislice] = pi.full_gradient.flat # , requirements=['C', 'W']).flat
|
||||
self.gradient_full[pislice] = pi.gradient_full.flat # , requirements=['C', 'W']).flat
|
||||
|
||||
pi.param_array.data = parray[pislice].data
|
||||
pi.full_gradient.data = garray[pislice].data
|
||||
pi.gradient_full.data = garray[pislice].data
|
||||
|
||||
pi._propagate_param_grad(parray[pislice], garray[pislice])
|
||||
pi_old_size += pi.size
|
||||
|
||||
class Parameterizable(OptimizationHandlable):
|
||||
"""
|
||||
A parameterisable class.
|
||||
|
||||
This class provides the parameters list (ArrayList) and standard parameter handling,
|
||||
such as {add|remove}_parameter(), traverse hierarchy and param_array, gradient_array
|
||||
and the empty parameters_changed().
|
||||
|
||||
This class is abstract and should not be instantiated.
|
||||
Use GPy.core.Parameterized() as node (or leaf) in the parameterized hierarchy.
|
||||
Use GPy.core.Param() for a leaf in the parameterized hierarchy.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Parameterizable, self).__init__(*args, **kwargs)
|
||||
from GPy.core.parameterization.lists_and_dicts import ArrayList
|
||||
self._parameters_ = ArrayList()
|
||||
self.size = 0
|
||||
self.parameters = ArrayList()
|
||||
self._param_array_ = None
|
||||
self._added_names_ = set()
|
||||
self.__visited = False # for traversing in reverse order we need to know if we were here already
|
||||
|
||||
@property
|
||||
def param_array(self):
|
||||
if not hasattr(self, '_param_array_'):
|
||||
"""
|
||||
Array representing the parameters of this class.
|
||||
There is only one copy of all parameters in memory, two during optimization.
|
||||
"""
|
||||
if self.__dict__.get('_param_array_', None) is None:
|
||||
self._param_array_ = np.empty(self.size, dtype=np.float64)
|
||||
return self._param_array_
|
||||
|
||||
|
|
@ -723,12 +782,58 @@ class Parameterizable(OptimizationHandlable):
|
|||
def param_array(self, arr):
|
||||
self._param_array_ = arr
|
||||
|
||||
def traverse(self, visit, *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.
|
||||
|
||||
Example:
|
||||
Collect all children:
|
||||
|
||||
children = []
|
||||
self.traverse(children.append)
|
||||
print children
|
||||
"""
|
||||
if not self.__visited:
|
||||
visit(self, *args, **kwargs)
|
||||
self.__visited = True
|
||||
for c in self.parameters:
|
||||
c.traverse(visit, *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
|
||||
|
||||
def _traverse_parents(self, visit, *args, **kwargs):
|
||||
if not self.__visited:
|
||||
self.__visited = True
|
||||
visit(self, *args, **kwargs)
|
||||
if self.has_parent():
|
||||
self._parent_._traverse_parents(visit, *args, **kwargs)
|
||||
self._parent_.traverse(visit, *args, **kwargs)
|
||||
self.__visited = False
|
||||
|
||||
#=========================================================================
|
||||
# Gradient handling
|
||||
#=========================================================================
|
||||
@property
|
||||
def gradient(self):
|
||||
if not hasattr(self, '_gradient_array_'):
|
||||
if self.__dict__.get('_gradient_array_', None) is None:
|
||||
self._gradient_array_ = np.empty(self.size, dtype=np.float64)
|
||||
return self._gradient_array_
|
||||
|
||||
|
|
@ -738,7 +843,7 @@ class Parameterizable(OptimizationHandlable):
|
|||
|
||||
@property
|
||||
def num_params(self):
|
||||
return len(self._parameters_)
|
||||
return len(self.parameters)
|
||||
|
||||
def _add_parameter_name(self, param, ignore_added_names=False):
|
||||
pname = adjust_name_for_printing(param.name)
|
||||
|
|
@ -771,130 +876,6 @@ class Parameterizable(OptimizationHandlable):
|
|||
self._remove_parameter_name(None, old_name)
|
||||
self._add_parameter_name(param)
|
||||
|
||||
def add_parameter(self, param, index=None, _ignore_added_names=False):
|
||||
"""
|
||||
:param parameters: the parameters to add
|
||||
:type parameters: list of or one :py:class:`GPy.core.param.Param`
|
||||
:param [index]: index of where to put parameters
|
||||
|
||||
:param bool _ignore_added_names: whether the name of the parameter overrides a possibly existing field
|
||||
|
||||
Add all parameters to this param class, you can insert parameters
|
||||
at any given index using the :func:`list.insert` syntax
|
||||
"""
|
||||
if param in self._parameters_ and index is not None:
|
||||
self.remove_parameter(param)
|
||||
self.add_parameter(param, index)
|
||||
# elif param.has_parent():
|
||||
# raise HierarchyError, "parameter {} already in another model ({}), create new object (or copy) for adding".format(param._short(), param._highest_parent_._short())
|
||||
elif param not in self._parameters_:
|
||||
if param.has_parent():
|
||||
parent = param._parent_
|
||||
while parent is not None:
|
||||
if parent is self:
|
||||
raise HierarchyError, "You cannot add a parameter twice into the hierarchy"
|
||||
parent = parent._parent_
|
||||
param._parent_.remove_parameter(param)
|
||||
# make sure the size is set
|
||||
if index is None:
|
||||
self.constraints.update(param.constraints, self.size)
|
||||
self.priors.update(param.priors, self.size)
|
||||
self._parameters_.append(param)
|
||||
else:
|
||||
start = sum(p.size for p in self._parameters_[:index])
|
||||
self.constraints.shift_right(start, param.size)
|
||||
self.priors.shift_right(start, param.size)
|
||||
self.constraints.update(param.constraints, start)
|
||||
self.priors.update(param.priors, start)
|
||||
self._parameters_.insert(index, param)
|
||||
|
||||
param.add_observer(self, self._pass_through_notify_observers, -np.inf)
|
||||
|
||||
parent = self
|
||||
while parent is not None:
|
||||
parent.size += param.size
|
||||
parent = parent._parent_
|
||||
|
||||
self._connect_parameters()
|
||||
|
||||
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"""
|
||||
|
||||
|
||||
def add_parameters(self, *parameters):
|
||||
"""
|
||||
convenience method for adding several
|
||||
parameters without gradient specification
|
||||
"""
|
||||
[self.add_parameter(p) for p in parameters]
|
||||
|
||||
def remove_parameter(self, param):
|
||||
"""
|
||||
:param param: param object to remove from being a parameter of this parameterized object.
|
||||
"""
|
||||
if not param in self._parameters_:
|
||||
raise RuntimeError, "Parameter {} does not belong to this object, remove parameters directly from their respective parents".format(param._short())
|
||||
|
||||
start = sum([p.size for p in self._parameters_[:param._parent_index_]])
|
||||
self._remove_parameter_name(param)
|
||||
self.size -= param.size
|
||||
del self._parameters_[param._parent_index_]
|
||||
|
||||
param._disconnect_parent()
|
||||
param.remove_observer(self, self._pass_through_notify_observers)
|
||||
self.constraints.shift_left(start, param.size)
|
||||
|
||||
self._connect_parameters()
|
||||
self._notify_parent_change()
|
||||
|
||||
parent = self._parent_
|
||||
while parent is not None:
|
||||
parent.size -= param.size
|
||||
parent = parent._parent_
|
||||
|
||||
self._highest_parent_._connect_parameters()
|
||||
self._highest_parent_._connect_fixes()
|
||||
self._highest_parent_._notify_parent_change()
|
||||
|
||||
def _connect_parameters(self, ignore_added_names=False):
|
||||
# connect parameterlist to this parameterized object
|
||||
# This just sets up the right connection for the params objects
|
||||
# to be used as parameters
|
||||
# it also sets the constraints for each parameter to the constraints
|
||||
# of their respective parents
|
||||
if not hasattr(self, "_parameters_") or len(self._parameters_) < 1:
|
||||
# no parameters for this class
|
||||
return
|
||||
old_size = 0
|
||||
self.param_array = np.empty(self.size, dtype=np.float64)
|
||||
self._gradient_array_ = np.empty(self.size, dtype=np.float64)
|
||||
|
||||
self._param_slices_ = []
|
||||
for i, p in enumerate(self._parameters_):
|
||||
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.full_gradient[pslice])
|
||||
# then connect children to self
|
||||
self.param_array[pslice] = p.param_array.flat # , requirements=['C', 'W']).ravel(order='C')
|
||||
self.full_gradient[pslice] = p.full_gradient.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.full_gradient.data = self.full_gradient[pslice].data
|
||||
|
||||
self._param_slices_.append(pslice)
|
||||
|
||||
self._add_parameter_name(p, ignore_added_names=ignore_added_names)
|
||||
old_size += p.size
|
||||
|
||||
#===========================================================================
|
||||
# notification system
|
||||
#===========================================================================
|
||||
|
|
@ -904,30 +885,13 @@ class Parameterizable(OptimizationHandlable):
|
|||
self.notify_observers(which=which)
|
||||
|
||||
#===========================================================================
|
||||
# Pickling
|
||||
#===========================================================================
|
||||
def __setstate__(self, state):
|
||||
super(Parameterizable, self).__setstate__(state)
|
||||
self._connect_parameters()
|
||||
self._connect_fixes()
|
||||
self._notify_parent_change()
|
||||
|
||||
self.parameters_changed()
|
||||
|
||||
def copy(self):
|
||||
c = super(Parameterizable, self).copy()
|
||||
c._connect_parameters()
|
||||
c._connect_fixes()
|
||||
c._notify_parent_change()
|
||||
return c
|
||||
#===========================================================================
|
||||
# From being parentable, we have to define the parent_change notification
|
||||
#===========================================================================
|
||||
def _notify_parent_change(self):
|
||||
"""
|
||||
Notify all parameters that the parent has changed
|
||||
"""
|
||||
for p in self._parameters_:
|
||||
for p in self.parameters:
|
||||
p._parent_changed(self)
|
||||
|
||||
def parameters_changed(self):
|
||||
|
|
|
|||
|
|
@ -3,13 +3,10 @@
|
|||
|
||||
|
||||
import numpy; np = numpy
|
||||
import cPickle
|
||||
import itertools
|
||||
from re import compile, _pattern_type
|
||||
from param import ParamConcatenation
|
||||
from parameter_core import Pickleable, Parameterizable, adjust_name_for_printing
|
||||
from transformations import __fixed__
|
||||
from lists_and_dicts import ArrayList
|
||||
from parameter_core import HierarchyError, Parameterizable, adjust_name_for_printing
|
||||
|
||||
class ParametersChangedMeta(type):
|
||||
def __call__(self, *args, **kw):
|
||||
|
|
@ -68,8 +65,7 @@ class Parameterized(Parameterizable):
|
|||
def __init__(self, name=None, parameters=[], *a, **kw):
|
||||
super(Parameterized, self).__init__(name=name, *a, **kw)
|
||||
self._in_init_ = True
|
||||
self._parameters_ = ArrayList()
|
||||
self.size = sum(p.size for p in self._parameters_)
|
||||
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
|
||||
|
|
@ -82,15 +78,15 @@ class Parameterized(Parameterizable):
|
|||
import pydot # @UnresolvedImport
|
||||
iamroot = False
|
||||
if G is None:
|
||||
G = pydot.Dot(graph_type='digraph')
|
||||
G = pydot.Dot(graph_type='digraph', bgcolor=None)
|
||||
iamroot=True
|
||||
node = pydot.Node(id(self), shape='record', label=self.name)
|
||||
node = pydot.Node(id(self), shape='box', label=self.name)#, color='white')
|
||||
G.add_node(node)
|
||||
for child in self._parameters_:
|
||||
for child in self.parameters:
|
||||
child_node = child.build_pydot(G)
|
||||
G.add_edge(pydot.Edge(node, child_node))
|
||||
G.add_edge(pydot.Edge(node, child_node))#, color='white'))
|
||||
|
||||
for o in self.observers.keys():
|
||||
for _, o, _ in self.observers:
|
||||
label = o.name if hasattr(o, 'name') else str(o)
|
||||
observed_node = pydot.Node(id(o), label=label)
|
||||
G.add_node(observed_node)
|
||||
|
|
@ -102,58 +98,133 @@ class Parameterized(Parameterizable):
|
|||
return node
|
||||
|
||||
#===========================================================================
|
||||
# Gradient control
|
||||
# Add remove parameters:
|
||||
#===========================================================================
|
||||
def _transform_gradients(self, g):
|
||||
if self.has_parent():
|
||||
return g
|
||||
[numpy.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
|
||||
|
||||
|
||||
#===========================================================================
|
||||
# Indexable
|
||||
#===========================================================================
|
||||
def _offset_for(self, param):
|
||||
# get the offset in the parameterized index array for 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
|
||||
return self._offset_for(param._parent_) + param._parent_._offset_for(param)
|
||||
return 0
|
||||
|
||||
def _raveled_index_for(self, param):
|
||||
def add_parameter(self, param, index=None, _ignore_added_names=False):
|
||||
"""
|
||||
get the raveled index for a param
|
||||
that is an int array, containing the indexes for the flattened
|
||||
param inside this parameterized logic.
|
||||
"""
|
||||
if isinstance(param, ParamConcatenation):
|
||||
return numpy.hstack((self._raveled_index_for(p) for p in param.params))
|
||||
return param._raveled_index() + self._offset_for(param)
|
||||
:param parameters: the parameters to add
|
||||
:type parameters: list of or one :py:class:`GPy.core.param.Param`
|
||||
:param [index]: index of where to put parameters
|
||||
|
||||
def _raveled_index(self):
|
||||
"""
|
||||
get the raveled index for this object,
|
||||
this is not in the global view of things!
|
||||
"""
|
||||
return numpy.r_[:self.size]
|
||||
:param bool _ignore_added_names: whether the name of the parameter overrides a possibly existing field
|
||||
|
||||
#===========================================================================
|
||||
# Convenience for fixed, tied checking of param:
|
||||
#===========================================================================
|
||||
@property
|
||||
def is_fixed(self):
|
||||
for p in self._parameters_:
|
||||
if not p.is_fixed: return False
|
||||
return True
|
||||
Add all parameters to this param class, you can insert parameters
|
||||
at any given index using the :func:`list.insert` syntax
|
||||
"""
|
||||
if param in self.parameters and index is not None:
|
||||
self.remove_parameter(param)
|
||||
self.add_parameter(param, index)
|
||||
# elif param.has_parent():
|
||||
# raise HierarchyError, "parameter {} already in another model ({}), create new object (or copy) for adding".format(param._short(), param._highest_parent_._short())
|
||||
elif param not in self.parameters:
|
||||
if param.has_parent():
|
||||
def visit(parent, self):
|
||||
if parent is self:
|
||||
raise HierarchyError, "You cannot add a parameter twice into the hierarchy"
|
||||
param.traverse_parents(visit, self)
|
||||
param._parent_.remove_parameter(param)
|
||||
# make sure the size is set
|
||||
if index is None:
|
||||
self.constraints.update(param.constraints, self.size)
|
||||
self.priors.update(param.priors, self.size)
|
||||
self.parameters.append(param)
|
||||
else:
|
||||
start = sum(p.size for p in self.parameters[:index])
|
||||
self.constraints.shift_right(start, param.size)
|
||||
self.priors.shift_right(start, param.size)
|
||||
self.constraints.update(param.constraints, start)
|
||||
self.priors.update(param.priors, start)
|
||||
self.parameters.insert(index, param)
|
||||
|
||||
def _get_original(self, param):
|
||||
# if advanced indexing is activated it happens that the array is a copy
|
||||
# you can retrieve the original param through this method, by passing
|
||||
# the copy here
|
||||
return self._parameters_[param._parent_index_]
|
||||
param.add_observer(self, self._pass_through_notify_observers, -np.inf)
|
||||
|
||||
parent = self
|
||||
while parent is not None:
|
||||
parent.size += param.size
|
||||
parent = parent._parent_
|
||||
|
||||
self._connect_parameters()
|
||||
|
||||
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"""
|
||||
|
||||
|
||||
def add_parameters(self, *parameters):
|
||||
"""
|
||||
convenience method for adding several
|
||||
parameters without gradient specification
|
||||
"""
|
||||
[self.add_parameter(p) for p in parameters]
|
||||
|
||||
def remove_parameter(self, param):
|
||||
"""
|
||||
:param param: param object to remove from being a parameter of this parameterized object.
|
||||
"""
|
||||
if not param in self.parameters:
|
||||
raise RuntimeError, "Parameter {} does not belong to this object {}, remove parameters directly from their respective parents".format(param._short(), self.name)
|
||||
|
||||
start = sum([p.size for p in self.parameters[:param._parent_index_]])
|
||||
self._remove_parameter_name(param)
|
||||
self.size -= param.size
|
||||
del self.parameters[param._parent_index_]
|
||||
|
||||
param._disconnect_parent()
|
||||
param.remove_observer(self, self._pass_through_notify_observers)
|
||||
self.constraints.shift_left(start, param.size)
|
||||
|
||||
self._connect_parameters()
|
||||
self._notify_parent_change()
|
||||
|
||||
parent = self._parent_
|
||||
while parent is not None:
|
||||
parent.size -= param.size
|
||||
parent = parent._parent_
|
||||
|
||||
self._highest_parent_._connect_parameters()
|
||||
self._highest_parent_._connect_fixes()
|
||||
self._highest_parent_._notify_parent_change()
|
||||
|
||||
def _connect_parameters(self, ignore_added_names=False):
|
||||
# connect parameterlist to this parameterized object
|
||||
# This just sets up the right connection for the params objects
|
||||
# to be used as parameters
|
||||
# it also sets the constraints for each parameter to the constraints
|
||||
# of their respective parents
|
||||
if not hasattr(self, "parameters") or len(self.parameters) < 1:
|
||||
# no parameters for this class
|
||||
return
|
||||
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)
|
||||
|
||||
old_size = 0
|
||||
self._param_slices_ = []
|
||||
for i, p in enumerate(self.parameters):
|
||||
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
|
||||
|
||||
self._param_slices_.append(pslice)
|
||||
|
||||
self._add_parameter_name(p, ignore_added_names=ignore_added_names)
|
||||
old_size += p.size
|
||||
|
||||
#===========================================================================
|
||||
# Get/set parameters:
|
||||
|
|
@ -200,10 +271,28 @@ class Parameterized(Parameterizable):
|
|||
|
||||
def __setattr__(self, name, val):
|
||||
# override the default behaviour, if setting a param, so broadcasting can by used
|
||||
if hasattr(self, "_parameters_"):
|
||||
if hasattr(self, "parameters"):
|
||||
pnames = self.parameter_names(False, adjust_for_printing=True, recursive=False)
|
||||
if name in pnames: self._parameters_[pnames.index(name)][:] = val; return
|
||||
if name in pnames: self.parameters[pnames.index(name)][:] = val; return
|
||||
object.__setattr__(self, name, val);
|
||||
|
||||
#===========================================================================
|
||||
# Pickling
|
||||
#===========================================================================
|
||||
def __setstate__(self, state):
|
||||
super(Parameterized, self).__setstate__(state)
|
||||
self._connect_parameters()
|
||||
self._connect_fixes()
|
||||
self._notify_parent_change()
|
||||
|
||||
self.parameters_changed()
|
||||
def copy(self):
|
||||
c = super(Parameterized, self).copy()
|
||||
c._connect_parameters()
|
||||
c._connect_fixes()
|
||||
c._notify_parent_change()
|
||||
return c
|
||||
|
||||
#===========================================================================
|
||||
# Printing:
|
||||
#===========================================================================
|
||||
|
|
@ -211,22 +300,22 @@ class Parameterized(Parameterizable):
|
|||
return self.hierarchy_name()
|
||||
@property
|
||||
def flattened_parameters(self):
|
||||
return [xi for x in self._parameters_ for xi in x.flattened_parameters]
|
||||
return [xi for x in self.parameters for xi in x.flattened_parameters]
|
||||
@property
|
||||
def _parameter_sizes_(self):
|
||||
return [x.size for x in self._parameters_]
|
||||
return [x.size for x in self.parameters]
|
||||
@property
|
||||
def parameter_shapes(self):
|
||||
return [xi for x in self._parameters_ for xi in x.parameter_shapes]
|
||||
return [xi for x in self.parameters for xi in x.parameter_shapes]
|
||||
@property
|
||||
def _constraints_str(self):
|
||||
return [cs for p in self._parameters_ for cs in p._constraints_str]
|
||||
return [cs for p in self.parameters for cs in p._constraints_str]
|
||||
@property
|
||||
def _priors_str(self):
|
||||
return [cs for p in self._parameters_ for cs in p._priors_str]
|
||||
return [cs for p in self.parameters for cs in p._priors_str]
|
||||
@property
|
||||
def _description_str(self):
|
||||
return [xi for x in self._parameters_ for xi in x._description_str]
|
||||
return [xi for x in self.parameters for xi in x._description_str]
|
||||
@property
|
||||
def _ties_str(self):
|
||||
return [','.join(x._ties_str) for x in self.flattened_parameters]
|
||||
|
|
@ -246,7 +335,7 @@ class Parameterized(Parameterizable):
|
|||
to_print = []
|
||||
for n, d, c, t, p in itertools.izip(names, desc, constrs, ts, prirs):
|
||||
to_print.append(format_spec.format(name=n, desc=d, const=c, t=t, pri=p))
|
||||
# to_print = [format_spec.format(p=p, const=c, t=t) if isinstance(p, Param) else p.__str__(header=False) for p, c, t in itertools.izip(self._parameters_, constrs, ts)]
|
||||
# to_print = [format_spec.format(p=p, const=c, t=t) if isinstance(p, Param) else p.__str__(header=False) for p, c, t in itertools.izip(self.parameters, constrs, ts)]
|
||||
sep = '-' * (nl + sl + cl + + pl + tl + 8 * 2 + 3)
|
||||
if header:
|
||||
header = " {{0:<{0}s}} | {{1:^{1}s}} | {{2:^{2}s}} | {{3:^{3}s}} | {{4:^{4}s}}".format(nl, sl, cl, pl, tl).format(name, "Value", "Constraint", "Prior", "Tied to")
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class VariationalPosterior(Parameterized):
|
|||
def _raveled_index(self):
|
||||
index = np.empty(dtype=int, shape=0)
|
||||
size = 0
|
||||
for p in self._parameters_:
|
||||
for p in self.parameters:
|
||||
index = np.hstack((index, p._raveled_index()+size))
|
||||
size += p._realsize_ if hasattr(p, '_realsize_') else p.size
|
||||
return index
|
||||
|
|
@ -96,10 +96,13 @@ class VariationalPosterior(Parameterized):
|
|||
dc = self.__dict__.copy()
|
||||
dc['mean'] = self.mean[s]
|
||||
dc['variance'] = self.variance[s]
|
||||
dc['_parameters_'] = copy.copy(self._parameters_)
|
||||
dc['parameters'] = copy.copy(self.parameters)
|
||||
n.__dict__.update(dc)
|
||||
n._parameters_[dc['mean']._parent_index_] = dc['mean']
|
||||
n._parameters_[dc['variance']._parent_index_] = dc['variance']
|
||||
n.parameters[dc['mean']._parent_index_] = dc['mean']
|
||||
n.parameters[dc['variance']._parent_index_] = dc['variance']
|
||||
n._gradient_array_ = None
|
||||
oversize = self.size - self.mean.size - self.variance.size
|
||||
n.size = n.mean.size + n.variance.size + oversize
|
||||
n.ndim = n.mean.ndim
|
||||
n.shape = n.mean.shape
|
||||
n.num_data = n.mean.shape[0]
|
||||
|
|
@ -147,11 +150,11 @@ class SpikeAndSlabPosterior(VariationalPosterior):
|
|||
dc['mean'] = self.mean[s]
|
||||
dc['variance'] = self.variance[s]
|
||||
dc['binary_prob'] = self.binary_prob[s]
|
||||
dc['_parameters_'] = copy.copy(self._parameters_)
|
||||
dc['parameters'] = copy.copy(self.parameters)
|
||||
n.__dict__.update(dc)
|
||||
n._parameters_[dc['mean']._parent_index_] = dc['mean']
|
||||
n._parameters_[dc['variance']._parent_index_] = dc['variance']
|
||||
n._parameters_[dc['binary_prob']._parent_index_] = dc['binary_prob']
|
||||
n.parameters[dc['mean']._parent_index_] = dc['mean']
|
||||
n.parameters[dc['variance']._parent_index_] = dc['variance']
|
||||
n.parameters[dc['binary_prob']._parent_index_] = dc['binary_prob']
|
||||
n.ndim = n.mean.ndim
|
||||
n.shape = n.mean.shape
|
||||
n.num_data = n.mean.shape[0]
|
||||
|
|
|
|||
|
|
@ -79,29 +79,32 @@ class SparseGP(GP):
|
|||
self.Z.gradient = self.kern.gradients_X(self.grad_dict['dL_dKmm'], self.Z)
|
||||
self.Z.gradient += self.kern.gradients_X(self.grad_dict['dL_dKnm'].T, self.Z, self.X)
|
||||
|
||||
def _raw_predict(self, Xnew, full_cov=False):
|
||||
def _raw_predict(self, Xnew, full_cov=False, kern=None):
|
||||
"""
|
||||
Make a prediction for the latent function values
|
||||
"""
|
||||
|
||||
if kern is None: kern = self.kern
|
||||
|
||||
if not isinstance(Xnew, VariationalPosterior):
|
||||
Kx = self.kern.K(self.Z, Xnew)
|
||||
Kx = kern.K(self.Z, Xnew)
|
||||
mu = np.dot(Kx.T, self.posterior.woodbury_vector)
|
||||
if full_cov:
|
||||
Kxx = self.kern.K(Xnew)
|
||||
Kxx = kern.K(Xnew)
|
||||
var = Kxx - np.dot(Kx.T, np.dot(self.posterior.woodbury_inv, Kx))
|
||||
#var = Kxx[:,:,None] - np.tensordot(np.dot(np.atleast_3d(self.posterior.woodbury_inv).T, Kx).T, Kx, [1,0]).swapaxes(1,2)
|
||||
var = var.squeeze()
|
||||
else:
|
||||
Kxx = self.kern.Kdiag(Xnew)
|
||||
Kxx = kern.Kdiag(Xnew)
|
||||
var = (Kxx - np.sum(np.dot(np.atleast_3d(self.posterior.woodbury_inv).T, Kx) * Kx[None,:,:], 1)).T
|
||||
else:
|
||||
Kx = self.kern.psi1(self.Z, Xnew)
|
||||
Kx = kern.psi1(self.Z, Xnew)
|
||||
mu = np.dot(Kx, self.posterior.woodbury_vector)
|
||||
if full_cov:
|
||||
raise NotImplementedError, "TODO"
|
||||
else:
|
||||
Kxx = self.kern.psi0(self.Z, Xnew)
|
||||
psi2 = self.kern.psi2(self.Z, Xnew)
|
||||
Kxx = kern.psi0(self.Z, Xnew)
|
||||
psi2 = kern.psi2(self.Z, Xnew)
|
||||
var = Kxx - np.sum(np.sum(psi2 * Kmmi_LmiBLmi[None, :, :], 1), 1)
|
||||
return mu, var
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue