Package topo :: Package sheet :: Module lissom
[hide private]
[frames] | no frames]

Source Code for Module topo.sheet.lissom

  1  """ 
  2  LISSOM and related sheet classes. 
  3   
  4  $Id: lissom.py 11316 2010-07-27 17:52:53Z ceball $ 
  5  """ 
  6  __version__='$Revision: 11316 $' 
  7   
  8  from numpy import zeros,ones 
  9  import copy 
 10   
 11  import param 
 12   
 13  import topo 
 14   
 15  from topo.base.projection import Projection 
 16  from topo.base.sheet import activity_type 
 17  from topo.base.simulation import EPConnectionEvent 
 18  from topo.transferfn.basic import PiecewiseLinear 
 19  from topo.sheet import JointNormalizingCFSheet 
 20   
 21   
22 -class LISSOM(JointNormalizingCFSheet):
23 """ 24 A Sheet class implementing the LISSOM algorithm 25 (Sirosh and Miikkulainen, Biological Cybernetics 71:66-78, 1994). 26 27 A LISSOM sheet is a JointNormalizingCFSheet slightly modified to 28 enforce a fixed number of settling steps. Settling is controlled 29 by the tsettle parameter; once that number of settling steps has 30 been reached, an external input is required before the sheet will 31 activate again. 32 """ 33 34 strict_tsettle = param.Parameter(default = None,doc=""" 35 If non-None, delay sending output until activation_count reaches this value.""") 36 37 mask_init_time=param.Integer(default=5,bounds=(0,None),doc=""" 38 Determines when a new mask is initialized in each new iteration. 39 40 The mask is reset whenever new input comes in. Once the 41 activation_count (see tsettle) reaches mask_init_time, the mask 42 is initialized to reflect the current activity profile.""") 43 44 tsettle=param.Integer(default=8,bounds=(0,None),doc=""" 45 Number of times to activate the LISSOM sheet for each external input event. 46 47 A counter is incremented each time an input is received from any 48 source, and once the counter reaches tsettle, the last activation 49 step is skipped so that there will not be any further recurrent 50 activation. The next external (i.e., afferent or feedback) 51 event will then start the counter over again.""") 52 53 continuous_learning = param.Boolean(default=False, doc=""" 54 Whether to modify the weights after every settling step. 55 If false, waits until settling is completed before doing learning.""") 56 57 output_fns = param.HookList(default=[PiecewiseLinear(lower_bound=0.1,upper_bound=0.65)]) 58 59 precedence = param.Number(0.6) 60 61 post_initialization_weights_output_fns = param.HookList([],doc=""" 62 If not empty, weights output_fns that will replace the 63 existing ones after an initial normalization step.""") 64 65 beginning_of_iteration = param.HookList(default=[],instantiate=False,doc=""" 66 List of callables to be executed at the beginning of each iteration.""") 67 68 end_of_iteration = param.HookList(default=[],instantiate=False,doc=""" 69 List of callables to be executed at the end of each iteration.""") 70 71
72 - def __init__(self,**params):
73 super(LISSOM,self).__init__(**params) 74 self.__counter_stack=[] 75 self.activation_count = 0 76 self.new_iteration = True
77 78
79 - def start(self):
80 self._normalize_weights(active_units_mask=False) 81 if len(self.post_initialization_weights_output_fns)>0: 82 for proj in self.in_connections: 83 if not isinstance(proj,Projection): 84 self.debug("Skipping non-Projection ") 85 else: 86 proj.weights_output_fns=self.post_initialization_weights_output_fns
87 88
89 - def input_event(self,conn,data):
90 # On a new afferent input, clear the activity 91 if self.new_iteration: 92 for f in self.beginning_of_iteration: f() 93 self.new_iteration = False 94 self.activity *= 0.0 95 for proj in self.in_connections: 96 proj.activity *= 0.0 97 self.mask.reset() 98 super(LISSOM,self).input_event(conn,data)
99 100 101 ### JABALERT! There should be some sort of warning when 102 ### tsettle times the input delay is larger than the input period. 103 ### Right now it seems to do strange things in that case (does it 104 ### settle at all after the first iteration?), but of course that 105 ### is arguably an error condition anyway (and should thus be 106 ### flagged).
107 - def process_current_time(self):
108 """ 109 Pass the accumulated stimulation through self.output_fns and 110 send it out on the default output port. 111 """ 112 if self.new_input: 113 self.new_input = False 114 115 if self.activation_count == self.mask_init_time: 116 self.mask.calculate() 117 118 if self.tsettle == 0: 119 # Special case: behave just like a CFSheet 120 self.activate() 121 self.learn() 122 123 elif self.activation_count == self.tsettle: 124 # Once we have been activated the required number of times 125 # (determined by tsettle), reset various counters, learn 126 # if appropriate, and avoid further activation until an 127 # external event arrives. 128 for f in self.end_of_iteration: f() 129 130 self.activation_count = 0 131 self.new_iteration = True # used by input_event when it is called 132 if (self.plastic and not self.continuous_learning): 133 self.learn() 134 else: 135 self.activate() 136 self.activation_count += 1 137 if (self.plastic and self.continuous_learning): 138 self.learn()
139 140 141 # print the weights of a unit
142 - def printwts(self,x,y):
143 for proj in self.in_connections: 144 print proj.name, x, y 145 print proj.cfs[x,y].weights
146 147
148 - def state_push(self,**args):
149 super(LISSOM,self).state_push(**args) 150 self.__counter_stack.append((self.activation_count,self.new_iteration))
151 152
153 - def state_pop(self,**args):
154 super(LISSOM,self).state_pop(**args) 155 self.activation_count,self.new_iteration=self.__counter_stack.pop()
156
157 - def send_output(self,src_port=None,data=None):
158 """Send some data out to all connections on the given src_port.""" 159 160 out_conns_on_src_port = [conn for conn in self.out_connections 161 if self._port_match(conn.src_port,[src_port])] 162 163 for conn in out_conns_on_src_port: 164 if self.strict_tsettle != None: 165 if self.activation_count < self.strict_tsettle: 166 if len(conn.dest_port)>2 and conn.dest_port[2] == 'Afferent': 167 continue 168 self.verbose("Sending output on src_port %s via connection %s to %s" % 169 (str(src_port), conn.name, conn.dest.name)) 170 e=EPConnectionEvent(conn.delay+self.simulation.time(),conn,data) 171 self.simulation.enqueue_event(e)
172 173
174 -class JointScaling(LISSOM):
175 """ 176 LISSOM sheet extended to allow joint auto-scaling of Afferent input projections. 177 178 An exponentially weighted average is used to calculate the average 179 joint activity across all jointly-normalized afferent projections. 180 This average is then used to calculate a scaling factor for the 181 current afferent activity and for the afferent learning rate. 182 183 The target average activity for the afferent projections depends 184 on the statistics of the input; if units are activated more often 185 (e.g. the number of Gaussian patterns on the retina during each 186 iteration is increased) the target average activity should be 187 larger in order to maintain a constant average response to similar 188 inputs in V1. The target activity for learning rate scaling does 189 not need to change, because the learning rate should be scaled 190 regardless of what causes the change in average activity. 191 """ 192 # ALERT: Should probably be extended to jointly scale different 193 # groups of projections. Currently only works for the joint 194 # scaling of projections named "Afferent", grouped together by 195 # JointNormalize in dest_port. 196 197 target = param.Number(default=0.045, doc=""" 198 Target average activity for jointly scaled projections.""") 199 200 # JABALERT: I cannot parse the docstring; is it an activity or a learning rate? 201 target_lr = param.Number(default=0.045, doc=""" 202 Target average activity for jointly scaled projections. 203 204 Used for calculating a learning rate scaling factor.""") 205 206 smoothing = param.Number(default=0.999, doc=""" 207 Influence of previous activity, relative to current, for computing the average.""") 208 209 apply_scaling = param.Boolean(default=True, doc="""Whether to apply the scaling factors.""") 210 211 precedence = param.Number(0.65) 212 213
214 - def __init__(self,**params):
215 super(JointScaling,self).__init__(**params) 216 self.x_avg=None 217 self.sf=None 218 self.lr_sf=None 219 self.scaled_x_avg=None 220 self.__current_state_stack=[]
221
222 - def calculate_joint_sf(self, joint_total):
223 """ 224 Calculate current scaling factors based on the target and previous average joint activities. 225 226 Keeps track of the scaled average for debugging. Could be 227 overridden by a subclass to calculate the factors differently. 228 """ 229 230 if self.plastic: 231 self.sf *=0.0 232 self.lr_sf *=0.0 233 self.sf += self.target/self.x_avg 234 self.lr_sf += self.target_lr/self.x_avg 235 self.x_avg = (1.0-self.smoothing)*joint_total + self.smoothing*self.x_avg 236 self.scaled_x_avg = (1.0-self.smoothing)*joint_total*self.sf + self.smoothing*self.scaled_x_avg
237 238
239 - def do_joint_scaling(self):
240 """ 241 Scale jointly normalized projections together. 242 243 Assumes that the projections to be jointly scaled are those 244 that are being jointly normalized. Calculates the joint total 245 of the grouped projections, and uses this to calculate the 246 scaling factor. 247 """ 248 joint_total = zeros(self.shape, activity_type) 249 250 for key,projlist in self._grouped_in_projections('JointNormalize'): 251 if key is not None: 252 if key =='Afferent': 253 for proj in projlist: 254 joint_total += proj.activity 255 self.calculate_joint_sf(joint_total) 256 if self.apply_scaling: 257 for proj in projlist: 258 proj.activity *= self.sf 259 if hasattr(proj.learning_fn,'learning_rate_scaling_factor'): 260 proj.learning_fn.update_scaling_factor(self.lr_sf) 261 else: 262 raise ValueError("Projections to be joint scaled must have a learning_fn that supports scaling, such as CFPLF_PluginScaled") 263 264 else: 265 raise ValueError("Only Afferent scaling currently supported")
266 267
268 - def activate(self):
269 """ 270 Compute appropriate scaling factors, apply them, and collect resulting activity. 271 272 Scaling factors are first computed for each set of jointly 273 normalized projections, and the resulting activity patterns 274 are then scaled. Then the activity is collected from each 275 projection, combined to calculate the activity for this sheet, 276 and the result is sent out. 277 """ 278 279 self.activity *= 0.0 280 281 if self.x_avg is None: 282 self.x_avg=self.target*ones(self.shape, activity_type) 283 if self.scaled_x_avg is None: 284 self.scaled_x_avg=self.target*ones(self.shape, activity_type) 285 if self.sf is None: 286 self.sf=ones(self.shape, activity_type) 287 if self.lr_sf is None: 288 self.lr_sf=ones(self.shape, activity_type) 289 290 #Afferent projections are only activated once at the beginning of each iteration 291 #therefore we only scale the projection activity and learning rate once. 292 if self.activation_count == 0: 293 self.do_joint_scaling() 294 295 for proj in self.in_connections: 296 self.activity += proj.activity 297 298 if self.apply_output_fns: 299 for of in self.output_fns: 300 of(self.activity) 301 302 self.send_output(src_port='Activity',data=self.activity)
303 304
305 - def state_push(self,**args):
306 super(JointScaling,self).state_push(**args) 307 self.__current_state_stack.append((copy.copy(self.x_avg),copy.copy(self.scaled_x_avg), 308 copy.copy(self.sf), copy.copy(self.lr_sf)))
309 310
311 - def state_pop(self,**args):
312 super(JointScaling,self).state_pop(**args) 313 self.x_avg,self.scaled_x_avg, self.sf, self.lr_sf=self.__current_state_stack.pop()
314 315 316
317 -def schedule_events(sheet_str="topo.sim['V1']",st=0.5,aff_name="Afferent", 318 ids=1.0,ars=1.0,increase_inhibition=False):
319 """ 320 Convenience function for scheduling a default set of events 321 typically used with a LISSOM sheet. The parameters used 322 are the defaults from Miikkulainen, Bednar, Choe, and Sirosh 323 (2005), Computational Maps in the Visual Cortex, Springer. 324 325 Note that Miikulainen 2005 specifies only one output_fn for the 326 LISSOM sheet; where these scheduled actions operate on an 327 output_fn, they do so only on the first output_fn in the sheet's 328 list of output_fns. 329 330 Installs afferent learning rate changes for any projection whose 331 name contains the keyword specified by aff_name (typically 332 "Afferent"). 333 334 The st argument determines the timescale relative to a 335 20000-iteration simulation, and results in the default 336 10000-iteration simulation for the default st=0.5. 337 338 The ids argument specifies the input density scale, i.e. how much 339 input there is at each iteration, on average, relative to the 340 default. The ars argument specifies how much to scale the 341 afferent learning rate, if necessary. 342 343 If increase_inhibition is true, gradually increases the strength 344 of the inhibitory connection, typically used for natural image 345 simulations. 346 """ 347 348 # Allow sheet.BoundingBox calls (below) after reloading a snapshot 349 topo.sim.startup_commands.append("from topo import sheet") 350 351 # Lateral excitatory bounds changes 352 # Convenience variable: excitatory projection 353 LE=sheet_str+".projections()['LateralExcitatory']" 354 355 topo.sim.schedule_command( 200*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.06250))') 356 topo.sim.schedule_command( 500*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.04375))') 357 topo.sim.schedule_command( 1000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.03500))') 358 topo.sim.schedule_command( 2000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.02800))') 359 topo.sim.schedule_command( 3000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.02240))') 360 topo.sim.schedule_command( 4000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.01344))') 361 topo.sim.schedule_command( 5000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.00806))') 362 topo.sim.schedule_command( 6500*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.00484))') 363 topo.sim.schedule_command( 8000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.00290))') 364 topo.sim.schedule_command(20000*st,LE+'.change_bounds(sheet.BoundingBox(radius=0.00174))') 365 366 # Lateral excitatory learning rate changes 367 idss=("" if ids==1 else "/%3.1f"%ids) 368 estr='%s.learning_rate=%%s%s*%s.n_units'%(LE,idss,LE) 369 370 topo.sim.schedule_command( 200*st,estr%'0.12168') 371 topo.sim.schedule_command( 500*st,estr%'0.06084') 372 topo.sim.schedule_command( 1000*st,estr%'0.06084') 373 topo.sim.schedule_command( 2000*st,estr%'0.06084') 374 topo.sim.schedule_command( 3000*st,estr%'0.06084') 375 topo.sim.schedule_command( 4000*st,estr%'0.06084') 376 topo.sim.schedule_command( 5000*st,estr%'0.06084') 377 topo.sim.schedule_command( 6500*st,estr%'0.06084') 378 topo.sim.schedule_command( 8000*st,estr%'0.06084') 379 topo.sim.schedule_command(20000*st,estr%'0.06084') 380 381 ### Lateral inhibitory learning rate and strength changes 382 if increase_inhibition: 383 LI=sheet_str+".projections()['LateralInhibitory']" 384 istr='%s.learning_rate=%%s%s'%(LI,idss) 385 386 topo.sim.schedule_command( 1000*st,istr%'1.80873/5.0*2.0') 387 topo.sim.schedule_command( 2000*st,istr%'1.80873/5.0*3.0') 388 topo.sim.schedule_command( 5000*st,istr%'1.80873/5.0*5.0') 389 390 topo.sim.schedule_command( 1000*st,LI+'.strength=-2.2') 391 topo.sim.schedule_command( 2000*st,LI+'.strength=-2.6') 392 393 394 # Afferent learning rate changes (for every Projection named Afferent) 395 sheet_=eval(sheet_str) 396 projs = [pn for pn in sheet_.projections().keys() if pn.count(aff_name)] 397 num_aff=len(projs) 398 arss="" if ars==1.0 else "*%3.1f"%ars 399 for pn in projs: 400 ps="%s.projections()['%s'].learning_rate=%%s%s%s" % \ 401 (sheet_str,pn,idss if num_aff==1 else "%s/%d"%(idss,num_aff),arss) 402 topo.sim.schedule_command( 500*st,ps%('0.6850')) 403 topo.sim.schedule_command( 2000*st,ps%('0.5480')) 404 topo.sim.schedule_command( 4000*st,ps%('0.4110')) 405 topo.sim.schedule_command(20000*st,ps%('0.2055')) 406 407 # Activation function threshold changes 408 bstr = sheet_str+'.output_fns[0].lower_bound=%5.3f;'+\ 409 sheet_str+'.output_fns[0].upper_bound=%5.3f' 410 lbi=sheet_.output_fns[0].lower_bound 411 ubi=sheet_.output_fns[0].upper_bound 412 413 topo.sim.schedule_command( 200*st,bstr%(lbi+0.01,ubi+0.01)) 414 topo.sim.schedule_command( 500*st,bstr%(lbi+0.02,ubi+0.02)) 415 topo.sim.schedule_command( 1000*st,bstr%(lbi+0.05,ubi+0.03)) 416 topo.sim.schedule_command( 2000*st,bstr%(lbi+0.08,ubi+0.05)) 417 topo.sim.schedule_command( 3000*st,bstr%(lbi+0.10,ubi+0.08)) 418 topo.sim.schedule_command( 4000*st,bstr%(lbi+0.10,ubi+0.11)) 419 topo.sim.schedule_command( 5000*st,bstr%(lbi+0.11,ubi+0.14)) 420 topo.sim.schedule_command( 6500*st,bstr%(lbi+0.12,ubi+0.17)) 421 topo.sim.schedule_command( 8000*st,bstr%(lbi+0.13,ubi+0.20)) 422 topo.sim.schedule_command(20000*st,bstr%(lbi+0.14,ubi+0.23)) 423 424 # Just to get more progress reports 425 topo.sim.schedule_command(12000*st,'pass') 426 topo.sim.schedule_command(16000*st,'pass') 427 428 # Settling steps changes 429 topo.sim.schedule_command( 2000*st,sheet_str+'.tsettle=10') 430 topo.sim.schedule_command( 5000*st,sheet_str+'.tsettle=11') 431 topo.sim.schedule_command( 6500*st,sheet_str+'.tsettle=12') 432 topo.sim.schedule_command( 8000*st,sheet_str+'.tsettle=13')
433