1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
################################################################################
# Copyright (C) 2016 Jaakko Luttinen
#
# This file is licensed under the MIT License.
################################################################################
import numpy as np
from .node import Moments
from .deterministic import Deterministic
from .stochastic import Stochastic
class DeltaMoments(Moments):
r"""
Class for the moments of constants or delta distributed variables
"""
def __init__(self, shape):
self.shape = shape
self.dims = (shape,)
return super().__init__()
@classmethod
def from_values(cls, x, ndim):
if np.ndim(x) < ndim:
raise ValueError("Broadcasting not (yet) supported in DeltaMoments")
if ndim == 0:
return cls(())
else:
return cls(np.shape(x)[-ndim:])
def get_converter(self, moments_to):
if issubclass(DeltaMoments, moments_to):
return lambda x: x
return get_delta_moments_class_converter(moments_to)
def compute_fixed_moments(self, x):
r"""
Compute the moments for a fixed value
"""
return [x]
def compute_dims_from_values(self, x):
r"""
Return the shape of the moments for a fixed value.
"""
return ((),)
def get_instance_conversion_kwargs(self):
return dict(shape=self.shape)
def get_instance_converter(self, shape):
if shape != self.shape:
raise ValueError()
return None
class DeltaClassConverterMoments(Moments):
def __init__(self, x, moments_class):
self.x = x
self.moments_class = moments_class
return
def get_instance_conversion_kwargs(self):
return dict(i_am_delta=True)
def get_instance_converter(self, **kwargs):
if kwargs.get('i_am_delta'):
return None
moments = self.moments_class.from_values(
self.x.get_moments()[0],
**kwargs
)
return DeltaInstanceConverter(moments)
def get_delta_moments_class_converter(moments_class):
class DeltaClassConverter(Deterministic):
def __init__(self, node):
self._parent_moments = (node._moments,)
self._moments = DeltaClassConverterMoments(node, moments_class)
return super().__init__(node, dims=((),))
def _compute_moments(self, u):
return u
def _compute_message_to_parent(self, index, m, u):
return m
return DeltaClassConverter
class DeltaInstanceConverter():
def __init__(self, moments):
self.moments = moments
return
def compute_moments(self, u):
return self.moments.compute_fixed_moments(u[0])
def compute_message_to_parent(self, m, u_parent):
x = u_parent[0]
(u, du) = self.moments.compute_fixed_moments(x, gradient=m)
return [du]
def compute_weights_to_parent(self, weights):
return 1
def plates_multiplier_from_parent(self, plates_multiplier):
return ()
def plates_from_parent(self, plates):
return self.moments.plates_from_shape(plates)
def plates_to_parent(self, plates):
return self.moments.shape_from_plates(plates)
class MaximumLikelihood(Stochastic):
_parent_moments = ()
def __init__(self, array, regularization=None, **kwargs):
self._x = array
self._moments = DeltaMoments(np.shape(array))
self._regularization = regularization
return super().__init__(
plates=np.shape(array),
dims=( (), ),
initialize=False,
**kwargs
)
def _get_id_list(self):
return []
def get_moments(self):
return [self._x]
def lower_bound_contribution(self, ignore_masked=None):
if self._regularization is None:
return 0
return -np.sum(self._regularization(self._x))
def get_riemannian_gradient(self):
m_children = self._message_from_children(u_self=self.get_moments())
g = m_children
# TODO/FIXME: REGULARIZATION GRADIENT!!
return g
def get_gradient(self, rg):
return rg
def get_parameters(self):
return [self._x]
def set_parameters(self, x):
if len(x) != 1:
raise Exception("Wrong number of parameters. Should be 1, is {0}".format(len(x)))
self._x = x[0]
return
def _update_distribution_and_lowerbound(self, m):
raise NotImplementedError()
class Function(Deterministic):
def __init__(self, function, *nodes_gradients, shape=None, **kwargs):
self._function = function
(nodes, gradients) = zip(*nodes_gradients)
self._parent_moments = tuple(node._moments for node in nodes)
self._gradients = gradients
if shape is None:
# Shape wasn't given explicitly. Computes the output value once to
# determine the shape.
y = self._compute_moments(
*[
node.get_moments()
for node in nodes
]
)
shape = np.shape(y[0])
self._moments = DeltaMoments(shape)
return super().__init__(*nodes, dims=((),), **kwargs)
def _compute_moments(self, *u_nodes):
x = [u[0] for u in u_nodes]
return [self._function(*x)]
def _compute_message_to_parent(self, index, m, *u_nodes):
x = [u[0] for u in u_nodes]
return [self._gradients[index](m[0], *x)]
def _compute_weights_to_parent(self, index, mask):
return 1
def _compute_plates_from_parent(self, index, plates):
return self._moments.shape
def _compute_plates_to_parent(self, index, plates):
return self.parents[index].plates
|