def change_bounds(self, nominal_bounds_template): """ Change the bounding box for all of the ConnectionFields in this Projection. Calls change_bounds() on each ConnectionField. Currently only allows reducing the size, but should be extended to allow increasing as well. """ slice_template = Slice(copy(nominal_bounds_template), self.src, force_odd=True, min_matrix_radius=self.min_matrix_radius) bounds_template = slice_template.compute_bounds(self.src) if not self.bounds_template.containsbb_exclusive(bounds_template): if self.bounds_template.containsbb_inclusive(bounds_template): self.debug('Initial and final bounds are the same.') else: self.warning( 'Unable to change_bounds; currently allows reducing only.') return # it's ok so we can store the bounds and resize the weights mask_template = _create_mask(self.cf_shape, bounds_template, self.src, self.autosize_mask, self.mask_threshold) self.mask_template = mask_template self.n_units = self._calc_n_units() self.nominal_bounds_template = nominal_bounds_template self.bounds_template = bounds_template self._slice_template = slice_template cfs = self.cfs rows, cols = cfs.shape output_fns = [wof.single_cf_fn for wof in self.weights_output_fns] for r in xrange(rows): for c in xrange(cols): xcf, ycf = self.X_cf[0, c], self.Y_cf[r, 0] # CB: listhack - loop is candidate for replacement by numpy fn self._change_cf_bounds( cfs[r, c], input_sheet=self.src, x=xcf, y=ycf, template=slice_template, mask=mask_template, output_fns=output_fns, min_matrix_radius=self.min_matrix_radius)
def __init__(self, initialize_cfs=True, **params): """ Initialize the Projection with a set of cf_type objects (typically ConnectionFields), each located at the location in the source sheet corresponding to the unit in the target sheet. The cf_type objects are stored in the 'cfs' array. The nominal_bounds_template specified may be altered: the bounds must be fitted to the Sheet's matrix, and the weights matrix must have odd dimensions. These altered bounds are passed to the individual connection fields. A mask for the weights matrix is constructed. The shape is specified by cf_shape; the size defaults to the size of the nominal_bounds_template. """ super(CFProjection, self).__init__(**params) self.weights_generator.set_dynamic_time_fn(None, sublistattr='generators') # get the actual bounds_template by adjusting a copy of the # nominal_bounds_template to ensure an odd slice, and to be # cropped to sheet if necessary self._slice_template = Slice(copy(self.nominal_bounds_template), self.src, force_odd=True, min_matrix_radius=self.min_matrix_radius) self.bounds_template = self._slice_template.compute_bounds(self.src) self.mask_template = _create_mask(self.cf_shape, self.bounds_template, self.src, self.autosize_mask, self.mask_threshold) self.n_units = self._calc_n_units() if initialize_cfs: self._create_cfs() if self.apply_output_fns_init: self.apply_learn_output_fns(active_units_mask=False) ### JCALERT! We might want to change the default value of the ### input value to self.src.activity; but it fails, raising a ### type error. It probably has to be clarified why this is ### happening self.input_buffer = None self.activity = np.array(self.dest.activity)
def change_bounds(self, nominal_bounds_template): """ Change the bounding box for all of the ConnectionFields in this Projection. Calls change_bounds() on each ConnectionField. Currently only allows reducing the size, but should be extended to allow increasing as well. """ slice_template = Slice(copy(nominal_bounds_template), self.src,force_odd=True, min_matrix_radius=self.min_matrix_radius) bounds_template = slice_template.compute_bounds(self.src) if not self.bounds_template.containsbb_exclusive(bounds_template): if self.bounds_template.containsbb_inclusive(bounds_template): self.debug('Initial and final bounds are the same.') else: self.warning('Unable to change_bounds; currently allows reducing only.') return # it's ok so we can store the bounds and resize the weights mask_template = _create_mask(self.cf_shape,bounds_template,self.src, self.autosize_mask,self.mask_threshold) self.mask_template = mask_template self.n_units = self._calc_n_units() self.nominal_bounds_template = nominal_bounds_template self.bounds_template = bounds_template self._slice_template = slice_template cfs = self.cfs rows,cols = cfs.shape output_fns = [wof.single_cf_fn for wof in self.weights_output_fns] for r in xrange(rows): for c in xrange(cols): xcf,ycf = self.X_cf[0,c],self.Y_cf[r,0] # CB: listhack - loop is candidate for replacement by numpy fn self._change_cf_bounds(cfs[r,c],input_sheet=self.src, x=xcf,y=ycf, template=slice_template, mask=mask_template, output_fns=output_fns, min_matrix_radius=self.min_matrix_radius)
def __init__(self,initialize_cfs=True,**params): """ Initialize the Projection with a set of cf_type objects (typically ConnectionFields), each located at the location in the source sheet corresponding to the unit in the target sheet. The cf_type objects are stored in the 'cfs' array. The nominal_bounds_template specified may be altered: the bounds must be fitted to the Sheet's matrix, and the weights matrix must have odd dimensions. These altered bounds are passed to the individual connection fields. A mask for the weights matrix is constructed. The shape is specified by cf_shape; the size defaults to the size of the nominal_bounds_template. """ super(CFProjection,self).__init__(**params) self.weights_generator.set_dynamic_time_fn(None,sublistattr='generators') # get the actual bounds_template by adjusting a copy of the # nominal_bounds_template to ensure an odd slice, and to be # cropped to sheet if necessary self._slice_template = Slice(copy(self.nominal_bounds_template), self.src,force_odd=True, min_matrix_radius=self.min_matrix_radius) self.bounds_template = self._slice_template.compute_bounds(self.src) self.mask_template = _create_mask(self.cf_shape,self.bounds_template, self.src,self.autosize_mask, self.mask_threshold) self.n_units = self._calc_n_units() if initialize_cfs: self._create_cfs() if self.apply_output_fns_init: self.apply_learn_output_fns(active_units_mask=False) ### JCALERT! We might want to change the default value of the ### input value to self.src.activity; but it fails, raising a ### type error. It probably has to be clarified why this is ### happening self.input_buffer = None self.activity = np.array(self.dest.activity) if 'cfs' not in self.dest.views: self.dest.views.cfs = AttrDict() self.dest.views.cfs[self.name] = self._cf_grid()
class CFProjection(Projection): """ A projection composed of ConnectionFields from a Sheet into a ProjectionSheet. CFProjection computes its activity using a response_fn of type CFPResponseFn (typically a CF-aware version of mdot) and output_fns (typically none). The initial contents of the ConnectionFields mapping from the input Sheet into the target ProjectionSheet are controlled by the weights_generator, cf_shape, and weights_output_fn parameters, while the location of the ConnectionField is controlled by the coord_mapper parameter. Any subclass has to implement the interface activate(self,input_activity) that computes the response from the input and stores it in the activity array. """ response_fn = param.ClassSelector(CFPResponseFn, default=CFPRF_Plugin(), doc='Function for computing the Projection response to an input pattern.') cf_type = param.Parameter(default=ConnectionField,constant=True, doc="Type of ConnectionField to use when creating individual CFs.") # JPHACKALERT: Not all support for null CFs has been implemented. # CF plotting and C-optimized CFPxF_ functions need # to be fixed to support null CFs without crashing. allow_null_cfs = param.Boolean(default=False, doc="Whether or not the projection can have entirely empty CFs") nominal_bounds_template = BoundingRegionParameter( default=BoundingBox(radius=0.1),doc=""" Bounds defining the Sheet area covered by a prototypical ConnectionField. The true bounds will differ depending on the density (see create_slice_template()).""") weights_generator = param.ClassSelector(PatternGenerator, default=patterngenerator.Constant(),constant=True, doc="Generate initial weights values.") cf_shape = param.ClassSelector(PatternGenerator, default=patterngenerator.Constant(),constant=True, doc="Mask pattern to define the shape of the connection fields.") same_cf_shape_for_all_cfs = param.Boolean(default=True,doc=""" Whether or not to share a single cf_shape mask for all CFs. If True, the cf_shape is evaluated only once and shared for all CFs, which saves computation time and memory. If False, the cf_shape is evaluated once for each CF, allowing each to have its own shape.""") learning_fn = param.ClassSelector(CFPLearningFn, default=CFPLF_Plugin(), doc='Function for computing changes to the weights based on one activation step.') # JABALERT: Shouldn't learning_rate be owned by the learning_fn? learning_rate = param.Number(default=0.0,softbounds=(0,100),doc=""" Amount of learning at each step for this projection, specified in units that are independent of the density of each Sheet.""") weights_output_fns = param.HookList(default=[CFPOF_Plugin()], class_=CFPOutputFn, doc='Functions applied to each CF after learning.') strength = param.Number(default=1.0,doc=""" Global multiplicative scaling applied to the Activity of this Sheet.""") coord_mapper = param.ClassSelector(CoordinateMapperFn, default=IdentityMF(), doc='Function to map a projected coordinate into the target sheet.') # CEBALERT: this is temporary (allows c++ matching in certain # cases). We will allow the user to override the mask size, but # by offering a scaling parameter. autosize_mask = param.Boolean( default=True,constant=True,precedence=-1,doc=""" Topographica sets the mask size so that it is the same as the connection field's size, unless this parameter is False - in which case the user-specified size of the cf_shape is used. In normal usage of Topographica, this parameter should remain True.""") mask_threshold = param.Number(default=0.5,constant=True,doc=""" If a unit is above this value in the cf_shape mask, it is included; otherwise it is excluded from the mask.""") apply_output_fns_init=param.Boolean(default=True,doc=""" Whether to apply the output function to connection fields (e.g. for normalization) when the CFs are first created.""") min_matrix_radius = param.Integer(default=1,bounds=(0,None),doc=""" Enforced minimum for radius of weights matrix. The default of 1 gives a minimum matrix of 3x3. 0 would allow a 1x1 matrix.""") precedence = param.Number(default=0.8) def __init__(self,initialize_cfs=True,**params): """ Initialize the Projection with a set of cf_type objects (typically ConnectionFields), each located at the location in the source sheet corresponding to the unit in the target sheet. The cf_type objects are stored in the 'cfs' array. The nominal_bounds_template specified may be altered: the bounds must be fitted to the Sheet's matrix, and the weights matrix must have odd dimensions. These altered bounds are passed to the individual connection fields. A mask for the weights matrix is constructed. The shape is specified by cf_shape; the size defaults to the size of the nominal_bounds_template. """ super(CFProjection,self).__init__(**params) self.weights_generator.set_dynamic_time_fn(None,sublistattr='generators') # get the actual bounds_template by adjusting a copy of the # nominal_bounds_template to ensure an odd slice, and to be # cropped to sheet if necessary self._slice_template = Slice(copy(self.nominal_bounds_template), self.src,force_odd=True, min_matrix_radius=self.min_matrix_radius) self.bounds_template = self._slice_template.compute_bounds(self.src) self.mask_template = _create_mask(self.cf_shape,self.bounds_template, self.src,self.autosize_mask, self.mask_threshold) self.n_units = self._calc_n_units() if initialize_cfs: self._create_cfs() ### JCALERT! We might want to change the default value of the ### input value to self.src.activity; but it fails, raising a ### type error. It probably has to be clarified why this is ### happening self.input_buffer = None self.activity = array(self.dest.activity) def _generate_coords(self): X,Y = self.dest.sheetcoords_of_idx_grid() vectorized_coord_mapper = simple_vectorize(self.coord_mapper, num_outputs=2, # CB: could switch to float32? output_type=float) return vectorized_coord_mapper(X,Y) # CB: should be _initialize_cfs() since we already have 'initialize_cfs' flag? def _create_cfs(self): vectorized_create_cf = simple_vectorize(self._create_cf) self.cfs = vectorized_create_cf(*self._generate_coords()) self.flatcfs = list(self.cfs.flat) def _create_cf(self,x,y): """ Create a ConnectionField at x,y in the src sheet. """ # (to restore would need to have an r,c counter) # self.debug("Creating CF(%d,%d) from src (%.3f,%.3f) to dest (%.3f,%.3f)"%(r,c,x_cf,y_cf,x,y)) try: if self.apply_output_fns_init: ofs = [wof.single_cf_fn for wof in self.weights_output_fns] else: ofs = [] if self.same_cf_shape_for_all_cfs: mask_template = self.mask_template else: mask_template = _create_mask(self.cf_shape,self.bounds_template, self.src,self.autosize_mask, self.mask_threshold) CF = self.cf_type(self.src,x=x,y=y, template=self._slice_template, weights_generator=self.weights_generator, mask=mask_template, output_fns=ofs, min_matrix_radius=self.min_matrix_radius) except NullCFError: if self.allow_null_cfs: CF = None else: raise return CF def _calc_n_units(self): """Return the number of unmasked units in a typical ConnectionField.""" return min(len(self.mask_template.ravel().nonzero()[0]), # CEBALERT: if the mask_template is bigger than the # src sheet (e.g. conn radius bigger than src # radius), return the size of the source sheet self.src.shape[0]*self.src.shape[1]) def cf(self,r,c): """Return the specified ConnectionField""" # CB: should we offer convenience cf(x,y) (i.e. sheetcoords) method instead? self.warning("CFProjection.cf(r,c) is deprecated: use cfs[r,c] instead") return self.cfs[r,c] def cf_bounds(self,r,c): """Return the bounds of the specified ConnectionField.""" return self.cfs[r,c].get_bounds(self.src) def get_view(self, sheet_x, sheet_y, timestamp): """ Return a single connection field UnitView, for the unit located nearest to sheet coordinate (sheet_x,sheet_y). """ matrix_data = zeros(self.src.activity.shape,Float) (r,c) = self.dest.sheet2matrixidx(sheet_x,sheet_y) r1,r2,c1,c2 = self.cfs[r,c].input_sheet_slice matrix_data[r1:r2,c1:c2] = self.cfs[r,c].weights # CB: the following would be equivalent with Slice __call__ # cf = self.cf(self.dest.sheet2matrixidx(sheet_x,sheet_y)) # matrix_data = numpy.zeros(self.src.activity.shape,Numeric.Float) # matrix_data[cf.input_sheet_slice()]=cf.weights return UnitView((matrix_data,self.src.bounds),sheet_x,sheet_y,self,timestamp) def activate(self,input_activity): """Activate using the specified response_fn and output_fn.""" self.input_buffer = input_activity self.activity *=0.0 self.response_fn(MaskedCFIter(self), input_activity, self.activity, self.strength) for of in self.output_fns: of(self.activity) # CEBALERT: should add active_units_mask to match # apply_learn_output_fns. def learn(self): """ For a CFProjection, learn consists of calling the learning_fn. """ # Learning is performed if the input_buffer has already been set, # i.e. there is an input to the Projection. if self.input_buffer != None: self.learning_fn(MaskedCFIter(self),self.input_buffer,self.dest.activity,self.learning_rate) # CEBALERT: called 'learn' output fns here, but called 'weights' output fns # elsewhere (mostly). Change all to 'learn'? def apply_learn_output_fns(self,active_units_mask=True): """ Apply the weights_output_fns to each unit. If active_units_mask is True, inactive units will be skipped. """ for of in self.weights_output_fns: of(MaskedCFIter(self,active_units_mask=active_units_mask)) # CEBALERT: see gc alert in simulation.__new__ def _cleanup(self): for cf in self.cfs.flat: # cf could be None or maybe something else if hasattr(cf,'input_sheet'): cf.input_sheet=None if hasattr(cf,'input_sheet_slice'): cf.input_sheet_slice=None if hasattr(cf,'weights_slice'): cf.weights_slice=None def n_bytes(self): # Could also count the input_sheet_slice rows,cols=self.cfs.shape return super(CFProjection,self).n_bytes() + \ sum([cf.weights.nbytes + cf.mask.nbytes for cf,i in CFIter(self,ignore_sheet_mask=True)()]) def n_conns(self): # Counts non-masked values, if mask is available; otherwise counts # weights as connections if nonzero rows,cols=self.cfs.shape return sum([len((cf.mask if cf.mask is not None else cf.weights).ravel().nonzero()[0]) for cf,i in MaskedCFIter(self)()])
def __init__(self,input_sheet,x=0.0,y=0.0,template=BoundingBox(radius=0.1), weights_generator=patterngenerator.Constant(), mask=patterngenerator.Constant(), output_fns=None,min_matrix_radius=1): """ Create weights at the specified (x,y) location on the specified input_sheet. The supplied template (if a BoundingRegion) is converted to a Slice, moved to the specified (x,y) location, and then the weights pattern is drawn inside by the weights_generator. Note that if the appropriate template Slice is already known, then it can be passed in instead of a BoundingRegion template. This slice will then be used directly, instead of converting the template into a Slice. The supplied template object itself will not be modified (it is copied before use). The mask allows the weights to be limited to being non-zero in a subset of the rectangular weights area. The actual mask used is a view of the given mask created by cropping to the boundaries of the input_sheet, so that the weights all correspond to actual locations in the input sheet. For instance, if a circular pattern of weights is desired, the mask should have a disk-shaped pattern of elements with value 1, surrounded by elements with the value 0. If the CF extends over the edge of the input sheet then the weights will actually be half-moon (or similar) rather than circular. """ #print "Create CF",input_sheet.name,x,y,"template=",template,"wg=",weights_generator,"m=",mask,"ofs=",output_fns,"min r=",min_matrix_radius template = copy(template) if not isinstance(template,Slice): template = Slice(template,input_sheet,force_odd=True, min_matrix_radius=min_matrix_radius) # Note: if passed in, mask is shared between CFs (but not if created here) if not hasattr(mask,'view'): mask = _create_mask(mask,template.compute_bounds(input_sheet), # CEBALERT: it's not really worth adding more ALERTs on this # topic, but...there's no way for the CF to control autosize # and threshold. input_sheet,True,0.5) # CB: has to be set for C code. Can't be initialized at the # class level, or it would become a read-only class attribute # (because it's a slot: # http://docs.python.org/reference/datamodel.html). Can we # somehow avoid having to think about _has_norm_total in the # python code? Could the C code initialize this value? self._has_norm_total=False if output_fns is None: output_fns = [] # CEBALERT: now even more confusing; weights_slice is # different from input_sheet_slice. At least need to rename. weights_slice = self._create_input_sheet_slice(input_sheet,x,y,template,min_matrix_radius) # CBNOTE: this would be clearer (but not perfect, and probably slower) # m = mask_template[self.weights_slice()] self.mask = weights_slice.submatrix(mask) # view of original mask self.mask = array(self.mask,copy=1) # CEBALERT: why is this necessary? # (without it, optimized learning function creates artifacts in CFs at # left and right edges of sheet, at some densities) # CBENHANCEMENT: might want to do something about a size # that's specified (right now the size is assumed to be that # of the bounds) # shouldn't be extra computation of boundingbox because it's gone from Slice.__init__; could avoid extra lookups by getting straight from slice w = weights_generator(x=x,y=y,bounds=self.get_bounds(input_sheet), xdensity=input_sheet.xdensity, ydensity=input_sheet.ydensity, mask=self.mask) # CEBALERT: unnecessary copy! Pass type to PG & have it draw # in that. (Should be simple, except making it work for all # the PG subclasses that override array creation in various # ways (producing or using inconsistent types) turned out to # be too painful.) self.weights = w.astype(weight_type) # CEBHACKALERT: the system of masking through multiplication # by 0 works for now, while the output_fns are all # multiplicative. But in the long run we need a better way to # apply the mask. The same applies anywhere the mask is used, # including in learningfn/. We should investigate masked # arrays (from numpy). for of in output_fns: of(self.weights)
class CFProjection(Projection): """ A projection composed of ConnectionFields from a Sheet into a ProjectionSheet. CFProjection computes its activity using a response_fn of type CFPResponseFn (typically a CF-aware version of mdot) and output_fns (typically none). The initial contents of the ConnectionFields mapping from the input Sheet into the target ProjectionSheet are controlled by the weights_generator, cf_shape, and weights_output_fn parameters, while the location of the ConnectionField is controlled by the coord_mapper parameter. Any subclass has to implement the interface activate(self,input_activity) that computes the response from the input and stores it in the activity array. """ response_fn = param.ClassSelector( CFPResponseFn, default=CFPRF_Plugin(), doc= 'Function for computing the Projection response to an input pattern.') cf_type = param.Parameter( default=ConnectionField, constant=True, doc="Type of ConnectionField to use when creating individual CFs.") # JPHACKALERT: Not all support for null CFs has been implemented. # CF plotting and C-optimized CFPxF_ functions need # to be fixed to support null CFs without crashing. allow_null_cfs = param.Boolean( default=False, doc="Whether or not the projection can have entirely empty CFs") nominal_bounds_template = BoundingRegionParameter( default=BoundingBox(radius=0.1), doc=""" Bounds defining the Sheet area covered by a prototypical ConnectionField. The true bounds will differ depending on the density (see create_slice_template()).""" ) weights_generator = param.ClassSelector( PatternGenerator, default=patterngenerator.Constant(), constant=True, doc="Generate initial weights values.") cf_shape = param.ClassSelector( PatternGenerator, default=patterngenerator.Constant(), constant=True, doc="Mask pattern to define the shape of the connection fields.") same_cf_shape_for_all_cfs = param.Boolean(default=True, doc=""" Whether or not to share a single cf_shape mask for all CFs. If True, the cf_shape is evaluated only once and shared for all CFs, which saves computation time and memory. If False, the cf_shape is evaluated once for each CF, allowing each to have its own shape.""") learning_fn = param.ClassSelector( CFPLearningFn, default=CFPLF_Plugin(), doc= 'Function for computing changes to the weights based on one activation step.' ) # JABALERT: Shouldn't learning_rate be owned by the learning_fn? learning_rate = param.Number(default=0.0, softbounds=(0, 100), doc=""" Amount of learning at each step for this projection, specified in units that are independent of the density of each Sheet.""") weights_output_fns = param.HookList( default=[CFPOF_Plugin()], class_=CFPOutputFn, doc='Functions applied to each CF after learning.') strength = param.Number(default=1.0, doc=""" Global multiplicative scaling applied to the Activity of this Sheet.""" ) coord_mapper = param.ClassSelector( CoordinateMapperFn, default=IdentityMF(), doc='Function to map a projected coordinate into the target sheet.') # CEBALERT: this is temporary (allows c++ matching in certain # cases). We will allow the user to override the mask size, but # by offering a scaling parameter. autosize_mask = param.Boolean(default=True, constant=True, precedence=-1, doc=""" Topographica sets the mask size so that it is the same as the connection field's size, unless this parameter is False - in which case the user-specified size of the cf_shape is used. In normal usage of Topographica, this parameter should remain True.""") mask_threshold = param.Number(default=0.5, constant=True, doc=""" If a unit is above this value in the cf_shape mask, it is included; otherwise it is excluded from the mask.""") apply_output_fns_init = param.Boolean(default=True, doc=""" Whether to apply the output function to connection fields (e.g. for normalization) when the CFs are first created.""") min_matrix_radius = param.Integer(default=1, bounds=(0, None), doc=""" Enforced minimum for radius of weights matrix. The default of 1 gives a minimum matrix of 3x3. 0 would allow a 1x1 matrix.""") precedence = param.Number(default=0.8) def __init__(self, initialize_cfs=True, **params): """ Initialize the Projection with a set of cf_type objects (typically ConnectionFields), each located at the location in the source sheet corresponding to the unit in the target sheet. The cf_type objects are stored in the 'cfs' array. The nominal_bounds_template specified may be altered: the bounds must be fitted to the Sheet's matrix, and the weights matrix must have odd dimensions. These altered bounds are passed to the individual connection fields. A mask for the weights matrix is constructed. The shape is specified by cf_shape; the size defaults to the size of the nominal_bounds_template. """ super(CFProjection, self).__init__(**params) self.weights_generator.set_dynamic_time_fn(None, sublistattr='generators') # get the actual bounds_template by adjusting a copy of the # nominal_bounds_template to ensure an odd slice, and to be # cropped to sheet if necessary self._slice_template = Slice(copy(self.nominal_bounds_template), self.src, force_odd=True, min_matrix_radius=self.min_matrix_radius) self.bounds_template = self._slice_template.compute_bounds(self.src) self.mask_template = _create_mask(self.cf_shape, self.bounds_template, self.src, self.autosize_mask, self.mask_threshold) self.n_units = self._calc_n_units() if initialize_cfs: self._create_cfs() ### JCALERT! We might want to change the default value of the ### input value to self.src.activity; but it fails, raising a ### type error. It probably has to be clarified why this is ### happening self.input_buffer = None self.activity = array(self.dest.activity) def _generate_coords(self): X, Y = self.dest.sheetcoords_of_idx_grid() vectorized_coord_mapper = simple_vectorize( self.coord_mapper, num_outputs=2, # CB: could switch to float32? output_type=float) return vectorized_coord_mapper(X, Y) # CB: should be _initialize_cfs() since we already have 'initialize_cfs' flag? def _create_cfs(self): vectorized_create_cf = simple_vectorize(self._create_cf) #: self.cfs = vectorized_create_cf(*self._generate_coords()) self.flatcfs = list(self.cfs.flat) def _create_cf(self, x, y): """ Create a ConnectionField at x,y in the src sheet. """ # (to restore would need to have an r,c counter) # self.debug("Creating CF(%d,%d) from src (%.3f,%.3f) to dest (%.3f,%.3f)"%(r,c,x_cf,y_cf,x,y)) try: if self.apply_output_fns_init: ofs = [wof.single_cf_fn for wof in self.weights_output_fns] else: ofs = [] if self.same_cf_shape_for_all_cfs: mask_template = self.mask_template else: mask_template = _create_mask(self.cf_shape, self.bounds_template, self.src, self.autosize_mask, self.mask_threshold) CF = self.cf_type(self.src, x=x, y=y, template=self._slice_template, weights_generator=self.weights_generator, mask=mask_template, output_fns=ofs, min_matrix_radius=self.min_matrix_radius) except NullCFError: if self.allow_null_cfs: CF = None else: raise return CF def _calc_n_units(self): """Return the number of unmasked units in a typical ConnectionField.""" return min( len(self.mask_template.ravel().nonzero()[0]), # CEBALERT: if the mask_template is bigger than the # src sheet (e.g. conn radius bigger than src # radius), return the size of the source sheet self.src.shape[0] * self.src.shape[1]) def cf(self, r, c): """Return the specified ConnectionField""" # CB: should we offer convenience cf(x,y) (i.e. sheetcoords) method instead? self.warning( "CFProjection.cf(r,c) is deprecated: use cfs[r,c] instead") return self.cfs[r, c] def cf_bounds(self, r, c): """Return the bounds of the specified ConnectionField.""" return self.cfs[r, c].get_bounds(self.src) def get_view(self, sheet_x, sheet_y, timestamp): """ Return a single connection field UnitView, for the unit located nearest to sheet coordinate (sheet_x,sheet_y). """ matrix_data = zeros(self.src.activity.shape, Float) (r, c) = self.dest.sheet2matrixidx(sheet_x, sheet_y) r1, r2, c1, c2 = self.cfs[r, c].input_sheet_slice matrix_data[r1:r2, c1:c2] = self.cfs[r, c].weights # CB: the following would be equivalent with Slice __call__ # cf = self.cf(self.dest.sheet2matrixidx(sheet_x,sheet_y)) # matrix_data = numpy.zeros(self.src.activity.shape,Numeric.Float) # matrix_data[cf.input_sheet_slice()]=cf.weights return UnitView((matrix_data, self.src.bounds), sheet_x, sheet_y, self, timestamp) def activate(self, input_activity): """Activate using the specified response_fn and output_fn.""" self.input_buffer = input_activity self.activity *= 0.0 self.response_fn(MaskedCFIter(self), input_activity, self.activity, self.strength) for of in self.output_fns: of(self.activity) # CEBALERT: should add active_units_mask to match # apply_learn_output_fns. def learn(self): """ For a CFProjection, learn consists of calling the learning_fn. """ # Learning is performed if the input_buffer has already been set, # i.e. there is an input to the Projection. if self.input_buffer != None: self.learning_fn(MaskedCFIter(self), self.input_buffer, self.dest.activity, self.learning_rate) # CEBALERT: called 'learn' output fns here, but called 'weights' output fns # elsewhere (mostly). Change all to 'learn'? def apply_learn_output_fns(self, active_units_mask=True): """ Apply the weights_output_fns to each unit. If active_units_mask is True, inactive units will be skipped. """ for of in self.weights_output_fns: of(MaskedCFIter(self, active_units_mask=active_units_mask)) # CEBALERT: see gc alert in simulation.__new__ def _cleanup(self): for cf in self.cfs.flat: # cf could be None or maybe something else if hasattr(cf, 'input_sheet'): cf.input_sheet = None if hasattr(cf, 'input_sheet_slice'): cf.input_sheet_slice = None if hasattr(cf, 'weights_slice'): cf.weights_slice = None def n_bytes(self): # Could also count the input_sheet_slice rows, cols = self.cfs.shape return super(CFProjection,self).n_bytes() + \ sum([cf.weights.nbytes + cf.mask.nbytes for cf,i in CFIter(self,ignore_sheet_mask=True)()]) def n_conns(self): # Counts non-masked values, if mask is available; otherwise counts # weights as connections if nonzero rows, cols = self.cfs.shape return sum([ len((cf.mask if cf.mask is not None else cf.weights).ravel().nonzero()[0]) for cf, i in MaskedCFIter(self)() ])
def __init__(self, input_sheet, x=0.0, y=0.0, template=BoundingBox(radius=0.1), weights_generator=patterngenerator.Constant(), mask=patterngenerator.Constant(), output_fns=None, min_matrix_radius=1): """ Create weights at the specified (x,y) location on the specified input_sheet. The supplied template (if a BoundingRegion) is converted to a Slice, moved to the specified (x,y) location, and then the weights pattern is drawn inside by the weights_generator. Note that if the appropriate template Slice is already known, then it can be passed in instead of a BoundingRegion template. This slice will then be used directly, instead of converting the template into a Slice. The supplied template object itself will not be modified (it is copied before use). The mask allows the weights to be limited to being non-zero in a subset of the rectangular weights area. The actual mask used is a view of the given mask created by cropping to the boundaries of the input_sheet, so that the weights all correspond to actual locations in the input sheet. For instance, if a circular pattern of weights is desired, the mask should have a disk-shaped pattern of elements with value 1, surrounded by elements with the value 0. If the CF extends over the edge of the input sheet then the weights will actually be half-moon (or similar) rather than circular. """ #print "Create CF",input_sheet.name,x,y,"template=",template,"wg=",weights_generator,"m=",mask,"ofs=",output_fns,"min r=",min_matrix_radius template = copy(template) if not isinstance(template, Slice): template = Slice(template, input_sheet, force_odd=True, min_matrix_radius=min_matrix_radius) # Note: if passed in, mask is shared between CFs (but not if created here) if not hasattr(mask, 'view'): mask = _create_mask( mask, template.compute_bounds(input_sheet), # CEBALERT: it's not really worth adding more ALERTs on this # topic, but...there's no way for the CF to control autosize # and threshold. input_sheet, True, 0.5) # CB: has to be set for C code. Can't be initialized at the # class level, or it would become a read-only class attribute # (because it's a slot: # http://docs.python.org/reference/datamodel.html). Can we # somehow avoid having to think about _has_norm_total in the # python code? Could the C code initialize this value? self._has_norm_total = array([0], dtype=numpy.int32) self._norm_total = array([0.0], dtype=float) if output_fns is None: output_fns = [] # CEBALERT: now even more confusing; weights_slice is # different from input_sheet_slice. At least need to rename. weights_slice = self._create_input_sheet_slice(input_sheet, x, y, template, min_matrix_radius) # CBNOTE: this would be clearer (but not perfect, and probably slower) # m = mask_template[self.weights_slice()] self.mask = weights_slice.submatrix(mask) # view of original mask self.mask = array(self.mask, copy=1) # CEBALERT: why is this necessary? # (without it, optimized learning function creates artifacts in CFs at # left and right edges of sheet, at some densities) # CBENHANCEMENT: might want to do something about a size # that's specified (right now the size is assumed to be that # of the bounds) # shouldn't be extra computation of boundingbox because it's gone from Slice.__init__; could avoid extra lookups by getting straight from slice w = weights_generator(x=x, y=y, bounds=self.get_bounds(input_sheet), xdensity=input_sheet.xdensity, ydensity=input_sheet.ydensity, mask=self.mask) # CEBALERT: unnecessary copy! Pass type to PG & have it draw # in that. (Should be simple, except making it work for all # the PG subclasses that override array creation in various # ways (producing or using inconsistent types) turned out to # be too painful.) self.weights = w.astype(weight_type) # CEBHACKALERT: the system of masking through multiplication # by 0 works for now, while the output_fns are all # multiplicative. But in the long run we need a better way to # apply the mask. The same applies anywhere the mask is used, # including in learningfn/. We should investigate masked # arrays (from numpy). for of in output_fns: of(self.weights)