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

Source Code for Module topo.base.cf

   1  """ 
   2  ConnectionField and associated classes. 
   3   
   4  This module defines some basic classes of objects used to create 
   5  simulations of cortical sheets that take input through connection 
   6  fields that project from other cortical sheets (or laterally from 
   7  themselves). 
   8   
   9  ConnectionField: Holds a single connection field within a 
  10  CFProjection. 
  11   
  12  CFProjection: A set of ConnectionFields mapping from a Sheet into a 
  13  ProjectionSheet. 
  14   
  15  CFSheet: A subclass of ProjectionSheet that provides an interface to 
  16  the underlying ConnectionFields in any projection of type 
  17  CFProjection. 
  18   
  19  $Id: cf.py 11247 2010-07-18 20:39:43Z ceball $ 
  20  """ 
  21   
  22  __version__ = '$Revision: 11247 $' 
  23   
  24  from copy import copy 
  25   
  26  from numpy import abs,array,zeros,where 
  27  from numpy.oldnumeric import Float,Float32 
  28   
  29  import param 
  30   
  31  import patterngenerator 
  32  from patterngenerator import PatternGenerator 
  33  from functionfamily import TransferFn,IdentityTF 
  34  from functionfamily import LearningFn,Hebbian,IdentityLF 
  35  from functionfamily import ResponseFn,DotProduct 
  36  from functionfamily import CoordinateMapperFn,IdentityMF 
  37  from projection import Projection,ProjectionSheet 
  38  from sheetcoords import Slice 
  39  from sheetview import UnitView 
  40  from boundingregion import BoundingBox,BoundingRegionParameter 
  41   
  42   
  43  # CEBALERT: shouldn't be necessary, and depends on the implementation 
  44  # of numpy.vectorize 
45 -def simple_vectorize(fn,num_outputs=1,output_type=object,doc=''):
46 """ 47 Simplify creation of numpy.vectorize(fn) objects where all outputs 48 have the same typecode. 49 """ 50 from numpy import vectorize,sctype2char 51 52 # This function exists because I cannot figure out how I am 53 # supposed to stop vectorize() calling fn one extra time at the 54 # start. (It's supposed to call an extra time at the start to 55 # determine the output types UNLESS the output types are 56 # specified.) 57 58 vfn = vectorize(fn,doc=doc) 59 # stop vectorize calling fn an extra time at the start 60 # (works for our current numpy (1.1.1)) 61 vfn.nout=num_outputs # number of outputs of fn 62 output_typecode = sctype2char(output_type) 63 vfn.otypes=output_typecode*num_outputs # typecodes of outputs of fn 64 import inspect 65 66 try: 67 fn_code = fn.func_code if hasattr(fn,'func_code') else fn.__call__.func_code 68 except: 69 raise TypeError("Couldn't find code of %s"%fn) 70 71 fn_args = inspect.getargs(fn_code)[0] 72 extra = 1 if fn_args[0]=='self' else 0 73 vfn.lastcallargs=len(fn_args)-extra # num args of fn 74 return vfn
75 76 77 78 # Specified explicitly when creating weights matrix - required 79 # for optimized C functions. 80 weight_type = Float32 81 82
83 -class NullCFError(ValueError):
84 """ 85 Error thrown when trying to create an empty CF. 86 """
87 - def __init__(self,x,y,input,rows,cols):
88 ValueError.__init__(self,"ConnectionField at (%s,%s) (input_sheet=%s) has a zero-sized weights matrix (%s,%s); you may need to supply a larger bounds_template or increase the density of the sheet."%(x,y,input,rows,cols))
89 90
91 -class ConnectionField(object):
92 """ 93 A set of weights on one input Sheet. 94 95 Each ConnectionField contributes to the activity of one unit on 96 the output sheet, and is normally used as part of a Projection 97 including many other ConnectionFields. 98 """ 99 __slots__ = ['weights','input_sheet_slice','mask', 100 '_has_norm_total','_norm_total'] 101
102 - def __get_norm_total(self):
103 """ 104 Return the stored norm_value, if any, or else the current sum of the weights. 105 See the norm_total property for more details. 106 """ 107 # The actual value is cached in _norm_total. 108 if self._has_norm_total: 109 return self._norm_total 110 else: 111 return abs(self.weights).sum()
112
113 - def __set_norm_total(self,new_norm_total):
114 """ 115 Set an explicit value to be returned by norm_total. 116 See the norm_total property for more details. 117 """ 118 self._has_norm_total = True 119 self._norm_total = new_norm_total
120
121 - def __del_norm_total(self):
122 """ 123 Delete any cached norm_total that may have been set. 124 See the norm_total property for more details. 125 """ 126 self._has_norm_total = False
127 128 129 # CB: Accessing norm_total as a property from the C code takes 130 # about 2% of run time for 90 iterations of lissom_oo_or. (As of 131 # r8139, using floating-point simulation time.) 132 norm_total = property(__get_norm_total,__set_norm_total,__del_norm_total, 133 """ 134 The norm_total property returns a value useful in computing 135 a sum-based weight normalization. 136 137 By default, the value returned is simply the current sum of 138 the connection weights. However, another value can be 139 substituted by setting norm_total explicitly, and this cached 140 value will then be returned instead. 141 142 This mechanism has two main purposes. First, it allows a 143 learning function to cache the sum value for an output 144 function to use later without computation, which can result in 145 significant time savings. Second, the extra level of 146 indirection allows the sum value to be manipulated before it 147 is used, to implement operations like joint normalization 148 across corresponding CFs in multiple Projections. 149 150 Apart from such cases, norm_total can be ignored. 151 152 Note that every person who uses a class that sets or gets 153 norm_total must be very careful to ensure that stale values 154 will never be accessed. A good way to do this is to make sure 155 that the value is only set just before it will be used, and 156 deleted as soon as it has been accessed. 157 158 WARNING: Any c-optimized code can bypass this property and 159 access directly _has_norm_total, _norm_total 160 161 """) 162 163
164 - def get_bounds(self,input_sheet):
166 167 # CEBALERT: 168 # template and mask: usually created ONCE by CFProjection and 169 # specified as a Slice and array (respectively). Otherwise, 170 # can be specified as BoundingBox and patterngenerator. 171 172 # Note that BoundingBox() is ok for a default even though it's 173 # mutable because we copy it inside init. Constant() is ok too 174 # because mask and weights_generator are not modified.
175 - def __init__(self,input_sheet,x=0.0,y=0.0,template=BoundingBox(radius=0.1), 176 weights_generator=patterngenerator.Constant(), 177 mask=patterngenerator.Constant(), 178 output_fns=None,min_matrix_radius=1):
179 """ 180 Create weights at the specified (x,y) location on the 181 specified input_sheet. 182 183 The supplied template (if a BoundingRegion) is converted to a 184 Slice, moved to the specified (x,y) location, and then the 185 weights pattern is drawn inside by the weights_generator. 186 187 Note that if the appropriate template Slice is already known, 188 then it can be passed in instead of a BoundingRegion template. 189 This slice will then be used directly, instead of converting 190 the template into a Slice. 191 192 The supplied template object itself will not be modified (it 193 is copied before use). 194 195 The mask allows the weights to be limited to being non-zero in 196 a subset of the rectangular weights area. The actual mask 197 used is a view of the given mask created by cropping to the 198 boundaries of the input_sheet, so that the weights all 199 correspond to actual locations in the input sheet. For 200 instance, if a circular pattern of weights is desired, the 201 mask should have a disk-shaped pattern of elements with value 202 1, surrounded by elements with the value 0. If the CF extends 203 over the edge of the input sheet then the weights will 204 actually be half-moon (or similar) rather than circular. 205 """ 206 #print "Create CF",input_sheet.name,x,y,"template=",template,"wg=",weights_generator,"m=",mask,"ofs=",output_fns,"min r=",min_matrix_radius 207 208 template = copy(template) 209 210 if not isinstance(template,Slice): 211 template = Slice(template,input_sheet,force_odd=True, 212 min_matrix_radius=min_matrix_radius) 213 214 # Note: if passed in, mask is shared between CFs (but not if created here) 215 if not hasattr(mask,'view'): 216 mask = _create_mask(mask,template.compute_bounds(input_sheet), 217 # CEBALERT: it's not really worth adding more ALERTs on this 218 # topic, but...there's no way for the CF to control autosize 219 # and threshold. 220 input_sheet,True,0.5) 221 222 223 # CB: has to be set for C code. Can't be initialized at the 224 # class level, or it would become a read-only class attribute 225 # (because it's a slot: 226 # http://docs.python.org/reference/datamodel.html). Can we 227 # somehow avoid having to think about _has_norm_total in the 228 # python code? Could the C code initialize this value? 229 self._has_norm_total=False 230 231 if output_fns is None: 232 output_fns = [] 233 234 # CEBALERT: now even more confusing; weights_slice is 235 # different from input_sheet_slice. At least need to rename. 236 weights_slice = self._create_input_sheet_slice(input_sheet,x,y,template,min_matrix_radius) 237 238 # CBNOTE: this would be clearer (but not perfect, and probably slower) 239 # m = mask_template[self.weights_slice()] 240 self.mask = weights_slice.submatrix(mask) # view of original mask 241 self.mask = array(self.mask,copy=1) # CEBALERT: why is this necessary? 242 243 # (without it, optimized learning function creates artifacts in CFs at 244 # left and right edges of sheet, at some densities) 245 246 # CBENHANCEMENT: might want to do something about a size 247 # that's specified (right now the size is assumed to be that 248 # of the bounds) 249 # shouldn't be extra computation of boundingbox because it's gone from Slice.__init__; could avoid extra lookups by getting straight from slice 250 w = weights_generator(x=x,y=y,bounds=self.get_bounds(input_sheet), 251 xdensity=input_sheet.xdensity, 252 ydensity=input_sheet.ydensity, 253 mask=self.mask) 254 255 # CEBALERT: unnecessary copy! Pass type to PG & have it draw 256 # in that. (Should be simple, except making it work for all 257 # the PG subclasses that override array creation in various 258 # ways (producing or using inconsistent types) turned out to 259 # be too painful.) 260 self.weights = w.astype(weight_type) 261 262 # CEBHACKALERT: the system of masking through multiplication 263 # by 0 works for now, while the output_fns are all 264 # multiplicative. But in the long run we need a better way to 265 # apply the mask. The same applies anywhere the mask is used, 266 # including in learningfn/. We should investigate masked 267 # arrays (from numpy). 268 for of in output_fns: 269 of(self.weights)
270 271 272 # CB: can this be renamed to something better?
273 - def _create_input_sheet_slice(self,input_sheet,x,y,template,min_matrix_radius):
274 """ 275 Create the input_sheet_slice, which provides the appropriate 276 Slice for this CF on the input_sheet (as well as providing 277 this CF's exact bounds). 278 279 Also creates the weights_slice, which provides the Slice for 280 this weights matrix (in case it must be cropped at an edge). 281 """ 282 # copy required because the template gets modified here but 283 # needs to be used again 284 input_sheet_slice = copy(template) 285 input_sheet_slice.positionedcrop(x,y,input_sheet) 286 input_sheet_slice.crop_to_sheet(input_sheet) 287 288 # weights matrix cannot have a zero-sized dimension (could 289 # happen at this stage because of cropping) 290 nrows,ncols = input_sheet_slice.shape_on_sheet() 291 if nrows<1 or ncols<1: 292 raise NullCFError(x,y,input_sheet,nrows,ncols) 293 294 self.input_sheet_slice = input_sheet_slice 295 296 # not copied because we don't use again 297 template.positionlesscrop(x,y,input_sheet) 298 return template
299 300 301 # CEBALERT: unnecessary method; can use something like 302 # activity[cf.input_sheet_slice()]
303 - def get_input_matrix(self, activity):
304 # CBNOTE: again, this might be clearer (but probably slower): 305 # activity[self.input_sheet_slice()] 306 return self.input_sheet_slice.submatrix(activity)
307 308 309
310 -class CFPResponseFn(param.Parameterized):
311 """ 312 Map an input activity matrix into an output matrix using the CFs 313 in a CFProjection. 314 315 Objects in this hierarchy of callable function objects compute a 316 response matrix when given an input pattern and a set of 317 ConnectionField objects. Typically used as part of the activation 318 function for a neuron, computing activation for one Projection. 319 320 Objects in this class must support being called as a function with 321 the arguments specified below, and are assumed to modify the 322 activity matrix in place. 323 """ 324 __abstract=True 325
326 - def __call__(self, iterator, input_activity, activity, strength, **params):
327 raise NotImplementedError
328 329
330 -class CFPRF_Plugin(CFPResponseFn):
331 """ 332 Generic large-scale response function based on a simple single-CF function. 333 334 Applies the single_cf_fn to each CF in turn. For the default 335 single_cf_fn of DotProduct(), does a basic dot product of each CF with the 336 corresponding slice of the input array. This function is likely 337 to be slow to run, but it is easy to extend with any arbitrary 338 single-CF response function. 339 340 The single_cf_fn must be a function f(X,W) that takes two 341 identically shaped matrices X (the input) and W (the 342 ConnectionField weights) and computes a scalar activation value 343 based on those weights. 344 """ 345 single_cf_fn = param.ClassSelector(ResponseFn,default=DotProduct(), 346 doc="Accepts a ResponseFn that will be applied to each CF individually.") 347
348 - def __call__(self, iterator, input_activity, activity, strength):
349 single_cf_fn = self.single_cf_fn 350 for cf,i in iterator(): 351 X = cf.input_sheet_slice.submatrix(input_activity) 352 activity.flat[i] = single_cf_fn(X,cf.weights) 353 activity *= strength
354 355
356 -class CFPLearningFn(param.Parameterized):
357 """ 358 Compute new CFs for a CFProjection based on input and output activity values. 359 360 Objects in this hierarchy of callable function objects compute a 361 new set of CFs when given input and output patterns and a set of 362 ConnectionField objects. Used for updating the weights of one 363 CFProjection. 364 365 Objects in this class must support being called as a function with 366 the arguments specified below. 367 """ 368 __abstract = True 369 370
371 - def constant_sum_connection_rate(self,n_units,learning_rate):
372 """ 373 Return the learning rate for a single connection assuming that 374 the total rate is to be divided evenly among all the units in 375 the connection field. 376 """ 377 return float(learning_rate)/n_units
378 379 380 # JABALERT: Should the learning_rate be a parameter of this object instead of an argument?
381 - def __call__(self, iterator, input_activity, output_activity, learning_rate, **params):
382 """ 383 Apply this learning function to the given set of ConnectionFields, 384 and input and output activities, using the given learning_rate. 385 """ 386 raise NotImplementedError
387 388
389 -class CFPLF_Identity(CFPLearningFn):
390 """CFLearningFunction performing no learning.""" 391 single_cf_fn = param.ClassSelector(LearningFn,default=IdentityLF(),constant=True) 392
393 - def __call__(self, iterator, input_activity, output_activity, learning_rate, **params):
394 pass
395 396
397 -class CFPLF_Plugin(CFPLearningFn):
398 """CFPLearningFunction applying the specified single_cf_fn to each CF.""" 399 single_cf_fn = param.ClassSelector(LearningFn,default=Hebbian(), 400 doc="Accepts a LearningFn that will be applied to each CF individually.")
401 - def __call__(self, iterator, input_activity, output_activity, learning_rate, **params):
402 """Apply the specified single_cf_fn to every CF.""" 403 single_connection_learning_rate = self.constant_sum_connection_rate(iterator.proj_n_units,learning_rate) 404 # avoid evaluating these references each time in the loop 405 single_cf_fn = self.single_cf_fn 406 407 for cf,i in iterator(): 408 single_cf_fn(cf.get_input_matrix(input_activity), 409 output_activity.flat[i], cf.weights, 410 single_connection_learning_rate) 411 cf.weights *= cf.mask
412 413
414 -class CFPOutputFn(param.Parameterized):
415 """ 416 Type for an object that applies some operation (typically something 417 like normalization) to all CFs in a CFProjection for which the specified 418 mask (typically the activity at the destination of this projection) 419 is nonzero. 420 """ 421 __abstract = True 422
423 - def __call__(self, iterator, **params):
424 """Operate on each CF for which the mask is nonzero.""" 425 raise NotImplementedError
426 427
428 -class CFPOF_Plugin(CFPOutputFn):
429 """ 430 Applies the specified single_cf_fn to each CF in the CFProjection 431 for which the mask is nonzero. 432 """ 433 single_cf_fn = param.ClassSelector(TransferFn,default=IdentityTF(), 434 doc="Accepts a TransferFn that will be applied to each CF individually.") 435
436 - def __call__(self, iterator, **params):
437 if type(self.single_cf_fn) is not IdentityTF: 438 single_cf_fn = self.single_cf_fn 439 440 for cf,i in iterator(): 441 single_cf_fn(cf.weights) 442 del cf.norm_total
443 444
445 -class CFPOF_Identity(CFPOutputFn):
446 """ 447 CFPOutputFn that leaves the CFs unchanged. 448 449 Must never be changed or subclassed, because it might never 450 be called. (I.e., it could simply be tested for and skipped.) 451 """ 452 single_cf_fn = param.ClassSelector(TransferFn,default=IdentityTF(),constant=True) 453
454 - def __call__(self, iterator, **params):
455 pass
456 457 458 # CB: need to make usage of 'src' and 'input_sheet' consistent between 459 # ConnectionField and CFProjection (i.e. pick one of them).
460 -class CFProjection(Projection):
461 """ 462 A projection composed of ConnectionFields from a Sheet into a ProjectionSheet. 463 464 CFProjection computes its activity using a response_fn of type 465 CFPResponseFn (typically a CF-aware version of mdot) and output_fns 466 (typically none). The initial contents of the 467 ConnectionFields mapping from the input Sheet into the target 468 ProjectionSheet are controlled by the weights_generator, cf_shape, 469 and weights_output_fn parameters, while the location of the 470 ConnectionField is controlled by the coord_mapper parameter. 471 472 Any subclass has to implement the interface 473 activate(self,input_activity) that computes the response from the 474 input and stores it in the activity array. 475 """ 476 477 response_fn = param.ClassSelector(CFPResponseFn, 478 default=CFPRF_Plugin(), 479 doc='Function for computing the Projection response to an input pattern.') 480 481 cf_type = param.Parameter(default=ConnectionField,constant=True, 482 doc="Type of ConnectionField to use when creating individual CFs.") 483 484 # JPHACKALERT: Not all support for null CFs has been implemented. 485 # CF plotting and C-optimized CFPxF_ functions need 486 # to be fixed to support null CFs without crashing. 487 allow_null_cfs = param.Boolean(default=False, 488 doc="Whether or not the projection can have entirely empty CFs") 489 490 nominal_bounds_template = BoundingRegionParameter( 491 default=BoundingBox(radius=0.1),doc=""" 492 Bounds defining the Sheet area covered by a prototypical ConnectionField. 493 The true bounds will differ depending on the density (see create_slice_template()).""") 494 495 weights_generator = param.ClassSelector(PatternGenerator, 496 default=patterngenerator.Constant(),constant=True, 497 doc="Generate initial weights values.") 498 499 cf_shape = param.ClassSelector(PatternGenerator, 500 default=patterngenerator.Constant(),constant=True, 501 doc="Mask pattern to define the shape of the connection fields.") 502 503 same_cf_shape_for_all_cfs = param.Boolean(default=True,doc=""" 504 Whether or not to share a single cf_shape mask for all CFs. 505 If True, the cf_shape is evaluated only once and shared for 506 all CFs, which saves computation time and memory. If False, 507 the cf_shape is evaluated once for each CF, allowing each to 508 have its own shape.""") 509 510 learning_fn = param.ClassSelector(CFPLearningFn, 511 default=CFPLF_Plugin(), 512 doc='Function for computing changes to the weights based on one activation step.') 513 514 # JABALERT: Shouldn't learning_rate be owned by the learning_fn? 515 learning_rate = param.Number(default=0.0,softbounds=(0,100),doc=""" 516 Amount of learning at each step for this projection, specified 517 in units that are independent of the density of each Sheet.""") 518 519 weights_output_fns = param.HookList(default=[CFPOF_Plugin()], 520 class_=CFPOutputFn, 521 doc='Functions applied to each CF after learning.') 522 523 strength = param.Number(default=1.0,doc=""" 524 Global multiplicative scaling applied to the Activity of this Sheet.""") 525 526 coord_mapper = param.ClassSelector(CoordinateMapperFn, 527 default=IdentityMF(), 528 doc='Function to map a projected coordinate into the target sheet.') 529 530 # CEBALERT: this is temporary (allows c++ matching in certain 531 # cases). We will allow the user to override the mask size, but 532 # by offering a scaling parameter. 533 autosize_mask = param.Boolean( 534 default=True,constant=True,precedence=-1,doc=""" 535 Topographica sets the mask size so that it is the same as the connection field's 536 size, unless this parameter is False - in which case the user-specified size of 537 the cf_shape is used. In normal usage of Topographica, this parameter should 538 remain True.""") 539 540 mask_threshold = param.Number(default=0.5,constant=True,doc=""" 541 If a unit is above this value in the cf_shape mask, it is 542 included; otherwise it is excluded from the mask.""") 543 544 apply_output_fns_init=param.Boolean(default=True,doc=""" 545 Whether to apply the output function to connection fields (e.g. for 546 normalization) when the CFs are first created.""") 547 548 min_matrix_radius = param.Integer(default=1,bounds=(0,None),doc=""" 549 Enforced minimum for radius of weights matrix. 550 The default of 1 gives a minimum matrix of 3x3. 0 would 551 allow a 1x1 matrix.""") 552 553 554 precedence = param.Number(default=0.8) 555 556
557 - def __init__(self,initialize_cfs=True,**params):
558 """ 559 Initialize the Projection with a set of cf_type objects 560 (typically ConnectionFields), each located at the location 561 in the source sheet corresponding to the unit in the target 562 sheet. The cf_type objects are stored in the 'cfs' array. 563 564 The nominal_bounds_template specified may be altered: the 565 bounds must be fitted to the Sheet's matrix, and the weights 566 matrix must have odd dimensions. These altered bounds are 567 passed to the individual connection fields. 568 569 A mask for the weights matrix is constructed. The shape is 570 specified by cf_shape; the size defaults to the size 571 of the nominal_bounds_template. 572 """ 573 super(CFProjection,self).__init__(**params) 574 575 self.weights_generator.set_dynamic_time_fn(None,sublistattr='generators') 576 # get the actual bounds_template by adjusting a copy of the 577 # nominal_bounds_template to ensure an odd slice, and to be 578 # cropped to sheet if necessary 579 self._slice_template = Slice(copy(self.nominal_bounds_template), 580 self.src,force_odd=True, 581 min_matrix_radius=self.min_matrix_radius) 582 583 self.bounds_template = self._slice_template.compute_bounds(self.src) 584 585 self.mask_template = _create_mask(self.cf_shape,self.bounds_template, 586 self.src,self.autosize_mask, 587 self.mask_threshold) 588 589 self.n_units = self._calc_n_units() 590 591 if initialize_cfs: 592 self._create_cfs() 593 594 595 ### JCALERT! We might want to change the default value of the 596 ### input value to self.src.activity; but it fails, raising a 597 ### type error. It probably has to be clarified why this is 598 ### happening 599 self.input_buffer = None 600 self.activity = array(self.dest.activity)
601 602
603 - def _generate_coords(self):
604 X,Y = self.dest.sheetcoords_of_idx_grid() 605 vectorized_coord_mapper = simple_vectorize(self.coord_mapper, 606 num_outputs=2, 607 # CB: could switch to float32? 608 output_type=float) 609 return vectorized_coord_mapper(X,Y)
610 611 612 # CB: should be _initialize_cfs() since we already have 'initialize_cfs' flag?
613 - def _create_cfs(self):
614 vectorized_create_cf = simple_vectorize(self._create_cf) 615 self.cfs = vectorized_create_cf(*self._generate_coords()) 616 self.flatcfs = list(self.cfs.flat)
617 618
619 - def _create_cf(self,x,y):
620 """ 621 Create a ConnectionField at x,y in the src sheet. 622 """ 623 # (to restore would need to have an r,c counter) 624 # self.debug("Creating CF(%d,%d) from src (%.3f,%.3f) to dest (%.3f,%.3f)"%(r,c,x_cf,y_cf,x,y)) 625 626 try: 627 if self.apply_output_fns_init: 628 ofs = [wof.single_cf_fn for wof in self.weights_output_fns] 629 else: 630 ofs = [] 631 632 if self.same_cf_shape_for_all_cfs: 633 mask_template = self.mask_template 634 else: 635 mask_template = _create_mask(self.cf_shape,self.bounds_template, 636 self.src,self.autosize_mask, 637 self.mask_threshold) 638 639 CF = self.cf_type(self.src,x=x,y=y, 640 template=self._slice_template, 641 weights_generator=self.weights_generator, 642 mask=mask_template, 643 output_fns=ofs, 644 min_matrix_radius=self.min_matrix_radius) 645 except NullCFError: 646 if self.allow_null_cfs: 647 CF = None 648 else: 649 raise 650 651 return CF
652 653 654
655 - def _calc_n_units(self):
656 """Return the number of unmasked units in a typical ConnectionField.""" 657 658 return min(len(self.mask_template.ravel().nonzero()[0]), 659 # CEBALERT: if the mask_template is bigger than the 660 # src sheet (e.g. conn radius bigger than src 661 # radius), return the size of the source sheet 662 self.src.shape[0]*self.src.shape[1])
663 664
665 - def cf(self,r,c):
666 """Return the specified ConnectionField""" 667 # CB: should we offer convenience cf(x,y) (i.e. sheetcoords) method instead? 668 self.warning("CFProjection.cf(r,c) is deprecated: use cfs[r,c] instead") 669 return self.cfs[r,c]
670 671
672 - def cf_bounds(self,r,c):
673 """Return the bounds of the specified ConnectionField.""" 674 return self.cfs[r,c].get_bounds(self.src)
675 676
677 - def get_view(self, sheet_x, sheet_y, timestamp):
678 """ 679 Return a single connection field UnitView, for the unit 680 located nearest to sheet coordinate (sheet_x,sheet_y). 681 """ 682 matrix_data = zeros(self.src.activity.shape,Float) 683 (r,c) = self.dest.sheet2matrixidx(sheet_x,sheet_y) 684 r1,r2,c1,c2 = self.cfs[r,c].input_sheet_slice 685 matrix_data[r1:r2,c1:c2] = self.cfs[r,c].weights 686 687 # CB: the following would be equivalent with Slice __call__ 688 689 # cf = self.cf(self.dest.sheet2matrixidx(sheet_x,sheet_y)) 690 # matrix_data = numpy.zeros(self.src.activity.shape,Numeric.Float) 691 # matrix_data[cf.input_sheet_slice()]=cf.weights 692 693 return UnitView((matrix_data,self.src.bounds),sheet_x,sheet_y,self,timestamp)
694 695
696 - def activate(self,input_activity):
697 """Activate using the specified response_fn and output_fn.""" 698 self.input_buffer = input_activity 699 self.activity *=0.0 700 self.response_fn(MaskedCFIter(self), input_activity, self.activity, self.strength) 701 for of in self.output_fns: 702 of(self.activity)
703 704 705 # CEBALERT: should add active_units_mask to match 706 # apply_learn_output_fns.
707 - def learn(self):
708 """ 709 For a CFProjection, learn consists of calling the learning_fn. 710 """ 711 # Learning is performed if the input_buffer has already been set, 712 # i.e. there is an input to the Projection. 713 if self.input_buffer != None: 714 self.learning_fn(MaskedCFIter(self),self.input_buffer,self.dest.activity,self.learning_rate)
715 716 717 # CEBALERT: called 'learn' output fns here, but called 'weights' output fns 718 # elsewhere (mostly). Change all to 'learn'?
719 - def apply_learn_output_fns(self,active_units_mask=True):
720 """ 721 Apply the weights_output_fns to each unit. 722 723 If active_units_mask is True, inactive units will be skipped. 724 """ 725 for of in self.weights_output_fns: 726 of(MaskedCFIter(self,active_units_mask=active_units_mask))
727 728 729 # CEBALERT: see gc alert in simulation.__new__
730 - def _cleanup(self):
731 for cf in self.cfs.flat: 732 # cf could be None or maybe something else 733 if hasattr(cf,'input_sheet'): 734 cf.input_sheet=None 735 if hasattr(cf,'input_sheet_slice'): 736 cf.input_sheet_slice=None 737 if hasattr(cf,'weights_slice'): 738 cf.weights_slice=None
739 740
741 - def n_bytes(self):
742 # Could also count the input_sheet_slice 743 rows,cols=self.cfs.shape 744 return super(CFProjection,self).n_bytes() + \ 745 sum([cf.weights.nbytes + 746 cf.mask.nbytes 747 for cf,i in CFIter(self,ignore_sheet_mask=True)()])
748 749
750 - def n_conns(self):
751 # Counts non-masked values, if mask is available; otherwise counts 752 # weights as connections if nonzero 753 rows,cols=self.cfs.shape 754 return sum([len((cf.mask if cf.mask is not None else cf.weights).ravel().nonzero()[0]) 755 for cf,i in MaskedCFIter(self)()])
756 757 758 # CEB: have not yet decided proper location for this method 759 # JAB: should it be in PatternGenerator?
760 -def _create_mask(shape,bounds_template,sheet,autosize=True,threshold=0.5):
761 """ 762 Create the mask (see ConnectionField.__init__()). 763 """ 764 # Calculate the size & aspect_ratio of the mask if appropriate; 765 # mask size set to be that of the weights matrix 766 if hasattr(shape, 'size') and autosize: 767 l,b,r,t = bounds_template.lbrt() 768 shape.size = t-b 769 shape.aspect_ratio = (r-l)/shape.size 770 771 # Center mask to matrixidx center 772 center_r,center_c = sheet.sheet2matrixidx(0,0) 773 center_x,center_y = sheet.matrixidx2sheet(center_r,center_c) 774 775 mask = shape(x=center_x,y=center_y, 776 bounds=bounds_template, 777 xdensity=sheet.xdensity, 778 ydensity=sheet.ydensity) 779 780 mask = where(mask>=threshold,mask,0.0) 781 782 # CB: unnecessary copy (same as for weights) 783 return mask.astype(weight_type)
784 785 786 787 import numpy
788 -class CFIter(object):
789 """ 790 Iterator to walk through all ConnectionFields of all neurons in 791 the destination Sheet of the given CFProjection. Each iteration 792 yields the tuple (cf,i) where cf is the ConnectionField at 793 position i in the projection's flatcfs list. 794 795 If active_units_mask is True, inactive units will be skipped. If 796 ignore_sheet_mask is True, even units excluded by the sheet mask 797 will be included. 798 """ 799 800 # CB: as noted elsewhere, rename active_units_mask (to e.g. 801 # ignore_inactive_units).
802 - def __init__(self,cfprojection,active_units_mask=False,ignore_sheet_mask=False):
803 804 self.flatcfs = cfprojection.flatcfs 805 self.activity = cfprojection.dest.activity 806 self.mask = cfprojection.dest.mask 807 self.cf_type = cfprojection.cf_type 808 self.proj_n_units = cfprojection.n_units 809 self.allow_skip_non_responding_units = cfprojection.dest.allow_skip_non_responding_units 810 811 self.active_units_mask = active_units_mask 812 self.ignore_sheet_mask = ignore_sheet_mask
813
814 - def __nomask(self):
815 # return an array indicating all units should be processed 816 817 # dtype for C functions. 818 # could just be flat. 819 return numpy.ones(self.activity.shape,dtype=self.activity.dtype)
820 821 # CEBALERT: make _
822 - def get_sheet_mask(self):
823 if not self.ignore_sheet_mask: 824 return self.mask.data 825 else: 826 return self.__nomask()
827 828 # CEBALERT: make _ (and probably drop '_mask').
829 - def get_active_units_mask(self):
830 if self.allow_skip_non_responding_units and self.active_units_mask: 831 return self.activity 832 else: 833 return self.__nomask()
834 835 # CEBALERT: rename?
836 - def get_overall_mask(self):
837 """ 838 Return an array indicating whether or not each unit should be 839 processed. 840 """ 841 # JPHACKALERT: Should really check for the existence of the 842 # mask, rather than checking its type. This is a hack to 843 # support higher-order projections whose dest is a CF, instead 844 # of a sheet. The right thing to do is refactor so that CF 845 # masks and SheetMasks are subclasses of an abstract Mask 846 # type so that they support the same interfaces. 847 # 848 # CEBALERT: put back when supporting neighborhood masking 849 # (though preferably do what Jeff suggests instead) 850 # if isinstance(self.proj.dest.mask,SheetMask): 851 # return get_active_units_mask() 852 # else: 853 854 # CB: note that it's faster for our optimized C functions to 855 # combine the masks themselves, rather than using this method. 856 sheet_mask = self.get_sheet_mask() 857 active_units_mask = self.get_active_units_mask() 858 return numpy.logical_and(sheet_mask,active_units_mask)
859 860
861 - def __call__(self):
862 mask = self.get_overall_mask() 863 for i,cf in enumerate(self.flatcfs): 864 if cf is not None: 865 if mask.flat[i]: 866 yield cf,i
867 868 # CEBALERT: remove this once MaskedCFIter has been replaced elsewhere. 869 MaskedCFIter = CFIter 870 871 872 ### We don't really need this class; its methods could probably be 873 ### moved up to ProjectionSheet, because they may in fact be valid for 874 ### all ProjectionSheets. But we're leaving it here, because it is 875 ### likely to be useful in the future.
876 -class CFSheet(ProjectionSheet):
877 """ 878 A ProjectionSheet providing access to the ConnectionFields in its CFProjections. 879 880 CFSheet is a Sheet built from units indexed by Sheet coordinates 881 (x,y). Each unit can have one or more ConnectionFields on another 882 Sheet (via this sheet's CFProjections). Thus CFSheet is a more 883 concrete version of a ProjectionSheet; a ProjectionSheet does not 884 require that there be units or weights of any kind. Unless you 885 need access to the underlying ConnectionFields for visualization 886 or analysis, CFSheet and ProjectionSheet are interchangeable. 887 """ 888 889 measure_maps = param.Boolean(True,doc=""" 890 Whether to include this Sheet when measuring various maps to create SheetViews.""") 891 892 precedence = param.Number(0.5) 893 894
895 - def update_unit_view(self,x,y,proj_name=''):
896 """ 897 Creates the list of UnitView objects for a particular unit in this CFSheet. 898 (There is one UnitView for each Projection to this CFSheet). 899 900 Each UnitView is then added to the sheet_views of its source sheet. 901 It returns the list of all UnitViews for the given unit. 902 """ 903 for p in self.in_connections: 904 if not isinstance(p,CFProjection): 905 self.debug("Skipping non-CFProjection "+p.name) 906 elif proj_name == '' or p.name==proj_name: 907 v = p.get_view(x,y,self.simulation.time()) 908 src = v.projection.src 909 key = ('Weights',v.projection.dest.name,v.projection.name,x,y) 910 v.proj_src_name = v.projection.src.name 911 src.sheet_views[key] = v
912 913 914 ### JCALERT! This should probably be deleted...
915 - def release_unit_view(self,x,y):
916 self.release_sheet_view(('Weights',x,y))
917 918 919 920
921 -class ResizableCFProjection(CFProjection):
922 """ 923 A CFProjection with resizable weights. 924 """ 925 # Less efficient memory usage than CFProjection because it stores 926 # the (x,y) position of each ConnectionField. 927 928
929 - def _generate_coords(self):