Package topo :: Package base :: Module projection
[hide private]
[frames] | no frames]

Source Code for Module topo.base.projection

  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 # CEBALERT: not in base 
 15   
 16  from sheet import Sheet 
 17  from simulation import EPConnection 
 18  from functionfamily import TransferFn 
 19  from sheetview import ProjectionView 
 20   
21 -class SheetMask(param.Parameterized):
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 # JPALERT: Is there anything about this class that assumes its 36 # sheet is a ProjectionSheet? 37
38 - def _get_data(self):
39 assert(self._sheet != None) 40 return self._data
41 - def _set_data(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
48 - def _get_sheet(self):
49 assert(self._sheet != None) 50 return self._sheet
51 - def _set_sheet(self,sheet):
52 self._sheet = sheet 53 if(self._sheet != None): self.reset() 54 55 sheet = overridable_property(_get_sheet,_set_sheet) 56
57 - def __init__(self,sheet=None,**params):
58 super(SheetMask,self).__init__(**params) 59 self.sheet = sheet
60
61 - def __and__(self,mask):
62 return AndMask(self._sheet,submasks=[self,mask])
63 - def __or__(self,mask):
64 return OrMask(self._sheet,submasks=[self,mask])
65 66 # JABALERT: Shouldn't this just keep one matrix around and zero it out, 67 # instead of allocating a new one each time?
68 - def reset(self):
69 """Initialize mask to default value (with no neurons masked out).""" 70 self.data = ones(self.sheet.shape)
71 72
73 - def calculate(self):
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 # JABALERT: Not clear what the user should do with this.
89 - def update(self):
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
102 -class CompositeSheetMask(SheetMask):
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):
111 super(CompositeSheetMask,self).__init__(sheet,**params) 112 assert self.submasks, "A composite mask must have at least one submask."
113
114 - def _combine_submasks(self):
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
123 - def _set_sheet(self,sheet):
124 for m in self.submasks: 125 m.sheet = sheet 126 super(CompositeSheetMask,self)._set_sheet(sheet)
127
128 - def reset(self):
129 for m in self.submasks: 130 m.reset() 131 self._combine_submasks()
132
133 - def calculate(self):
134 for m in self.submasks: 135 m.calculate() 136 self._combine_submasks()
137
138 - def update(self):
139 for m in self.submasks: 140 m.update() 141 self._combine_submasks()
142 143 144
145 -class AndMask(CompositeSheetMask):
146 """ 147 A composite SheetMask that computes its value as the logical AND (i.e. intersection) of its sub-masks. 148 """
149 - def _combine_submasks(self):
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 """
158 - def _combine_submasks(self):
159 self._data = asarray(reduce(logical_or,(m.data for m in self.submasks)),dtype=int)
160 161 162
163 -class Projection(EPConnection):
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 # CEBALERT: precedence should probably be defined at some higher level 200 # (and see other classes where it's defined, e.g. Sheet) 201 precedence = param.Number(default=0.5) 202 203
204 - def __init__(self,**params):
205 super(Projection,self).__init__(**params) 206 self.activity = array(self.dest.activity) 207 self._plasticity_setting_stack = []
208 209
210 - def activate(self,input_activity):
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
220 - def learn(self):
221 """ 222 This function has to be re-implemented by sub-classes, if they wish 223 to support learning. 224 """ 225 pass
226 227
228 - def apply_learn_output_fns(self,active_units_mask=True):
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
241 - def override_plasticity_state(self, new_plasticity_state):
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
279 - def restore_plasticity_state(self):
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
295 - def get_projection_view(self, timestamp):
296 """Returns the activity in a single projection""" 297 return ProjectionView((self.activity.copy(),self.dest.bounds),self,timestamp)
298 299
300 - def n_bytes(self):
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
314 - def n_conns(self):
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
325 -class ProjectionSheet(Sheet):
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 # CEBALERT: why isn't this a parameter of Sheet? 352 # Should be a MaskParameter for safety 353 #mask = ClassSelectorParameter(SheetMask,default=SheetMask(),instantiate=True,doc=""" 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 # CEBALERT: not sure what to call this, and the default should 364 # actually be False. True to match existing behavior. Not sure 365 # this parameter is necessary; clean up with NeighborhoodMask. 366 # (Plus same comment as mask: why not parameter of sheet?) 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
375 - def __init__(self,**params):
376 super(ProjectionSheet,self).__init__(**params) 377 self.new_input = False 378 self.mask.sheet = self 379 self.old_a = self.activity.copy()*0.0
380
381 - def _dest_connect(self, conn):
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
393 - def input_event(self,conn,data):
394 """ 395 Accept input from some sheet. Call .present_input() to 396 compute the stimulation from that sheet. 397 """ 398 self.verbose("Received input from %s on dest_port %s via connection %s." % (conn.src.name,str(conn.dest_port),conn.name)) 399 self.present_input(data,conn) 400 self.new_input = True
401 402
403 - def _port_match(self,key,portlist):
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
421 - def _grouped_in_projections(self,ptype):
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]=[] # Independent (ungrouped) connections 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 #elif isinstance(d,tuple): 449 # raise ValueError("Unable to determine appropriate action for dest_port: %s (connection %s)." % (d,c.name)) 450 else: 451 in_proj[None].append(c) 452 453 return in_proj
454 455
456 - def activate(self):
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
490 - def process_current_time(self):
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
504 - def learn(self):
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
521 - def present_input(self,input_activity,conn):
522 """ 523 Provide the given input_activity to each in_projection that has a dest_port 524 equal to the specified port, asking each one to compute its activity. 525 526 The sheet's own activity is not calculated until activate() 527 is called. 528 """ 529 conn.activate(input_activity)
530 531
532 - def projections(self,name=None):
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
552 - def override_plasticity_state(self, new_plasticity_state):
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 # Could instead check for a override_plasticity_state method 582 if isinstance(proj,Projection): 583 proj.override_plasticity_state(new_plasticity_state)
584 585
586 - def restore_plasticity_state(self):
587 super(ProjectionSheet,self).restore_plasticity_state() 588 589 for of in self.output_fns: 590 if hasattr(of,'restore_plasticity_state'): 591 of.restore_plasticity_state() 592 593 for proj in self.in_connections: 594 if isinstance(proj,Projection): 595 proj.restore_plasticity_state()
596 597
598 - def n_bytes(self):
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
620 - def n_conns(self):
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 # CEBALERT: untested
629 -class NeighborhoodMask(SheetMask):
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
649 - def __init__(self,sheet,**params):
651 652
653 - def calculate(self):
654 rows,cols = self.data.shape 655 656 # JAHACKALERT: Not sure whether this is OK. Another way to do 657 # this would be to ask for the sheet coordinates of each unit 658 # inside the loop. 659 660 # transforms the sheet bounds with bounds2slice() and then 661 # uses this to cut out the activity window 662 ignore1,matradius = self.sheet.sheet2matrixidx(self.radius,0) 663 ignore2,x = self.sheet.sheet2matrixidx(0,0) 664 matradius = abs(matradius-x) 665 for r in xrange(rows): 666 for c in xrange(cols): 667 rr = max(0,r-matradius) 668 cc = max(0,c-matradius) 669 neighbourhood = self.sheet.activity[rr:r+matradius+1,cc:c+matradius+1].ravel() 670 self.data[r][c] = sometrue(neighbourhood>self.threshold)
671