Example #1
0
    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)
Example #2
0
    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 = Layout()
        self.dest.views.CFs[self.name] = self._cf_grid()
Example #3
0
    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)
Example #4
0
    def _re_bound(self,plot_bounding_box,mat,box,density):

        # CEBHACKALERT: for Julien...
        # If plot_bounding_box is that of a Sheet, it will already have been
        # setup so that the density in the x direction and the density in the
        # y direction are equal.
        # If plot_bounding_box comes from elsewhere (i.e. you create it from
        # arbitrary bounds), it might need to be adjusted to ensure the density
        # in both directions is the same (see Sheet.__init__()). I don't know where
        # you want to do that; presumably the code should be common to Sheet and
        # where it's used in the plotting?
        #
        # It's possible we can move some of the functionality
        # into SheetCoordinateSystem.
        if plot_bounding_box.containsbb_exclusive(box):
             ct = SheetCoordinateSystem(plot_bounding_box,density,density)
             new_mat = np.zeros(ct.shape,dtype=np.float)
             r1,r2,c1,c2 = Slice(box,ct)
             new_mat[r1:r2,c1:c2] = mat
        else:
             scs = SheetCoordinateSystem(box,density,density)
             s=Slice(plot_bounding_box,scs)
             s.crop_to_sheet(scs)
             new_mat = s.submatrix(mat)

        return new_mat
Example #5
0
    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 = Layout()
        self.dest.views.CFs[self.name] = self._cf_grid()
Example #6
0
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.""")

    hash_format = param.String(default="{name}-{src}-{dest}",
                               doc="""
       Format string to determine the hash value used to initialize
       random weight generation. Format keys available include {name}
       {src} and {dest}.""")

    seed = param.Integer(default=None,
                         allow_None=True,
                         doc="""
       The random seed used to determine the randomized weight
       initialization stream. If not None, equivalent to appending the
       chosen integer to the hash_format.""")

    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()

        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 = Layout()
        self.dest.views.CFs[self.name] = self._cf_grid()

    def _cf_grid(self, shape=None, **kwargs):
        "Create GridSpace with the correct metadata."
        grid = GridSpace({})
        grid.metadata = AttrDict(timestamp=self.src.simulation.time(),
                                 info=self.name,
                                 proj_src_name=self.src.name,
                                 proj_dest_name=self.dest.name,
                                 **kwargs)
        return grid

    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))

        label = self.hash_format.format(name=self.name,
                                        src=self.src.name,
                                        dest=self.dest.name)

        label = label + ('-%d' % self.seed if self.seed is not None else '')
        name = "%s_CF (%.5f, %.5f)" % ('' if label is None else label, x, y)
        try:
            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,
                                             name=name)

            CF = self.cf_type(self.src,
                              x=x,
                              y=y,
                              template=self._slice_template,
                              weights_generator=self.weights_generator,
                              mask=mask_template,
                              min_matrix_radius=self.min_matrix_radius,
                              label=label)
        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 grid(self, rows=11, cols=11, lbrt=None, situated=False, **kwargs):
        xdensity, ydensity = self.dest.xdensity, self.dest.ydensity
        l, b, r, t = self.dest.bounds.lbrt()
        half_x_unit = ((r - l) / xdensity) / 2.
        half_y_unit = ((t - b) / ydensity) / 2.
        if lbrt is None:
            l, b, r, t = (l + half_x_unit, b + half_y_unit, r - half_x_unit,
                          t - half_y_unit)
        else:
            l, b = self.dest.closest_cell_center(lbrt[0], lbrt[1])
            r, t = self.dest.closest_cell_center(lbrt[2], lbrt[3])
        x, y = np.meshgrid(np.linspace(l, r, cols), np.linspace(b, t, rows))
        coords = zip(x.flat, y.flat)

        grid_items = {}
        for x, y in coords:
            grid_items[x, y] = self.view(x, y, situated=situated, **kwargs)

        grid = GridSpace(grid_items,
                         label=' '.join([self.dest.name, self.name]),
                         group='CFs')
        grid.metadata = AttrDict(info=self.name,
                                 proj_src_name=self.src.name,
                                 proj_dest_name=self.dest.name,
                                 timestamp=self.src.simulation.time(),
                                 **kwargs)
        return grid

    def view(self, sheet_x, sheet_y, timestamp=None, situated=False, **kwargs):
        """
        Return a single connection field Image, for the unit
        located nearest to sheet coordinate (sheet_x,sheet_y).
        """
        if timestamp is None:
            timestamp = self.src.simulation.time()
        time_dim = Dimension("Time", type=param.Dynamic.time_fn.time_type)
        (r, c) = self.dest.sheet2matrixidx(sheet_x, sheet_y)
        cf = self.cfs[r, c]
        r1, r2, c1, c2 = cf.input_sheet_slice
        situated_shape = self.src.activity.shape
        situated_bounds = self.src.bounds
        roi_bounds = cf.get_bounds(self.src)
        if situated:
            matrix_data = np.zeros(situated_shape, dtype=np.float64)
            matrix_data[r1:r2, c1:c2] = cf.weights.copy()
            bounds = situated_bounds
        else:
            matrix_data = cf.weights.copy()
            bounds = roi_bounds

        sv = CFView(matrix_data,
                    bounds,
                    situated_bounds=situated_bounds,
                    input_sheet_slice=(r1, r2, c1, c2),
                    roi_bounds=roi_bounds,
                    label=self.name,
                    group='CF Weight')
        sv.metadata = AttrDict(timestamp=timestamp)

        viewmap = HoloMap((timestamp, sv), kdims=[time_dim])
        viewmap.metadata = AttrDict(coords=(sheet_x, sheet_y),
                                    dest_name=self.dest.name,
                                    precedence=self.src.precedence,
                                    proj_name=self.name,
                                    src_name=self.src.name,
                                    row_precedence=self.src.row_precedence,
                                    timestamp=timestamp,
                                    **kwargs)
        return viewmap

    def get_view(self, sheet_x, sheet_y, timestamp=None):
        self.warning("Deprecated, call 'view' method instead.")
        return self.view(sheet_x, sheet_y, timestamp)

    def activate(self, input_activity):
        """Activate using the specified response_fn and output_fn."""
        if self.input_fns:
            input_activity = input_activity.copy()
        for iaf in self.input_fns:
            iaf(input_activity)
        self.input_buffer = input_activity
        self.activity *= 0.0
        self.response_fn(CFIter(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 is not None:
            self.learning_fn(CFIter(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(CFIter(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 np.sum([
            len((cf.mask
                 if cf.mask is not None else cf.weights).ravel().nonzero()[0])
            for cf, i in CFIter(self)()
        ])
Example #7
0
    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,
                 label=None):
        """
        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 = np.array([0], dtype=np.int32)
        self._norm_total = np.array([0.0], dtype=np.float64)

        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 = np.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

        pattern_params = dict(x=x,
                              y=y,
                              bounds=self.get_bounds(input_sheet),
                              xdensity=input_sheet.xdensity,
                              ydensity=input_sheet.ydensity,
                              mask=self.mask)

        controlled_weights = (param.Dynamic.time_dependent
                              and isinstance(param.Dynamic.time_fn, param.Time)
                              and self.independent_weight_generation)

        if controlled_weights:
            with param.Dynamic.time_fn as t:
                t(0)  # Initialize weights at time zero.
                # Controls random streams
                name = "%s_CF (%.5f, %.5f)" % ('' if label is None else label,
                                               x, y)
                w = weights_generator(**dict(pattern_params, name=name))
        else:
            w = weights_generator(**pattern_params)

        # 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)
Example #8
0
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.""")

    hash_format = param.String(default="{name}-{src}-{dest}", doc="""
       Format string to determine the hash value used to initialize
       random weight generation. Format keys available include {name}
       {src} and {dest}.""")

    seed = param.Integer(default=None, allow_None=True, doc="""
       The random seed used to determine the randomized weight
       initialization stream. If not None, equivalent to appending the
       chosen integer to the hash_format.""")

    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()

        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 = Layout()
        self.dest.views.CFs[self.name] = self._cf_grid()


    def _cf_grid(self, shape=None, **kwargs):
        "Create GridSpace with the correct metadata."
        grid = GridSpace({})
        grid.metadata = AttrDict(timestamp=self.src.simulation.time(),
                                 info=self.name,
                                 proj_src_name=self.src.name,
                                 proj_dest_name=self.dest.name,
                                 **kwargs)
        return grid


    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))

        label = self.hash_format.format(name=self.name,
                                        src=self.src.name,
                                        dest=self.dest.name)

        label = label + ('-%d' % self.seed if self.seed is not None else '')
        name = "%s_CF (%.5f, %.5f)" % ('' if label is None else label, x,y)
        try:
            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,
                                             name=name)

            CF = self.cf_type(self.src, x=x, y=y,
                              template=self._slice_template,
                              weights_generator=self.weights_generator,
                              mask=mask_template,
                              min_matrix_radius=self.min_matrix_radius,
                              label = label)
        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 grid(self, rows=11, cols=11, lbrt=None, situated=False, **kwargs):
        xdensity, ydensity = self.dest.xdensity, self.dest.ydensity
        l, b, r, t = self.dest.bounds.lbrt()
        half_x_unit = ((r-l) / xdensity) / 2.
        half_y_unit = ((t-b) / ydensity) / 2.
        if lbrt is None:
            l, b, r, t = (l+half_x_unit, b+half_y_unit, r-half_x_unit, t-half_y_unit)
        else:
            l, b = self.dest.closest_cell_center(lbrt[0], lbrt[1])
            r, t = self.dest.closest_cell_center(lbrt[2], lbrt[3])
        x, y = np.meshgrid(np.linspace(l, r, cols),
                           np.linspace(b, t, rows))
        coords = zip(x.flat, y.flat)

        grid_items = {}
        for x, y in coords:
            grid_items[x, y] = self.view(x, y, situated=situated, **kwargs)

        grid = GridSpace(grid_items, label=' '.join([self.dest.name, self.name]),
                         group='CFs')
        grid.metadata = AttrDict(info=self.name,
                                 proj_src_name=self.src.name,
                                 proj_dest_name=self.dest.name,
                                 timestamp=self.src.simulation.time(),
                                 **kwargs)
        return grid


    def view(self, sheet_x, sheet_y, timestamp=None, situated=False, **kwargs):
        """
        Return a single connection field Image, for the unit
        located nearest to sheet coordinate (sheet_x,sheet_y).
        """
        if timestamp is None:
            timestamp = self.src.simulation.time()
        time_dim = Dimension("Time", type=param.Dynamic.time_fn.time_type)
        (r, c) = self.dest.sheet2matrixidx(sheet_x, sheet_y)
        cf = self.cfs[r, c]
        r1, r2, c1, c2 = cf.input_sheet_slice
        situated_shape = self.src.activity.shape
        situated_bounds = self.src.bounds
        roi_bounds = cf.get_bounds(self.src)
        if situated:
            matrix_data = np.zeros(situated_shape, dtype=np.float64)
            matrix_data[r1:r2, c1:c2] = cf.weights.copy()
            bounds = situated_bounds
        else:
            matrix_data = cf.weights.copy()
            bounds = roi_bounds

        sv = CFView(matrix_data, bounds, situated_bounds=situated_bounds,
                    input_sheet_slice=(r1, r2, c1, c2), roi_bounds=roi_bounds,
                    label=self.name, group='CF Weight')
        sv.metadata=AttrDict(timestamp=timestamp)

        viewmap = HoloMap((timestamp, sv), kdims=[time_dim])
        viewmap.metadata = AttrDict(coords=(sheet_x, sheet_y),
                                    dest_name=self.dest.name,
                                    precedence=self.src.precedence,
                                    proj_name=self.name,
                                    src_name=self.src.name,
                                    row_precedence=self.src.row_precedence,
                                    timestamp=timestamp, **kwargs)
        return viewmap


    def get_view(self, sheet_x, sheet_y, timestamp=None):
        self.warning("Deprecated, call 'view' method instead.")
        return self.view(sheet_x, sheet_y, timestamp)


    def activate(self,input_activity):
        """Activate using the specified response_fn and output_fn."""
        if self.input_fns:
            input_activity = input_activity.copy()
        for iaf in self.input_fns:
            iaf(input_activity)
        self.input_buffer = input_activity
        self.activity *=0.0
        self.response_fn(CFIter(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 is not None:
            self.learning_fn(CFIter(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(CFIter(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 np.sum([len((cf.mask if cf.mask is not None else cf.weights).ravel().nonzero()[0])
                    for cf,i in CFIter(self)()])
Example #9
0
    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, label=None):
        """
        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=np.array([0],dtype=np.int32)
        self._norm_total=np.array([0.0],dtype=np.float64)

        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 = np.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

        pattern_params = dict(x=x,y=y,bounds=self.get_bounds(input_sheet),
                              xdensity=input_sheet.xdensity,
                              ydensity=input_sheet.ydensity,
                              mask=self.mask)

        controlled_weights = (param.Dynamic.time_dependent
                              and isinstance(param.Dynamic.time_fn, param.Time)
                              and self.independent_weight_generation)

        if controlled_weights:
            with param.Dynamic.time_fn as t:
                t(0)                        # Initialize weights at time zero.
                # Controls random streams
                name = "%s_CF (%.5f, %.5f)" % ('' if label is None else label, x,y)
                w = weights_generator(**dict(pattern_params, name=name))
        else:
            w = weights_generator(**pattern_params)


        # 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)