Ejemplo n.º 1
0
class SensorToSpecificObjectModule(object):
  """
  Represents the sensor location relative to a specific object. Typically
  these modules are arranged in an array, and the combined population SDR is
  used to predict a feature-location pair.

  This class has two sets of connections. Both of them compute the "sensor's
  location relative to a specific object" in different ways.

  The "metric connections" compute it from the
    "body's location relative to a specific object"
  and the
    "sensor's location relative to body"
  These connections are learned once and then never need to be updated. They
  might be genetically hardcoded. They're initialized externally, e.g. in
  BodyToSpecificObjectModule2D.

  The "anchor connections" compute it from the sensory input. Whenever a
  cortical column learns a feature-location pair, this layer forms reciprocal
  connections with the feature-location pair layer.

  These segments receive input at different times. The metric connections
  receive input first, and they activate a set of cells. This set of cells is
  used externally to predict a feature-location pair. Then this feature-location
  pair is the input to the anchor connections.
  """

  def __init__(self, cellDimensions, anchorInputSize,
               activationThreshold=10,
               initialPermanence=0.21,
               connectedPermanence=0.50,
               learningThreshold=10,
               sampleSize=20,
               permanenceIncrement=0.1,
               permanenceDecrement=0.0,
               maxSynapsesPerSegment=-1,
               seed=42):
    """
    @param cellDimensions (sequence of ints)
    @param anchorInputSize (int)
    @param activationThreshold (int)
    """
    self.activationThreshold = activationThreshold
    self.initialPermanence = initialPermanence
    self.connectedPermanence = connectedPermanence
    self.learningThreshold = learningThreshold
    self.sampleSize = sampleSize
    self.permanenceIncrement = permanenceIncrement
    self.permanenceDecrement = permanenceDecrement
    self.activationThreshold = activationThreshold
    self.maxSynapsesPerSegment = maxSynapsesPerSegment

    self.rng = Random(seed)

    self.cellCount = np.prod(cellDimensions)
    cellCountBySource = {
      "bodyToSpecificObject": self.cellCount,
      "sensorToBody": self.cellCount,
    }
    self.metricConnections = Multiconnections(self.cellCount,
                                              cellCountBySource)
    self.anchorConnections = SparseMatrixConnections(self.cellCount,
                                                     anchorInputSize)


  def reset(self):
    self.activeCells = np.empty(0, dtype="int")


  def metricCompute(self, sensorToBody, bodyToSpecificObject):
    """
    Compute the
      "sensor's location relative to a specific object"
    from the
      "body's location relative to a specific object"
    and the
      "sensor's location relative to body"

    @param sensorToBody (numpy array)
    Active cells of a single module that represents the sensor's location
    relative to the body

    @param bodyToSpecificObject (numpy array)
    Active cells of a single module that represents the body's location relative
    to a specific object
    """
    overlaps = self.metricConnections.computeActivity({
      "bodyToSpecificObject": bodyToSpecificObject,
      "sensorToBody": sensorToBody,
    })

    self.activeMetricSegments = np.where(overlaps >= 2)[0]
    self.activeCells = np.unique(
      self.metricConnections.mapSegmentsToCells(
        self.activeMetricSegments))


  def anchorCompute(self, anchorInput, learn):
    """
    Compute the
      "sensor's location relative to a specific object"
    from the feature-location pair.

    @param anchorInput (numpy array)
    Active cells in the feature-location pair layer

    @param learn (bool)
    If true, maintain current cell activity and learn this input on the
    currently active cells
    """
    if learn:
      self._anchorComputeLearningMode(anchorInput)
    else:
      overlaps = self.anchorConnections.computeActivity(
        anchorInput, self.connectedPermanence)

      self.activeSegments = np.where(overlaps >= self.activationThreshold)[0]
      self.activeCells = np.unique(
        self.anchorConnections.mapSegmentsToCells(self.activeSegments))


  def _anchorComputeLearningMode(self, anchorInput):
    """
    Associate this location with a sensory input. Subsequently, anchorInput will
    activate the current location during anchor().

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """

    overlaps = self.anchorConnections.computeActivity(
      anchorInput, self.connectedPermanence)

    activeSegments = np.where(overlaps >= self.activationThreshold)[0]

    potentialOverlaps = self.anchorConnections.computeActivity(anchorInput)
    matchingSegments = np.where(potentialOverlaps >=
                                self.learningThreshold)[0]

    # Cells with a active segment: reinforce the segment
    cellsForActiveSegments = self.anchorConnections.mapSegmentsToCells(
      activeSegments)
    learningActiveSegments = activeSegments[
      np.in1d(cellsForActiveSegments, self.activeCells)]
    remainingCells = np.setdiff1d(self.activeCells, cellsForActiveSegments)

    # Remaining cells with a matching segment: reinforce the best
    # matching segment.
    candidateSegments = self.anchorConnections.filterSegmentsByCell(
      matchingSegments, remainingCells)
    cellsForCandidateSegments = (
      self.anchorConnections.mapSegmentsToCells(candidateSegments))
    candidateSegments = candidateSegments[
      np.in1d(cellsForCandidateSegments, remainingCells)]
    onePerCellFilter = np2.argmaxMulti(potentialOverlaps[candidateSegments],
                                       cellsForCandidateSegments)
    learningMatchingSegments = candidateSegments[onePerCellFilter]

    newSegmentCells = np.setdiff1d(remainingCells, cellsForCandidateSegments)

    for learningSegments in (learningActiveSegments,
                             learningMatchingSegments):
      self._learn(self.anchorConnections, self.rng, learningSegments,
                  anchorInput, potentialOverlaps,
                  self.initialPermanence, self.sampleSize,
                  self.permanenceIncrement, self.permanenceDecrement,
                  self.maxSynapsesPerSegment)

    # Remaining cells without a matching segment: grow one.
    numNewSynapses = len(anchorInput)

    if self.sampleSize != -1:
      numNewSynapses = min(numNewSynapses, self.sampleSize)

    if self.maxSynapsesPerSegment != -1:
      numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

    newSegments = self.anchorConnections.createSegments(newSegmentCells)

    self.anchorConnections.growSynapsesToSample(
      newSegments, anchorInput, numNewSynapses,
      self.initialPermanence, self.rng)

    self.activeSegments = activeSegments


  @staticmethod
  def _learn(connections, rng, learningSegments, activeInput,
             potentialOverlaps, initialPermanence, sampleSize,
             permanenceIncrement, permanenceDecrement, maxSynapsesPerSegment):
    """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param potentialOverlaps (numpy array)
    """
    # Learn on existing segments
    connections.adjustSynapses(learningSegments, activeInput,
                               permanenceIncrement, -permanenceDecrement)

    # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
    # grow per segment. "maxNew" might be a number or it might be a list of
    # numbers.
    if sampleSize == -1:
      maxNew = len(activeInput)
    else:
      maxNew = sampleSize - potentialOverlaps[learningSegments]

    if maxSynapsesPerSegment != -1:
      synapseCounts = connections.mapSegmentsToSynapseCounts(
        learningSegments)
      numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
      maxNew = np.where(maxNew <= numSynapsesToReachMax,
                        maxNew, numSynapsesToReachMax)

    connections.growSynapsesToSample(learningSegments, activeInput,
                                     maxNew, initialPermanence, rng)


  def getActiveCells(self):
    return self.activeCells
Ejemplo n.º 2
0
class SensorToSpecificObjectModule(object):
    """
  Represents the sensor location relative to a specific object. Typically
  these modules are arranged in an array, and the combined population SDR is
  used to predict a feature-location pair.

  This class has two sets of connections. Both of them compute the "sensor's
  location relative to a specific object" in different ways.

  The "metric connections" compute it from the
    "body's location relative to a specific object"
  and the
    "sensor's location relative to body"
  These connections are learned once and then never need to be updated. They
  might be genetically hardcoded. They're initialized externally, e.g. in
  BodyToSpecificObjectModule2D.

  The "anchor connections" compute it from the sensory input. Whenever a
  cortical column learns a feature-location pair, this layer forms reciprocal
  connections with the feature-location pair layer.

  These segments receive input at different times. The metric connections
  receive input first, and they activate a set of cells. This set of cells is
  used externally to predict a feature-location pair. Then this feature-location
  pair is the input to the anchor connections.
  """
    def __init__(self,
                 cellDimensions,
                 anchorInputSize,
                 activationThreshold=10,
                 initialPermanence=0.21,
                 connectedPermanence=0.50,
                 learningThreshold=10,
                 sampleSize=20,
                 permanenceIncrement=0.1,
                 permanenceDecrement=0.0,
                 maxSynapsesPerSegment=-1,
                 seed=42):
        """
    @param cellDimensions (sequence of ints)
    @param anchorInputSize (int)
    @param activationThreshold (int)
    """
        self.activationThreshold = activationThreshold
        self.initialPermanence = initialPermanence
        self.connectedPermanence = connectedPermanence
        self.learningThreshold = learningThreshold
        self.sampleSize = sampleSize
        self.permanenceIncrement = permanenceIncrement
        self.permanenceDecrement = permanenceDecrement
        self.activationThreshold = activationThreshold
        self.maxSynapsesPerSegment = maxSynapsesPerSegment

        self.rng = Random(seed)

        self.cellCount = np.prod(cellDimensions)
        cellCountBySource = {
            "bodyToSpecificObject": self.cellCount,
            "sensorToBody": self.cellCount,
        }
        self.metricConnections = Multiconnections(self.cellCount,
                                                  cellCountBySource)
        self.anchorConnections = SparseMatrixConnections(
            self.cellCount, anchorInputSize)

    def reset(self):
        self.activeCells = np.empty(0, dtype="int")

    def metricCompute(self, sensorToBody, bodyToSpecificObject):
        """
    Compute the
      "sensor's location relative to a specific object"
    from the
      "body's location relative to a specific object"
    and the
      "sensor's location relative to body"

    @param sensorToBody (numpy array)
    Active cells of a single module that represents the sensor's location
    relative to the body

    @param bodyToSpecificObject (numpy array)
    Active cells of a single module that represents the body's location relative
    to a specific object
    """
        overlaps = self.metricConnections.computeActivity({
            "bodyToSpecificObject":
            bodyToSpecificObject,
            "sensorToBody":
            sensorToBody,
        })

        self.activeMetricSegments = np.where(overlaps >= 2)[0]
        self.activeCells = np.unique(
            self.metricConnections.mapSegmentsToCells(
                self.activeMetricSegments))

    def anchorCompute(self, anchorInput, learn):
        """
    Compute the
      "sensor's location relative to a specific object"
    from the feature-location pair.

    @param anchorInput (numpy array)
    Active cells in the feature-location pair layer

    @param learn (bool)
    If true, maintain current cell activity and learn this input on the
    currently active cells
    """
        if learn:
            self._anchorComputeLearningMode(anchorInput)
        else:
            overlaps = self.anchorConnections.computeActivity(
                anchorInput, self.connectedPermanence)

            self.activeSegments = np.where(
                overlaps >= self.activationThreshold)[0]
            self.activeCells = np.unique(
                self.anchorConnections.mapSegmentsToCells(self.activeSegments))

    def _anchorComputeLearningMode(self, anchorInput):
        """
    Associate this location with a sensory input. Subsequently, anchorInput will
    activate the current location during anchor().

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """

        overlaps = self.anchorConnections.computeActivity(
            anchorInput, self.connectedPermanence)

        activeSegments = np.where(overlaps >= self.activationThreshold)[0]

        potentialOverlaps = self.anchorConnections.computeActivity(anchorInput)
        matchingSegments = np.where(
            potentialOverlaps >= self.learningThreshold)[0]

        # Cells with a active segment: reinforce the segment
        cellsForActiveSegments = self.anchorConnections.mapSegmentsToCells(
            activeSegments)
        learningActiveSegments = activeSegments[np.in1d(
            cellsForActiveSegments, self.activeCells)]
        remainingCells = np.setdiff1d(self.activeCells, cellsForActiveSegments)

        # Remaining cells with a matching segment: reinforce the best
        # matching segment.
        candidateSegments = self.anchorConnections.filterSegmentsByCell(
            matchingSegments, remainingCells)
        cellsForCandidateSegments = (
            self.anchorConnections.mapSegmentsToCells(candidateSegments))
        candidateSegments = candidateSegments[np.in1d(
            cellsForCandidateSegments, remainingCells)]
        onePerCellFilter = np2.argmaxMulti(
            potentialOverlaps[candidateSegments], cellsForCandidateSegments)
        learningMatchingSegments = candidateSegments[onePerCellFilter]

        newSegmentCells = np.setdiff1d(remainingCells,
                                       cellsForCandidateSegments)

        for learningSegments in (learningActiveSegments,
                                 learningMatchingSegments):
            self._learn(self.anchorConnections, self.rng, learningSegments,
                        anchorInput, potentialOverlaps, self.initialPermanence,
                        self.sampleSize, self.permanenceIncrement,
                        self.permanenceDecrement, self.maxSynapsesPerSegment)

        # Remaining cells without a matching segment: grow one.
        numNewSynapses = len(anchorInput)

        if self.sampleSize != -1:
            numNewSynapses = min(numNewSynapses, self.sampleSize)

        if self.maxSynapsesPerSegment != -1:
            numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

        newSegments = self.anchorConnections.createSegments(newSegmentCells)

        self.anchorConnections.growSynapsesToSample(newSegments, anchorInput,
                                                    numNewSynapses,
                                                    self.initialPermanence,
                                                    self.rng)

        self.activeSegments = activeSegments

    @staticmethod
    def _learn(connections, rng, learningSegments, activeInput,
               potentialOverlaps, initialPermanence, sampleSize,
               permanenceIncrement, permanenceDecrement,
               maxSynapsesPerSegment):
        """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param potentialOverlaps (numpy array)
    """
        # Learn on existing segments
        connections.adjustSynapses(learningSegments, activeInput,
                                   permanenceIncrement, -permanenceDecrement)

        # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
        # grow per segment. "maxNew" might be a number or it might be a list of
        # numbers.
        if sampleSize == -1:
            maxNew = len(activeInput)
        else:
            maxNew = sampleSize - potentialOverlaps[learningSegments]

        if maxSynapsesPerSegment != -1:
            synapseCounts = connections.mapSegmentsToSynapseCounts(
                learningSegments)
            numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
            maxNew = np.where(maxNew <= numSynapsesToReachMax, maxNew,
                              numSynapsesToReachMax)

        connections.growSynapsesToSample(learningSegments, activeInput, maxNew,
                                         initialPermanence, rng)

    def getActiveCells(self):
        return self.activeCells
Ejemplo n.º 3
0
class Superficial2DLocationModule(object):
  """
  A model of a location module. It's similar to a grid cell module, but it uses
  squares rather than triangles.

  The cells are arranged into a m*n rectangle which is tiled onto 2D space.
  Each cell represents a small rectangle in each tile.

  +------+------+------++------+------+------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #1  |  #2  |  #3  ||  #1  |  #2  |  #3  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #4  |  #5  |  #6  ||  #4  |  #5  |  #6  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #7  |  #8  |  #9  ||  #7  |  #8  |  #9  |
  |      |      |      ||      |      |      |
  +------+------+------++------+------+------+

  We assume that path integration works *somehow*. This model receives a "delta
  location" vector, and it shifts the active cells accordingly. The model stores
  intermediate coordinates of active cells. Whenever sensory cues activate a
  cell, the model adds this cell to the list of coordinates being shifted.
  Whenever sensory cues cause a cell to become inactive, that cell is removed
  from the list of coordinates.

  (This model doesn't attempt to propose how "path integration" works. It
  attempts to show how locations are anchored to sensory cues.)

  When orientation is set to 0 degrees, the displacement is a [di, dj],
  moving di cells "down" and dj cells "right".

  When orientation is set to 90 degrees, the displacement is essentially a
  [dx, dy], applied in typical x,y coordinates with the origin on the bottom
  left.

  Usage:
  - When the sensor moves, call movementCompute.
  - When the sensor senses something, call sensoryCompute.

  The "anchor input" is typically a feature-location pair SDR.

  To specify how points are tracked, pass anchoringMethod = "corners",
  "narrowing" or "discrete".  "discrete" will cause the network to operate in a
  fully discrete space, where uncertainty is impossible as long as movements are
  integers.  "narrowing" is designed to narrow down uncertainty of initial
  locations of sensory stimuli.  "corners" is designed for noise-tolerance, and
  will activate all cells that are possible outcomes of path integration.
  """

  def __init__(self,
               cellDimensions,
               moduleMapDimensions,
               orientation,
               anchorInputSize,
               cellCoordinateOffsets=(0.5,),
               activationThreshold=10,
               initialPermanence=0.21,
               connectedPermanence=0.50,
               learningThreshold=10,
               sampleSize=20,
               permanenceIncrement=0.1,
               permanenceDecrement=0.0,
               maxSynapsesPerSegment=-1,
               anchoringMethod="narrowing",
               rotationMatrix = None,
               seed=42):
    """
    @param cellDimensions (tuple(int, int))
    Determines the number of cells. Determines how space is divided between the
    cells.

    @param moduleMapDimensions (tuple(float, float))
    Determines the amount of world space covered by all of the cells combined.
    In grid cell terminology, this is equivalent to the "scale" of a module.
    A module with a scale of "5cm" would have moduleMapDimensions=(5.0, 5.0).

    @param orientation (float)
    The rotation of this map, measured in radians.

    @param anchorInputSize (int)
    The number of input bits in the anchor input.

    @param cellCoordinateOffsets (list of floats)
    These must each be between 0.0 and 1.0. Every time a cell is activated by
    anchor input, this class adds a "phase" which is shifted in subsequent
    motions. By default, this phase is placed at the center of the cell. This
    parameter allows you to control where the point is placed and whether multiple
    are placed. For example, with value [0.2, 0.8], when cell [2, 3] is activated
    it will place 4 phases, corresponding to the following points in cell
    coordinates: [2.2, 3.2], [2.2, 3.8], [2.8, 3.2], [2.8, 3.8]
    """

    self.cellDimensions = np.asarray(cellDimensions, dtype="int")
    self.moduleMapDimensions = np.asarray(moduleMapDimensions, dtype="float")
    self.phasesPerUnitDistance = 1.0 / self.moduleMapDimensions

    if rotationMatrix is None:
      self.orientation = orientation
      self.rotationMatrix = np.array(
        [[math.cos(orientation), -math.sin(orientation)],
         [math.sin(orientation), math.cos(orientation)]])
      if anchoringMethod == "discrete":
        # Need to convert matrix to have integer values
        nonzeros = self.rotationMatrix[np.where(np.abs(self.rotationMatrix)>0)]
        smallestValue = np.amin(nonzeros)
        self.rotationMatrix /= smallestValue
        self.rotationMatrix = np.ceil(self.rotationMatrix)
    else:
      self.rotationMatrix = rotationMatrix

    self.cellCoordinateOffsets = cellCoordinateOffsets

    # Phase is measured as a number in the range [0.0, 1.0)
    self.activePhases = np.empty((0,2), dtype="float")
    self.cellsForActivePhases = np.empty(0, dtype="int")
    self.phaseDisplacement = np.empty((0,2), dtype="float")

    self.activeCells = np.empty(0, dtype="int")
    self.activeSegments = np.empty(0, dtype="uint32")

    self.connections = SparseMatrixConnections(np.prod(cellDimensions),
                                               anchorInputSize)

    self.initialPermanence = initialPermanence
    self.connectedPermanence = connectedPermanence
    self.learningThreshold = learningThreshold
    self.sampleSize = sampleSize
    self.permanenceIncrement = permanenceIncrement
    self.permanenceDecrement = permanenceDecrement
    self.activationThreshold = activationThreshold
    self.maxSynapsesPerSegment = maxSynapsesPerSegment

    self.anchoringMethod = anchoringMethod

    self.rng = Random(seed)


  def reset(self):
    """
    Clear the active cells.
    """
    self.activePhases = np.empty((0,2), dtype="float")
    self.phaseDisplacement = np.empty((0,2), dtype="float")
    self.cellsForActivePhases = np.empty(0, dtype="int")
    self.activeCells = np.empty(0, dtype="int")


  def _computeActiveCells(self):
    # Round each coordinate to the nearest cell.
    activeCellCoordinates = np.floor(
      self.activePhases * self.cellDimensions).astype("int")

    # Convert coordinates to cell numbers.
    self.cellsForActivePhases = (
      np.ravel_multi_index(activeCellCoordinates.T, self.cellDimensions))
    self.activeCells = np.unique(self.cellsForActivePhases)


  def activateRandomLocation(self):
    """
    Set the location to a random point.
    """
    self.activePhases = np.array([np.random.random(2)])
    if self.anchoringMethod == "discrete":
      # Need to place the phase in the middle of a cell
      self.activePhases = np.floor(
        self.activePhases * self.cellDimensions)/self.cellDimensions
    self._computeActiveCells()


  def movementCompute(self, displacement, noiseFactor = 0):
    """
    Shift the current active cells by a vector.

    @param displacement (pair of floats)
    A translation vector [di, dj].
    """

    if noiseFactor != 0:
      displacement = copy.deepcopy(displacement)
      xnoise = np.random.normal(0, noiseFactor)
      ynoise = np.random.normal(0, noiseFactor)
      displacement[0] += xnoise
      displacement[1] += ynoise


    # Calculate delta in the module's coordinates.
    phaseDisplacement = (np.matmul(self.rotationMatrix, displacement) *
                         self.phasesPerUnitDistance)

    # Shift the active coordinates.
    np.add(self.activePhases, phaseDisplacement, out=self.activePhases)

    # In Python, (x % 1.0) can return 1.0 because of floating point goofiness.
    # Generally this doesn't cause problems, it's just confusing when you're
    # debugging.
    np.round(self.activePhases, decimals=9, out=self.activePhases)
    np.mod(self.activePhases, 1.0, out=self.activePhases)

    self._computeActiveCells()
    self.phaseDisplacement = phaseDisplacement


  def _sensoryComputeInferenceMode(self, anchorInput):
    """
    Infer the location from sensory input. Activate any cells with enough active
    synapses to this sensory input. Deactivate all other cells.

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
    if len(anchorInput) == 0:
      return

    overlaps = self.connections.computeActivity(anchorInput,
                                                self.connectedPermanence)
    activeSegments = np.where(overlaps >= self.activationThreshold)[0]

    sensorySupportedCells = np.unique(
      self.connections.mapSegmentsToCells(activeSegments))

    inactivated = np.setdiff1d(self.activeCells, sensorySupportedCells)
    inactivatedIndices = np.in1d(self.cellsForActivePhases,
                                 inactivated).nonzero()[0]
    if inactivatedIndices.size > 0:
      self.activePhases = np.delete(self.activePhases, inactivatedIndices,
                                    axis=0)

    activated = np.setdiff1d(sensorySupportedCells, self.activeCells)

    # Find centers of point clouds
    if "corners" in self.anchoringMethod:
      activatedCoordsBase = np.transpose(
        np.unravel_index(sensorySupportedCells,
                         self.cellDimensions)).astype('float')
    else:
      activatedCoordsBase = np.transpose(
        np.unravel_index(activated, self.cellDimensions)).astype('float')

    # Generate points to add
    activatedCoords = np.concatenate(
      [activatedCoordsBase + [iOffset, jOffset]
       for iOffset in self.cellCoordinateOffsets
       for jOffset in self.cellCoordinateOffsets]
    )
    if "corners" in self.anchoringMethod:
      self.activePhases = activatedCoords / self.cellDimensions

    else:
      if activatedCoords.size > 0:
        self.activePhases = np.append(self.activePhases,
                                      activatedCoords / self.cellDimensions,
                                      axis=0)

    self._computeActiveCells()
    self.activeSegments = activeSegments


  def _sensoryComputeLearningMode(self, anchorInput):
    """
    Associate this location with a sensory input. Subsequently, anchorInput will
    activate the current location during anchor().

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
    overlaps = self.connections.computeActivity(anchorInput,
                                                self.connectedPermanence)
    activeSegments = np.where(overlaps >= self.activationThreshold)[0]

    potentialOverlaps = self.connections.computeActivity(anchorInput)
    matchingSegments = np.where(potentialOverlaps >=
                                self.learningThreshold)[0]

    # Cells with a active segment: reinforce the segment
    cellsForActiveSegments = self.connections.mapSegmentsToCells(
      activeSegments)
    learningActiveSegments = activeSegments[
      np.in1d(cellsForActiveSegments, self.activeCells)]
    remainingCells = np.setdiff1d(self.activeCells, cellsForActiveSegments)

    # Remaining cells with a matching segment: reinforce the best
    # matching segment.
    candidateSegments = self.connections.filterSegmentsByCell(
      matchingSegments, remainingCells)
    cellsForCandidateSegments = (
      self.connections.mapSegmentsToCells(candidateSegments))
    candidateSegments = candidateSegments[
      np.in1d(cellsForCandidateSegments, remainingCells)]
    onePerCellFilter = np2.argmaxMulti(potentialOverlaps[candidateSegments],
                                       cellsForCandidateSegments)
    learningMatchingSegments = candidateSegments[onePerCellFilter]

    newSegmentCells = np.setdiff1d(remainingCells, cellsForCandidateSegments)

    for learningSegments in (learningActiveSegments,
                             learningMatchingSegments):
      self._learn(self.connections, self.rng, learningSegments,
                  anchorInput, potentialOverlaps,
                  self.initialPermanence, self.sampleSize,
                  self.permanenceIncrement, self.permanenceDecrement,
                  self.maxSynapsesPerSegment)

    # Remaining cells without a matching segment: grow one.
    numNewSynapses = len(anchorInput)

    if self.sampleSize != -1:
      numNewSynapses = min(numNewSynapses, self.sampleSize)

    if self.maxSynapsesPerSegment != -1:
      numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

    newSegments = self.connections.createSegments(newSegmentCells)

    self.connections.growSynapsesToSample(
      newSegments, anchorInput, numNewSynapses,
      self.initialPermanence, self.rng)
    self.activeSegments = activeSegments


  def sensoryCompute(self, anchorInput, anchorGrowthCandidates, learn):
    if learn:
      self._sensoryComputeLearningMode(anchorGrowthCandidates)
    else:
      self._sensoryComputeInferenceMode(anchorInput)


  @staticmethod
  def _learn(connections, rng, learningSegments, activeInput,
             potentialOverlaps, initialPermanence, sampleSize,
             permanenceIncrement, permanenceDecrement, maxSynapsesPerSegment):
    """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param potentialOverlaps (numpy array)
    """
    # Learn on existing segments
    connections.adjustSynapses(learningSegments, activeInput,
                               permanenceIncrement, -permanenceDecrement)

    # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
    # grow per segment. "maxNew" might be a number or it might be a list of
    # numbers.
    if sampleSize == -1:
      maxNew = len(activeInput)
    else:
      maxNew = sampleSize - potentialOverlaps[learningSegments]

    if maxSynapsesPerSegment != -1:
      synapseCounts = connections.mapSegmentsToSynapseCounts(
        learningSegments)
      numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
      maxNew = np.where(maxNew <= numSynapsesToReachMax,
                        maxNew, numSynapsesToReachMax)

    connections.growSynapsesToSample(learningSegments, activeInput,
                                     maxNew, initialPermanence, rng)


  def getActiveCells(self):
    return self.activeCells


  def numberOfCells(self):
    return np.prod(self.cellDimensions)
class SingleLayerLocationMemory(object):
  """
  A layer of cells which learns how to take a "delta location" (e.g. a motor
  command or a proprioceptive delta) and update its active cells to represent
  the new location.

  Its active cells might represent a union of locations.
  As the location changes, the featureLocationInput causes this union to narrow
  down until the location is inferred.

  This layer receives absolute proprioceptive info as proximal input.
  For now, we assume that there's a one-to-one mapping between absolute
  proprioceptive input and the location SDR. So rather than modeling
  proximal synapses, we'll just relay the proprioceptive SDR. In the future
  we might want to consider a many-to-one mapping of proprioceptive inputs
  to location SDRs.

  After this layer is trained, it no longer needs the proprioceptive input.
  The delta location will drive the layer. The current active cells and the
  other distal connections will work together with this delta location to
  activate a new set of cells.

  When no cells are active, activate a large union of possible locations.
  With subsequent inputs, the union will narrow down to a single location SDR.
  """

  def __init__(self,
               cellCount,
               deltaLocationInputSize,
               featureLocationInputSize,
               activationThreshold=13,
               initialPermanence=0.21,
               connectedPermanence=0.50,
               learningThreshold=10,
               sampleSize=20,
               permanenceIncrement=0.1,
               permanenceDecrement=0.1,
               maxSynapsesPerSegment=-1,
               seed=42):

    # For transition learning, every segment is split into two parts.
    # For the segment to be active, both parts must be active.
    self.internalConnections = SparseMatrixConnections(
      cellCount, cellCount)
    self.deltaConnections = SparseMatrixConnections(
      cellCount, deltaLocationInputSize)

    # Distal segments that receive input from the layer that represents
    # feature-locations.
    self.featureLocationConnections = SparseMatrixConnections(
      cellCount, featureLocationInputSize)

    self.activeCells = np.empty(0, dtype="uint32")
    self.activeDeltaSegments = np.empty(0, dtype="uint32")
    self.activeFeatureLocationSegments = np.empty(0, dtype="uint32")

    self.initialPermanence = initialPermanence
    self.connectedPermanence = connectedPermanence
    self.learningThreshold = learningThreshold
    self.sampleSize = sampleSize
    self.permanenceIncrement = permanenceIncrement
    self.permanenceDecrement = permanenceDecrement
    self.activationThreshold = activationThreshold
    self.maxSynapsesPerSegment = maxSynapsesPerSegment

    self.rng = Random(seed)


  def reset(self):
    """
    Deactivate all cells.
    """

    self.activeCells = np.empty(0, dtype="uint32")
    self.activeDeltaSegments = np.empty(0, dtype="uint32")
    self.activeFeatureLocationSegments = np.empty(0, dtype="uint32")


  def compute(self, deltaLocation=(), newLocation=(),
              featureLocationInput=(), featureLocationGrowthCandidates=(),
              learn=True):
    """
    Run one time step of the Location Memory algorithm.

    @param deltaLocation (sorted numpy array)
    @param newLocation (sorted numpy array)
    @param featureLocationInput (sorted numpy array)
    @param featureLocationGrowthCandidates (sorted numpy array)
    """
    prevActiveCells = self.activeCells

    self.activeDeltaSegments = np.where(
      (self.internalConnections.computeActivity(
        prevActiveCells, self.connectedPermanence
      ) >= self.activationThreshold)
      &
      (self.deltaConnections.computeActivity(
        deltaLocation, self.connectedPermanence
      ) >= self.activationThreshold))[0]

    # When we're moving, the feature-location input has no effect.
    if len(deltaLocation) == 0:
      self.activeFeatureLocationSegments = np.where(
        self.featureLocationConnections.computeActivity(
          featureLocationInput, self.connectedPermanence
        ) >= self.activationThreshold)[0]
    else:
      self.activeFeatureLocationSegments = np.empty(0, dtype="uint32")


    if len(newLocation) > 0:
      # Drive activations by relaying this location SDR.
      self.activeCells = newLocation

      if learn:
        # Learn the delta.
        self._learnTransition(prevActiveCells, deltaLocation, newLocation)

        # Learn the featureLocationInput.
        self._learnFeatureLocationPair(newLocation, featureLocationInput,
                                       featureLocationGrowthCandidates)


    elif len(prevActiveCells) > 0:
      if len(deltaLocation) > 0:
        # Drive activations by applying the deltaLocation to the current location.
        # Completely ignore the featureLocationInput. It's outdated, associated
        # with the previous location.

        cellsForDeltaSegments = self.internalConnections.mapSegmentsToCells(
          self.activeDeltaSegments)

        self.activeCells = np.unique(cellsForDeltaSegments)
      else:
        # Keep previous active cells active.
        # Modulate with the featureLocationInput.

        if len(self.activeFeatureLocationSegments) > 0:

          cellsForFeatureLocationSegments = (
            self.featureLocationConnections.mapSegmentsToCells(
              self.activeFeatureLocationSegments))
          self.activeCells = np.intersect1d(prevActiveCells,
                                            cellsForFeatureLocationSegments)
        else:
          self.activeCells = prevActiveCells

    elif len(featureLocationInput) > 0:
      # Drive activations with the featureLocationInput.

      cellsForFeatureLocationSegments = (
        self.featureLocationConnections.mapSegmentsToCells(
          self.activeFeatureLocationSegments))

      self.activeCells = np.unique(cellsForFeatureLocationSegments)


  def _learnTransition(self, prevActiveCells, deltaLocation, newLocation):
    """
    For each cell in the newLocation SDR, learn the transition of prevLocation
    (i.e. prevActiveCells) + deltaLocation.

    The transition might be already known. In that case, just reinforce the
    existing segments.
    """

    prevLocationPotentialOverlaps = self.internalConnections.computeActivity(
      prevActiveCells)
    deltaPotentialOverlaps = self.deltaConnections.computeActivity(
      deltaLocation)

    matchingDeltaSegments = np.where(
      (prevLocationPotentialOverlaps >= self.learningThreshold) &
      (deltaPotentialOverlaps >= self.learningThreshold))[0]

    # Cells with a active segment pair: reinforce the segment
    cellsForActiveSegments = self.internalConnections.mapSegmentsToCells(
      self.activeDeltaSegments)
    learningActiveDeltaSegments = self.activeDeltaSegments[
      np.in1d(cellsForActiveSegments, newLocation)]
    remainingCells = np.setdiff1d(newLocation, cellsForActiveSegments)

    # Remaining cells with a matching segment pair: reinforce the best matching
    # segment pair.
    candidateSegments = self.internalConnections.filterSegmentsByCell(
      matchingDeltaSegments, remainingCells)
    cellsForCandidateSegments = self.internalConnections.mapSegmentsToCells(
      candidateSegments)
    candidateSegments = matchingDeltaSegments[
      np.in1d(cellsForCandidateSegments, remainingCells)]
    onePerCellFilter = np2.argmaxMulti(
      prevLocationPotentialOverlaps[candidateSegments] +
      deltaPotentialOverlaps[candidateSegments],
      cellsForCandidateSegments)
    learningMatchingDeltaSegments = candidateSegments[onePerCellFilter]

    newDeltaSegmentCells = np.setdiff1d(remainingCells, cellsForCandidateSegments)

    for learningSegments in (learningActiveDeltaSegments,
                             learningMatchingDeltaSegments):
      self._learn(self.internalConnections, self.rng, learningSegments,
                  prevActiveCells, prevActiveCells,
                  prevLocationPotentialOverlaps,
                  self.initialPermanence, self.sampleSize,
                  self.permanenceIncrement, self.permanenceDecrement,
                  self.maxSynapsesPerSegment)
      self._learn(self.deltaConnections, self.rng, learningSegments,
                  deltaLocation, deltaLocation, deltaPotentialOverlaps,
                  self.initialPermanence, self.sampleSize,
                  self.permanenceIncrement, self.permanenceDecrement,
                  self.maxSynapsesPerSegment)

    numNewLocationSynapses = len(prevActiveCells)
    numNewDeltaSynapses = len(deltaLocation)

    if self.sampleSize != -1:
      numNewLocationSynapses = min(numNewLocationSynapses, self.sampleSize)
      numNewDeltaSynapses = min(numNewDeltaSynapses, self.sampleSize)

    if self.maxSynapsesPerSegment != -1:
      numNewLocationSynapses = min(numNewLocationSynapses,
                                   self.maxSynapsesPerSegment)
      numNewDeltaSynapses = min(numNewLocationSynapses,
                                self.maxSynapsesPerSegment)

    newPrevLocationSegments = self.internalConnections.createSegments(
      newDeltaSegmentCells)
    newDeltaSegments = self.deltaConnections.createSegments(
      newDeltaSegmentCells)

    assert np.array_equal(newPrevLocationSegments, newDeltaSegments)

    self.internalConnections.growSynapsesToSample(
      newPrevLocationSegments, prevActiveCells, numNewLocationSynapses,
      self.initialPermanence, self.rng)
    self.deltaConnections.growSynapsesToSample(
      newDeltaSegments, deltaLocation, numNewDeltaSynapses,
      self.initialPermanence, self.rng)


  def _learnFeatureLocationPair(self, newLocation, featureLocationInput,
                                featureLocationGrowthCandidates):
    """
    Grow / reinforce synapses between the location layer's dendrites and the
    input layer's active cells.
    """

    potentialOverlaps = self.featureLocationConnections.computeActivity(
      featureLocationInput)
    matchingSegments = np.where(potentialOverlaps > self.learningThreshold)[0]

    # Cells with a active segment pair: reinforce the segment
    cellsForActiveSegments = self.featureLocationConnections.mapSegmentsToCells(
      self.activeFeatureLocationSegments)
    learningActiveSegments = self.activeFeatureLocationSegments[
      np.in1d(cellsForActiveSegments, newLocation)]
    remainingCells = np.setdiff1d(newLocation, cellsForActiveSegments)

    # Remaining cells with a matching segment pair: reinforce the best matching
    # segment pair.
    candidateSegments = self.featureLocationConnections.filterSegmentsByCell(
      matchingSegments, remainingCells)
    cellsForCandidateSegments = (
      self.featureLocationConnections.mapSegmentsToCells(
        candidateSegments))
    candidateSegments = candidateSegments[
      np.in1d(cellsForCandidateSegments, remainingCells)]
    onePerCellFilter = np2.argmaxMulti(potentialOverlaps[candidateSegments],
                                       cellsForCandidateSegments)
    learningMatchingSegments = candidateSegments[onePerCellFilter]

    newSegmentCells = np.setdiff1d(remainingCells, cellsForCandidateSegments)

    for learningSegments in (learningActiveSegments,
                             learningMatchingSegments):
      self._learn(self.featureLocationConnections, self.rng, learningSegments,
                  featureLocationInput, featureLocationGrowthCandidates,
                  potentialOverlaps,
                  self.initialPermanence, self.sampleSize,
                  self.permanenceIncrement, self.permanenceDecrement,
                  self.maxSynapsesPerSegment)

    numNewSynapses = len(featureLocationInput)

    if self.sampleSize != -1:
      numNewSynapses = min(numNewSynapses, self.sampleSize)

    if self.maxSynapsesPerSegment != -1:
      numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

    newSegments = self.featureLocationConnections.createSegments(
      newSegmentCells)

    self.featureLocationConnections.growSynapsesToSample(
      newSegments, featureLocationGrowthCandidates, numNewSynapses,
      self.initialPermanence, self.rng)



  @staticmethod
  def _learn(connections, rng, learningSegments, activeInput, growthCandidates,
             potentialOverlaps, initialPermanence, sampleSize,
             permanenceIncrement, permanenceDecrement, maxSynapsesPerSegment):
    """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param growthCandidates (numpy array)
    @param potentialOverlaps (numpy array)
    """

    # Learn on existing segments
    connections.adjustSynapses(learningSegments, activeInput,
                               permanenceIncrement, -permanenceDecrement)

    # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
    # grow per segment. "maxNew" might be a number or it might be a list of
    # numbers.
    if sampleSize == -1:
      maxNew = len(growthCandidates)
    else:
      maxNew = sampleSize - potentialOverlaps[learningSegments]

    if maxSynapsesPerSegment != -1:
      synapseCounts = connections.mapSegmentsToSynapseCounts(
        learningSegments)
      numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
      maxNew = np.where(maxNew <= numSynapsesToReachMax,
                        maxNew, numSynapsesToReachMax)

    connections.growSynapsesToSample(learningSegments, growthCandidates,
                                     maxNew, initialPermanence, rng)


  def getActiveCells(self):
    return self.activeCells
Ejemplo n.º 5
0
class Superficial2DLocationModule(object):
    """
  A model of a location module. It's similar to a grid cell module, but it uses
  squares rather than triangles.

  The cells are arranged into a m*n rectangle which is tiled onto 2D space.
  Each cell represents a small rectangle in each tile.

  +------+------+------++------+------+------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #1  |  #2  |  #3  ||  #1  |  #2  |  #3  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #4  |  #5  |  #6  ||  #4  |  #5  |  #6  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #7  |  #8  |  #9  ||  #7  |  #8  |  #9  |
  |      |      |      ||      |      |      |
  +------+------+------++------+------+------+

  We assume that path integration works *somehow*. This model receives a "delta
  location" vector, and it shifts the active cells accordingly. The model stores
  intermediate coordinates of active cells. Whenever sensory cues activate a
  cell, the model adds this cell to the list of coordinates being shifted.
  Whenever sensory cues cause a cell to become inactive, that cell is removed
  from the list of coordinates.

  (This model doesn't attempt to propose how "path integration" works. It
  attempts to show how locations are anchored to sensory cues.)

  When orientation is set to 0 degrees, the displacement is a [di, dj],
  moving di cells "down" and dj cells "right".

  When orientation is set to 90 degrees, the displacement is essentially a
  [dx, dy], applied in typical x,y coordinates with the origin on the bottom
  left.

  Usage:
  - When the sensor moves, call movementCompute.
  - When the sensor senses something, call sensoryCompute.

  The "anchor input" is typically a feature-location pair SDR.

  To specify how points are tracked, pass anchoringMethod = "corners",
  "narrowing" or "discrete".  "discrete" will cause the network to operate in a
  fully discrete space, where uncertainty is impossible as long as movements are
  integers.  "narrowing" is designed to narrow down uncertainty of initial
  locations of sensory stimuli.  "corners" is designed for noise-tolerance, and
  will activate all cells that are possible outcomes of path integration.
  """
    def __init__(self,
                 cellDimensions,
                 moduleMapDimensions,
                 orientation,
                 anchorInputSize,
                 cellCoordinateOffsets=(0.5, ),
                 activationThreshold=10,
                 initialPermanence=0.21,
                 connectedPermanence=0.50,
                 learningThreshold=10,
                 sampleSize=20,
                 permanenceIncrement=0.1,
                 permanenceDecrement=0.0,
                 maxSynapsesPerSegment=-1,
                 anchoringMethod="narrowing",
                 rotationMatrix=None,
                 seed=42):
        """
    @param cellDimensions (tuple(int, int))
    Determines the number of cells. Determines how space is divided between the
    cells.

    @param moduleMapDimensions (tuple(float, float))
    Determines the amount of world space covered by all of the cells combined.
    In grid cell terminology, this is equivalent to the "scale" of a module.
    A module with a scale of "5cm" would have moduleMapDimensions=(5.0, 5.0).

    @param orientation (float)
    The rotation of this map, measured in radians.

    @param anchorInputSize (int)
    The number of input bits in the anchor input.

    @param cellCoordinateOffsets (list of floats)
    These must each be between 0.0 and 1.0. Every time a cell is activated by
    anchor input, this class adds a "phase" which is shifted in subsequent
    motions. By default, this phase is placed at the center of the cell. This
    parameter allows you to control where the point is placed and whether multiple
    are placed. For example, with value [0.2, 0.8], when cell [2, 3] is activated
    it will place 4 phases, corresponding to the following points in cell
    coordinates: [2.2, 3.2], [2.2, 3.8], [2.8, 3.2], [2.8, 3.8]
    """

        self.cellDimensions = np.asarray(cellDimensions, dtype="int")
        self.moduleMapDimensions = np.asarray(moduleMapDimensions,
                                              dtype="float")
        self.phasesPerUnitDistance = 1.0 / self.moduleMapDimensions

        if rotationMatrix is None:
            self.orientation = orientation
            self.rotationMatrix = np.array(
                [[math.cos(orientation), -math.sin(orientation)],
                 [math.sin(orientation),
                  math.cos(orientation)]])
            if anchoringMethod == "discrete":
                # Need to convert matrix to have integer values
                nonzeros = self.rotationMatrix[np.where(
                    np.abs(self.rotationMatrix) > 0)]
                smallestValue = np.amin(nonzeros)
                self.rotationMatrix /= smallestValue
                self.rotationMatrix = np.ceil(self.rotationMatrix)
        else:
            self.rotationMatrix = rotationMatrix

        self.cellCoordinateOffsets = cellCoordinateOffsets

        # Phase is measured as a number in the range [0.0, 1.0)
        self.activePhases = np.empty((0, 2), dtype="float")
        self.cellsForActivePhases = np.empty(0, dtype="int")
        self.phaseDisplacement = np.empty((0, 2), dtype="float")

        self.activeCells = np.empty(0, dtype="int")
        self.activeSegments = np.empty(0, dtype="uint32")

        self.connections = SparseMatrixConnections(np.prod(cellDimensions),
                                                   anchorInputSize)

        self.initialPermanence = initialPermanence
        self.connectedPermanence = connectedPermanence
        self.learningThreshold = learningThreshold
        self.sampleSize = sampleSize
        self.permanenceIncrement = permanenceIncrement
        self.permanenceDecrement = permanenceDecrement
        self.activationThreshold = activationThreshold
        self.maxSynapsesPerSegment = maxSynapsesPerSegment

        self.anchoringMethod = anchoringMethod

        self.rng = Random(seed)

    def reset(self):
        """
    Clear the active cells.
    """
        self.activePhases = np.empty((0, 2), dtype="float")
        self.phaseDisplacement = np.empty((0, 2), dtype="float")
        self.cellsForActivePhases = np.empty(0, dtype="int")
        self.activeCells = np.empty(0, dtype="int")

    def _computeActiveCells(self):
        # Round each coordinate to the nearest cell.
        activeCellCoordinates = np.floor(self.activePhases *
                                         self.cellDimensions).astype("int")

        # Convert coordinates to cell numbers.
        self.cellsForActivePhases = (np.ravel_multi_index(
            activeCellCoordinates.T, self.cellDimensions))
        self.activeCells = np.unique(self.cellsForActivePhases)

    def activateRandomLocation(self):
        """
    Set the location to a random point.
    """
        self.activePhases = np.array([np.random.random(2)])
        if self.anchoringMethod == "discrete":
            # Need to place the phase in the middle of a cell
            self.activePhases = np.floor(
                self.activePhases * self.cellDimensions) / self.cellDimensions
        self._computeActiveCells()

    def movementCompute(self, displacement, noiseFactor=0):
        """
    Shift the current active cells by a vector.

    @param displacement (pair of floats)
    A translation vector [di, dj].
    """

        if noiseFactor != 0:
            displacement = copy.deepcopy(displacement)
            xnoise = np.random.normal(0, noiseFactor)
            ynoise = np.random.normal(0, noiseFactor)
            displacement[0] += xnoise
            displacement[1] += ynoise

        # Calculate delta in the module's coordinates.
        phaseDisplacement = (np.matmul(self.rotationMatrix, displacement) *
                             self.phasesPerUnitDistance)

        # Shift the active coordinates.
        np.add(self.activePhases, phaseDisplacement, out=self.activePhases)

        # In Python, (x % 1.0) can return 1.0 because of floating point goofiness.
        # Generally this doesn't cause problems, it's just confusing when you're
        # debugging.
        np.round(self.activePhases, decimals=9, out=self.activePhases)
        np.mod(self.activePhases, 1.0, out=self.activePhases)

        self._computeActiveCells()
        self.phaseDisplacement = phaseDisplacement

    def _sensoryComputeInferenceMode(self, anchorInput):
        """
    Infer the location from sensory input. Activate any cells with enough active
    synapses to this sensory input. Deactivate all other cells.

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
        if len(anchorInput) == 0:
            return

        overlaps = self.connections.computeActivity(anchorInput,
                                                    self.connectedPermanence)
        activeSegments = np.where(overlaps >= self.activationThreshold)[0]

        sensorySupportedCells = np.unique(
            self.connections.mapSegmentsToCells(activeSegments))

        inactivated = np.setdiff1d(self.activeCells, sensorySupportedCells)
        inactivatedIndices = np.in1d(self.cellsForActivePhases,
                                     inactivated).nonzero()[0]
        if inactivatedIndices.size > 0:
            self.activePhases = np.delete(self.activePhases,
                                          inactivatedIndices,
                                          axis=0)

        activated = np.setdiff1d(sensorySupportedCells, self.activeCells)

        # Find centers of point clouds
        if "corners" in self.anchoringMethod:
            activatedCoordsBase = np.transpose(
                np.unravel_index(sensorySupportedCells,
                                 self.cellDimensions)).astype('float')
        else:
            activatedCoordsBase = np.transpose(
                np.unravel_index(activated,
                                 self.cellDimensions)).astype('float')

        # Generate points to add
        activatedCoords = np.concatenate([
            activatedCoordsBase + [iOffset, jOffset]
            for iOffset in self.cellCoordinateOffsets
            for jOffset in self.cellCoordinateOffsets
        ])
        if "corners" in self.anchoringMethod:
            self.activePhases = activatedCoords / self.cellDimensions

        else:
            if activatedCoords.size > 0:
                self.activePhases = np.append(self.activePhases,
                                              activatedCoords /
                                              self.cellDimensions,
                                              axis=0)

        self._computeActiveCells()
        self.activeSegments = activeSegments

    def _sensoryComputeLearningMode(self, anchorInput):
        """
    Associate this location with a sensory input. Subsequently, anchorInput will
    activate the current location during anchor().

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
        overlaps = self.connections.computeActivity(anchorInput,
                                                    self.connectedPermanence)
        activeSegments = np.where(overlaps >= self.activationThreshold)[0]

        potentialOverlaps = self.connections.computeActivity(anchorInput)
        matchingSegments = np.where(
            potentialOverlaps >= self.learningThreshold)[0]

        # Cells with a active segment: reinforce the segment
        cellsForActiveSegments = self.connections.mapSegmentsToCells(
            activeSegments)
        learningActiveSegments = activeSegments[np.in1d(
            cellsForActiveSegments, self.activeCells)]
        remainingCells = np.setdiff1d(self.activeCells, cellsForActiveSegments)

        # Remaining cells with a matching segment: reinforce the best
        # matching segment.
        candidateSegments = self.connections.filterSegmentsByCell(
            matchingSegments, remainingCells)
        cellsForCandidateSegments = (
            self.connections.mapSegmentsToCells(candidateSegments))
        candidateSegments = candidateSegments[np.in1d(
            cellsForCandidateSegments, remainingCells)]
        onePerCellFilter = np2.argmaxMulti(
            potentialOverlaps[candidateSegments], cellsForCandidateSegments)
        learningMatchingSegments = candidateSegments[onePerCellFilter]

        newSegmentCells = np.setdiff1d(remainingCells,
                                       cellsForCandidateSegments)

        for learningSegments in (learningActiveSegments,
                                 learningMatchingSegments):
            self._learn(self.connections, self.rng, learningSegments,
                        anchorInput, potentialOverlaps, self.initialPermanence,
                        self.sampleSize, self.permanenceIncrement,
                        self.permanenceDecrement, self.maxSynapsesPerSegment)

        # Remaining cells without a matching segment: grow one.
        numNewSynapses = len(anchorInput)

        if self.sampleSize != -1:
            numNewSynapses = min(numNewSynapses, self.sampleSize)

        if self.maxSynapsesPerSegment != -1:
            numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

        newSegments = self.connections.createSegments(newSegmentCells)

        self.connections.growSynapsesToSample(newSegments, anchorInput,
                                              numNewSynapses,
                                              self.initialPermanence, self.rng)
        self.activeSegments = activeSegments

    def sensoryCompute(self, anchorInput, anchorGrowthCandidates, learn):
        if learn:
            self._sensoryComputeLearningMode(anchorGrowthCandidates)
        else:
            self._sensoryComputeInferenceMode(anchorInput)

    @staticmethod
    def _learn(connections, rng, learningSegments, activeInput,
               potentialOverlaps, initialPermanence, sampleSize,
               permanenceIncrement, permanenceDecrement,
               maxSynapsesPerSegment):
        """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param potentialOverlaps (numpy array)
    """
        # Learn on existing segments
        connections.adjustSynapses(learningSegments, activeInput,
                                   permanenceIncrement, -permanenceDecrement)

        # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
        # grow per segment. "maxNew" might be a number or it might be a list of
        # numbers.
        if sampleSize == -1:
            maxNew = len(activeInput)
        else:
            maxNew = sampleSize - potentialOverlaps[learningSegments]

        if maxSynapsesPerSegment != -1:
            synapseCounts = connections.mapSegmentsToSynapseCounts(
                learningSegments)
            numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
            maxNew = np.where(maxNew <= numSynapsesToReachMax, maxNew,
                              numSynapsesToReachMax)

        connections.growSynapsesToSample(learningSegments, activeInput, maxNew,
                                         initialPermanence, rng)

    def getActiveCells(self):
        return self.activeCells

    def numberOfCells(self):
        return np.prod(self.cellDimensions)
Ejemplo n.º 6
0
    def test_growSynapsesToSample_multi(self):

        rng = Random()

        for (name, cells, growingSegments, initialConnectedInputs,
             presynapticInputs, activeInputs, initialPermanence,
             connectedPermanence, sampleSizes,
             expected) in (("Basic test", [1, 2, 3], [0,
                                                      2], [], [42, 43, 44, 45],
                            [42, 43, 44, 45], 0.55, 0.5, [2, 3], [2, 0, 3]),
                           ("One already connected", [1, 2, 3], [0, 2], [42],
                            [42, 43, 44, 45], [42, 43, 44,
                                               45], 0.55, 0.5, [1,
                                                                2], [2, 0, 3]),
                           ("Higher sample size than axon count", [1, 2,
                                                                   3], [0, 2],
                            [], [42, 43, 44,
                                 45], [42, 43, 44,
                                       45], 0.55, 0.5, [5, 10], [4, 0, 4]),
                           ("Higher sample size than available axon count",
                            [1, 2, 3], [0, 2], [42, 43], [42, 43, 44, 45],
                            [42, 43, 44, 45], 0.55, 0.5, [3, 3], [4, 0, 4])):

            connections = SparseMatrixConnections(2048, 2048)

            segments = connections.createSegments(cells)

            connections.growSynapses(segments[growingSegments],
                                     initialConnectedInputs, initialPermanence)

            connections.growSynapsesToSample(segments[growingSegments],
                                             presynapticInputs, sampleSizes,
                                             initialPermanence, rng)

            overlaps = connections.computeActivity(activeInputs,
                                                   connectedPermanence)

            np.testing.assert_equal(overlaps[segments], expected, name)

        for (name, cells, growingSegments, initialConnectedInputs,
             presynapticInputs, activeInputs, initialPermanence,
             connectedPermanence,
             sampleSizes) in (("Basic randomness test", [1, 2, 3], [0, 2], [],
                               [42, 43, 44, 45], [42, 43], 0.55, 0.5, [2,
                                                                       3]), ):

            # Activate a subset of the inputs. The resulting overlaps should
            # differ on various trials.

            firstResult = None
            differingResults = False

            for _ in range(20):
                connections = SparseMatrixConnections(2048, 2048)

                segments = connections.createSegments(cells)

                connections.growSynapses(segments[growingSegments],
                                         initialConnectedInputs,
                                         initialPermanence)

                connections.growSynapsesToSample(segments[growingSegments],
                                                 presynapticInputs,
                                                 sampleSizes,
                                                 initialPermanence, rng)

                overlaps = connections.computeActivity(activeInputs,
                                                       connectedPermanence)

                if firstResult is None:
                    firstResult = overlaps[segments]
                else:
                    differingResults = not np.array_equal(
                        overlaps[segments], firstResult)
                    if differingResults:
                        break

            self.assertTrue(differingResults, name)
Ejemplo n.º 7
0
  def test_growSynapsesToSample_multi(self):

    rng = Random()

    for (name,
         cells, growingSegments,
         initialConnectedInputs, presynapticInputs, activeInputs,
         initialPermanence, connectedPermanence, sampleSizes,
         expected) in (("Basic test",
                        [1, 2, 3], [0, 2],
                        [], [42, 43, 44, 45],
                        [42, 43, 44, 45], 0.55, 0.5,
                        [2, 3],
                        [2, 0, 3]),
                       ("One already connected",
                        [1, 2, 3], [0, 2],
                        [42], [42, 43, 44, 45],
                        [42, 43, 44, 45], 0.55, 0.5,
                        [1, 2],
                        [2, 0, 3]),
                       ("Higher sample size than axon count",
                        [1, 2, 3], [0, 2],
                        [], [42, 43, 44, 45],
                        [42, 43, 44, 45], 0.55, 0.5,
                        [5, 10],
                        [4, 0, 4]),
                       ("Higher sample size than available axon count",
                        [1, 2, 3], [0, 2],
                        [42, 43], [42, 43, 44, 45],
                        [42, 43, 44, 45], 0.55, 0.5,
                        [3, 3],
                        [4, 0, 4])
                       ):

      connections = SparseMatrixConnections(2048, 2048)

      segments = connections.createSegments(cells)

      connections.growSynapses(
        segments[growingSegments], initialConnectedInputs, initialPermanence)

      connections.growSynapsesToSample(
        segments[growingSegments], presynapticInputs,
        sampleSizes, initialPermanence, rng)

      overlaps = connections.computeActivity(activeInputs, connectedPermanence)

      np.testing.assert_equal(overlaps[segments], expected, name)


    for (name,
         cells, growingSegments,
         initialConnectedInputs, presynapticInputs, activeInputs,
         initialPermanence, connectedPermanence,
         sampleSizes) in (("Basic randomness test",
                           [1, 2, 3], [0, 2],
                           [], [42, 43, 44, 45],
                           [42, 43], 0.55, 0.5, [2, 3]),
         ):

      # Activate a subset of the inputs. The resulting overlaps should
      # differ on various trials.

      firstResult = None
      differingResults = False

      for _ in xrange(20):
        connections = SparseMatrixConnections(2048, 2048)

        segments = connections.createSegments(cells)

        connections.growSynapses(
          segments[growingSegments], initialConnectedInputs, initialPermanence)

        connections.growSynapsesToSample(
          segments[growingSegments], presynapticInputs,
          sampleSizes, initialPermanence, rng)

        overlaps = connections.computeActivity(activeInputs,
                                               connectedPermanence)

        if firstResult is None:
          firstResult = overlaps[segments]
        else:
          differingResults = not np.array_equal(overlaps[segments], firstResult)
          if differingResults:
            break

      self.assertTrue(differingResults, name)
class SuperficialLocationModule2D(object):
    """
  A model of a location module. It's similar to a grid cell module, but it uses
  squares rather than triangles.

  The cells are arranged into a m*n rectangle which is tiled onto 2D space.
  Each cell represents a small rectangle in each tile.

  +------+------+------++------+------+------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #1  |  #2  |  #3  ||  #1  |  #2  |  #3  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #4  |  #5  |  #6  ||  #4  |  #5  |  #6  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #7  |  #8  |  #9  ||  #7  |  #8  |  #9  |
  |      |      |      ||      |      |      |
  +------+------+------++------+------+------+

  We assume that path integration works *somehow*. This model receives a "delta
  location" vector, and it shifts the active cells accordingly. The model stores
  intermediate coordinates of active cells. Whenever sensory cues activate a
  cell, the model adds this cell to the list of coordinates being shifted.
  Whenever sensory cues cause a cell to become inactive, that cell is removed
  from the list of coordinates.

  (This model doesn't attempt to propose how "path integration" works. It
  attempts to show how locations are anchored to sensory cues.)

  When orientation is set to 0 degrees, the deltaLocation is a [di, dj],
  moving di cells "down" and dj cells "right".

  When orientation is set to 90 degrees, the deltaLocation is essentially a
  [dx, dy], applied in typical x,y coordinates with the origin on the bottom
  left.

  Usage:

  Adjust the location in response to motor input:
    lm.shift([di, dj])

  Adjust the location in response to sensory input:
    lm.anchor(anchorInput)

  Learn an anchor input for the current location:
    lm.learn(anchorInput)

  The "anchor input" is typically a feature-location pair SDR.

  During inference, you'll typically call:
    lm.shift(...)
    # Consume lm.getActiveCells()
    # ...
    lm.anchor(...)

  During learning, you'll do the same, but you'll call lm.learn() instead of
  lm.anchor().
  """
    def __init__(self,
                 cellDimensions,
                 moduleMapDimensions,
                 orientation,
                 anchorInputSize,
                 pointOffsets=(0.5, ),
                 activationThreshold=10,
                 initialPermanence=0.21,
                 connectedPermanence=0.50,
                 learningThreshold=10,
                 sampleSize=20,
                 permanenceIncrement=0.1,
                 permanenceDecrement=0.0,
                 maxSynapsesPerSegment=-1,
                 seed=42):
        """
    @param cellDimensions (tuple(int, int))
    Determines the number of cells. Determines how space is divided between the
    cells.

    @param moduleMapDimensions (tuple(float, float))
    Determines the amount of world space covered by all of the cells combined.
    In grid cell terminology, this is equivalent to the "scale" of a module.
    A module with a scale of "5cm" would have moduleMapDimensions=(5.0, 5.0).

    @param orientation (float)
    The rotation of this map, measured in radians.

    @param anchorInputSize (int)
    The number of input bits in the anchor input.

    @param pointOffsets (list of floats)
    These must each be between 0.0 and 1.0. Every time a cell is activated by
    anchor input, this class adds a "point" which is shifted in subsequent
    motions. By default, this point is placed at the center of the cell. This
    parameter allows you to control where the point is placed and whether multiple
    are placed. For example, With value [0.2, 0.8], it will place 4 points:
    [0.2, 0.2], [0.2, 0.8], [0.8, 0.2], [0.8, 0.8]
    """

        self.cellDimensions = np.asarray(cellDimensions, dtype="int")
        self.moduleMapDimensions = np.asarray(moduleMapDimensions,
                                              dtype="float")
        self.cellFieldsPerUnitDistance = self.cellDimensions / self.moduleMapDimensions

        self.orientation = orientation
        self.rotationMatrix = np.array(
            [[math.cos(orientation), -math.sin(orientation)],
             [math.sin(orientation),
              math.cos(orientation)]])

        self.pointOffsets = pointOffsets

        # These coordinates are in units of "cell fields".
        self.activePoints = np.empty((0, 2), dtype="float")
        self.cellsForActivePoints = np.empty(0, dtype="int")

        self.activeCells = np.empty(0, dtype="int")
        self.activeSegments = np.empty(0, dtype="uint32")

        self.connections = SparseMatrixConnections(np.prod(cellDimensions),
                                                   anchorInputSize)

        self.initialPermanence = initialPermanence
        self.connectedPermanence = connectedPermanence
        self.learningThreshold = learningThreshold
        self.sampleSize = sampleSize
        self.permanenceIncrement = permanenceIncrement
        self.permanenceDecrement = permanenceDecrement
        self.activationThreshold = activationThreshold
        self.maxSynapsesPerSegment = maxSynapsesPerSegment

        self.rng = Random(seed)

    def reset(self):
        """
    Clear the active cells.
    """
        self.activePoints = np.empty((0, 2), dtype="float")
        self.cellsForActivePoints = np.empty(0, dtype="int")
        self.activeCells = np.empty(0, dtype="int")

    def _computeActiveCells(self):
        # Round each coordinate to the nearest cell.
        flooredActivePoints = np.floor(self.activePoints).astype("int")

        # Convert coordinates to cell numbers.
        self.cellsForActivePoints = (np.ravel_multi_index(
            flooredActivePoints.T, self.cellDimensions))
        self.activeCells = np.unique(self.cellsForActivePoints)

    def activateRandomLocation(self):
        """
    Set the location to a random point.
    """
        self.activePoints = np.array(
            [np.random.random(2) * self.cellDimensions])
        self._computeActiveCells()

    def shift(self, deltaLocation):
        """
    Shift the current active cells by a vector.

    @param deltaLocation (pair of floats)
    A translation vector [di, dj].
    """
        # Calculate delta in the module's coordinates.
        deltaLocationInCellFields = (
            np.matmul(self.rotationMatrix, deltaLocation) *
            self.cellFieldsPerUnitDistance)

        # Shift the active coordinates.
        np.add(self.activePoints,
               deltaLocationInCellFields,
               out=self.activePoints)
        np.mod(self.activePoints, self.cellDimensions, out=self.activePoints)

        self._computeActiveCells()

    def anchor(self, anchorInput):
        """
    Infer the location from sensory input. Activate any cells with enough active
    synapses to this sensory input. Deactivate all other cells.

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
        if len(anchorInput) == 0:
            return

        overlaps = self.connections.computeActivity(anchorInput,
                                                    self.connectedPermanence)
        activeSegments = np.where(overlaps >= self.activationThreshold)[0]

        sensorySupportedCells = np.unique(
            self.connections.mapSegmentsToCells(activeSegments))

        inactivated = np.setdiff1d(self.activeCells, sensorySupportedCells)
        inactivatedIndices = np.in1d(self.cellsForActivePoints,
                                     inactivated).nonzero()[0]
        if inactivatedIndices.size > 0:
            self.activePoints = np.delete(self.activePoints,
                                          inactivatedIndices,
                                          axis=0)

        activated = np.setdiff1d(sensorySupportedCells, self.activeCells)

        activatedCoordsBase = np.transpose(
            np.unravel_index(activated, self.cellDimensions)).astype('float')

        activatedCoords = np.concatenate([
            activatedCoordsBase + [iOffset, jOffset]
            for iOffset in self.pointOffsets for jOffset in self.pointOffsets
        ])
        if activatedCoords.size > 0:
            self.activePoints = np.append(self.activePoints,
                                          activatedCoords,
                                          axis=0)

        self._computeActiveCells()
        self.activeSegments = activeSegments

    def learn(self, anchorInput):
        """
    Associate this location with a sensory input. Subsequently, anchorInput will
    activate the current location during anchor().

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
        overlaps = self.connections.computeActivity(anchorInput,
                                                    self.connectedPermanence)
        activeSegments = np.where(overlaps >= self.activationThreshold)[0]

        potentialOverlaps = self.connections.computeActivity(anchorInput)
        matchingSegments = np.where(
            potentialOverlaps >= self.learningThreshold)[0]

        # Cells with a active segment: reinforce the segment
        cellsForActiveSegments = self.connections.mapSegmentsToCells(
            activeSegments)
        learningActiveSegments = activeSegments[np.in1d(
            cellsForActiveSegments, self.activeCells)]
        remainingCells = np.setdiff1d(self.activeCells, cellsForActiveSegments)

        # Remaining cells with a matching segment: reinforce the best
        # matching segment.
        candidateSegments = self.connections.filterSegmentsByCell(
            matchingSegments, remainingCells)
        cellsForCandidateSegments = (
            self.connections.mapSegmentsToCells(candidateSegments))
        candidateSegments = candidateSegments[np.in1d(
            cellsForCandidateSegments, remainingCells)]
        onePerCellFilter = np2.argmaxMulti(
            potentialOverlaps[candidateSegments], cellsForCandidateSegments)
        learningMatchingSegments = candidateSegments[onePerCellFilter]

        newSegmentCells = np.setdiff1d(remainingCells,
                                       cellsForCandidateSegments)

        for learningSegments in (learningActiveSegments,
                                 learningMatchingSegments):
            self._learn(self.connections, self.rng, learningSegments,
                        anchorInput, potentialOverlaps, self.initialPermanence,
                        self.sampleSize, self.permanenceIncrement,
                        self.permanenceDecrement, self.maxSynapsesPerSegment)

        # Remaining cells without a matching segment: grow one.
        numNewSynapses = len(anchorInput)

        if self.sampleSize != -1:
            numNewSynapses = min(numNewSynapses, self.sampleSize)

        if self.maxSynapsesPerSegment != -1:
            numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

        newSegments = self.connections.createSegments(newSegmentCells)

        self.connections.growSynapsesToSample(newSegments, anchorInput,
                                              numNewSynapses,
                                              self.initialPermanence, self.rng)

        self.activeSegments = activeSegments

    @staticmethod
    def _learn(connections, rng, learningSegments, activeInput,
               potentialOverlaps, initialPermanence, sampleSize,
               permanenceIncrement, permanenceDecrement,
               maxSynapsesPerSegment):
        """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param potentialOverlaps (numpy array)
    """
        # Learn on existing segments
        connections.adjustSynapses(learningSegments, activeInput,
                                   permanenceIncrement, -permanenceDecrement)

        # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
        # grow per segment. "maxNew" might be a number or it might be a list of
        # numbers.
        if sampleSize == -1:
            maxNew = len(activeInput)
        else:
            maxNew = sampleSize - potentialOverlaps[learningSegments]

        if maxSynapsesPerSegment != -1:
            synapseCounts = connections.mapSegmentsToSynapseCounts(
                learningSegments)
            numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
            maxNew = np.where(maxNew <= numSynapsesToReachMax, maxNew,
                              numSynapsesToReachMax)

        connections.growSynapsesToSample(learningSegments, activeInput, maxNew,
                                         initialPermanence, rng)

    def getActiveCells(self):
        return self.activeCells

    def numberOfCells(self):
        return np.prod(self.cellDimensions)
class SuperficialLocationModule2D(object):
  """
  A model of a location module. It's similar to a grid cell module, but it uses
  squares rather than triangles.

  The cells are arranged into a m*n rectangle which is tiled onto 2D space.
  Each cell represents a small rectangle in each tile.

  +------+------+------++------+------+------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #1  |  #2  |  #3  ||  #1  |  #2  |  #3  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #4  |  #5  |  #6  ||  #4  |  #5  |  #6  |
  |      |      |      ||      |      |      |
  +--------------------++--------------------+
  | Cell | Cell | Cell || Cell | Cell | Cell |
  |  #7  |  #8  |  #9  ||  #7  |  #8  |  #9  |
  |      |      |      ||      |      |      |
  +------+------+------++------+------+------+

  We assume that path integration works *somehow*. This model receives a "delta
  location" vector, and it shifts the active cells accordingly. The model stores
  intermediate coordinates of active cells. Whenever sensory cues activate a
  cell, the model adds this cell to the list of coordinates being shifted.
  Whenever sensory cues cause a cell to become inactive, that cell is removed
  from the list of coordinates.

  (This model doesn't attempt to propose how "path integration" works. It
  attempts to show how locations are anchored to sensory cues.)

  When orientation is set to 0 degrees, the deltaLocation is a [di, dj],
  moving di cells "down" and dj cells "right".

  When orientation is set to 90 degrees, the deltaLocation is essentially a
  [dx, dy], applied in typical x,y coordinates with the origin on the bottom
  left.

  Usage:

  Adjust the location in response to motor input:
    lm.shift([di, dj])

  Adjust the location in response to sensory input:
    lm.anchor(anchorInput)

  Learn an anchor input for the current location:
    lm.learn(anchorInput)

  The "anchor input" is typically a feature-location pair SDR.

  During inference, you'll typically call:
    lm.shift(...)
    # Consume lm.getActiveCells()
    # ...
    lm.anchor(...)

  During learning, you'll do the same, but you'll call lm.learn() instead of
  lm.anchor().
  """


  def __init__(self,
               cellDimensions,
               moduleMapDimensions,
               orientation,
               anchorInputSize,
               pointOffsets=(0.5,),
               activationThreshold=10,
               initialPermanence=0.21,
               connectedPermanence=0.50,
               learningThreshold=10,
               sampleSize=20,
               permanenceIncrement=0.1,
               permanenceDecrement=0.0,
               maxSynapsesPerSegment=-1,
               seed=42):
    """
    @param cellDimensions (tuple(int, int))
    Determines the number of cells. Determines how space is divided between the
    cells.

    @param moduleMapDimensions (tuple(float, float))
    Determines the amount of world space covered by all of the cells combined.
    In grid cell terminology, this is equivalent to the "scale" of a module.
    A module with a scale of "5cm" would have moduleMapDimensions=(5.0, 5.0).

    @param orientation (float)
    The rotation of this map, measured in radians.

    @param anchorInputSize (int)
    The number of input bits in the anchor input.

    @param pointOffsets (list of floats)
    These must each be between 0.0 and 1.0. Every time a cell is activated by
    anchor input, this class adds a "point" which is shifted in subsequent
    motions. By default, this point is placed at the center of the cell. This
    parameter allows you to control where the point is placed and whether multiple
    are placed. For example, With value [0.2, 0.8], it will place 4 points:
    [0.2, 0.2], [0.2, 0.8], [0.8, 0.2], [0.8, 0.8]
    """

    self.cellDimensions = np.asarray(cellDimensions, dtype="int")
    self.moduleMapDimensions = np.asarray(moduleMapDimensions, dtype="float")
    self.cellFieldsPerUnitDistance = self.cellDimensions / self.moduleMapDimensions

    self.orientation = orientation
    self.rotationMatrix = np.array(
      [[math.cos(orientation), -math.sin(orientation)],
       [math.sin(orientation), math.cos(orientation)]])

    self.pointOffsets = pointOffsets

    # These coordinates are in units of "cell fields".
    self.activePoints = np.empty((0,2), dtype="float")
    self.cellsForActivePoints = np.empty(0, dtype="int")

    self.activeCells = np.empty(0, dtype="int")
    self.activeSegments = np.empty(0, dtype="uint32")

    self.connections = SparseMatrixConnections(np.prod(cellDimensions),
                                               anchorInputSize)

    self.initialPermanence = initialPermanence
    self.connectedPermanence = connectedPermanence
    self.learningThreshold = learningThreshold
    self.sampleSize = sampleSize
    self.permanenceIncrement = permanenceIncrement
    self.permanenceDecrement = permanenceDecrement
    self.activationThreshold = activationThreshold
    self.maxSynapsesPerSegment = maxSynapsesPerSegment

    self.rng = Random(seed)


  def reset(self):
    """
    Clear the active cells.
    """
    self.activePoints = np.empty((0,2), dtype="float")
    self.cellsForActivePoints = np.empty(0, dtype="int")
    self.activeCells = np.empty(0, dtype="int")


  def _computeActiveCells(self):
    # Round each coordinate to the nearest cell.
    flooredActivePoints = np.floor(self.activePoints).astype("int")

    # Convert coordinates to cell numbers.
    self.cellsForActivePoints = (
      np.ravel_multi_index(flooredActivePoints.T, self.cellDimensions))
    self.activeCells = np.unique(self.cellsForActivePoints)


  def activateRandomLocation(self):
    """
    Set the location to a random point.
    """
    self.activePoints = np.array([np.random.random(2) * self.cellDimensions])
    self._computeActiveCells()


  def shift(self, deltaLocation):
    """
    Shift the current active cells by a vector.

    @param deltaLocation (pair of floats)
    A translation vector [di, dj].
    """
    # Calculate delta in the module's coordinates.
    deltaLocationInCellFields = (np.matmul(self.rotationMatrix, deltaLocation) *
                                 self.cellFieldsPerUnitDistance)

    # Shift the active coordinates.
    np.add(self.activePoints, deltaLocationInCellFields, out=self.activePoints)
    np.mod(self.activePoints, self.cellDimensions, out=self.activePoints)

    self._computeActiveCells()


  def anchor(self, anchorInput):
    """
    Infer the location from sensory input. Activate any cells with enough active
    synapses to this sensory input. Deactivate all other cells.

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
    if len(anchorInput) == 0:
      return

    overlaps = self.connections.computeActivity(anchorInput,
                                                self.connectedPermanence)
    activeSegments = np.where(overlaps >= self.activationThreshold)[0]

    sensorySupportedCells = np.unique(
      self.connections.mapSegmentsToCells(activeSegments))

    inactivated = np.setdiff1d(self.activeCells, sensorySupportedCells)
    inactivatedIndices = np.in1d(self.cellsForActivePoints,
                                 inactivated).nonzero()[0]
    if inactivatedIndices.size > 0:
      self.activePoints = np.delete(self.activePoints, inactivatedIndices,
                                    axis=0)

    activated = np.setdiff1d(sensorySupportedCells, self.activeCells)

    activatedCoordsBase = np.transpose(
      np.unravel_index(activated, self.cellDimensions)).astype('float')

    activatedCoords = np.concatenate(
      [activatedCoordsBase + [iOffset, jOffset]
       for iOffset in self.pointOffsets
       for jOffset in self.pointOffsets]
    )
    if activatedCoords.size > 0:
      self.activePoints = np.append(self.activePoints, activatedCoords, axis=0)

    self._computeActiveCells()
    self.activeSegments = activeSegments


  def learn(self, anchorInput):
    """
    Associate this location with a sensory input. Subsequently, anchorInput will
    activate the current location during anchor().

    @param anchorInput (numpy array)
    A sensory input. This will often come from a feature-location pair layer.
    """
    overlaps = self.connections.computeActivity(anchorInput,
                                                self.connectedPermanence)
    activeSegments = np.where(overlaps >= self.activationThreshold)[0]

    potentialOverlaps = self.connections.computeActivity(anchorInput)
    matchingSegments = np.where(potentialOverlaps >=
                                self.learningThreshold)[0]

    # Cells with a active segment: reinforce the segment
    cellsForActiveSegments = self.connections.mapSegmentsToCells(
      activeSegments)
    learningActiveSegments = activeSegments[
      np.in1d(cellsForActiveSegments, self.activeCells)]
    remainingCells = np.setdiff1d(self.activeCells, cellsForActiveSegments)

    # Remaining cells with a matching segment: reinforce the best
    # matching segment.
    candidateSegments = self.connections.filterSegmentsByCell(
      matchingSegments, remainingCells)
    cellsForCandidateSegments = (
      self.connections.mapSegmentsToCells(candidateSegments))
    candidateSegments = candidateSegments[
      np.in1d(cellsForCandidateSegments, remainingCells)]
    onePerCellFilter = np2.argmaxMulti(potentialOverlaps[candidateSegments],
                                       cellsForCandidateSegments)
    learningMatchingSegments = candidateSegments[onePerCellFilter]

    newSegmentCells = np.setdiff1d(remainingCells, cellsForCandidateSegments)

    for learningSegments in (learningActiveSegments,
                             learningMatchingSegments):
      self._learn(self.connections, self.rng, learningSegments,
                  anchorInput, potentialOverlaps,
                  self.initialPermanence, self.sampleSize,
                  self.permanenceIncrement, self.permanenceDecrement,
                  self.maxSynapsesPerSegment)

    # Remaining cells without a matching segment: grow one.
    numNewSynapses = len(anchorInput)

    if self.sampleSize != -1:
      numNewSynapses = min(numNewSynapses, self.sampleSize)

    if self.maxSynapsesPerSegment != -1:
      numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

    newSegments = self.connections.createSegments(newSegmentCells)

    self.connections.growSynapsesToSample(
      newSegments, anchorInput, numNewSynapses,
      self.initialPermanence, self.rng)

    self.activeSegments = activeSegments


  @staticmethod
  def _learn(connections, rng, learningSegments, activeInput,
             potentialOverlaps, initialPermanence, sampleSize,
             permanenceIncrement, permanenceDecrement, maxSynapsesPerSegment):
    """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param potentialOverlaps (numpy array)
    """
    # Learn on existing segments
    connections.adjustSynapses(learningSegments, activeInput,
                               permanenceIncrement, -permanenceDecrement)

    # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
    # grow per segment. "maxNew" might be a number or it might be a list of
    # numbers.
    if sampleSize == -1:
      maxNew = len(activeInput)
    else:
      maxNew = sampleSize - potentialOverlaps[learningSegments]

    if maxSynapsesPerSegment != -1:
      synapseCounts = connections.mapSegmentsToSynapseCounts(
        learningSegments)
      numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
      maxNew = np.where(maxNew <= numSynapsesToReachMax,
                        maxNew, numSynapsesToReachMax)

    connections.growSynapsesToSample(learningSegments, activeInput,
                                     maxNew, initialPermanence, rng)


  def getActiveCells(self):
    return self.activeCells


  def numberOfCells(self):
    return np.prod(self.cellDimensions)
Ejemplo n.º 10
0
class SingleLayerLocationMemory(object):
    """
  A layer of cells which learns how to take a "delta location" (e.g. a motor
  command or a proprioceptive delta) and update its active cells to represent
  the new location.

  Its active cells might represent a union of locations.
  As the location changes, the featureLocationInput causes this union to narrow
  down until the location is inferred.

  This layer receives absolute proprioceptive info as proximal input.
  For now, we assume that there's a one-to-one mapping between absolute
  proprioceptive input and the location SDR. So rather than modeling
  proximal synapses, we'll just relay the proprioceptive SDR. In the future
  we might want to consider a many-to-one mapping of proprioceptive inputs
  to location SDRs.

  After this layer is trained, it no longer needs the proprioceptive input.
  The delta location will drive the layer. The current active cells and the
  other distal connections will work together with this delta location to
  activate a new set of cells.

  When no cells are active, activate a large union of possible locations.
  With subsequent inputs, the union will narrow down to a single location SDR.
  """
    def __init__(self,
                 cellCount,
                 deltaLocationInputSize,
                 featureLocationInputSize,
                 activationThreshold=13,
                 initialPermanence=0.21,
                 connectedPermanence=0.50,
                 learningThreshold=10,
                 sampleSize=20,
                 permanenceIncrement=0.1,
                 permanenceDecrement=0.1,
                 maxSynapsesPerSegment=-1,
                 seed=42):

        # For transition learning, every segment is split into two parts.
        # For the segment to be active, both parts must be active.
        self.internalConnections = SparseMatrixConnections(
            cellCount, cellCount)
        self.deltaConnections = SparseMatrixConnections(
            cellCount, deltaLocationInputSize)

        # Distal segments that receive input from the layer that represents
        # feature-locations.
        self.featureLocationConnections = SparseMatrixConnections(
            cellCount, featureLocationInputSize)

        self.activeCells = np.empty(0, dtype="uint32")
        self.activeDeltaSegments = np.empty(0, dtype="uint32")
        self.activeFeatureLocationSegments = np.empty(0, dtype="uint32")

        self.initialPermanence = initialPermanence
        self.connectedPermanence = connectedPermanence
        self.learningThreshold = learningThreshold
        self.sampleSize = sampleSize
        self.permanenceIncrement = permanenceIncrement
        self.permanenceDecrement = permanenceDecrement
        self.activationThreshold = activationThreshold
        self.maxSynapsesPerSegment = maxSynapsesPerSegment

        self.rng = Random(seed)

    def reset(self):
        """
    Deactivate all cells.
    """

        self.activeCells = np.empty(0, dtype="uint32")
        self.activeDeltaSegments = np.empty(0, dtype="uint32")
        self.activeFeatureLocationSegments = np.empty(0, dtype="uint32")

    def compute(self,
                deltaLocation=(),
                newLocation=(),
                featureLocationInput=(),
                featureLocationGrowthCandidates=(),
                learn=True):
        """
    Run one time step of the Location Memory algorithm.

    @param deltaLocation (sorted numpy array)
    @param newLocation (sorted numpy array)
    @param featureLocationInput (sorted numpy array)
    @param featureLocationGrowthCandidates (sorted numpy array)
    """
        prevActiveCells = self.activeCells

        self.activeDeltaSegments = np.where(
            (self.internalConnections.computeActivity(
                prevActiveCells, self.connectedPermanence) >=
             self.activationThreshold)
            & (self.deltaConnections.computeActivity(
                deltaLocation, self.connectedPermanence) >=
               self.activationThreshold))[0]

        # When we're moving, the feature-location input has no effect.
        if len(deltaLocation) == 0:
            self.activeFeatureLocationSegments = np.where(
                self.featureLocationConnections.computeActivity(
                    featureLocationInput, self.connectedPermanence) >=
                self.activationThreshold)[0]
        else:
            self.activeFeatureLocationSegments = np.empty(0, dtype="uint32")

        if len(newLocation) > 0:
            # Drive activations by relaying this location SDR.
            self.activeCells = newLocation

            if learn:
                # Learn the delta.
                self._learnTransition(prevActiveCells, deltaLocation,
                                      newLocation)

                # Learn the featureLocationInput.
                self._learnFeatureLocationPair(
                    newLocation, featureLocationInput,
                    featureLocationGrowthCandidates)

        elif len(prevActiveCells) > 0:
            if len(deltaLocation) > 0:
                # Drive activations by applying the deltaLocation to the current location.
                # Completely ignore the featureLocationInput. It's outdated, associated
                # with the previous location.

                cellsForDeltaSegments = self.internalConnections.mapSegmentsToCells(
                    self.activeDeltaSegments)

                self.activeCells = np.unique(cellsForDeltaSegments)
            else:
                # Keep previous active cells active.
                # Modulate with the featureLocationInput.

                if len(self.activeFeatureLocationSegments) > 0:

                    cellsForFeatureLocationSegments = (
                        self.featureLocationConnections.mapSegmentsToCells(
                            self.activeFeatureLocationSegments))
                    self.activeCells = np.intersect1d(
                        prevActiveCells, cellsForFeatureLocationSegments)
                else:
                    self.activeCells = prevActiveCells

        elif len(featureLocationInput) > 0:
            # Drive activations with the featureLocationInput.

            cellsForFeatureLocationSegments = (
                self.featureLocationConnections.mapSegmentsToCells(
                    self.activeFeatureLocationSegments))

            self.activeCells = np.unique(cellsForFeatureLocationSegments)

    def _learnTransition(self, prevActiveCells, deltaLocation, newLocation):
        """
    For each cell in the newLocation SDR, learn the transition of prevLocation
    (i.e. prevActiveCells) + deltaLocation.

    The transition might be already known. In that case, just reinforce the
    existing segments.
    """

        prevLocationPotentialOverlaps = self.internalConnections.computeActivity(
            prevActiveCells)
        deltaPotentialOverlaps = self.deltaConnections.computeActivity(
            deltaLocation)

        matchingDeltaSegments = np.where(
            (prevLocationPotentialOverlaps >= self.learningThreshold)
            & (deltaPotentialOverlaps >= self.learningThreshold))[0]

        # Cells with a active segment pair: reinforce the segment
        cellsForActiveSegments = self.internalConnections.mapSegmentsToCells(
            self.activeDeltaSegments)
        learningActiveDeltaSegments = self.activeDeltaSegments[np.in1d(
            cellsForActiveSegments, newLocation)]
        remainingCells = np.setdiff1d(newLocation, cellsForActiveSegments)

        # Remaining cells with a matching segment pair: reinforce the best matching
        # segment pair.
        candidateSegments = self.internalConnections.filterSegmentsByCell(
            matchingDeltaSegments, remainingCells)
        cellsForCandidateSegments = self.internalConnections.mapSegmentsToCells(
            candidateSegments)
        candidateSegments = matchingDeltaSegments[np.in1d(
            cellsForCandidateSegments, remainingCells)]
        onePerCellFilter = np2.argmaxMulti(
            prevLocationPotentialOverlaps[candidateSegments] +
            deltaPotentialOverlaps[candidateSegments],
            cellsForCandidateSegments)
        learningMatchingDeltaSegments = candidateSegments[onePerCellFilter]

        newDeltaSegmentCells = np.setdiff1d(remainingCells,
                                            cellsForCandidateSegments)

        for learningSegments in (learningActiveDeltaSegments,
                                 learningMatchingDeltaSegments):
            self._learn(self.internalConnections, self.rng, learningSegments,
                        prevActiveCells, prevActiveCells,
                        prevLocationPotentialOverlaps, self.initialPermanence,
                        self.sampleSize, self.permanenceIncrement,
                        self.permanenceDecrement, self.maxSynapsesPerSegment)
            self._learn(self.deltaConnections, self.rng, learningSegments,
                        deltaLocation, deltaLocation, deltaPotentialOverlaps,
                        self.initialPermanence, self.sampleSize,
                        self.permanenceIncrement, self.permanenceDecrement,
                        self.maxSynapsesPerSegment)

        numNewLocationSynapses = len(prevActiveCells)
        numNewDeltaSynapses = len(deltaLocation)

        if self.sampleSize != -1:
            numNewLocationSynapses = min(numNewLocationSynapses,
                                         self.sampleSize)
            numNewDeltaSynapses = min(numNewDeltaSynapses, self.sampleSize)

        if self.maxSynapsesPerSegment != -1:
            numNewLocationSynapses = min(numNewLocationSynapses,
                                         self.maxSynapsesPerSegment)
            numNewDeltaSynapses = min(numNewLocationSynapses,
                                      self.maxSynapsesPerSegment)

        newPrevLocationSegments = self.internalConnections.createSegments(
            newDeltaSegmentCells)
        newDeltaSegments = self.deltaConnections.createSegments(
            newDeltaSegmentCells)

        assert np.array_equal(newPrevLocationSegments, newDeltaSegments)

        self.internalConnections.growSynapsesToSample(newPrevLocationSegments,
                                                      prevActiveCells,
                                                      numNewLocationSynapses,
                                                      self.initialPermanence,
                                                      self.rng)
        self.deltaConnections.growSynapsesToSample(newDeltaSegments,
                                                   deltaLocation,
                                                   numNewDeltaSynapses,
                                                   self.initialPermanence,
                                                   self.rng)

    def _learnFeatureLocationPair(self, newLocation, featureLocationInput,
                                  featureLocationGrowthCandidates):
        """
    Grow / reinforce synapses between the location layer's dendrites and the
    input layer's active cells.
    """

        potentialOverlaps = self.featureLocationConnections.computeActivity(
            featureLocationInput)
        matchingSegments = np.where(
            potentialOverlaps > self.learningThreshold)[0]

        # Cells with a active segment pair: reinforce the segment
        cellsForActiveSegments = self.featureLocationConnections.mapSegmentsToCells(
            self.activeFeatureLocationSegments)
        learningActiveSegments = self.activeFeatureLocationSegments[np.in1d(
            cellsForActiveSegments, newLocation)]
        remainingCells = np.setdiff1d(newLocation, cellsForActiveSegments)

        # Remaining cells with a matching segment pair: reinforce the best matching
        # segment pair.
        candidateSegments = self.featureLocationConnections.filterSegmentsByCell(
            matchingSegments, remainingCells)
        cellsForCandidateSegments = (self.featureLocationConnections.
                                     mapSegmentsToCells(candidateSegments))
        candidateSegments = candidateSegments[np.in1d(
            cellsForCandidateSegments, remainingCells)]
        onePerCellFilter = np2.argmaxMulti(
            potentialOverlaps[candidateSegments], cellsForCandidateSegments)
        learningMatchingSegments = candidateSegments[onePerCellFilter]

        newSegmentCells = np.setdiff1d(remainingCells,
                                       cellsForCandidateSegments)

        for learningSegments in (learningActiveSegments,
                                 learningMatchingSegments):
            self._learn(self.featureLocationConnections, self.rng,
                        learningSegments, featureLocationInput,
                        featureLocationGrowthCandidates, potentialOverlaps,
                        self.initialPermanence, self.sampleSize,
                        self.permanenceIncrement, self.permanenceDecrement,
                        self.maxSynapsesPerSegment)

        numNewSynapses = len(featureLocationInput)

        if self.sampleSize != -1:
            numNewSynapses = min(numNewSynapses, self.sampleSize)

        if self.maxSynapsesPerSegment != -1:
            numNewSynapses = min(numNewSynapses, self.maxSynapsesPerSegment)

        newSegments = self.featureLocationConnections.createSegments(
            newSegmentCells)

        self.featureLocationConnections.growSynapsesToSample(
            newSegments, featureLocationGrowthCandidates, numNewSynapses,
            self.initialPermanence, self.rng)

    @staticmethod
    def _learn(connections, rng, learningSegments, activeInput,
               growthCandidates, potentialOverlaps, initialPermanence,
               sampleSize, permanenceIncrement, permanenceDecrement,
               maxSynapsesPerSegment):
        """
    Adjust synapse permanences, grow new synapses, and grow new segments.

    @param learningActiveSegments (numpy array)
    @param learningMatchingSegments (numpy array)
    @param segmentsToPunish (numpy array)
    @param activeInput (numpy array)
    @param growthCandidates (numpy array)
    @param potentialOverlaps (numpy array)
    """

        # Learn on existing segments
        connections.adjustSynapses(learningSegments, activeInput,
                                   permanenceIncrement, -permanenceDecrement)

        # Grow new synapses. Calculate "maxNew", the maximum number of synapses to
        # grow per segment. "maxNew" might be a number or it might be a list of
        # numbers.
        if sampleSize == -1:
            maxNew = len(growthCandidates)
        else:
            maxNew = sampleSize - potentialOverlaps[learningSegments]

        if maxSynapsesPerSegment != -1:
            synapseCounts = connections.mapSegmentsToSynapseCounts(
                learningSegments)
            numSynapsesToReachMax = maxSynapsesPerSegment - synapseCounts
            maxNew = np.where(maxNew <= numSynapsesToReachMax, maxNew,
                              numSynapsesToReachMax)

        connections.growSynapsesToSample(learningSegments, growthCandidates,
                                         maxNew, initialPermanence, rng)

    def getActiveCells(self):
        return self.activeCells