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 import six
32 import numpy; np = numpy
33 from re import compile, _pattern_type
34
35 from .core.parameter_core import Parameterizable, adjust_name_for_printing
36 from .core import HierarchyError
37
38 import logging
39 from collections import OrderedDict
40 from functools import reduce
41 logger = logging.getLogger("parameters changed meta")
61
62 from six import with_metaclass
63
64 -class Parameterized(with_metaclass(ParametersChangedMeta, Parameterizable)):
65 """
66 Say m is a handle to a parameterized class.
67
68 Printing parameters::
69
70 - print m: prints a nice summary over all parameters
71 - print m.name: prints details for param with name 'name'
72 - print m[regexp]: prints details for all the parameters
73 which match (!) regexp
74 - print m['']: prints details for all parameters
75
76 Fields::
77
78 Name: The name of the param, can be renamed!
79 Value: Shape or value, if one-valued
80 Constrain: constraint of the param, curly "{c}" brackets indicate
81 some parameters are constrained by c. See detailed print
82 to get exact constraints.
83 Tied_to: which paramter it is tied to.
84
85 Getting and setting parameters::
86
87 - Set all values in param to one: m.name.to.param = 1
88 - Set all values in parameterized: m.name[:] = 1
89 - Set values to random values: m[:] = np.random.norm(m.size)
90
91 Handling of constraining, fixing and tieing parameters::
92
93 - You can constrain parameters by calling the constrain on the param itself, e.g:
94
95 - m.name[:,1].constrain_positive()
96 - m.name[0].tie_to(m.name[1])
97
98 - Fixing parameters will fix them to the value they are right now. If you change
99 the parameters value, the param will be fixed to the new value!
100
101 - If you want to operate on all parameters use m[''] to wildcard select all paramters
102 and concatenate them. Printing m[''] will result in printing of all parameters in detail.
103 """
104
105
106
107
108
109
110
111
112 - def __init__(self, name=None, parameters=[]):
120
121
122
123
125 """
126 :param parameters: the parameters to add
127 :type parameters: list of or one :py:class:`paramz.param.Param`
128 :param [index]: index of where to put parameters
129
130 Add all parameters to this param class, you can insert parameters
131 at any given index using the :func:`list.insert` syntax
132 """
133 if param in self.parameters and index is not None:
134 self.unlink_parameter(param)
135 return self.link_parameter(param, index)
136
137
138 elif param not in self.parameters:
139 if param.has_parent():
140 def visit(parent, self):
141 if parent is self:
142 raise HierarchyError("You cannot add a parameter twice into the hierarchy")
143 param.traverse_parents(visit, self)
144 param._parent_.unlink_parameter(param)
145
146 if index is None:
147 start = sum(p.size for p in self.parameters)
148 for name, iop in self._index_operations.items():
149 iop.shift_right(start, param.size)
150 iop.update(param._index_operations[name], self.size)
151 param._parent_ = self
152 param._parent_index_ = len(self.parameters)
153 self.parameters.append(param)
154 else:
155 start = sum(p.size for p in self.parameters[:index])
156 for name, iop in self._index_operations.items():
157 iop.shift_right(start, param.size)
158 iop.update(param._index_operations[name], start)
159 param._parent_ = self
160 param._parent_index_ = index if index>=0 else len(self.parameters[:index])
161 for p in self.parameters[index:]:
162 p._parent_index_ += 1
163 self.parameters.insert(index, param)
164
165 param.add_observer(self, self._pass_through_notify_observers, -np.inf)
166
167 parent = self
168 while parent is not None:
169 parent.size += param.size
170 parent = parent._parent_
171 self._notify_parent_change()
172
173 if not self._in_init_ and self._highest_parent_._model_initialized_:
174
175
176
177 self._highest_parent_._connect_parameters()
178 self._highest_parent_._notify_parent_change()
179 self._highest_parent_._connect_fixes()
180 return param
181 else:
182 raise HierarchyError("""Parameter exists already, try making a copy""")
183
184
186 """
187 convenience method for adding several
188 parameters without gradient specification
189 """
190 [self.link_parameter(p) for p in parameters]
191
224
226
227
228
229
230
231 self._model_initialized_ = True
232
233 if not hasattr(self, "parameters") or len(self.parameters) < 1:
234
235 return
236
237 old_size = 0
238 self._param_slices_ = []
239 for i, p in enumerate(self.parameters):
240 if not p.param_array.flags['C_CONTIGUOUS']:
241 raise ValueError("""
242 Have you added an additional dimension to a Param object?
243
244 p[:,None], where p is of type Param does not work
245 and is expected to fail! Try increasing the
246 dimensionality of the param array before making
247 a Param out of it:
248 p = Param("<name>", array[:,None])
249
250 Otherwise this should not happen!
251 Please write an email to the developers with the code,
252 which reproduces this error.
253 All parameter arrays must be C_CONTIGUOUS
254 """)
255
256 p._parent_ = self
257 p._parent_index_ = i
258
259 pslice = slice(old_size, old_size + p.size)
260
261
262 p._propagate_param_grad(self.param_array[pslice], self.gradient_full[pslice])
263
264
265 self.param_array[pslice] = p.param_array.flat
266 self.gradient_full[pslice] = p.gradient_full.flat
267
268 p.param_array.data = self.param_array[pslice].data
269 p.gradient_full.data = self.gradient_full[pslice].data
270
271 self._param_slices_.append(pslice)
272
273 self._add_parameter_name(p)
274 old_size += p.size
275
276
277
278
280 """
281 create a list of parameters, matching regular expression regexp
282 """
283 if not isinstance(regexp, _pattern_type): regexp = compile(regexp)
284 found_params = []
285 def visit(innerself, regexp):
286 if (innerself is not self) and regexp.match(innerself.hierarchy_name().partition('.')[2]):
287 found_params.append(innerself)
288 self.traverse(visit, regexp)
289 return found_params
290
306
308 if not self._model_initialized_:
309 raise AttributeError("""Model is not initialized, this change will only be reflected after initialization if in leaf.
310
311 If you are loading a model, set updates off, then initialize, then set the values, then update the model to be fully initialized:
312 >>> m.update_model(False)
313 >>> m.initialize_parameter()
314 >>> m[:] = loaded_parameters
315 >>> m.update_model(True)
316 """)
317 if value is None:
318 return
319 if isinstance(name, (slice, tuple, np.ndarray)):
320 try:
321 self.param_array[name] = value
322 except:
323 raise ValueError("Setting by slice or index only allowed with array-like")
324 self.trigger_update()
325 else:
326 param = self.__getitem__(name, paramlist)
327 param[:] = value
328
337
338
339
340
348
349 - def copy(self, memo=None):
359
360
361
362
365 @property
368
374
375 @property
378
380 """Representation of the parameters in html for notebook display."""
381 name = adjust_name_for_printing(self.name) + "."
382 names = self.parameter_names()
383 desc = self._description_str
384 iops = OrderedDict()
385 for opname in self._index_operations:
386 iop = []
387 for p in self.parameters:
388 iop.extend(p.get_property_string(opname))
389 iops[opname] = iop
390
391 format_spec = self._format_spec(name, names, desc, iops, False)
392 to_print = []
393
394 if header:
395 to_print.append("<tr><th><b>" + '</b></th><th><b>'.join(format_spec).format(name=name, desc='value', **dict((name, name) for name in iops)) + "</b></th></tr>")
396
397 format_spec = "<tr><td class=tg-left>" + format_spec[0] + '</td><td class=tg-right>' + format_spec[1] + '</td><td class=tg-center>' + '</td><td class=tg-center>'.join(format_spec[2:]) + "</td></tr>"
398 for i in range(len(names)):
399 to_print.append(format_spec.format(name=names[i], desc=desc[i], **dict((name, iops[name][i]) for name in iops)))
400
401 style = """<style type="text/css">
402 .tg {font-family:"Courier New", Courier, monospace !important;padding:2px 3px;word-break:normal;border-collapse:collapse;border-spacing:0;border-color:#DCDCDC;margin:0px auto;width:100%;}
403 .tg td{font-family:"Courier New", Courier, monospace !important;font-weight:bold;color:#444;background-color:#F7FDFA;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#DCDCDC;}
404 .tg th{font-family:"Courier New", Courier, monospace !important;font-weight:normal;color:#fff;background-color:#26ADE4;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#DCDCDC;}
405 .tg .tg-left{font-family:"Courier New", Courier, monospace !important;font-weight:normal;text-align:left;}
406 .tg .tg-center{font-family:"Courier New", Courier, monospace !important;font-weight:normal;text-align:center;}
407 .tg .tg-right{font-family:"Courier New", Courier, monospace !important;font-weight:normal;text-align:right;}
408 </style>"""
409 return style + '\n' + '<table class="tg">' + '\n'.join(to_print) + '\n</table>'
410
427
428 - def __str__(self, header=True, VT100=True):
429 name = adjust_name_for_printing(self.name) + "."
430 names = self.parameter_names(adjust_for_printing=True)
431 desc = self._description_str
432 iops = OrderedDict()
433 for opname in self._index_operations:
434 iops[opname] = self.get_property_string(opname)
435
436 format_spec = ' | '.join(self._format_spec(name, names, desc, iops, VT100))
437
438 to_print = []
439
440 if header:
441 to_print.append(format_spec.format(name=name, desc='value', **dict((name, name) for name in iops)))
442
443 for i in range(len(names)):
444 to_print.append(format_spec.format(name=names[i], desc=desc[i], **dict((name, iops[name][i]) for name in iops)))
445 return '\n'.join(to_print)
446
448 """
449 Build a pydot representation of this model. This needs pydot installed.
450
451 Example Usage::
452
453 np.random.seed(1000)
454 X = np.random.normal(0,1,(20,2))
455 beta = np.random.uniform(0,1,(2,1))
456 Y = X.dot(beta)
457 m = RidgeRegression(X, Y)
458 G = m.build_pydot()
459 G.write_png('example_hierarchy_layout.png')
460
461 The output looks like:
462
463 .. image:: ./example_hierarchy_layout.png
464
465 Rectangles are parameterized objects (nodes or leafs of hierarchy).
466
467 Trapezoids are param objects, which represent the arrays for parameters.
468
469 Black arrows show parameter hierarchical dependence. The arrow points
470 from parents towards children.
471
472 Orange arrows show the observer pattern. Self references (here) are
473 the references to the call to parameters changed and references upwards
474 are the references to tell the parents they need to update.
475 """
476 import pydot
477 iamroot = False
478 if G is None:
479 G = pydot.Dot(graph_type='digraph', bgcolor=None)
480 iamroot=True
481 node = pydot.Node(id(self), shape='box', label=self.name)
482
483 G.add_node(node)
484 for child in self.parameters:
485 child_node = child.build_pydot(G)
486 G.add_edge(pydot.Edge(node, child_node))
487
488 for _, o, _ in self.observers:
489 label = o.name if hasattr(o, 'name') else str(o)
490 observed_node = pydot.Node(id(o), label=label)
491 if str(id(o)) not in G.obj_dict['nodes']:
492 G.add_node(observed_node)
493 edge = pydot.Edge(str(id(self)), str(id(o)), color='darkorange2', arrowhead='vee')
494 G.add_edge(edge)
495
496 if iamroot:
497 return G
498 return node
499