def _set_image(self,image):
        # Stores a SheetCoordinateSystem with an activity matrix
        # representing the image
        if not isinstance(image,numpy.ndarray):
            image = array(image,Float)

        rows,cols = image.shape
        self.scs = SheetCoordinateSystem(xdensity=1.0,ydensity=1.0,
                                         bounds=BoundingBox(points=((-cols/2.0,-rows/2.0),
                                                                    ( cols/2.0, rows/2.0))))
        self.scs.activity=image
示例#2
0
    def __equalize_densities(self, nominal_bounds, nominal_density):
        """
        Calculate the true density along x, and adjust the top and
        bottom bounds so that the density along y will be equal.

        Returns (adjusted_bounds,true_density)
        """
        left, bottom, right, top = nominal_bounds.lbrt()
        width = right - left
        height = top - bottom
        center_y = bottom + height / 2.0
        # The true density is not equal to the nominal_density
        # when nominal_density*(right-left) is not an integer.
        true_density = int(nominal_density * (width)) / float(width)

        n_cells = round(height * true_density, 0)
        adjusted_half_height = n_cells / true_density / 2.0
        # (The above might be clearer as (step*n_units)/2.0, where
        # step=1.0/density.)

        return (BoundingBox(points=((left, center_y - adjusted_half_height),
                                    (right, center_y + adjusted_half_height))),
                true_density)
示例#3
0
class PatternGenerator(param.Parameterized):
    """
    A class hierarchy for callable objects that can generate 2D patterns.

    Once initialized, PatternGenerators can be called to generate a
    value or a matrix of values from a 2D function, typically
    accepting at least x and y.

    A PatternGenerator's Parameters can make use of Parameter's
    precedence attribute to specify the order in which they should
    appear, e.g. in a GUI. The precedence attribute has a nominal
    range of 0.0 to 1.0, with ordering going from 0.0 (first) to 1.0
    (last), but any value is allowed.

    The orientation and layout of the pattern matrices is defined by
    the SheetCoordinateSystem class, which see.

    Note that not every parameter defined for a PatternGenerator will
    be used by every subclass.  For instance, a Constant pattern will
    ignore the x, y, orientation, and size parameters, because the
    pattern does not vary with any of those parameters.  However,
    those parameters are still defined for all PatternGenerators, even
    Constant patterns, to allow PatternGenerators to be scaled, rotated,
    translated, etc. uniformly.
    """
    __abstract = True

    bounds  = BoundingRegionParameter(
        default=BoundingBox(points=((-0.5,-0.5), (0.5,0.5))),precedence=-1,
        doc="BoundingBox of the area in which the pattern is generated.")

    xdensity = param.Number(default=10,bounds=(0,None),precedence=-1,doc="""
        Density (number of samples per 1.0 length) in the x direction.""")

    ydensity = param.Number(default=10,bounds=(0,None),precedence=-1,doc="""
        Density (number of samples per 1.0 length) in the y direction.
        Typically the same as the xdensity.""")

    x = param.Number(default=0.0,softbounds=(-1.0,1.0),precedence=0.20,doc="""
        X-coordinate location of pattern center.""")

    y = param.Number(default=0.0,softbounds=(-1.0,1.0),precedence=0.21,doc="""
        Y-coordinate location of pattern center.""")


    position = param.Composite(attribs=['x','y'],precedence=-1,doc="""
        Coordinates of location of pattern center.
        Provides a convenient way to set the x and y parameters together
        as a tuple (x,y), but shares the same actual storage as x and y
        (and thus only position OR x and y need to be specified).""")

    orientation = param.Number(default=0.0,softbounds=(0.0,2*pi),precedence=0.40,doc="""
        Polar angle of pattern, i.e., the orientation in the Cartesian coordinate
        system, with zero at 3 o'clock and increasing counterclockwise.""")

    size = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,6.0),
        precedence=0.30,doc="""Determines the overall size of the pattern.""")

    scale = param.Number(default=1.0,softbounds=(0.0,2.0),precedence=0.10,doc="""
        Multiplicative strength of input pattern, defaulting to 1.0""")

    offset = param.Number(default=0.0,softbounds=(-1.0,1.0),precedence=0.11,doc="""
        Additive offset to input pattern, defaulting to 0.0""")

    mask = param.Parameter(default=None,precedence=-1,doc="""
        Optional object (expected to be an array) with which to multiply the
        pattern array after it has been created, before any output_fns are
        applied. This can be used to shape the pattern.""")

    # Note that the class type is overridden to PatternGenerator below
    mask_shape = param.ClassSelector(param.Parameterized,default=None,precedence=0.06,doc="""
        Optional PatternGenerator used to construct a mask to be applied to
        the pattern.""")

    output_fns = param.HookList(default=[],class_=TransferFn,precedence=0.08,doc="""
        Optional function(s) to apply to the pattern array after it has been created.
        Can be used for normalization, thresholding, etc.""")


    def __init__(self,**params):
        super(PatternGenerator, self).__init__(**params)
        self.set_matrix_dimensions(self.bounds, self.xdensity, self.ydensity)


    def __call__(self,**params_to_override):
        """
        Call the subclass's 'function' method on a rotated and scaled coordinate system.

        Creates and fills an array with the requested pattern.  If
        called without any params, uses the values for the Parameters
        as currently set on the object. Otherwise, any params
        specified override those currently set on the object.
        """
        p=ParamOverrides(self,params_to_override)

        # CEBERRORALERT: position parameter is not currently
        # supported. We should delete the position parameter or fix
        # this.
        #
        # position=params_to_override.get('position',None) if position
        # is not None: x,y = position

        self._setup_xy(p.bounds,p.xdensity,p.ydensity,p.x,p.y,p.orientation)
        fn_result = self.function(p)
        self._apply_mask(p,fn_result)
        result = p.scale*fn_result+p.offset

        for of in p.output_fns:
            of(result)

        return result


    def _setup_xy(self,bounds,xdensity,ydensity,x,y,orientation):
        """
        Produce pattern coordinate matrices from the bounds and
        density (or rows and cols), and transforms them according to
        x, y, and orientation.
        """
        self.debug(lambda:"bounds=%s, xdensity=%s, ydensity=%s, x=%s, y=%s, orientation=%s"%(bounds,xdensity,ydensity,x,y,orientation))
        # Generate vectors representing coordinates at which the pattern
        # will be sampled.

        # CB: note to myself - use slice_._scs if supplied?
        x_points,y_points = SheetCoordinateSystem(bounds,xdensity,ydensity).sheetcoordinates_of_matrixidx()

        # Generate matrices of x and y sheet coordinates at which to
        # sample pattern, at the correct orientation
        self.pattern_x, self.pattern_y = self._create_and_rotate_coordinate_arrays(x_points-x,y_points-y,orientation)


    def function(self,p):
        """
        Function to draw a pattern that will then be scaled and rotated.

        Instead of implementing __call__ directly, PatternGenerator
        subclasses will typically implement this helper function used
        by __call__, because that way they can let __call__ handle the
        scaling and rotation for them.  Alternatively, __call__ itself
        can be reimplemented entirely by a subclass (e.g. if it does
        not need to do any scaling or rotation), in which case this
        function will be ignored.
        """
        raise NotImplementedError


    def _create_and_rotate_coordinate_arrays(self, x, y, orientation):
        """
        Create pattern matrices from x and y vectors, and rotate
        them to the specified orientation.
        """
        # Using this two-liner requires that x increase from left to
        # right and y decrease from left to right; I don't think it
        # can be rewritten in so little code otherwise - but please
        # prove me wrong.
        pattern_y = subtract.outer(cos(orientation)*y, sin(orientation)*x)
        pattern_x = add.outer(sin(orientation)*y, cos(orientation)*x)
        return pattern_x, pattern_y


    def _apply_mask(self,p,mat):
        """Create (if necessary) and apply the mask to the given matrix mat."""
        mask = p.mask
        ms=p.mask_shape
        if ms is not None:
            mask = ms(x=p.x+p.size*(ms.x*cos(p.orientation)-ms.y*sin(p.orientation)),
                      y=p.y+p.size*(ms.x*sin(p.orientation)+ms.y*cos(p.orientation)),
                      orientation=ms.orientation+p.orientation,size=ms.size*p.size,
                      bounds=p.bounds,ydensity=p.ydensity,xdensity=p.xdensity)
        if mask is not None:
            mat*=mask


    def set_matrix_dimensions(self, bounds, xdensity, ydensity):
        """
        Change the dimensions of the matrix into which the pattern will be drawn.
        Users of this class should call this method rather than changing
        the bounds, xdensity, and ydensity parameters directly.  Subclasses
        can override this method to update any internal data structures that
        may depend on the matrix dimensions.
        """
        self.bounds = bounds
        self.xdensity = xdensity
        self.ydensity = ydensity
示例#4
0
class Sheet(EventProcessor,
            SheetCoordinateSystem):  # pylint: disable-msg=W0223
    """
    The generic base class for neural sheets.

    See SheetCoordinateSystem for how Sheet represents space, and
    EventProcessor for how Sheet handles time.

    output_fns are functions that take an activity matrix and produce
    an identically shaped output matrix. The default is having no
    output_fns.
    """
    __abstract = True

    nominal_bounds = BoundingRegionParameter(BoundingBox(radius=0.5),
                                             constant=True,
                                             doc="""
            User-specified BoundingBox of the Sheet coordinate area
            covered by this Sheet.  The left and right bounds--if
            specified--will always be observed, but the top and bottom
            bounds may be adjusted to ensure the density in the y
            direction is the same as the density in the x direction.
            In such a case, the top and bottom bounds are adjusted
            so that the center y point remains the same, and each
            bound is as close as possible to its specified value. The
            actual value of this Parameter is not adjusted, but the
            true bounds may be found from the 'bounds' attribute
            of this object.
            """)

    nominal_density = param.Number(default=10,
                                   constant=True,
                                   doc="""
            User-specified number of processing units per 1.0 distance
            horizontally or vertically in Sheet coordinates. The actual
            number may be different because of discretization; the matrix
            needs to tile the plane exactly, and for that to work the
            density might need to be adjusted.  For instance, an area of 3x2
            cannot have a density of 2 in each direction. The true density
            may be obtained from either the xdensity or ydensity attribute
            (since these are identical for a Sheet).
            """)

    plastic = param.Boolean(True,
                            doc="""
            Setting this to False tells the Sheet not to change its
            permanent state (e.g. any connection weights) based on
            incoming events.
            """)

    precedence = param.Number(default=0.1,
                              softbounds=(0.0, 1.0),
                              doc="""
            Allows a sorting order for Sheets, e.g. in the GUI.""")

    row_precedence = param.Number(default=0.5,
                                  softbounds=(0.0, 1.0),
                                  doc="""
            Allows grouping of Sheets before sorting precedence is
            applied, e.g. for two-dimensional plots in the GUI.""")

    layout_location = param.NumericTuple(default=(-1, -1),
                                         precedence=-1,
                                         doc="""
            Location for this Sheet in an arbitrary pixel-based space
            in which Sheets can be laid out for visualization.""")

    output_fns = param.HookList(
        default=[],
        class_=TransferFn,
        doc=
        "Output function(s) to apply (if apply_output_fns is true) to this Sheet's activity."
    )

    apply_output_fns = param.Boolean(
        default=True,
        doc="Whether to apply the output_fn after computing an Activity matrix."
    )

    def _get_density(self):
        return self.xdensity

    density = property(
        _get_density,
        doc=
        """The sheet's true density (i.e. the xdensity, which is equal to the ydensity for a Sheet.)"""
    )

    def __init__(self, **params):
        """
        Initialize this object as an EventProcessor, then also as
        a SheetCoordinateSystem with equal xdensity and ydensity.

        sheet_views is a dictionary that stores SheetViews,
        i.e. representations of the sheet for use by analysis or plotting
        code.    
        """
        EventProcessor.__init__(self, **params)

        # Initialize this object as a SheetCoordinateSystem, with
        # the same density along y as along x.
        SheetCoordinateSystem.__init__(self, self.nominal_bounds,
                                       self.nominal_density)

        n_units = round((self.lbrt[2] - self.lbrt[0]) * self.xdensity, 0)
        if n_units < 1:            raise ValueError(
                "Sheet bounds and density must be specified such that the "+ \
 "sheet has at least one unit in each direction; " \
 +self.name+ " does not.")

        # setup the activity matrix
        self.activity = zeros(self.shape, activity_type)

        # For non-plastic inputs
        self.__saved_activity = []
        self._plasticity_setting_stack = []
        self.sheet_views = {}

    ### JABALERT: This should be deleted now that sheet_views is public
    ### JC: shouldn't we keep that, or at least write a function in
    ### utils that deletes a value in a dictinnary without returning an
    ### error if the key is not in the dict?  I leave for the moment,
    ### and have to ask Jim to advise.
    def release_sheet_view(self, view_name):
        """
        Delete the dictionary entry with key entry 'view_name' to save
        memory.
        """
        if self.sheet_views.has_key(view_name):
            del self.sheet_views[view_name]

    # CB: what to call this? sheetcoords()? sheetcoords_of_grid()? idxsheetcoords()?
    def sheetcoords_of_idx_grid(self):
        """
        Return an array of x-coordinates and an array of y-coordinates
        corresponding to the activity matrix of the sheet.
        """
        nrows, ncols = self.activity.shape

        C, R = meshgrid(arange(ncols), arange(nrows))

        X, Y = self.matrixidx2sheet(R, C)
        return X, Y

    # CB: check whether we need this function any more.
    def row_col_sheetcoords(self):
        """
        Return an array of Y-coordinates corresponding to the rows of
        the activity matrix of the sheet, and an array of
        X-coordinates corresponding to the columns.
        """
        # The row and column centers are returned in matrix (not
        # sheet) order (hence the reversals below).
        nrows, ncols = self.activity.shape
        return self.matrixidx2sheet(arange(nrows - 1, -1, -1),
                                    arange(ncols))[::-1]

    # CBALERT: to be removed once other code uses
    # row_col_sheetcoords() or sheetcoords_of_idx_grid().
    def sheet_rows(self):
        return self.row_col_sheetcoords()[0]

    def sheet_cols(self):
        return self.row_col_sheetcoords()[1]

    # CEBALERT: haven't really thought about what to put in this. The
    # way it is now, subclasses could make a super.activate() call to
    # avoid repeating some stuff.
    def activate(self):
        """
        Collect activity from each projection, combine it to calculate
        the activity for this sheet, and send the result out. 

        Subclasses will need to override this method to whatever it
        means to calculate activity in that subclass.
        """
        if self.apply_output_fns:
            for of in self.output_fns:
                of(self.activity)

        self.send_output(src_port='Activity', data=self.activity)

    def state_push(self):
        """
        Save the current state of this sheet to an internal stack.

        This method is used by operations that need to test the
        response of the sheet without permanently altering its state,
        e.g. for measuring maps or probing the current behavior
        non-invasively.  By default, only the activity pattern of this
        sheet is saved, but subclasses should add saving for any
        additional state that they maintain, or strange bugs are
        likely to occur.  The state can be restored using state_pop().

        Note that Sheets that do learning need not save the
        values of all connection weights, if any, because
        plasticity can be turned off explicitly.  Thus this method
        is intended only for shorter-term state.
        """
        self.__saved_activity.append(array(self.activity))
        EventProcessor.state_push(self)
        for of in self.output_fns:
            if hasattr(of, 'state_push'):
                of.state_push()

    def state_pop(self):
        """
        Pop the most recently saved state off the stack.

        See state_push() for more details.
        """
        self.activity = self.__saved_activity.pop()
        EventProcessor.state_pop(self)
        for of in self.output_fns:
            if hasattr(of, 'state_pop'):
                of.state_pop()

    def activity_len(self):
        """Return the number of items that have been saved by state_push()."""
        return len(self.__saved_activity)

    def override_plasticity_state(self, new_plasticity_state):
        """
        Temporarily override plasticity of medium and long term internal state.

        This function should be implemented by all subclasses so that
        it preserves the ability of the Sheet to compute activity,
        i.e. to operate over a short time scale, while preventing any
        lasting changes to the state (if new_plasticity_state=False).

        Any operation that does not have any lasting state, such as
        those affecting only the current activity level, should not
        be affected by this call.

        By default, simply saves a copy of the plastic flag to an
        internal stack (so that it can be restored by
        restore_plasticity_state()), and then sets plastic to
        new_plasticity_state.
        """
        self._plasticity_setting_stack.append(self.plastic)
        self.plastic = new_plasticity_state

    def restore_plasticity_state(self):
        """
        Restores plasticity of medium and long term internal state after
        a override_plasticity_state call.

        This function should be implemented by all subclasses to
        remove the effect of the most recent override_plasticity_state call,
        i.e. to restore plasticity of any type that was overridden.
        """
        self.plastic = self._plasticity_setting_stack.pop()

    def n_bytes(self):
        """
        Return a lower bound for the memory taken by this sheet, in bytes.

        Typically, this number will include the activity array and any
        similar arrays, plus any other significant data owned (in some
        sense) by this Sheet.  It will not usually include memory
        taken by the Python dictionary or various "housekeeping"
        attributes, which usually contribute only a small amount to
        the memory requirements.

        Subclasses should reimplement this method if they store a
        significant amount of data other than in the activity array.
        """
        return self.activity.nbytes
示例#5
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.""")

    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)()
        ])
示例#6
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):
        """
        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)
示例#7
0
 def compute_bounds(self, scs):
     spec = self._slicespec2boundsspec(self, scs)
     return BoundingBox(points=spec)