Handpicked James Hensman's code that ensures that fixes the PDF of transformed variables. Fixed minor plotting bug.

This commit is contained in:
Ilias Bilionis 2015-08-10 16:36:49 -04:00
parent d3730bb518
commit 8b384fd000
2 changed files with 169 additions and 87 deletions

View file

@ -13,11 +13,12 @@ Observable Pattern for patameterization
""" """
from transformations import Transformation,Logexp, NegativeLogexp, Logistic, __fixed__, FIXED, UNFIXED from .transformations import Transformation,Logexp, NegativeLogexp, Logistic, __fixed__, FIXED, UNFIXED
import numpy as np import numpy as np
import re import re
import logging import logging
from updateable import Updateable from .updateable import Updateable
from functools import reduce
class HierarchyError(Exception): class HierarchyError(Exception):
""" """
@ -36,7 +37,7 @@ def adjust_name_for_printing(name):
name = name.replace("/", "_l_").replace("@", '_at_') name = name.replace("/", "_l_").replace("@", '_at_')
name = name.replace("(", "_of_").replace(")", "") name = name.replace("(", "_of_").replace(")", "")
if re.match(r'^[a-zA-Z_][a-zA-Z0-9-_]*$', name) is None: if re.match(r'^[a-zA-Z_][a-zA-Z0-9-_]*$', name) is None:
raise NameError, "name {} converted to {} cannot be further converted to valid python variable name!".format(name2, name) raise NameError("name {} converted to {} cannot be further converted to valid python variable name!".format(name2, name))
return name return name
return '' return ''
@ -65,13 +66,13 @@ class Parentable(object):
Gets called, when the parent changed, so we can adjust our Gets called, when the parent changed, so we can adjust our
inner attributes according to the new parent. inner attributes according to the new parent.
""" """
raise NotImplementedError, "shouldnt happen, Parentable objects need to be able to change their parent" raise NotImplementedError("shouldnt happen, Parentable objects need to be able to change their parent")
def _disconnect_parent(self, *args, **kw): def _disconnect_parent(self, *args, **kw):
""" """
Disconnect this object from its parent Disconnect this object from its parent
""" """
raise NotImplementedError, "Abstract superclass" raise NotImplementedError("Abstract superclass")
@property @property
def _highest_parent_(self): def _highest_parent_(self):
@ -109,7 +110,10 @@ class Pickleable(object):
it properly. it properly.
:param protocol: pickling protocol to use, python-pickle for details. :param protocol: pickling protocol to use, python-pickle for details.
""" """
import cPickle as pickle try: #Py2
import cPickle as pickle
except ImportError: #Py3
import pickle
if isinstance(f, str): if isinstance(f, str):
with open(f, 'wb') as f: with open(f, 'wb') as f:
pickle.dump(self, f, protocol) pickle.dump(self, f, protocol)
@ -138,9 +142,9 @@ class Pickleable(object):
which = self which = self
which.traverse_parents(parents.append) # collect parents which.traverse_parents(parents.append) # collect parents
for p in parents: for p in parents:
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 id(p) in memo :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 id(self.gradient) in memo: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 if not id(self._fixes_) in memo :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 = copy.deepcopy(self, memo) # and start the copy
copy._parent_index_ = None copy._parent_index_ = None
copy._trigger_params_changed() copy._trigger_params_changed()
@ -163,14 +167,16 @@ class Pickleable(object):
'_Cacher_wrap__cachers', # never pickle cachers '_Cacher_wrap__cachers', # never pickle cachers
] ]
dc = dict() dc = dict()
for k,v in self.__dict__.iteritems(): #py3 fix
#for k,v in self.__dict__.iteritems():
for k,v in self.__dict__.items():
if k not in ignore_list: if k not in ignore_list:
dc[k] = v dc[k] = v
return dc return dc
def __setstate__(self, state): def __setstate__(self, state):
self.__dict__.update(state) self.__dict__.update(state)
from lists_and_dicts import ObserverList from .lists_and_dicts import ObserverList
self.observers = ObserverList() self.observers = ObserverList()
self._setup_observers() self._setup_observers()
self._optimizer_copy_transformed = False self._optimizer_copy_transformed = False
@ -214,7 +220,7 @@ class Gradcheckable(Pickleable, Parentable):
Perform the checkgrad on the model. Perform the checkgrad on the model.
TODO: this can be done more efficiently, when doing it inside here TODO: this can be done more efficiently, when doing it inside here
""" """
raise HierarchyError, "This parameter is not in a model with a likelihood, and, therefore, cannot be gradient checked!" raise HierarchyError("This parameter is not in a model with a likelihood, and, therefore, cannot be gradient checked!")
class Nameable(Gradcheckable): class Nameable(Gradcheckable):
""" """
@ -268,7 +274,7 @@ class Indexable(Nameable, Updateable):
def __init__(self, name, default_constraint=None, *a, **kw): def __init__(self, name, default_constraint=None, *a, **kw):
super(Indexable, self).__init__(name=name, *a, **kw) super(Indexable, self).__init__(name=name, *a, **kw)
self._default_constraint_ = default_constraint self._default_constraint_ = default_constraint
from index_operations import ParameterIndexOperations from .index_operations import ParameterIndexOperations
self.constraints = ParameterIndexOperations() self.constraints = ParameterIndexOperations()
self.priors = ParameterIndexOperations() self.priors = ParameterIndexOperations()
if self._default_constraint_ is not None: if self._default_constraint_ is not None:
@ -310,7 +316,7 @@ class Indexable(Nameable, Updateable):
that is an int array, containing the indexes for the flattened that is an int array, containing the indexes for the flattened
param inside this parameterized logic. param inside this parameterized logic.
""" """
from param import ParamConcatenation from .param import ParamConcatenation
if isinstance(param, ParamConcatenation): if isinstance(param, ParamConcatenation):
return np.hstack((self._raveled_index_for(p) for p in param.params)) return np.hstack((self._raveled_index_for(p) for p in param.params))
return param._raveled_index() + self._offset_for(param) return param._raveled_index() + self._offset_for(param)
@ -407,7 +413,7 @@ class Indexable(Nameable, Updateable):
repriorized = self.unset_priors() repriorized = self.unset_priors()
self._add_to_index_operations(self.priors, repriorized, prior, warning) self._add_to_index_operations(self.priors, repriorized, prior, warning)
from domains import _REAL, _POSITIVE, _NEGATIVE from .domains import _REAL, _POSITIVE, _NEGATIVE
if prior.domain is _POSITIVE: if prior.domain is _POSITIVE:
self.constrain_positive(warning) self.constrain_positive(warning)
elif prior.domain is _NEGATIVE: elif prior.domain is _NEGATIVE:
@ -424,19 +430,38 @@ class Indexable(Nameable, Updateable):
def log_prior(self): def log_prior(self):
"""evaluate the prior""" """evaluate the prior"""
if self.priors.size > 0: if self.priors.size == 0:
x = self.param_array return 0.
return reduce(lambda a, b: a + b, (p.lnpdf(x[ind]).sum() for p, ind in self.priors.iteritems()), 0) x = self.param_array
return 0. #evaluate the prior log densities
log_p = reduce(lambda a, b: a + b, (p.lnpdf(x[ind]).sum() for p, ind in self.priors.items()), 0)
#account for the transformation by evaluating the log Jacobian (where things are transformed)
log_j = 0.
priored_indexes = np.hstack([i for p, i in self.priors.items()])
for c,j in self.constraints.items():
if not isinstance(c, Transformation):continue
for jj in j:
if jj in priored_indexes:
log_j += c.log_jacobian(x[jj])
return log_p + log_j
def _log_prior_gradients(self): def _log_prior_gradients(self):
"""evaluate the gradients of the priors""" """evaluate the gradients of the priors"""
if self.priors.size > 0: if self.priors.size == 0:
x = self.param_array return 0.
ret = np.zeros(x.size) x = self.param_array
[np.put(ret, ind, p.lnpdf_grad(x[ind])) for p, ind in self.priors.iteritems()] ret = np.zeros(x.size)
return ret #compute derivate of prior density
return 0. [np.put(ret, ind, p.lnpdf_grad(x[ind])) for p, ind in self.priors.items()]
#add in jacobian derivatives if transformed
priored_indexes = np.hstack([i for p, i in self.priors.items()])
for c,j in self.constraints.items():
if not isinstance(c, Transformation):continue
for jj in j:
if jj in priored_indexes:
ret[jj] += c.log_jacobian_grad(x[jj])
return ret
#=========================================================================== #===========================================================================
# Tie parameters together # Tie parameters together
@ -471,7 +496,7 @@ class Indexable(Nameable, Updateable):
self.param_array[...] = transform.initialize(self.param_array) self.param_array[...] = transform.initialize(self.param_array)
reconstrained = self.unconstrain() reconstrained = self.unconstrain()
added = self._add_to_index_operations(self.constraints, reconstrained, transform, warning) added = self._add_to_index_operations(self.constraints, reconstrained, transform, warning)
self.notify_observers(self, None if trigger_parent else -np.inf) self.trigger_update(trigger_parent)
return added return added
def unconstrain(self, *transforms): def unconstrain(self, *transforms):
@ -536,7 +561,7 @@ class Indexable(Nameable, Updateable):
update the constraints and priors view, so that update the constraints and priors view, so that
constraining is automized for the parent. constraining is automized for the parent.
""" """
from index_operations import ParameterIndexOperationsView from .index_operations import ParameterIndexOperationsView
#if getattr(self, "_in_init_"): #if getattr(self, "_in_init_"):
#import ipdb;ipdb.set_trace() #import ipdb;ipdb.set_trace()
#self.constraints.update(param.constraints, start) #self.constraints.update(param.constraints, start)
@ -558,7 +583,7 @@ class Indexable(Nameable, Updateable):
""" """
if warning and reconstrained.size > 0: if warning and reconstrained.size > 0:
# TODO: figure out which parameters have changed and only print those # TODO: figure out which parameters have changed and only print those
print "WARNING: reconstraining parameters {}".format(self.hierarchy_name() or self.name) print("WARNING: reconstraining parameters {}".format(self.hierarchy_name() or self.name))
index = self._raveled_index() index = self._raveled_index()
which.add(what, index) which.add(what, index)
return index return index
@ -571,7 +596,7 @@ class Indexable(Nameable, Updateable):
if len(transforms) == 0: if len(transforms) == 0:
transforms = which.properties() transforms = which.properties()
removed = np.empty((0,), dtype=int) removed = np.empty((0,), dtype=int)
for t in transforms: for t in list(transforms):
unconstrained = which.remove(t, self._raveled_index()) unconstrained = which.remove(t, self._raveled_index())
removed = np.union1d(removed, unconstrained) removed = np.union1d(removed, unconstrained)
if t is __fixed__: if t is __fixed__:
@ -612,7 +637,9 @@ class OptimizationHandlable(Indexable):
if not self._optimizer_copy_transformed: if not self._optimizer_copy_transformed:
self._optimizer_copy_.flat = self.param_array.flat 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__] #py3 fix
#[np.put(self._optimizer_copy_, ind, c.finv(self.param_array[ind])) for c, ind in self.constraints.iteritems() if c != __fixed__]
[np.put(self._optimizer_copy_, ind, c.finv(self.param_array[ind])) for c, ind in self.constraints.items() if c != __fixed__]
if self.has_parent() and (self.constraints[__fixed__].size != 0 or self._has_ties()): if self.has_parent() and (self.constraints[__fixed__].size != 0 or self._has_ties()):
fixes = np.ones(self.size).astype(bool) fixes = np.ones(self.size).astype(bool)
fixes[self.constraints[__fixed__]] = FIXED fixes[self.constraints[__fixed__]] = FIXED
@ -641,21 +668,25 @@ class OptimizationHandlable(Indexable):
if f is None: if f is None:
self.param_array.flat = p self.param_array.flat = p
[np.put(self.param_array, ind, c.f(self.param_array.flat[ind])) [np.put(self.param_array, ind, c.f(self.param_array.flat[ind]))
for c, ind in self.constraints.iteritems() if c != __fixed__] #py3 fix
#for c, ind in self.constraints.iteritems() if c != __fixed__]
for c, ind in self.constraints.items() if c != __fixed__]
else: else:
self.param_array.flat[f] = p self.param_array.flat[f] = p
[np.put(self.param_array, ind[f[ind]], c.f(self.param_array.flat[ind[f[ind]]])) [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__] #py3 fix
#for c, ind in self.constraints.iteritems() if c != __fixed__]
for c, ind in self.constraints.items() if c != __fixed__]
#self._highest_parent_.tie.propagate_val() #self._highest_parent_.tie.propagate_val()
self._optimizer_copy_transformed = False self._optimizer_copy_transformed = False
self.trigger_update() self.trigger_update()
def _get_params_transformed(self): def _get_params_transformed(self):
raise DeprecationWarning, "_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!" raise DeprecationWarning("_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!")
# #
def _set_params_transformed(self, p): def _set_params_transformed(self, p):
raise DeprecationWarning, "_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!" raise DeprecationWarning("_get|set_params{_optimizer_copy_transformed} is deprecated, use self.optimizer array insetad!")
def _trigger_params_changed(self, trigger_parent=True): def _trigger_params_changed(self, trigger_parent=True):
""" """
@ -680,19 +711,24 @@ class OptimizationHandlable(Indexable):
constraint to it. constraint to it.
""" """
self._highest_parent_.tie.collate_gradient() self._highest_parent_.tie.collate_gradient()
[np.put(g, i, c.gradfactor(self.param_array[i], g[i])) for c, i in self.constraints.iteritems() if c != __fixed__] #py3 fix
#[np.put(g, i, c.gradfactor(self.param_array[i], g[i])) for c, i in self.constraints.iteritems() if c != __fixed__]
[np.put(g, i, c.gradfactor(self.param_array[i], g[i])) for c, i in self.constraints.items() if c != __fixed__]
if self._has_fixes(): return g[self._fixes_] if self._has_fixes(): return g[self._fixes_]
return g return g
def _log_det_jacobian(self): def _transform_gradients_non_natural(self, g):
""" """
Return the logarithm of the Jacobian needed for a proper change of Transform the gradients by multiplying the gradient factor for each
variables. constraint to it.
""" """
J = np.ones(self.param_array.shape) self._highest_parent_.tie.collate_gradient()
[np.put(J, i, c.jacobianfactor(self.param_array[i])) #py3 fix
for c, i in self.constraints.iteritems() if c != __fixed__] #[np.put(g, i, c.gradfactor_non_natural(self.param_array[i], g[i])) for c, i in self.constraints.iteritems() if c != __fixed__]
return np.log(J).sum() [np.put(g, i, c.gradfactor_non_natural(self.param_array[i], g[i])) for c, i in self.constraints.items() if c != __fixed__]
if self._has_fixes(): return g[self._fixes_]
return g
@property @property
def num_params(self): def num_params(self):
@ -700,7 +736,7 @@ class OptimizationHandlable(Indexable):
Return the number of parameters of this parameter_handle. Return the number of parameters of this parameter_handle.
Param objects will always return 0. Param objects will always return 0.
""" """
raise NotImplemented, "Abstract, please implement in respective classes" raise NotImplemented("Abstract, please implement in respective classes")
def parameter_names(self, add_self=False, adjust_for_printing=False, recursive=True): def parameter_names(self, add_self=False, adjust_for_printing=False, recursive=True):
""" """
@ -749,7 +785,9 @@ class OptimizationHandlable(Indexable):
self.optimizer_array = 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...)
# now draw from prior where possible # now draw from prior where possible
x = self.param_array.copy() x = self.param_array.copy()
[np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.iteritems() if not p is None] #Py3 fix
#[np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.iteritems() if not p is None]
[np.put(x, ind, p.rvs(ind.size)) for p, ind in self.priors.items() if not p is None]
unfixlist = np.ones((self.size,),dtype=np.bool) unfixlist = np.ones((self.size,),dtype=np.bool)
unfixlist[self.constraints[__fixed__]] = False unfixlist[self.constraints[__fixed__]] = False
self.param_array.flat[unfixlist] = x.view(np.ndarray).ravel()[unfixlist] self.param_array.flat[unfixlist] = x.view(np.ndarray).ravel()[unfixlist]
@ -808,7 +846,7 @@ class Parameterizable(OptimizationHandlable):
A parameterisable class. A parameterisable class.
This class provides the parameters list (ArrayList) and standard parameter handling, This class provides the parameters list (ArrayList) and standard parameter handling,
such as {add|remove}_parameter(), traverse hierarchy and param_array, gradient_array such as {link|unlink}_parameter(), traverse hierarchy and param_array, gradient_array
and the empty parameters_changed(). and the empty parameters_changed().
This class is abstract and should not be instantiated. This class is abstract and should not be instantiated.
@ -946,7 +984,7 @@ class Parameterizable(OptimizationHandlable):
self._add_parameter_name(param, ignore_added_names) self._add_parameter_name(param, ignore_added_names)
# and makes sure to not delete programmatically added parameters # and makes sure to not delete programmatically added parameters
for other in self.parameters[::-1]: for other in self.parameters[::-1]:
if other is not param and other.name.startswith(param.name): if other is not param and other.name == param.name:
warn_and_retry(param, _name_digit.match(other.name)) warn_and_retry(param, _name_digit.match(other.name))
return return
if pname not in dir(self): if pname not in dir(self):
@ -1041,6 +1079,9 @@ class Parameterizable(OptimizationHandlable):
p = param_to_array(p) p = param_to_array(p)
d = f.create_dataset(n,p.shape,dtype=p.dtype) d = f.create_dataset(n,p.shape,dtype=p.dtype)
d[:] = p d[:] = p
if hasattr(self, 'param_array'):
d = f.create_dataset('param_array',self.param_array.shape, dtype=self.param_array.dtype)
d[:] = self.param_array
f.close() f.close()
except: except:
raise 'Fails to write the parameters into a HDF5 file!' raise 'Fails to write the parameters into a HDF5 file!'

View file

@ -3,7 +3,7 @@
import numpy as np import numpy as np
from domains import _POSITIVE,_NEGATIVE, _BOUNDED from .domains import _POSITIVE,_NEGATIVE, _BOUNDED
import weakref import weakref
import sys import sys
@ -31,13 +31,16 @@ class Transformation(object):
raise NotImplementedError raise NotImplementedError
def finv(self, model_param): def finv(self, model_param):
raise NotImplementedError raise NotImplementedError
def jacobianfactor(self, model_param): def log_jacobian(self, model_param):
""" """
Return log|det J| where J is the Jacobian of the inverse of the compute the log of the jacobian of f, evaluated at f(x)= model_param
transformation.
""" """
return np.abs([self.gradfactor(np.array([theta]), np.ones((1,)))[0] raise NotImplementedError
for theta in model_param]) def log_jacobian_grad(self, model_param):
"""
compute the drivative of the log of the jacobian of f, evaluated at f(x)= model_param
"""
raise NotImplementedError
def gradfactor(self, model_param, dL_dmodel_param): def gradfactor(self, model_param, dL_dmodel_param):
""" df(opt_param)_dopt_param evaluated at self.f(opt_param)=model_param, times the gradient dL_dmodel_param, """ df(opt_param)_dopt_param evaluated at self.f(opt_param)=model_param, times the gradient dL_dmodel_param,
@ -49,6 +52,8 @@ class Transformation(object):
\frac{\frac{\partial L}{\partial f}\left(\left.\partial f(x)}{\partial x}\right|_{x=f^{-1}(f)\right)} \frac{\frac{\partial L}{\partial f}\left(\left.\partial f(x)}{\partial x}\right|_{x=f^{-1}(f)\right)}
""" """
raise NotImplementedError raise NotImplementedError
def gradfactor_non_natural(self, model_param, dL_dmodel_param):
return self.gradfactor(model_param, dL_dmodel_param)
def initialize(self, f): def initialize(self, f):
""" produce a sensible initial value for f(x)""" """ produce a sensible initial value for f(x)"""
raise NotImplementedError raise NotImplementedError
@ -57,12 +62,10 @@ class Transformation(object):
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from ...plotting.matplot_dep import base_plots from ...plotting.matplot_dep import base_plots
x = np.linspace(-8,8) x = np.linspace(-8,8)
base_plots.meanplot(x, self.f(x), ax=axes, *args, **kw) base_plots.meanplot(x, self.f(x),axes=axes*args,**kw)
axes = plt.gca() axes = plt.gca()
axes.set_xlabel(xlabel) axes.set_xlabel(xlabel)
axes.set_ylabel(ylabel) axes.set_ylabel(ylabel)
return axes
def __str__(self): def __str__(self):
raise NotImplementedError raise NotImplementedError
def __repr__(self): def __repr__(self):
@ -74,20 +77,46 @@ class Logexp(Transformation):
return np.where(x>_lim_val, x, np.log1p(np.exp(np.clip(x, -_lim_val, _lim_val)))) + epsilon return np.where(x>_lim_val, x, np.log1p(np.exp(np.clip(x, -_lim_val, _lim_val)))) + epsilon
#raises overflow warning: return np.where(x>_lim_val, x, np.log(1. + np.exp(x))) #raises overflow warning: return np.where(x>_lim_val, x, np.log(1. + np.exp(x)))
def finv(self, f): def finv(self, f):
return np.where(f>_lim_val, f, np.log(np.exp(f+epsilon) - 1.)) return np.where(f>_lim_val, f, np.log(np.exp(f+1e-20) - 1.))
def gradfactor(self, f, df): def gradfactor(self, f, df):
return np.einsum('i,i->i', df, np.where(f>_lim_val, 1., 1. - np.exp(-f))) return np.einsum('i,i->i', df, np.where(f>_lim_val, 1., 1. - np.exp(-f)))
def initialize(self, f): def initialize(self, f):
if np.any(f < 0.): if np.any(f < 0.):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
return np.abs(f) return np.abs(f)
def log_jacobian(self, model_param):
return np.where(model_param>_lim_val, model_param, np.log(np.exp(model_param+1e-20) - 1.)) - model_param
def log_jacobian_grad(self, model_param):
return 1./(np.exp(model_param)-1.)
def __str__(self):
return '+ve'
class Exponent(Transformation):
domain = _POSITIVE
def f(self, x):
return np.where(x<_lim_val, np.where(x>-_lim_val, np.exp(x), np.exp(-_lim_val)), np.exp(_lim_val))
def finv(self, x):
return np.log(x)
def gradfactor(self, f, df):
return np.einsum('i,i->i', df, f)
def initialize(self, f):
if np.any(f < 0.):
print("Warning: changing parameters to satisfy constraints")
return np.abs(f)
def log_jacobian(self, model_param):
return np.log(model_param)
def log_jacobian_grad(self, model_param):
return 1./model_param
def __str__(self): def __str__(self):
return '+ve' return '+ve'
class NormalTheta(Transformation): class NormalTheta(Transformation):
"Do not use, not officially supported!"
_instances = [] _instances = []
def __new__(cls, mu_indices, var_indices): def __new__(cls, mu_indices=None, var_indices=None):
"Do not use, not officially supported!"
if cls._instances: if cls._instances:
cls._instances[:] = [instance for instance in cls._instances if instance()] cls._instances[:] = [instance for instance in cls._instances if instance()]
for instance in cls._instances: for instance in cls._instances:
@ -107,6 +136,7 @@ class NormalTheta(Transformation):
# that the values are ok # that the values are ok
# Before: # Before:
theta[self.var_indices] = np.abs(-.5/theta[self.var_indices]) theta[self.var_indices] = np.abs(-.5/theta[self.var_indices])
#theta[self.var_indices] = np.exp(-.5/theta[self.var_indices])
theta[self.mu_indices] *= theta[self.var_indices] theta[self.mu_indices] *= theta[self.var_indices]
return theta # which is now {mu, var} return theta # which is now {mu, var}
@ -115,6 +145,7 @@ class NormalTheta(Transformation):
varp = muvar[self.var_indices] varp = muvar[self.var_indices]
muvar[self.mu_indices] /= varp muvar[self.mu_indices] /= varp
muvar[self.var_indices] = -.5/varp muvar[self.var_indices] = -.5/varp
#muvar[self.var_indices] = -.5/np.log(varp)
return muvar # which is now {theta1, theta2} return muvar # which is now {theta1, theta2}
@ -133,7 +164,7 @@ class NormalTheta(Transformation):
def initialize(self, f): def initialize(self, f):
if np.any(f[self.var_indices] < 0.): if np.any(f[self.var_indices] < 0.):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
f[self.var_indices] = np.abs(f[self.var_indices]) f[self.var_indices] = np.abs(f[self.var_indices])
return f return f
@ -148,9 +179,10 @@ class NormalTheta(Transformation):
self.var_indices = state[1] self.var_indices = state[1]
class NormalNaturalAntti(NormalTheta): class NormalNaturalAntti(NormalTheta):
"Do not use, not officially supported!"
_instances = [] _instances = []
_logexp = Logexp() def __new__(cls, mu_indices=None, var_indices=None):
def __new__(cls, mu_indices, var_indices): "Do not use, not officially supported!"
if cls._instances: if cls._instances:
cls._instances[:] = [instance for instance in cls._instances if instance()] cls._instances[:] = [instance for instance in cls._instances if instance()]
for instance in cls._instances: for instance in cls._instances:
@ -179,7 +211,7 @@ class NormalNaturalAntti(NormalTheta):
def initialize(self, f): def initialize(self, f):
if np.any(f[self.var_indices] < 0.): if np.any(f[self.var_indices] < 0.):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
f[self.var_indices] = np.abs(f[self.var_indices]) f[self.var_indices] = np.abs(f[self.var_indices])
return f return f
@ -187,8 +219,10 @@ class NormalNaturalAntti(NormalTheta):
return "natantti" return "natantti"
class NormalEta(Transformation): class NormalEta(Transformation):
"Do not use, not officially supported!"
_instances = [] _instances = []
def __new__(cls, mu_indices, var_indices): def __new__(cls, mu_indices=None, var_indices=None):
"Do not use, not officially supported!"
if cls._instances: if cls._instances:
cls._instances[:] = [instance for instance in cls._instances if instance()] cls._instances[:] = [instance for instance in cls._instances if instance()]
for instance in cls._instances: for instance in cls._instances:
@ -220,7 +254,7 @@ class NormalEta(Transformation):
def initialize(self, f): def initialize(self, f):
if np.any(f[self.var_indices] < 0.): if np.any(f[self.var_indices] < 0.):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
f[self.var_indices] = np.abs(f[self.var_indices]) f[self.var_indices] = np.abs(f[self.var_indices])
return f return f
@ -228,8 +262,10 @@ class NormalEta(Transformation):
return "eta" return "eta"
class NormalNaturalThroughTheta(NormalTheta): class NormalNaturalThroughTheta(NormalTheta):
"Do not use, not officially supported!"
_instances = [] _instances = []
def __new__(cls, mu_indices, var_indices): def __new__(cls, mu_indices=None, var_indices=None):
"Do not use, not officially supported!"
if cls._instances: if cls._instances:
cls._instances[:] = [instance for instance in cls._instances if instance()] cls._instances[:] = [instance for instance in cls._instances if instance()]
for instance in cls._instances: for instance in cls._instances:
@ -259,13 +295,28 @@ class NormalNaturalThroughTheta(NormalTheta):
#======================================================================= #=======================================================================
return dmuvar # which is now the gradient multiplicator return dmuvar # which is now the gradient multiplicator
def gradfactor_non_natural(self, muvar, dmuvar):
mu = muvar[self.mu_indices]
var = muvar[self.var_indices]
#=======================================================================
# theta gradients
# This works and the gradient checks!
dmuvar[self.mu_indices] *= var
dmuvar[self.var_indices] *= 2*(var)**2
dmuvar[self.var_indices] += 2*dmuvar[self.mu_indices]*mu
#=======================================================================
return dmuvar # which is now the gradient multiplicator for {theta1, theta2}
def __str__(self): def __str__(self):
return "natgrad" return "natgrad"
class NormalNaturalWhooot(NormalTheta): class NormalNaturalWhooot(NormalTheta):
"Do not use, not officially supported!"
_instances = [] _instances = []
def __new__(cls, mu_indices, var_indices): def __new__(cls, mu_indices=None, var_indices=None):
"Do not use, not officially supported!"
if cls._instances: if cls._instances:
cls._instances[:] = [instance for instance in cls._instances if instance()] cls._instances[:] = [instance for instance in cls._instances if instance()]
for instance in cls._instances: for instance in cls._instances:
@ -299,8 +350,10 @@ class NormalNaturalWhooot(NormalTheta):
return "natgrad" return "natgrad"
class NormalNaturalThroughEta(NormalEta): class NormalNaturalThroughEta(NormalEta):
"Do not use, not officially supported!"
_instances = [] _instances = []
def __new__(cls, mu_indices, var_indices): def __new__(cls, mu_indices=None, var_indices=None):
"Do not use, not officially supported!"
if cls._instances: if cls._instances:
cls._instances[:] = [instance for instance in cls._instances if instance()] cls._instances[:] = [instance for instance in cls._instances if instance()]
for instance in cls._instances: for instance in cls._instances:
@ -341,7 +394,7 @@ class LogexpNeg(Transformation):
return np.einsum('i,i->i', df, np.where(f>_lim_val, -1, -1 + np.exp(-f))) return np.einsum('i,i->i', df, np.where(f>_lim_val, -1, -1 + np.exp(-f)))
def initialize(self, f): def initialize(self, f):
if np.any(f < 0.): if np.any(f < 0.):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
return np.abs(f) return np.abs(f)
def __str__(self): def __str__(self):
return '+ve' return '+ve'
@ -393,27 +446,11 @@ class LogexpClipped(Logexp):
return np.einsum('i,i->i', df, gf) # np.where(f < self.lower, 0, gf) return np.einsum('i,i->i', df, gf) # np.where(f < self.lower, 0, gf)
def initialize(self, f): def initialize(self, f):
if np.any(f < 0.): if np.any(f < 0.):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
return np.abs(f) return np.abs(f)
def __str__(self): def __str__(self):
return '+ve_c' return '+ve_c'
class Exponent(Transformation):
# TODO: can't allow this to go to zero, need to set a lower bound. Similar with negative Exponent below. See old MATLAB code.
domain = _POSITIVE
def f(self, x):
return np.where(x<_lim_val, np.where(x>-_lim_val, np.exp(x), np.exp(-_lim_val)), np.exp(_lim_val))
def finv(self, x):
return np.log(x)
def gradfactor(self, f, df):
return np.einsum('i,i->i', df, f)
def initialize(self, f):
if np.any(f < 0.):
print "Warning: changing parameters to satisfy constraints"
return np.abs(f)
def __str__(self):
return '+ve'
class NegativeExponent(Exponent): class NegativeExponent(Exponent):
domain = _NEGATIVE domain = _NEGATIVE
def f(self, x): def f(self, x):
@ -449,7 +486,11 @@ class Logistic(Transformation):
for instance in cls._instances: for instance in cls._instances:
if instance().lower == lower and instance().upper == upper: if instance().lower == lower and instance().upper == upper:
return instance() return instance()
o = super(Transformation, cls).__new__(cls, lower, upper, *args, **kwargs) newfunc = super(Transformation, cls).__new__
if newfunc is object.__new__:
o = newfunc(cls)
else:
o = newfunc(cls, lower, upper, *args, **kwargs)
cls._instances.append(weakref.ref(o)) cls._instances.append(weakref.ref(o))
return cls._instances[-1]() return cls._instances[-1]()
def __init__(self, lower, upper): def __init__(self, lower, upper):
@ -467,7 +508,7 @@ class Logistic(Transformation):
return np.einsum('i,i->i', df, (f - self.lower) * (self.upper - f) / self.difference) return np.einsum('i,i->i', df, (f - self.lower) * (self.upper - f) / self.difference)
def initialize(self, f): def initialize(self, f):
if np.any(np.logical_or(f < self.lower, f > self.upper)): if np.any(np.logical_or(f < self.lower, f > self.upper)):
print "Warning: changing parameters to satisfy constraints" print("Warning: changing parameters to satisfy constraints")
#return np.where(np.logical_or(f < self.lower, f > self.upper), self.f(f * 0.), f) #return np.where(np.logical_or(f < self.lower, f > self.upper), self.f(f * 0.), f)
#FIXME: Max, zeros_like right? #FIXME: Max, zeros_like right?
return np.where(np.logical_or(f < self.lower, f > self.upper), self.f(np.zeros_like(f)), f) return np.where(np.logical_or(f < self.lower, f > self.upper), self.f(np.zeros_like(f)), f)