1 """
2 Projection and related classes.
3
4 $Id: projection.py 11129 2010-07-07 00:03:09Z jbednar $
5 """
6 __version__='$Revision: 11129 $'
7
8 import numpy
9 from numpy import array,asarray,ones,sometrue, logical_and, logical_or
10
11 import param
12 from param.parameterized import overridable_property
13
14 from topo.misc.keyedlist import KeyedList
15
16 from sheet import Sheet
17 from simulation import EPConnection
18 from functionfamily import TransferFn
19 from sheetview import ProjectionView
20
22 """
23 An abstract class that defines a mask over a ProjectionSheet object.
24
25 This class is typically used for optimization, where mask
26 indicates which neurons are active and should be processed
27 further. A mask can also be used for lesion experiments, to
28 specify which units should be kept inactive.
29
30 See the code for CFProjection and CFResponseFn to see how this
31 class can be used to restrict the computation to only those
32 neurons that the Mask lists as active.
33 """
34
35
36
37
39 assert(self._sheet != None)
40 return self._data
42 assert(self._sheet != None)
43 self._data = data
44
45 data = overridable_property(_get_data,_set_data,doc="""
46 Ensure that whenever somebody accesses the data they are not None.""")
47
49 assert(self._sheet != None)
50 return self._sheet
52 self._sheet = sheet
53 if(self._sheet != None): self.reset()
54
55 sheet = overridable_property(_get_sheet,_set_sheet)
56
60
65
66
67
69 """Initialize mask to default value (with no neurons masked out)."""
70 self.data = ones(self.sheet.shape)
71
72
74 """
75 Calculate a new mask based on the activity of the sheet.
76
77 For instance, in an algorithm like LISSOM that is based on a
78 process of feedforward activation followed by lateral settling,
79 the calculation is done at the beginning of each iteration after
80 the feedforward activity has been calculated.
81
82 Subclasses should override this method to compute some non-default
83 mask.
84 """
85 pass
86
87
88
90 """
91 Update the current mask based on the current activity and a previous mask.
92
93 Should be called only if calculate() has already been called since the last
94 reset(); potentially faster to compute than redoing the entire calculate().
95
96 Subclasses should override this method to compute some non-default
97 mask.
98 """
99 pass
100
101
103 """
104 A SheetMask that computes its value from other SheetMasks.
105 """
106 __abstract = True
107
108 submasks = param.List(class_=SheetMask)
109
110 - def __init__(self,sheet=None,**params):
113
115 """
116 A method that combines the submasks.
117
118 Subclasses should override this method to do their respective
119 composite calculations. The result should be stored in self.data.
120 """
121 raise NotImplementedError
122
127
132
137
142
143
144
146 """
147 A composite SheetMask that computes its value as the logical AND (i.e. intersection) of its sub-masks.
148 """
150 self._data = asarray(reduce(logical_and,(m.data for m in self.submasks)),dtype=int)
151
152
153
154 -class OrMask(CompositeSheetMask):
155 """
156 A composite SheetMask that computes its value as the logical OR (i.e. union) of its sub-masks.
157 """
159 self._data = asarray(reduce(logical_or,(m.data for m in self.submasks)),dtype=int)
160
161
162
164 """
165 A projection from a Sheet into a ProjectionSheet.
166
167 Projections are required to support the activate() method, which
168 will construct a matrix the same size as the target
169 ProjectionSheet, from an input matrix of activity from the source
170 Sheet. Other than that, a Projection may be of any type.
171 """
172 __abstract=True
173
174 strength = param.Number(default=1.0)
175
176 src_port = param.Parameter(default='Activity')
177
178 dest_port = param.Parameter(default='Activity')
179
180 output_fns = param.HookList(default=[],class_=TransferFn,doc="""
181 Function(s) applied to the Projection activity after it is computed.""")
182
183 plastic = param.Boolean(default=True, doc="""
184 Whether or not to update the internal state on each call.
185 Allows plasticity to be turned off during analysis, and then re-enabled.""")
186
187 activity_group = param.Parameter(default=(0.5,numpy.add), doc="""
188 Grouping and precedence specifier for computing activity from
189 Projections. In a ProjectionSheet, all Projections in the
190 same activity_group will be summed, and then the results from
191 each group will be combined in the order of the activity_group
192 using the operator specified by the activity_operator. For
193 instance, if there are two Projections with
194 activity_group==(0.2,numpy.add) and two with
195 activity_group==(0.6,numpy.divide), activity
196 from the first two will be added together, and the result
197 divided by the sum of the second two.""")
198
199
200
201 precedence = param.Number(default=0.5)
202
203
208
209
211 """
212 Compute an activity matrix for output, based on the specified input_activity.
213
214 Subclasses must override this method to whatever it means to
215 calculate activity in that subclass.
216 """
217 raise NotImplementedError
218
219
221 """
222 This function has to be re-implemented by sub-classes, if they wish
223 to support learning.
224 """
225 pass
226
227
229 """
230 Sub-classes can implement this function if they wish to
231 perform an operation after learning has completed, such as
232 normalizing weight values across different projections.
233
234 The active_units_mask argument determines whether or not to
235 apply the output_fn to non-responding units.
236 """
237 pass
238
239
240
242 """
243 Temporarily override plasticity of medium and long term internal
244 state.
245
246 This function should be implemented by all subclasses so that
247 it preserves the ability of the Projection to compute
248 activity, i.e. to operate over a short time scale, while
249 preventing any lasting changes to the state.
250
251 For instance, if new_plasticity_state is False, in a
252 Projection with modifiable connection weights, the values of
253 those weights should temporarily be made fixed and unchanging
254 after this call. For a Projection with automatic
255 normalization, homeostatic plasticity, or other features that
256 depend on a history of events (rather than just the current
257 item being processed), changes in those properties would be
258 disabled temporarily. Setting the plasticity state to False
259 is useful during analysis operations (e.g. map measurement)
260 that would otherwise change the state of the underlying
261 network.
262
263 Any process that does not have any lasting state, such as
264 those affecting only the current activity level, should not
265 be affected by this call.
266
267 By default, this call simply calls override_plasticity_state()
268 on the Projection's output_fn, and sets the 'plastic'
269 parameter to False.
270 """
271 self._plasticity_setting_stack.append(self.plastic)
272 self.plastic=new_plasticity_state
273
274 for of in self.output_fns:
275 if hasattr(of,'override_plasticity_state'):
276 of.override_plasticity_state(new_plasticity_state)
277
278
280 """
281 Restore previous plasticity state of medium and long term
282 internal state after a override_plasticity_state call.
283
284 This function should be implemented by all subclasses to
285 remove the effect of the most recent override_plasticity_state call,
286 e.g. to reenable plasticity of any type that was disabled.
287 """
288 self.plastic = self._plasticity_setting_stack.pop()
289
290 for of in self.output_fns:
291 if hasattr(of,'restore_plasticity_state'):
292 of.restore_plasticity_state()
293
294
298
299
301 """
302 Estimate the memory bytes taken by this Projection.
303
304 By default, counts only the activity array, but subclasses
305 should implement this method to include also the bytes taken
306 by weight arrays and any similar arrays, as a rough lower
307 bound from which memory requirements and memory usage patterns
308 can be estimated.
309 """
310 (rows,cols) = self.activity.shape
311 return rows*cols
312
313
315 """
316 Return the size of this projection, in number of connections.
317
318 Must be implemented by subclasses, if only to declare that no
319 connections are stored.
320 """
321 raise NotImplementedError
322
323
324
326 """
327 A Sheet whose activity is computed using Projections from other sheets.
328
329 A standard ProjectionSheet expects its input to be generated from
330 other Sheets. Upon receiving an input event, the ProjectionSheet
331 interprets the event data to be (a copy of) an activity matrix
332 from another sheet. The ProjectionSheet provides a copy of this
333 matrix to each Projection from that input Sheet, asking each one
334 to compute their own activity in response. The same occurs for
335 any other pending input events.
336
337 After all events have been processed for a given time, the
338 ProjectionSheet computes its own activity matrix using its
339 activate() method, which by default sums all its Projections'
340 activity matrices and passes the result through user-specified
341 output_fns() before sending it out on the default output port.
342 The activate() method can be overridden to sum some of the
343 projections, multiply that by the sum of other projections, etc.,
344 to model modulatory or other more complicated types of connections.
345 """
346
347 dest_ports=['Activity']
348
349 src_ports=['Activity']
350
351
352
353
354 mask = param.Parameter(default=SheetMask(),instantiate=True,doc="""
355 SheetMask object for computing which units need to be computed further.
356 The object should be an instance of SheetMask, and will
357 compute which neurons will be considered active for the
358 purposes of further processing. The default mask effectively
359 disables all masking, but subclasses can use this mask to
360 implement optimizations, non-rectangular Sheet shapes,
361 lesions, etc.""")
362
363
364
365
366
367 allow_skip_non_responding_units = param.Boolean(default=True,doc="""
368 If true, then units that are inactive after the response
369 function has been called can be skipped in subsequent
370 processing. Whether or not the units will actually be skipped
371 depends on the implementation of learning and learning output
372 functions.""")
373
374
380
382 """
383 See EventProcessor's _dest_connect(); raises an error if conn is not
384 a Projection. Subclasses of ProjectionSheet that know how to handle
385 other types of Connections should override this method.
386 """
387 if isinstance(conn, Projection):
388 super(ProjectionSheet,self)._dest_connect(conn)
389 else:
390 raise TypeError('ProjectionSheets only accept Projections, not other types of connection.')
391
392
401
402
404 """
405 Returns True if the given key matches any port on the given list.
406
407 A port is considered a match if the port is == to the key,
408 or if the port is a tuple whose first element is == to the key,
409 or if both the key and the port are tuples whose first elements are ==.
410
411 This approach allows connections to be grouped using tuples.
412 """
413 port=portlist[0]
414 return [port for port in portlist
415 if (port == key or
416 (isinstance(key,tuple) and key[0] == port) or
417 (isinstance(port,tuple) and port[0] == key) or
418 (isinstance(key,tuple) and isinstance(port,tuple) and port[0] == key[0]))]
419
420
422 """
423 Return a dictionary of lists of incoming Projections, grouped by type.
424
425 Each projection of type <ptype> is grouped according to the
426 name of the port, into a single list within the dictionary.
427
428 The entry None will contain those that are not of type
429 <ptype>, while the other entries will contain a list of
430 Projections, each of which has type ptype.
431
432 Example: to obtain the lists of projections that should be
433 jointly normalised together, call
434 __grouped_in_projection('JointNormalize').
435 """
436 in_proj = KeyedList()
437 in_proj[None]=[]
438
439 for c in self.in_connections:
440 d = c.dest_port
441 if not isinstance(c,Projection):
442 self.debug("Skipping non-Projection "+c.name)
443 elif isinstance(d,tuple) and len(d)>2 and d[1]==ptype:
444 if in_proj.get(d[2]):
445 in_proj[d[2]].append(c)
446 else:
447 in_proj[d[2]]=[c]
448
449
450 else:
451 in_proj[None].append(c)
452
453 return in_proj
454
455
457 """
458 Collect activity from each projection, combine it to calculate
459 the activity for this sheet, and send the result out.
460
461 Subclasses may override this method to whatever it means to
462 calculate activity in that subclass.
463 """
464
465 self.activity *= 0.0
466 tmp_dict={}
467
468 for proj in self.in_connections:
469 if (proj.activity_group != None) | (proj.dest_port[0] != 'Activity'):
470 if not tmp_dict.has_key(proj.activity_group[0]):
471 tmp_dict[proj.activity_group[0]]=[]
472 tmp_dict[proj.activity_group[0]].append(proj)
473
474 keys = tmp_dict.keys()
475 keys.sort()
476 for priority in keys:
477 tmp_activity = self.activity.copy() * 0.0
478
479 for proj in tmp_dict[priority]:
480 tmp_activity += proj.activity
481 self.activity=tmp_dict[priority][0].activity_group[1](self.activity,tmp_activity)
482
483 if self.apply_output_fns:
484 for of in self.output_fns:
485 of(self.activity)
486
487 self.send_output(src_port='Activity',data=self.activity)
488
489
491 """
492 Called by the simulation after all the events are processed for the
493 current time but before time advances. Allows the event processor
494 to send any events that must be sent before time advances to drive
495 the simulation.
496 """
497 if self.new_input:
498 self.activate()
499 self.new_input = False
500 if self.plastic:
501 self.learn()
502
503
505 """
506 By default, call the learn() and apply_learn_output_fns()
507 methods on every Projection to this Sheet.
508
509 Any other type of learning can be implemented by overriding this method.
510 Called from self.process_current_time() _after_ activity has
511 been propagated.
512 """
513 for proj in self.in_connections:
514 if not isinstance(proj,Projection):
515 self.debug("Skipping non-Projection "+proj.name)
516 else:
517 proj.learn()
518 proj.apply_learn_output_fns()
519
520
530
531
533 """
534
535 Return either a named input p, or a dictionary
536 {projection_name, projection} of all the in_connections for
537 this ProjectionSheet.
538
539 A minor convenience function for finding projections by name;
540 the sheet's list of in_connections usually provides simpler
541 access to the Projections.
542 """
543 if not name:
544 return dict([(p.name,p) for p in self.in_connections])
545 else:
546 for c in self.in_connections:
547 if c.name == name:
548 return c
549 raise KeyError(name)
550
551
553 """
554 Temporarily override plasticity state of medium and long term
555 internal state.
556
557 This function should be implemented by all subclasses so that
558 when new_plasticity_state=False, it preserves the ability of
559 the ProjectionSheet to compute activity, i.e. to operate over
560 a short time scale, while preventing any lasting changes to
561 the state.
562
563 Any process that does not have any lasting state, such as
564 those affecting only the current activity level, should not
565 be affected by this call.
566
567 By default, calls override_plasticity_state() on the
568 ProjectionSheet's output_fns and all of its incoming
569 Projections, and also enables the 'plastic' parameter for this
570 ProjectionSheet. The old value of the plastic parameter is
571 saved to an internal stack to be restored by
572 restore_plasticity_state().
573 """
574 super(ProjectionSheet,self).override_plasticity_state(new_plasticity_state)
575
576 for of in self.output_fns:
577 if hasattr(of,'override_plasticity_state'):
578 of.override_plasticity_state(new_plasticity_state)
579
580 for proj in self.in_connections:
581
582 if isinstance(proj,Projection):
583 proj.override_plasticity_state(new_plasticity_state)
584
585
596
597
599 """
600 Estimate the memory bytes taken by this Sheet and its Projections.
601
602 Typically, this number will include the activity array and any
603 similar arrays, plus memory taken by all incoming Projections.
604 It will not usually include memory taken by the Python
605 dictionary or various "housekeeping" attributes, which usually
606 contribute only a small amount to the memory requirements.
607 Thus this value should be considered only a rough lower bound
608 from which memory requirements and memory usage patterns can
609 be estimated.
610
611 Subclasses should reimplement this method if they store a
612 significant amount of data other than in the activity array
613 and the projections.
614 """
615 return self.activity.nbytes + \
616 sum([p.n_bytes() for p in self.in_connections
617 if isinstance(p,Projection)])
618
619
621 """
622 Count the total size of all incoming projections, in number of connections.
623 """
624 return sum([p.n_conns() for p in self.in_connections
625 if isinstance(p,Projection)])
626
627
628
630 """
631 A SheetMask where the mask includes a neighborhood around active neurons.
632
633 Given a radius and a threshold, considers a neuron active if at
634 least one neuron in the radius is over the threshold.
635 """
636
637 threshold = param.Number(default=0.00001,bounds=(0,None),doc="""
638 Threshold for considering a neuron active.
639 This value should be small to avoid discarding significantly active
640 neurons.""")
641
642 radius = param.Number(default=0.05,bounds=(0,None),doc="""
643 Radius in Sheet coordinates around active neurons to consider
644 neighbors active as well. Using a larger radius ensures that
645 the calculation will be unaffected by the mask, but it will
646 reduce any computational benefit from the mask.""")
647
648
651
652
671