def init(): global numColumns, numInputs, inputDimensions, columnDimensions, connectedSynapses global numColumns inputDimensions = [32, 32] columnDimensions = [64, 64] initConnectedPct = 0.5 # Check the matrix SM_01_32_32 inputDimensions = numpy.array(inputDimensions, ndmin=1) columnDimensions = numpy.array(columnDimensions, ndmin=1) numColumns = columnDimensions.prod() numInputs = inputDimensions.prod() potentialPools = SparseBinaryMatrix(numInputs) potentialPools.resize(numColumns, numInputs) permances = SparseMatrix(numColumns, numInputs) random = NupicRandom() tieBreaker = 0.01*numpy.array([random.getReal64() for i in xrange(numColumns)]) connectedSynapses = SparseBinaryMatrix(numInputs) connectedSynapses.resize(numColumns, numInputs) connectedCounts = numpy.zeros(numColumns, dtype=realDType) potentialPools.replaceSparseRow(0, numpy.array([0, 1], dtype='int'))
class TemporalPooler(SpatialPooler): """ This class constitutes the default implementation of the temporal pooler (TP). The main goal of the TP is to form stable and unique representations of an input data stream of cell activity from a Temporal Memory. More specifically, the TP forms its stable representation based on input cells that were correctly temporally predicted by Temporal Memory. If this active cell input sequence was not predicted, a competition, similar to that of the Spatial Pooler's, is used to select active TP cells. Note that while the temporal pooler functions like a Spatial Pooler in that it selects a sparse active set of columns, these columns are treated as if they only have one cell, and will be referred to as TP cells. If a TP cell is sufficiently activated by inputs that were predicted by the Temporal Memory, it enters into a "pooling state." A pooling cell can, for a limited period, maintain its active status even if it does not receive any bottom-up input activity. This is implemented using a timer for each cell. When the timer "runs out," the cell loses its pooling status. Whenever significant active predicted input overlaps with a TP cell's proximal synapses, its timer is reset. """ def __init__(self, inputDimensions=[32,32], columnDimensions=[64,64], potentialRadius=16, potentialPct=0.9, globalInhibition=True, localAreaDensity=-1.0, numActiveColumnsPerInhArea=10.0, stimulusThreshold=2, synPermInactiveDec=0.01, synPermActiveInc=0.03, synPredictedInc=0.5, synPermConnected=0.3, minPctOverlapDutyCycle=0.001, dutyCyclePeriod=1000, boostStrength=0.0, useBurstingRule = False, usePoolingRule = True, poolingLife = 1000, poolingThreshUnpredicted = 0.0, initConnectedPct = 0.2, seed=-1, spVerbosity=0, wrapAround=True ): """ Please see spatial_pooler.py in NuPIC for descriptions of common constructor parameters. Class-specific parameters: ------------------------------------- @param synPredictedInc: In this model, a metabotropically active synapse is implemented as an active synapse whose input originates from a correctly predicted cell in Temporal Memory. synPredictedInc is then the amount of permanence added to each metabotropically active synapse in each round @param useBurstingRule: A Boolean indicating whether bursting columns in the TM will have a strong effect on overlap calculation @param usePoolingRule: A Boolean indicating whether inputs representing correctly predicted cells in the Temporal Memory will contribute to column overlap calculation @param poolingLife: The maximum number of timesteps that a pooling column will pool for in the absence of any predicted input. @param poolingThreshUnpredicted: A threshold, ranging from 0 to 1, on the fraction of bottom-up input that is unpredicted. If this threshold is exceeded, the temporal pooler will stop pooling across all TP cells. @param initConnectedPct: A number between 0 or 1 governing the chance, for each permanence, that the initial permanence value will be a value that is considered connected. """ self.initialize(inputDimensions, columnDimensions, potentialRadius, potentialPct, globalInhibition, localAreaDensity, numActiveColumnsPerInhArea, stimulusThreshold, synPermInactiveDec, synPermActiveInc, synPredictedInc, synPermConnected, minPctOverlapDutyCycle, dutyCyclePeriod, boostStrength, useBurstingRule, usePoolingRule, poolingLife, poolingThreshUnpredicted, seed, initConnectedPct, spVerbosity, wrapAround) def initialize(self, inputDimensions=[32,32], columnDimensions=[64,64], potentialRadius=16, potentialPct=0.5, globalInhibition=False, localAreaDensity=-1.0, numActiveColumnsPerInhArea=10.0, stimulusThreshold=0, synPermInactiveDec=0.01, synPermActiveInc=0.1, synPredictedInc=0.1, synPermConnected=0.10, minPctOverlapDutyCycle=0.001, dutyCyclePeriod=1000, boostStrength=10.0, useBurstingRule=True, usePoolingRule=True, poolingLife=1000, poolingThreshUnpredicted=0.0, seed=-1, initConnectedPct=0.1, spVerbosity=0, wrapAround=True): # Verify input is valid inputDimensions = numpy.array(inputDimensions) columnDimensions = numpy.array(columnDimensions) numColumns = columnDimensions.prod() numInputs = inputDimensions.prod() assert(numColumns > 0) assert(numInputs > 0) assert (numActiveColumnsPerInhArea > 0 or (localAreaDensity > 0 and localAreaDensity <= 0.5)) # save arguments self._numInputs = int(numInputs) self._numColumns = int(numColumns) self._columnDimensions = columnDimensions self._inputDimensions = inputDimensions self._potentialRadius = int(min(potentialRadius, numInputs)) self._potentialPct = potentialPct self._globalInhibition = globalInhibition self._numActiveColumnsPerInhArea = int(numActiveColumnsPerInhArea) self._localAreaDensity = localAreaDensity self._stimulusThreshold = stimulusThreshold self._synPermInactiveDec = synPermInactiveDec self._synPermActiveInc = synPermActiveInc self._synPredictedInc = synPredictedInc self._synPermBelowStimulusInc = synPermConnected / 10.0 self._synPermConnected = synPermConnected self._minPctOverlapDutyCycles = minPctOverlapDutyCycle self._dutyCyclePeriod = dutyCyclePeriod self._boostStrength = boostStrength self._spVerbosity = spVerbosity self._wrapAround = wrapAround self.useBurstingRule = useBurstingRule self.usePoolingRule = usePoolingRule self._poolingLife = poolingLife self._initConnectedPct = initConnectedPct # Extra parameter settings self._synPermMin = 0.0 self._synPermMax = 1.0 self._synPermTrimThreshold = synPermActiveInc / 2.0 assert(self._synPermTrimThreshold < self._synPermConnected) self._updatePeriod = 20 self._poolingThreshUnpredicted = poolingThreshUnpredicted # Internal state self._version = 1.0 self._iterationNum = 0 self._iterationLearnNum = 0 # initialize the random number generators self._seed(seed) # A cell will enter pooling state if it receives enough predicted inputs # pooling cells have priority during competition self._poolingActivation = numpy.zeros((self._numColumns), dtype="int32") self._poolingColumns = [] # Initialize a tiny random tie breaker. This is used to determine winning # columns where the overlaps are identical. self._tieBreaker = 0.01 * numpy.array([self._random.getReal64() for i in xrange(self._numColumns)]) # initialize connection matrix self.initializeConnections() self._overlapDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._activeDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._minOverlapDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._minActiveDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._boostFactors = numpy.ones(numColumns, dtype=realDType) # The inhibition radius determines the size of a column's local # neighborhood. A cortical column must overcome the overlap # score of columns in its neighborhood in order to become active. This # radius is updated every _updatePeriod iterations. It grows and shrinks # with the average number of connected synapses per column. self._inhibitionRadius = 0 self._updateInhibitionRadius() if self._spVerbosity > 0: self.printParameters() def initializeConnections(self): """ Initialize connection matrix, including: _permanences : permanence of synaptic connections (sparse matrix) _potentialPools: potential pool of connections for each cell (sparse binary matrix) _connectedSynapses: connected synapses (binary sparse matrix) _connectedCounts: number of connections per cell (numpy array) """ numColumns = self._numColumns numInputs = self._numInputs # The SP should be setup so that stimulusThreshold is reasonably high, # similar to a TP's activation threshold. The pct of connected cells may # need to be low to avoid false positives given the monosynaptic rule initConnectedPct = self._initConnectedPct # Store the set of all inputs that are within each column's potential pool. # 'potentialPools' is a matrix, whose rows represent cortical columns, and # whose columns represent the input bits. if potentialPools[i][j] == 1, # then input bit 'j' is in column 'i's potential pool. A column can only be # connected to inputs in its potential pool. The indices refer to a # flattened version of both the inputs and columns. Namely, irrespective # of the topology of the inputs and columns, they are treated as being a # one dimensional array. Since a column is typically connected to only a # subset of the inputs, many of the entries in the matrix are 0. Therefore # the the potentialPool matrix is stored using the SparseBinaryMatrix # class, to reduce memory footprint and compuation time of algorithms that # require iterating over the data strcuture. self._potentialPools = SparseBinaryMatrix(numInputs) self._potentialPools.resize(numColumns, numInputs) # Initialize the permanences for each column. Similar to the # 'self._potentialPools', the permanences are stored in a matrix whose rows # represent the cortical columns, and whose columns represent the input # bits. if self._permanences[i][j] = 0.2, then the synapse connecting # cortical column 'i' to input bit 'j' has a permanence of 0.2. Here we # also use the SparseMatrix class to reduce the memory footprint and # computation time of algorithms that require iterating over the data # structure. This permanence matrix is only allowed to have non-zero # elements where the potential pool is non-zero. self._permanences = SparseMatrix(numColumns, numInputs) # 'self._connectedSynapses' is a similar matrix to 'self._permanences' # (rows represent cortical columns, columns represent input bits) whose # entries represent whether the cortial column is connected to the input # bit, i.e. its permanence value is greater than 'synPermConnected'. While # this information is readily available from the 'self._permanence' matrix, # it is stored separately for efficiency purposes. self._connectedSynapses = SparseBinaryMatrix(numInputs) self._connectedSynapses.resize(numColumns, numInputs) # Stores the number of connected synapses for each column. This is simply # a sum of each row of 'self._connectedSynapses'. again, while this # information is readily available from 'self._connectedSynapses', it is # stored separately for efficiency purposes. self._connectedCounts = numpy.zeros(numColumns, dtype=realDType) # Initialize the set of permanence values for each column. Ensure that # each column is connected to enough input bits to allow it to be # activated. for i in xrange(numColumns): potential = self._mapPotential(i, wrapAround=self._wrapAround) self._potentialPools.replaceSparseRow(i, potential.nonzero()[0]) perm = self._initPermanence(potential, initConnectedPct) self._updatePermanencesForColumn(perm, i, raisePerm=True) # TODO: The permanence initialization code below runs faster but is not # deterministic (doesn't use our RNG or the seed). The speed is # particularly important for temporal pooling because the number if inputs = # number of cells in the prevous level. We should consider cleaning up the # code and moving it to the base spatial pooler class itself. # for i in xrange(numColumns): # # NEW indices for inputs within _potentialRadius of the current column # indices = numpy.array(range(2*self._potentialRadius+1)) # indices += i # indices -= self._potentialRadius # indices %= self._numInputs # periodic boundary conditions # indices = numpy.array(list(set(indices))) # # # NEW Select a subset of the receptive field to serve as the # # potential pool # sample = numpy.empty((self._inputDimensions * self._potentialPct)\ # .astype('int32'),dtype=uintType) # self._random.getUInt32Sample(indices.astype(uintType),sample) # potential = numpy.zeros(self._numInputs) # potential[sample] = 1 # # # update potentialPool # self._potentialPools.replaceSparseRow(i, potential.nonzero()[0]) # # # NEW generate indicator for connected/unconnected # connected = numpy.random.rand(self._numInputs) < initConnectedPct # # # set permanence value for connected synapses to be slightly above _synPermConnected # permConnected = (self._synPermConnected + numpy.random.rand(self._numInputs) * # self._synPermActiveInc / 4.0) # # set permanence value for unconnected synapses below _synPermConnected # permNotConnected = self._synPermConnected * numpy.random.rand(self._numInputs) # # # update permamnce value # perm = permNotConnected # perm[connected] = permConnected[connected] # perm[potential < 1] = 0 # permanence value for cells not in the potential # # pool # # self._updatePermanencesForColumn(perm, i, raisePerm=True) def reset(self): """ Reset the state of the temporal pooler """ self._poolingActivation = numpy.zeros((self._numColumns), dtype="int32") self._poolingColumns = [] self._overlapDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._activeDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._minOverlapDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._minActiveDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._boostFactors = numpy.ones(self._numColumns, dtype=realDType) def compute(self, inputVector, learn, activeArray, burstingColumns, predictedCells): """ This is the primary public method of the class. This function takes an input vector and outputs the indices of the active columns. New parameters defined here: ---------------------------- @param inputVector: The active cells from a Temporal Memory @param learn: A Boolean specifying whether learning will be performed @param activeArray: An array representing the active columns produced by this method @param burstingColumns: A numpy array with numColumns elements having binary values with 1 representing a currently bursting column in Temporal Memory. @param predictedCells: A numpy array with numInputs elements. A 1 indicates that this cell switching from predicted state in the previous time step to active state in the current timestep """ assert (numpy.size(inputVector) == self._numInputs) assert (numpy.size(predictedCells) == self._numInputs) self._updateBookeepingVars(learn) inputVector = numpy.array(inputVector, dtype=realDType) predictedCells = numpy.array(predictedCells, dtype=realDType) inputVector.reshape(-1) if self._spVerbosity > 3: print " Input bits: ", inputVector.nonzero()[0] print " predictedCells: ", predictedCells.nonzero()[0] # Phase 1: Calculate overlap scores # The overlap score has 4 components: # (1) Overlap between correctly predicted input cells and pooling TP cells # (2) Overlap between active input cells and all TP cells # (like standard SP calculation) # (3) Overlap between correctly predicted input cells and all TP cells # (4) Overlap from bursting columns in TM and all TP cells # 1) Calculate pooling overlap if self.usePoolingRule: overlapsPooling = self._calculatePoolingActivity(predictedCells, learn) if self._spVerbosity > 4: print "usePoolingRule: Overlaps after step 1:" print " ", overlapsPooling else: overlapsPooling = 0 # 2) Calculate overlap between active input cells and connected synapses overlapsAllInput = self._calculateOverlap(inputVector) # 3) overlap with predicted inputs # NEW: Isn't this redundant with 1 and 2)? This looks at connected synapses # only. # If 1) is called with learning=False connected synapses are used and # it is somewhat redundant although there is a boosting factor in 1) which # makes 1's effect stronger. If 1) is called with learning=True it's less # redundant overlapsPredicted = self._calculateOverlap(predictedCells) if self._spVerbosity > 4: print "Overlaps with all inputs:" print " Number of On Bits: ", inputVector.sum() print " ", overlapsAllInput print "Overlaps with predicted inputs:" print " ", overlapsPredicted # 4) consider bursting columns if self.useBurstingRule: overlapsBursting = self._calculateBurstingColumns(burstingColumns) if self._spVerbosity > 4: print "Overlaps with bursting inputs:" print " ", overlapsBursting else: overlapsBursting = 0 overlaps = (overlapsPooling + overlapsPredicted + overlapsAllInput + overlapsBursting) # Apply boosting when learning is on if learn: boostedOverlaps = self._boostFactors * overlaps if self._spVerbosity > 4: print "Overlaps after boosting:" print " ", boostedOverlaps else: boostedOverlaps = overlaps # Apply inhibition to determine the winning columns activeColumns = self._inhibitColumns(boostedOverlaps) if learn: self._adaptSynapses(inputVector, activeColumns, predictedCells) self._updateDutyCycles(overlaps, activeColumns) self._bumpUpWeakColumns() self._updateBoostFactors() if self._isUpdateRound(): self._updateInhibitionRadius() self._updateMinDutyCycles() activeArray.fill(0) if activeColumns.size > 0: activeArray[activeColumns] = 1 # update pooling state of cells activeColumnIndices = numpy.where(overlapsPredicted[activeColumns] > 0)[0] activeColWithPredictedInput = activeColumns[activeColumnIndices] numUnPredictedInput = float(len(burstingColumns.nonzero()[0])) numPredictedInput = float(len(predictedCells)) fracUnPredicted = numUnPredictedInput / (numUnPredictedInput + numPredictedInput) self._updatePoolingState(activeColWithPredictedInput, fracUnPredicted) if self._spVerbosity > 2: activeColumns.sort() print "The following columns are finally active:" print " ", activeColumns print "The following columns are in pooling state:" print " ", self._poolingActivation.nonzero()[0] # print "Inputs to pooling columns" # print " ",overlapsPredicted[self._poolingColumns] return activeColumns def _updatePoolingState(self, activeColWithPredictedInput, fractionUnpredicted): """ This function updates the pooling state of TP cells. A cell will stop pooling if: (1) It hasn't received any predicted input in the last self._poolingLife steps or (2) the overall fraction of unpredicted input to the TP is above _poolingThreshUnpredicted """ if fractionUnpredicted > self._poolingThreshUnpredicted: # Reset pooling activation if the fraction of unpredicted input # is above the threshold if self._spVerbosity > 3: print " reset pooling state for all cells" self._poolingActivation = numpy.zeros(self._numColumns, dtype="int32") else: # decrement activation of all pooling cells self._poolingActivation[self._poolingColumns] -= 1 # reset activation of cells that are receiving predicted input self._poolingActivation[activeColWithPredictedInput] = self._poolingLife self._poolingColumns = self._poolingActivation.nonzero()[0] def _calculatePoolingActivity(self, predictedActiveCells, learn): """ Determines each column's overlap with predicted active cell input. If learning, overlap is calculated between predicted active input cells and potential synapses. Otherwise, connected synapses are used. The overlap of a TP cell that was previously active and has one or more active synapse due to predicted active input is set to (_numInputs + 1). This guarantees that such a cell wins the inhibition competition. TODO: check with Jeff, what happens in biology if a cell was not previously active but receives metabotropic input? Does it turn on, or does the met. input just extend the activity of already active cells? Does it matter whether the synapses are connected or not? Currently we are assuming no because most connected synapses become disconnected in the previous time step. If column i at time t is bursting and performs learning, the synapses from t+1 aren't active at time t, so their permanence will decrease. Parameters: ---------------------------- predictedActiveCells: a numpy array with numInputs elements. A 1 indicates that this cell switched from a predicted state in the previous time step to active state in the current timestep returns: an array of overlap values due to predicted active TM cells """ overlaps = numpy.zeros(self._numColumns).astype(realDType) # If no pooling columns or no predicted active inputs, return all zeros if (sum(self._poolingActivation) == 0 or len(predictedActiveCells.nonzero()[0]) == 0): return overlaps if learn: # During learning, overlap is calculated based on potential synapses. self._potentialPools.rightVecSumAtNZ_fast(predictedActiveCells, overlaps) else: # At inference stage, overlap is calculated based on connected synapses. self._connectedSynapses.rightVecSumAtNZ_fast(predictedActiveCells, overlaps) poolingColumns = self._poolingColumns # Only consider columns that are in pooling state mask = numpy.zeros(self._numColumns).astype(realDType) mask[poolingColumns] = 1 overlaps = overlaps * mask # Pooling TP cells that receive predicted input # will have their overlap boosted by a large factor so that they are likely # to win the inhibition competition boostFactorPooling = self._boostStrength * self._numInputs overlaps = boostFactorPooling * overlaps if self._spVerbosity > 3: print "\n============== In _calculatePoolingActivity ======" print "Received predicted cell inputs from following indices:" print " ", predictedActiveCells.nonzero()[0] print "The following column indices are in pooling state:" print " ", poolingColumns print "Overlap score of pooling columns:" print " ", overlaps[poolingColumns] print "============== Leaving _calculatePoolingActivity ======\n" return overlaps def _calculateBurstingColumns(self, burstingColumns): """ Returns the contribution to overlap due to bursting columns. If any column is bursting, its overlap score is set to stimulusThreshold. This means it will be guaranteed to win as long as no other column is metabotropic or has input > stimulusThreshold. Parameters: ---------------------------- burstingColumns: a numpy array with numColumns elements. A 1 indicates that column is currently bursting. """ overlaps = burstingColumns * self._stimulusThreshold return overlaps def _adaptSynapses(self, inputVector, activeColumns, predictedActiveCells): """ This is the primary learning method. It updates synapses' permanence based on the bottom-up input to the TP and the TP's active cells. For each active cell, its synapses' permanences are updated as follows: 1. if pre-synaptic input is ON due to a correctly predicted cell, increase permanence by _synPredictedInc 2. else if input is ON due to an active cell, increase permanence by _synPermActiveInc 3. else input is OFF, decrease permanence by _synPermInactiveDec Parameters: ---------------------------- inputVector: a numpy array whose ON bits represent the active cells from temporal memory activeColumns: an array containing the indices of the columns that survived the inhibition step predictedActiveCells: a numpy array with numInputs elements. A 1 indicates that this cell switched from predicted state in the previous time step to active state in the current timestep """ inputIndices = numpy.where(inputVector > 0)[0] predictedIndices = numpy.where(predictedActiveCells > 0)[0] permChanges = numpy.zeros(self._numInputs) # Decrement inactive TM cell -> active TP cell connections permChanges.fill(-1 * self._synPermInactiveDec) # Increment active TM cell -> active TP cell connections permChanges[inputIndices] = self._synPermActiveInc # Increment correctly predicted TM cell -> active TP cell connections permChanges[predictedIndices] = self._synPredictedInc if self._spVerbosity > 4: print "\n============== _adaptSynapses ======" print "Active input indices:",inputIndices print "predicted input indices:",predictedIndices print "\n============== _adaptSynapses ======\n" for i in activeColumns: # Get the permanences of the synapses of TP cell i perm = self._permanences.getRow(i) # Only consider connections in column's potential pool (receptive field) maskPotential = numpy.where(self._potentialPools.getRow(i) > 0)[0] perm[maskPotential] += permChanges[maskPotential] self._updatePermanencesForColumn(perm, i, raisePerm=False) def printParameters(self): """ Useful for debugging. """ print "------------PY TemporalPooler Parameters ------------------" print "numInputs = ", self.getNumInputs() print "numColumns = ", self.getNumColumns() print "columnDimensions = ", self._columnDimensions print "numActiveColumnsPerInhArea = ", self.getNumActiveColumnsPerInhArea() print "potentialPct = ", self.getPotentialPct() print "globalInhibition = ", self.getGlobalInhibition() print "localAreaDensity = ", self.getLocalAreaDensity() print "stimulusThreshold = ", self.getStimulusThreshold() print "synPermActiveInc = ", self.getSynPermActiveInc() print "synPermInactiveDec = ", self.getSynPermInactiveDec() print "synPermConnected = ", self.getSynPermConnected() print "minPctOverlapDutyCycle = ", self.getMinPctOverlapDutyCycles() print "dutyCyclePeriod = ", self.getDutyCyclePeriod() print "boostStrength = ", self.getBoostStrength() print "spVerbosity = ", self.getSpVerbosity() print "version = ", self._version def extractInputForTP(self, tm): """ Extract inputs for TP from the state of temporal memory three information are extracted 1. correctly predicted cells 2. all active cells 3. bursting cells (unpredicted input) """ # bursting cells in layer 4 burstingColumns = tm.activeState["t"].sum(axis=1) burstingColumns[ burstingColumns < tm.cellsPerColumn ] = 0 burstingColumns[ burstingColumns == tm.cellsPerColumn ] = 1 # print "Bursting column indices=",burstingColumns.nonzero()[0] # correctly predicted cells in layer 4 correctlyPredictedCells = numpy.zeros(self._inputDimensions).astype(realDType) idx = (tm.predictedState["t-1"] + tm.activeState["t"]) == 2 idx = idx.reshape(self._inputDimensions) correctlyPredictedCells[idx] = 1.0 # print "Predicted->active cell indices=",correctlyPredictedCells.nonzero()[0] # all currently active cells in layer 4 spInputVector = tm.learnState["t"].reshape(self._inputDimensions) # spInputVector = tm.activeState["t"].reshape(self._inputDimensions) return (correctlyPredictedCells, spInputVector, burstingColumns)
class TemporalPooler(SpatialPooler): """ This class constitutes the default implementation of the temporal pooler (TP). The main goal of the TP is to form stable and unique representations of an input data stream of cell activity from a Temporal Memory. More specifically, the TP forms its stable representation based on input cells that were correctly temporally predicted by Temporal Memory. If this active cell input sequence was not predicted, a competition, similar to that of the Spatial Pooler's, is used to select active TP cells. Note that while the temporal pooler functions like a Spatial Pooler in that it selects a sparse active set of columns, these columns are treated as if they only have one cell, and will be referred to as TP cells. If a TP cell is sufficiently activated by inputs that were predicted by the Temporal Memory, it enters into a "pooling state." A pooling cell can, for a limited period, maintain its active status even if it does not receive any bottom-up input activity. This is implemented using a timer for each cell. When the timer "runs out," the cell loses its pooling status. Whenever significant active predicted input overlaps with a TP cell's proximal synapses, its timer is reset. """ def __init__(self, inputDimensions=[32, 32], columnDimensions=[64, 64], potentialRadius=16, potentialPct=0.9, globalInhibition=True, localAreaDensity=-1.0, numActiveColumnsPerInhArea=10.0, stimulusThreshold=2, synPermInactiveDec=0.01, synPermActiveInc=0.03, synPredictedInc=0.5, synPermConnected=0.3, minPctOverlapDutyCycle=0.001, minPctActiveDutyCycle=0.001, dutyCyclePeriod=1000, maxBoost=1.0, useBurstingRule=False, usePoolingRule=True, poolingLife=1000, poolingThreshUnpredicted=0.0, initConnectedPct=0.2, seed=-1, spVerbosity=0, wrapAround=True): """ Please see spatial_pooler.py in NuPIC for descriptions of common constructor parameters. Class-specific parameters: ------------------------------------- @param synPredictedInc: In this model, a metabotropically active synapse is implemented as an active synapse whose input originates from a correctly predicted cell in Temporal Memory. synPredictedInc is then the amount of permanence added to each metabotropically active synapse in each round @param useBurstingRule: A Boolean indicating whether bursting columns in the TM will have a strong effect on overlap calculation @param usePoolingRule: A Boolean indicating whether inputs representing correctly predicted cells in the Temporal Memory will contribute to column overlap calculation @param poolingLife: The maximum number of timesteps that a pooling column will pool for in the absence of any predicted input. @param poolingThreshUnpredicted: A threshold, ranging from 0 to 1, on the fraction of bottom-up input that is unpredicted. If this threshold is exceeded, the temporal pooler will stop pooling across all TP cells. @param initConnectedPct: A number between 0 or 1 governing the chance, for each permanence, that the initial permanence value will be a value that is considered connected. """ self.initialize(inputDimensions, columnDimensions, potentialRadius, potentialPct, globalInhibition, localAreaDensity, numActiveColumnsPerInhArea, stimulusThreshold, synPermInactiveDec, synPermActiveInc, synPredictedInc, synPermConnected, minPctOverlapDutyCycle, minPctActiveDutyCycle, dutyCyclePeriod, maxBoost, useBurstingRule, usePoolingRule, poolingLife, poolingThreshUnpredicted, seed, initConnectedPct, spVerbosity, wrapAround) def initialize(self, inputDimensions=[32, 32], columnDimensions=[64, 64], potentialRadius=16, potentialPct=0.5, globalInhibition=False, localAreaDensity=-1.0, numActiveColumnsPerInhArea=10.0, stimulusThreshold=0, synPermInactiveDec=0.01, synPermActiveInc=0.1, synPredictedInc=0.1, synPermConnected=0.10, minPctOverlapDutyCycle=0.001, minPctActiveDutyCycle=0.001, dutyCyclePeriod=1000, maxBoost=10.0, useBurstingRule=True, usePoolingRule=True, poolingLife=1000, poolingThreshUnpredicted=0.0, seed=-1, initConnectedPct=0.1, spVerbosity=0, wrapAround=True): # Verify input is valid inputDimensions = numpy.array(inputDimensions) columnDimensions = numpy.array(columnDimensions) numColumns = columnDimensions.prod() numInputs = inputDimensions.prod() assert (numColumns > 0) assert (numInputs > 0) assert (numActiveColumnsPerInhArea > 0 or (localAreaDensity > 0 and localAreaDensity <= 0.5)) # save arguments self._numInputs = int(numInputs) self._numColumns = int(numColumns) self._columnDimensions = columnDimensions self._inputDimensions = inputDimensions self._potentialRadius = int(min(potentialRadius, numInputs)) self._potentialPct = potentialPct self._globalInhibition = globalInhibition self._numActiveColumnsPerInhArea = int(numActiveColumnsPerInhArea) self._localAreaDensity = localAreaDensity self._stimulusThreshold = stimulusThreshold self._synPermInactiveDec = synPermInactiveDec self._synPermActiveInc = synPermActiveInc self._synPredictedInc = synPredictedInc self._synPermBelowStimulusInc = synPermConnected / 10.0 self._synPermConnected = synPermConnected self._minPctOverlapDutyCycles = minPctOverlapDutyCycle self._minPctActiveDutyCycles = minPctActiveDutyCycle self._dutyCyclePeriod = dutyCyclePeriod self._maxBoost = maxBoost self._spVerbosity = spVerbosity self._wrapAround = wrapAround self.useBurstingRule = useBurstingRule self.usePoolingRule = usePoolingRule self._poolingLife = poolingLife self._initConnectedPct = initConnectedPct # Extra parameter settings self._synPermMin = 0.0 self._synPermMax = 1.0 self._synPermTrimThreshold = synPermActiveInc / 2.0 assert (self._synPermTrimThreshold < self._synPermConnected) self._updatePeriod = 20 self._poolingThreshUnpredicted = poolingThreshUnpredicted # Internal state self._version = 1.0 self._iterationNum = 0 self._iterationLearnNum = 0 # initialize the random number generators self._seed(seed) # A cell will enter pooling state if it receives enough predicted inputs # pooling cells have priority during competition self._poolingActivation = numpy.zeros((self._numColumns), dtype="int32") self._poolingColumns = [] # Initialize a tiny random tie breaker. This is used to determine winning # columns where the overlaps are identical. self._tieBreaker = 0.01 * numpy.array( [self._random.getReal64() for i in xrange(self._numColumns)]) # initialize connection matrix self.initializeConnections() self._overlapDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._activeDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._minOverlapDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._minActiveDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._boostFactors = numpy.ones(numColumns, dtype=realDType) # The inhibition radius determines the size of a column's local # neighborhood. A cortical column must overcome the overlap # score of columns in its neighborhood in order to become active. This # radius is updated every _updatePeriod iterations. It grows and shrinks # with the average number of connected synapses per column. self._inhibitionRadius = 0 self._updateInhibitionRadius() if self._spVerbosity > 0: self.printParameters() def initializeConnections(self): """ Initialize connection matrix, including: _permanences : permanence of synaptic connections (sparse matrix) _potentialPools: potential pool of connections for each cell (sparse binary matrix) _connectedSynapses: connected synapses (binary sparse matrix) _connectedCounts: number of connections per cell (numpy array) """ numColumns = self._numColumns numInputs = self._numInputs # The SP should be setup so that stimulusThreshold is reasonably high, # similar to a TP's activation threshold. The pct of connected cells may # need to be low to avoid false positives given the monosynaptic rule initConnectedPct = self._initConnectedPct # Store the set of all inputs that are within each column's potential pool. # 'potentialPools' is a matrix, whose rows represent cortical columns, and # whose columns represent the input bits. if potentialPools[i][j] == 1, # then input bit 'j' is in column 'i's potential pool. A column can only be # connected to inputs in its potential pool. The indices refer to a # flattened version of both the inputs and columns. Namely, irrespective # of the topology of the inputs and columns, they are treated as being a # one dimensional array. Since a column is typically connected to only a # subset of the inputs, many of the entries in the matrix are 0. Therefore # the the potentialPool matrix is stored using the SparseBinaryMatrix # class, to reduce memory footprint and compuation time of algorithms that # require iterating over the data strcuture. self._potentialPools = SparseBinaryMatrix(numInputs) self._potentialPools.resize(numColumns, numInputs) # Initialize the permanences for each column. Similar to the # 'self._potentialPools', the permanences are stored in a matrix whose rows # represent the cortical columns, and whose columns represent the input # bits. if self._permanences[i][j] = 0.2, then the synapse connecting # cortical column 'i' to input bit 'j' has a permanence of 0.2. Here we # also use the SparseMatrix class to reduce the memory footprint and # computation time of algorithms that require iterating over the data # structure. This permanence matrix is only allowed to have non-zero # elements where the potential pool is non-zero. self._permanences = SparseMatrix(numColumns, numInputs) # 'self._connectedSynapses' is a similar matrix to 'self._permanences' # (rows represent cortical columns, columns represent input bits) whose # entries represent whether the cortial column is connected to the input # bit, i.e. its permanence value is greater than 'synPermConnected'. While # this information is readily available from the 'self._permanence' matrix, # it is stored separately for efficiency purposes. self._connectedSynapses = SparseBinaryMatrix(numInputs) self._connectedSynapses.resize(numColumns, numInputs) # Stores the number of connected synapses for each column. This is simply # a sum of each row of 'self._connectedSynapses'. again, while this # information is readily available from 'self._connectedSynapses', it is # stored separately for efficiency purposes. self._connectedCounts = numpy.zeros(numColumns, dtype=realDType) # Initialize the set of permanence values for each column. Ensure that # each column is connected to enough input bits to allow it to be # activated. for i in xrange(numColumns): potential = self._mapPotential(i, wrapAround=self._wrapAround) self._potentialPools.replaceSparseRow(i, potential.nonzero()[0]) perm = self._initPermanence(potential, initConnectedPct) self._updatePermanencesForColumn(perm, i, raisePerm=True) # TODO: The permanence initialization code below runs faster but is not # deterministic (doesn't use our RNG or the seed). The speed is # particularly important for temporal pooling because the number if inputs = # number of cells in the prevous level. We should consider cleaning up the # code and moving it to the base spatial pooler class itself. # for i in xrange(numColumns): # # NEW indices for inputs within _potentialRadius of the current column # indices = numpy.array(range(2*self._potentialRadius+1)) # indices += i # indices -= self._potentialRadius # indices %= self._numInputs # periodic boundary conditions # indices = numpy.array(list(set(indices))) # # # NEW Select a subset of the receptive field to serve as the # # potential pool # sample = numpy.empty((self._inputDimensions * self._potentialPct)\ # .astype('int32'),dtype=uintType) # self._random.getUInt32Sample(indices.astype(uintType),sample) # potential = numpy.zeros(self._numInputs) # potential[sample] = 1 # # # update potentialPool # self._potentialPools.replaceSparseRow(i, potential.nonzero()[0]) # # # NEW generate indicator for connected/unconnected # connected = numpy.random.rand(self._numInputs) < initConnectedPct # # # set permanence value for connected synapses to be slightly above _synPermConnected # permConnected = (self._synPermConnected + numpy.random.rand(self._numInputs) * # self._synPermActiveInc / 4.0) # # set permanence value for unconnected synapses below _synPermConnected # permNotConnected = self._synPermConnected * numpy.random.rand(self._numInputs) # # # update permamnce value # perm = permNotConnected # perm[connected] = permConnected[connected] # perm[potential < 1] = 0 # permanence value for cells not in the potential # # pool # # self._updatePermanencesForColumn(perm, i, raisePerm=True) def reset(self): """ Reset the state of the temporal pooler """ self._poolingActivation = numpy.zeros((self._numColumns), dtype="int32") self._poolingColumns = [] self._overlapDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._activeDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._minOverlapDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._minActiveDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._boostFactors = numpy.ones(self._numColumns, dtype=realDType) def compute(self, inputVector, learn, activeArray, burstingColumns, predictedCells): """ This is the primary public method of the class. This function takes an input vector and outputs the indices of the active columns. New parameters defined here: ---------------------------- @param inputVector: The active cells from a Temporal Memory @param learn: A Boolean specifying whether learning will be performed @param activeArray: An array representing the active columns produced by this method @param burstingColumns: A numpy array with numColumns elements having binary values with 1 representing a currently bursting column in Temporal Memory. @param predictedCells: A numpy array with numInputs elements. A 1 indicates that this cell switching from predicted state in the previous time step to active state in the current timestep """ assert (numpy.size(inputVector) == self._numInputs) assert (numpy.size(predictedCells) == self._numInputs) self._updateBookeepingVars(learn) inputVector = numpy.array(inputVector, dtype=realDType) predictedCells = numpy.array(predictedCells, dtype=realDType) inputVector.reshape(-1) if self._spVerbosity > 3: print " Input bits: ", inputVector.nonzero()[0] print " predictedCells: ", predictedCells.nonzero()[0] # Phase 1: Calculate overlap scores # The overlap score has 4 components: # (1) Overlap between correctly predicted input cells and pooling TP cells # (2) Overlap between active input cells and all TP cells # (like standard SP calculation) # (3) Overlap between correctly predicted input cells and all TP cells # (4) Overlap from bursting columns in TM and all TP cells # 1) Calculate pooling overlap if self.usePoolingRule: overlapsPooling = self._calculatePoolingActivity( predictedCells, learn) if self._spVerbosity > 4: print "usePoolingRule: Overlaps after step 1:" print " ", overlapsPooling else: overlapsPooling = 0 # 2) Calculate overlap between active input cells and connected synapses overlapsAllInput = self._calculateOverlap(inputVector) # 3) overlap with predicted inputs # NEW: Isn't this redundant with 1 and 2)? This looks at connected synapses # only. # If 1) is called with learning=False connected synapses are used and # it is somewhat redundant although there is a boosting factor in 1) which # makes 1's effect stronger. If 1) is called with learning=True it's less # redundant overlapsPredicted = self._calculateOverlap(predictedCells) if self._spVerbosity > 4: print "Overlaps with all inputs:" print " Number of On Bits: ", inputVector.sum() print " ", overlapsAllInput print "Overlaps with predicted inputs:" print " ", overlapsPredicted # 4) consider bursting columns if self.useBurstingRule: overlapsBursting = self._calculateBurstingColumns(burstingColumns) if self._spVerbosity > 4: print "Overlaps with bursting inputs:" print " ", overlapsBursting else: overlapsBursting = 0 overlaps = (overlapsPooling + overlapsPredicted + overlapsAllInput + overlapsBursting) # Apply boosting when learning is on if learn: boostedOverlaps = self._boostFactors * overlaps if self._spVerbosity > 4: print "Overlaps after boosting:" print " ", boostedOverlaps else: boostedOverlaps = overlaps # Apply inhibition to determine the winning columns activeColumns = self._inhibitColumns(boostedOverlaps) if learn: self._adaptSynapses(inputVector, activeColumns, predictedCells) self._updateDutyCycles(overlaps, activeColumns) self._bumpUpWeakColumns() self._updateBoostFactors() if self._isUpdateRound(): self._updateInhibitionRadius() self._updateMinDutyCycles() activeArray.fill(0) if activeColumns.size > 0: activeArray[activeColumns] = 1 # update pooling state of cells activeColWithPredictedInput = activeColumns[numpy.where(\ overlapsPredicted[activeColumns]>0)[0]] numUnPredictedInput = float(len(burstingColumns.nonzero()[0])) numPredictedInput = float(len(predictedCells)) fracUnPredicted = numUnPredictedInput / (numUnPredictedInput + numPredictedInput) self._updatePoolingState(activeColWithPredictedInput, fracUnPredicted) if self._spVerbosity > 2: activeColumns.sort() print "The following columns are finally active:" print " ", activeColumns print "The following columns are in pooling state:" print " ", self._poolingActivation.nonzero()[0] # print "Inputs to pooling columns" # print " ",overlapsPredicted[self._poolingColumns] return activeColumns def _updatePoolingState(self, activeColWithPredictedInput, fractionUnpredicted): """ This function updates the pooling state of TP cells. A cell will stop pooling if: (1) It hasn't received any predicted input in the last self._poolingLife steps or (2) the overall fraction of unpredicted input to the TP is above _poolingThreshUnpredicted """ if fractionUnpredicted > self._poolingThreshUnpredicted: # Reset pooling activation if the fraction of unpredicted input # is above the threshold if self._spVerbosity > 3: print " reset pooling state for all cells" self._poolingActivation = numpy.zeros(self._numColumns, dtype="int32") else: # decrement activation of all pooling cells self._poolingActivation[self._poolingColumns] -= 1 # reset activation of cells that are receiving predicted input self._poolingActivation[ activeColWithPredictedInput] = self._poolingLife self._poolingColumns = self._poolingActivation.nonzero()[0] def _calculatePoolingActivity(self, predictedActiveCells, learn): """ Determines each column's overlap with predicted active cell input. If learning, overlap is calculated between predicted active input cells and potential synapses. Otherwise, connected synapses are used. The overlap of a TP cell that was previously active and has one or more active synapse due to predicted active input is set to (_numInputs + 1). This guarantees that such a cell wins the inhibition competition. TODO: check with Jeff, what happens in biology if a cell was not previously active but receives metabotropic input? Does it turn on, or does the met. input just extend the activity of already active cells? Does it matter whether the synapses are connected or not? Currently we are assuming no because most connected synapses become disconnected in the previous time step. If column i at time t is bursting and performs learning, the synapses from t+1 aren't active at time t, so their permanence will decrease. Parameters: ---------------------------- predictedActiveCells: a numpy array with numInputs elements. A 1 indicates that this cell switched from a predicted state in the previous time step to active state in the current timestep returns: an array of overlap values due to predicted active TM cells """ overlaps = numpy.zeros(self._numColumns).astype(realDType) # If no pooling columns or no predicted active inputs, return all zeros if (sum(self._poolingActivation) == 0 or len(predictedActiveCells.nonzero()[0]) == 0): return overlaps if learn: # During learning, overlap is calculated based on potential synapses. self._potentialPools.rightVecSumAtNZ_fast(predictedActiveCells, overlaps) else: # At inference stage, overlap is calculated based on connected synapses. self._connectedSynapses.rightVecSumAtNZ_fast( predictedActiveCells, overlaps) poolingColumns = self._poolingColumns # Only consider columns that are in pooling state mask = numpy.zeros(self._numColumns).astype(realDType) mask[poolingColumns] = 1 overlaps = overlaps * mask # Pooling TP cells that receive predicted input # will have their overlap boosted by a large factor so that they are likely # to win the inhibition competition boostFactorPooling = self._maxBoost * self._numInputs overlaps = boostFactorPooling * overlaps if self._spVerbosity > 3: print "\n============== In _calculatePoolingActivity ======" print "Received predicted cell inputs from following indices:" print " ", predictedActiveCells.nonzero()[0] print "The following column indices are in pooling state:" print " ", poolingColumns print "Overlap score of pooling columns:" print " ", overlaps[poolingColumns] print "============== Leaving _calculatePoolingActivity ======\n" return overlaps def _calculateBurstingColumns(self, burstingColumns): """ Returns the contribution to overlap due to bursting columns. If any column is bursting, its overlap score is set to stimulusThreshold. This means it will be guaranteed to win as long as no other column is metabotropic or has input > stimulusThreshold. Parameters: ---------------------------- burstingColumns: a numpy array with numColumns elements. A 1 indicates that column is currently bursting. """ overlaps = burstingColumns * self._stimulusThreshold return overlaps def _adaptSynapses(self, inputVector, activeColumns, predictedActiveCells): """ This is the primary learning method. It updates synapses' permanence based on the bottom-up input to the TP and the TP's active cells. For each active cell, its synapses' permanences are updated as follows: 1. if pre-synaptic input is ON due to a correctly predicted cell, increase permanence by _synPredictedInc 2. else if input is ON due to an active cell, increase permanence by _synPermActiveInc 3. else input is OFF, decrease permanence by _synPermInactiveDec Parameters: ---------------------------- inputVector: a numpy array whose ON bits represent the active cells from temporal memory activeColumns: an array containing the indices of the columns that survived the inhibition step predictedActiveCells: a numpy array with numInputs elements. A 1 indicates that this cell switched from predicted state in the previous time step to active state in the current timestep """ inputIndices = numpy.where(inputVector > 0)[0] predictedIndices = numpy.where(predictedActiveCells > 0)[0] permChanges = numpy.zeros(self._numInputs) # Decrement inactive TM cell -> active TP cell connections permChanges.fill(-1 * self._synPermInactiveDec) # Increment active TM cell -> active TP cell connections permChanges[inputIndices] = self._synPermActiveInc # Increment correctly predicted TM cell -> active TP cell connections permChanges[predictedIndices] = self._synPredictedInc if self._spVerbosity > 4: print "\n============== _adaptSynapses ======" print "Active input indices:", inputIndices print "predicted input indices:", predictedIndices print "\n============== _adaptSynapses ======\n" for i in activeColumns: # Get the permanences of the synapses of TP cell i perm = self._permanences.getRow(i) # Only consider connections in column's potential pool (receptive field) maskPotential = numpy.where(self._potentialPools.getRow(i) > 0)[0] perm[maskPotential] += permChanges[maskPotential] self._updatePermanencesForColumn(perm, i, raisePerm=False) def printParameters(self): """ Useful for debugging. """ print "------------PY TemporalPooler Parameters ------------------" print "numInputs = ", self.getNumInputs() print "numColumns = ", self.getNumColumns() print "columnDimensions = ", self._columnDimensions print "numActiveColumnsPerInhArea = ", self.getNumActiveColumnsPerInhArea( ) print "potentialPct = ", self.getPotentialPct() print "globalInhibition = ", self.getGlobalInhibition() print "localAreaDensity = ", self.getLocalAreaDensity() print "stimulusThreshold = ", self.getStimulusThreshold() print "synPermActiveInc = ", self.getSynPermActiveInc() print "synPermInactiveDec = ", self.getSynPermInactiveDec() print "synPermConnected = ", self.getSynPermConnected() print "minPctOverlapDutyCycle = ", self.getMinPctOverlapDutyCycles( ) print "minPctActiveDutyCycle = ", self.getMinPctActiveDutyCycles() print "dutyCyclePeriod = ", self.getDutyCyclePeriod() print "maxBoost = ", self.getMaxBoost() print "spVerbosity = ", self.getSpVerbosity() print "version = ", self._version def extractInputForTP(self, tm): """ Extract inputs for TP from the state of temporal memory three information are extracted 1. correctly predicted cells 2. all active cells 3. bursting cells (unpredicted input) """ # bursting cells in layer 4 burstingColumns = tm.activeState["t"].sum(axis=1) burstingColumns[burstingColumns < tm.cellsPerColumn] = 0 burstingColumns[burstingColumns == tm.cellsPerColumn] = 1 # print "Bursting column indices=",burstingColumns.nonzero()[0] # correctly predicted cells in layer 4 correctlyPredictedCells = numpy.zeros( self._inputDimensions).astype(realDType) idx = (tm.predictedState["t-1"] + tm.activeState["t"]) == 2 idx = idx.reshape(self._inputDimensions) correctlyPredictedCells[idx] = 1.0 # print "Predicted->active cell indices=",correctlyPredictedCells.nonzero()[0] # all currently active cells in layer 4 spInputVector = tm.learnState["t"].reshape(self._inputDimensions) # spInputVector = tm.activeState["t"].reshape(self._inputDimensions) return (correctlyPredictedCells, spInputVector, burstingColumns)
class SPTP(SpatialPooler): """ This class implements the new temporal pooler. It tries to form stable and unique representations for sequences that are correctly predicted. If the input is not predicted, it used similar competition rule as the spatial pooler to select active cells """ def __init__(self, inputDimensions=[32,32], columnDimensions=[64,64], cellsPerColumn = 8, potentialRadius=16, potentialPct=0.5, globalInhibition=False, localAreaDensity=-1.0, numActiveColumnsPerInhArea=10.0, stimulusThreshold=0, synPermInactiveDec=0.01, synPermActiveInc=0.03, synPermActiveInactiveDec = 0, synPredictedInc=0.5, synPermConnected=0.10, minPctOverlapDutyCycle=0.001, minPctActiveDutyCycle=0.001, dutyCyclePeriod=1000, maxBoost=10.0, useBurstingRule = True, usePoolingRule = True, poolingLife = 1000, poolingThreshUnpredicted = 0.0, initConnectedPct = 0.1, seed=-1, spVerbosity=0 ): """ Parameters: ---------------------------- inputDimensions: A list representing the dimensions of the input vector. Format is [height, width, depth, ...], where each value represents the size of the dimension. For a topology of one dimesion with 100 inputs use 100, or [100]. For a two dimensional topology of 10x5 use [10,5]. columnDimensions: A list representing the dimensions of the columns in the region. Format is [height, width, depth, ...], where each value represents the size of the dimension. For a topology of one dimesion with 2000 columns use 2000, or [2000]. For a three dimensional topology of 32x64x16 use [32, 64, 16]. potentialRadius: This parameter deteremines the extent of the input that each column can potentially be connected to. This can be thought of as the input bits that are visible to each column, or a 'receptiveField' of the field of vision. A large enough value will result in the 'global coverage', meaning that each column can potentially be connected to every input bit. This parameter defines a square (or hyper square) area: a column will have a max square potential pool with sides of length 2 * potentialRadius + 1. potentialPct: The percent of the inputs, within a column's potential radius, that a column can be connected to. If set to 1, the column will be connected to every input within its potential radius. This parameter is used to give each column a unique potential pool when a large potentialRadius causes overlap between the columns. At initialization time we choose ((2*potentialRadius + 1)^(# inputDimensions) * potentialPct) input bits to comprise the column's potential pool. globalInhibition: If true, then during inhibition phase the winning columns are selected as the most active columns from the region as a whole. Otherwise, the winning columns are selected with resepct to their local neighborhoods. using global inhibition boosts performance x60. localAreaDensity: The desired density of active columns within a local inhibition area (the size of which is set by the internally calculated inhibitionRadius, which is in turn determined from the average size of the connected potential pools of all columns). The inhibition logic will insure that at most N columns remain ON within a local inhibition area, where N = localAreaDensity * (total number of columns in inhibition area). numActivePerInhArea: An alternate way to control the density of the active columns. If numActivePerInhArea is specified then localAreaDensity must less than 0, and vice versa. When using numActivePerInhArea, the inhibition logic will insure that at most 'numActivePerInhArea' columns remain ON within a local inhibition area (the size of which is set by the internally calculated inhibitionRadius, which is in turn determined from the average size of the connected receptive fields of all columns). When using this method, as columns learn and grow their effective receptive fields, the inhibitionRadius will grow, and hence the net density of the active columns will *decrease*. This is in contrast to the localAreaDensity method, which keeps the density of active columns the same regardless of the size of their receptive fields. stimulusThreshold: This is a number specifying the minimum number of synapses that must be on in order for a columns to turn ON. The purpose of this is to prevent noise input from activating columns. Specified as a percent of a fully grown synapse. synPermInactiveDec: The amount by which an inactive synapse is decremented in each round. Specified as a percent of a fully grown synapse. synPermActiveInc: The amount by which an active synapse is incremented in each round. Specified as a percent of a fully grown synapse. synPermActiveInactiveDec: For inactive columns, synapses connected to input bits that are on are decreased by synPermActiveInactiveDec. synPredictedInc: The amount by which a metabotropically active synapse is incremented in each round. These are active synapses originating from a previously predicted cell. Specified as a percent of a fully grown synapse. synPermConnected: The default connected threshold. Any synapse whose permanence value is above the connected threshold is a "connected synapse", meaning it can contribute to the cell's firing. minPctOverlapDutyCycle: A number between 0 and 1.0, used to set a floor on how often a column should have at least stimulusThreshold active inputs. Periodically, each column looks at the overlap duty cycle of all other column within its inhibition radius and sets its own internal minimal acceptable duty cycle to: minPctDutyCycleBeforeInh * max(other columns' duty cycles). On each iteration, any column whose overlap duty cycle falls below this computed value will get all of its permanence values boosted up by synPermActiveInc. Raising all permanences in response to a sub-par duty cycle before inhibition allows a cell to search for new inputs when either its previously learned inputs are no longer ever active, or when the vast majority of them have been "hijacked" by other columns. minPctActiveDutyCycle: A number between 0 and 1.0, used to set a floor on how often a column should be activate. Periodically, each column looks at the activity duty cycle of all other columns within its inhibition radius and sets its own internal minimal acceptable duty cycle to: minPctDutyCycleAfterInh * max(other columns' duty cycles). On each iteration, any column whose duty cycle after inhibition falls below this computed value will get its internal boost factor increased. dutyCyclePeriod: The period used to calculate duty cycles. Higher values make it take longer to respond to changes in boost or synPerConnectedCell. Shorter values make it more unstable and likely to oscillate. maxBoost: The maximum overlap boost factor. Each column's overlap gets multiplied by a boost factor before it gets considered for inhibition. The actual boost factor for a column is number between 1.0 and maxBoost. A boost factor of 1.0 is used if the duty cycle is >= minOverlapDutyCycle, maxBoost is used if the duty cycle is 0, and any duty cycle in between is linearly extrapolated from these 2 endpoints. seed: Seed for our own pseudo-random number generator. spVerbosity: spVerbosity level: 0, 1, 2, or 3 useBurstingRule: A bool value indicating whether to use bursting rule usePoolingRule: A bool value indicating whether to use pooling rule poolingLife: A pooling cell will stop pooling if it hasn't received any predicted input in poolingLife steps poolingThreshUnpredicted: A number between 0 and 1. The temporal pooler will stop pooling if the fraction of unpredicted input exceeds this threshold. initConnectedPct: A number between 0 and 1, indicating fraction of the inputs that are initially connected. """ self.initialize(inputDimensions, columnDimensions, cellsPerColumn, potentialRadius, potentialPct, globalInhibition, localAreaDensity, numActiveColumnsPerInhArea, stimulusThreshold, synPermInactiveDec, synPermActiveInc, synPermActiveInactiveDec, synPredictedInc, synPermConnected, minPctOverlapDutyCycle, minPctActiveDutyCycle, dutyCyclePeriod, maxBoost, useBurstingRule, usePoolingRule, poolingLife, poolingThreshUnpredicted, seed, initConnectedPct, spVerbosity) def initialize(self, inputDimensions=[32,32], columnDimensions=[64,64], cellsPerColumn = 8, potentialRadius=16, potentialPct=0.5, globalInhibition=False, localAreaDensity=-1.0, numActiveColumnsPerInhArea=10.0, stimulusThreshold=0, synPermInactiveDec=0.01, synPermActiveInc=0.1, synPermActiveInactiveDec =0, synPredictedInc=0.1, synPermConnected=0.10, minPctOverlapDutyCycle=0.001, minPctActiveDutyCycle=0.001, dutyCyclePeriod=1000, maxBoost=10.0, useBurstingRule=True, usePoolingRule=True, poolingLife=1000, poolingThreshUnpredicted=0.0, seed=-1, initConnectedPct=0.1, spVerbosity=0): # Verify input is valid inputDimensions = numpy.array(inputDimensions) columnDimensions = numpy.array(columnDimensions) numColumns = columnDimensions.prod() numInputs = inputDimensions.prod() assert(numColumns > 0) assert(numInputs > 0) assert (numActiveColumnsPerInhArea > 0 or (localAreaDensity > 0 and localAreaDensity <= 0.5)) # save arguments self._numInputs = int(numInputs) self._numColumns = int(numColumns) self._columnDimensions = columnDimensions self._inputDimensions = inputDimensions self._potentialRadius = int(min(potentialRadius, numInputs)) self._potentialPct = potentialPct self._globalInhibition = globalInhibition self._numActiveColumnsPerInhArea = int(numActiveColumnsPerInhArea) self._localAreaDensity = localAreaDensity self._stimulusThreshold = stimulusThreshold self._synPermInactiveDec = synPermInactiveDec self._synPermActiveInc = synPermActiveInc self._synPredictedInc = synPredictedInc self._synPermBelowStimulusInc = synPermConnected / 10.0 self._synPermConnected = synPermConnected self._minPctOverlapDutyCycles = minPctOverlapDutyCycle self._minPctActiveDutyCycles = minPctActiveDutyCycle self._dutyCyclePeriod = dutyCyclePeriod self._maxBoost = maxBoost self._spVerbosity = spVerbosity self._synPermActiveInactiveDec = synPermActiveInactiveDec self.useBurstingRule = useBurstingRule self.usePoolingRule = usePoolingRule self._poolingLife = poolingLife self._initConnectedPct = initConnectedPct # Extra parameter settings self._synPermMin = 0.0 self._synPermMax = 1.0 self._synPermTrimThreshold = synPermActiveInc / 2.0 assert(self._synPermTrimThreshold < self._synPermConnected) self._updatePeriod = 20 self._poolingThreshUnpredicted = poolingThreshUnpredicted # Internal state self._version = 1.0 self._iterationNum = 0 self._iterationLearnNum = 0 # initialize the random number generators self._seed(seed) # A cell will enter pooling state if it receives enough predicted inputs # pooling cells have priority during competition self._poolingState = numpy.zeros((self._numColumns), dtype='int32') self._poolingColumns = [] # Initialize a tiny random tie breaker. This is used to determine winning # columns where the overlaps are identical. self._tieBreaker = 0.01*numpy.array([self._random.getReal64() for i in xrange(self._numColumns)]) # initialize connection matrix self.initializeConnections() self._overlapDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._activeDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._minOverlapDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._minActiveDutyCycles = numpy.zeros(numColumns, dtype=realDType) self._boostFactors = numpy.ones(numColumns, dtype=realDType) # The inhibition radius determines the size of a column's local # neighborhood. of a column. A cortical column must overcome the overlap # score of columns in its neighborhood in order to become active. This # radius is updated every updatePeriod iterations. It grows and shrinks # with the average number of connected synapses per column. self._inhibitionRadius = 0 self._updateInhibitionRadius() if self._spVerbosity > 0: self.printParameters() def initializeConnections(self): ''' Initialize connection matrix, including: _permanences : permanence of synaptic connections (sparse matrix) _potentialPools: potential pool of connections for each cell (sparse binary matrix) _connectedSynapses: connected synapses (binary sparse matrix) _connectedCounts: number of connections per cell (numpy array) _permanenceDecCache: a cache for permanence decremant. ''' numColumns = self._numColumns numInputs = self._numInputs # The SP should be setup so that stimulusThreshold is reasonably high, # similar to a TP's activation threshold. The pct of connected cells may # need to be low to avoid false positives given the monosynaptic rule initConnectedPct = self._initConnectedPct # Store the set of all inputs that are within each column's potential pool. # 'potentialPools' is a matrix, whose rows represent cortical columns, and # whose columns represent the input bits. if potentialPools[i][j] == 1, # then input bit 'j' is in column 'i's potential pool. A column can only be # connected to inputs in its potential pool. The indices refer to a # flattened version of both the inputs and columns. Namely, irrespective # of the topology of the inputs and columns, they are treated as being a # one dimensional array. Since a column is typically connected to only a # subset of the inputs, many of the entries in the matrix are 0. Therefore # the the potentialPool matrix is stored using the SparseBinaryMatrix # class, to reduce memory footprint and compuation time of algorithms that # require iterating over the data strcuture. self._potentialPools = SparseBinaryMatrix(numInputs) self._potentialPools.resize(numColumns, numInputs) # Initialize the permanences for each column. Similar to the # 'self._potentialPools', the permanences are stored in a matrix whose rows # represent the cortial columns, and whose columns represent the input # bits. if self._permanences[i][j] = 0.2, then the synapse connecting # cortical column 'i' to input bit 'j' has a permanence of 0.2. Here we # also use the SparseMatrix class to reduce the memory footprint and # computation time of algorithms that require iterating over the data # structure. This permanence matrix is only allowed to have non-zero # elements where the potential pool is non-zero. self._permanences = SparseMatrix(numColumns, numInputs) # A cache for permanence decrements of (Active->Inactive Type) # Permanence decrements won't be initiated until the next time # a cell fire self._permanenceDecCache = SparseMatrix(numColumns, numInputs) # 'self._connectedSynapses' is a similar matrix to 'self._permanences' # (rows represent cortial columns, columns represent input bits) whose # entries represent whether the cortial column is connected to the input # bit, i.e. its permanence value is greater than 'synPermConnected'. While # this information is readily available from the 'self._permanence' matrix, # it is stored separately for efficiency purposes. self._connectedSynapses = SparseBinaryMatrix(numInputs) self._connectedSynapses.resize(numColumns, numInputs) # Stores the number of connected synapses for each column. This is simply # a sum of each row of 'self._connectedSynapses'. again, while this # information is readily available from 'self._connectedSynapses', it is # stored separately for efficiency purposes. self._connectedCounts = numpy.zeros(numColumns, dtype=realDType) # Initialize the set of permanence values for each columns. Ensure that # each column is connected to enough input bits to allow it to be # activated for i in xrange(numColumns): # indice for inputs within _potentialRadius of the current column indices = numpy.array(range(2*self._potentialRadius+1)) indices += i indices -= self._potentialRadius indices %= self._numInputs # periodic boundary conditions indices = numpy.array(list(set(indices))) # Select a subset of the receptive field to serve as the # the potential pool sample = numpy.empty((self._inputDimensions * self._potentialPct)\ .astype('int32'),dtype=uintType) self._random.getUInt32Sample(indices.astype(uintType),sample) potential = numpy.zeros(self._numInputs) potential[sample] = 1 # update potentialPool self._potentialPools.replaceSparseRow(i, potential.nonzero()[0]) # number of potential connections numPotential = numpy.nonzero(potential)[0].size # generate indicator for connected/unconnected connected = numpy.random.rand(self._numInputs) < initConnectedPct # set permanence value for connected synapses to be slightly above _synPermConnected permConnected = (self._synPermConnected + numpy.random.rand(self._numInputs) * self._synPermActiveInc / 4.0) # set permamnce value for unconnected synapses below _synPermConnected permNotConnected = self._synPermConnected * numpy.random.rand(self._numInputs) # update permamnce value perm = permNotConnected perm[connected] = permConnected[connected] perm[potential < 1] = 0 # permanace value for cells not in the potential pool self._updatePermanencesForColumn(perm, i, raisePerm=True) def reset(self): """ Reset the status of temporal pooler """ self._poolingState = numpy.zeros((self._numColumns), dtype='int32') self._poolingColumns = [] self._overlapDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._activeDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._minOverlapDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._minActiveDutyCycles = numpy.zeros(self._numColumns, dtype=realDType) self._boostFactors = numpy.ones(self._numColumns, dtype=realDType) def compute(self, inputVector, learn, activeArray, burstingColumns, predictedCells): """ This is the primary public method of the SpatialPooler class. This function takes a input vector and outputs the indices of the active columns. If 'learn' is set to True, this method also updates the permanences of the columns. Parameters: ---------------------------- inputVector: a numpy array of 0's and 1's thata comprises the input to the spatial pooler. The array will be treated as a one dimensional array, therefore the dimensions of the array do not have to much the exact dimensions specified in the class constructor. In fact, even a list would suffice. The number of input bits in the vector must, however, match the number of bits specified by the call to the constructor. Therefore there must be a '0' or '1' in the array for every input bit. learn: a boolean value indicating whether learning should be performed. Learning entails updating the permanence values of the synapses, and hence modifying the 'state' of the model. Setting learning to 'off' freezes the SP and has many uses. For example, you might want to feed in various inputs and examine the resulting SDR's. activeArray: an array whose size is equal to the number of columns. Before the function returns this array will be populated with 1's at the indices of the active columns, and 0's everywhere else. burstingColumns: a numpy array with numColumns elements. A 1 indicates that column is currently bursting. predictedCells: a numpy array with numInputs elements. A 1 indicates that this cell switching from predicted state in the previous time step to active state in the current timestep """ assert (numpy.size(inputVector) == self._numInputs) assert (numpy.size(predictedCells) == self._numInputs) self._updateBookeepingVars(learn) inputVector = numpy.array(inputVector, dtype=realDType) predictedCells = numpy.array(predictedCells, dtype=realDType) inputVector.reshape(-1) if self._spVerbosity > 3: print " Input bits: ", inputVector.nonzero()[0] print " predictedCells: ", predictedCells.nonzero()[0] # Phase 1: Calculate overlap scores # The overlap score has 4 components # (1) Overlap with correctly predicted inputs for pooling cells # (2) Overlap with correctly predicted inputs for all cells # (3) Overlap with all inputs # (4) Overlap with cells in the bursting column # 1) Check pooling rule. if self.usePoolingRule: overlapsPooling = self._calculatePoolingActivity(predictedCells, learn) if self._spVerbosity > 4: print "Use Pooling Rule: Overlaps after step a:" print " ", overlapsPooling else: overlapsPooling = 0 # 2) Calculate overlap between input and connected synapses overlapsAllInput = self._calculateOverlap(inputVector) # 3) overlap with predicted inputs overlapsPredicted = self._calculateOverlap(predictedCells) if self._spVerbosity > 4: print "Overlaps with all inputs:" print " Number of On Bits: ", inputVector.sum() print " ", overlapsAllInput print "Overlaps with predicted inputs:" print " ", overlapsPredicted # 4) consider bursting columns if self.useBurstingRule: overlapsBursting = self._calculateBurstingColumns(burstingColumns) if self._spVerbosity > 4: print "Overlaps with bursting inputs:" print " ", overlapsBursting else: overlapsBursting = 0 overlaps = overlapsPooling + overlapsPredicted + \ overlapsAllInput + overlapsBursting # Apply boosting when learning is on if learn: boostedOverlaps = self._boostFactors * overlaps if self._spVerbosity > 4: print "Overlaps after boosting:" print " ", boostedOverlaps else: boostedOverlaps = overlaps # Apply inhibition to determine the winning columns activeColumns = self._inhibitColumns(boostedOverlaps) if learn: self._adaptSynapses(inputVector, activeColumns, predictedCells) self._updateDutyCycles(overlaps, activeColumns) self._bumpUpWeakColumns() self._updateBoostFactors() if self._isUpdateRound(): self._updateInhibitionRadius() self._updateMinDutyCycles() activeArray.fill(0) if activeColumns.size > 0: activeArray[activeColumns] = 1 # update pooling state of cells activeColWithPredictedInput = activeColumns[numpy.where(\ overlapsPredicted[activeColumns]>0)[0]] numUnPredictedInput = float(len(burstingColumns.nonzero()[0])) numPredictedInput = float(len(predictedCells)) fracUnPredicted = numUnPredictedInput/(numUnPredictedInput + numPredictedInput) self._updatePoolingState(activeColWithPredictedInput, fracUnPredicted) if self._spVerbosity > 2: activeColumns.sort() print "The following columns are finally active:" print " ",activeColumns print "The following columns are in pooling state:" print " ",self._poolingState.nonzero()[0] # print "Inputs to pooling columns" # print " ",overlapsPredicted[self._poolingColumns] def _updatePoolingState(self, activeColWithPredictedInput, fracUnPredicted): """ This function update pooling state of cells A cell will stop pooling if (1) it hasn't received any predicted input in the last self._poolingLife steps or (2) the fraction of unpredicted input is above poolingThreshUnpredicted """ if fracUnPredicted>self._poolingThreshUnpredicted: # reset pooling state if the fraction of unpredicted input # is above the threshold if self._spVerbosity > 3: print " reset pooling state for all cells" self._poolingState = numpy.zeros((self._numColumns), dtype='int32') else: # decremant life of all pooling cells self._poolingState[self._poolingColumns] -= 1 # reset life of cells that are receiving predicted input self._poolingState[activeColWithPredictedInput] = self._poolingLife self._poolingColumns = self._poolingState.nonzero()[0] def _calculatePoolingActivity(self, predictedCells, learn): """ This function determines each column's overlap with metabotropically activated inputs. Overlap is calculated based on potential synapses. The overlap of a column that was previously active and has even one active metabotropic synapses is set to _numInputs+1 so they are guaranteed to win during inhibition. TODO: check with Jeff, what happens in biology if a cell was not previously active but receives metabotropic input? Does it turn on, or does the met. input just extend the activity of already active cells? Does it matter whether the synapses are connected or not? Currently we are assuming no because most connected synapses become disconnected in the previous time step. If column i at time t is bursting and performs learning, the synapses from t+1 aren't active at time t, so their permanence will decrease. Parameters: ---------------------------- predictedCells: a numpy array with numInputs elements. A 1 indicates that this cell switching from predicted state in the previous time step to active state in the current timestep """ overlaps = numpy.zeros(self._numColumns).astype(realDType) # no predicted inputs or no cell in pooling state, return zero if sum(self._poolingState)==0 or len(predictedCells.nonzero()[0])==0: return overlaps # self._connectedSynapses.rightVecSumAtNZ_fast(predictedCells, overlaps) if learn: # During learning, overlap is calculated based on potential synapses. self._potentialPools.rightVecSumAtNZ_fast(predictedCells, overlaps) else: # At inference stage, overlap is calculated based on connected synapses. self._connectedSynapses.rightVecSumAtNZ_fast(predictedCells, overlaps) poolingColumns = self._poolingColumns # # only consider columns that are in pooling state mask = numpy.zeros(self._numColumns).astype(realDType) mask[poolingColumns] = 1 overlaps = overlaps * mask # columns that are in polling state and receives predicted input # will be boosted by a large factor boostFactorPooling = self._maxBoost*self._numInputs overlaps = boostFactorPooling * overlaps if self._spVerbosity > 3: print "\n============== Entering _calculateMetabotropicActivity ======" print "Received metabotropic inputs from following indices:" print " ",predictedCells.nonzero()[0] print "The following column indices are in pooling state:" print " ",poolingColumns print "Overlap score of pooling columns:" print " ",overlaps[poolingColumns] print "============== Leaving _calculateMetabotropicActivity ======\n" return overlaps def _calculateBurstingColumns(self, burstingColumns): """ Return the contribution to overlap due to bursting columns. If any column is bursting, its overlap score is set to stimulusThreshold. This means it will be guaranteed to win as long as no other column is metabotropic or has input > stimulusThreshold. Parameters: ---------------------------- burstingColumns: a numpy array with numColumns elements. A 1 indicates that column is currently bursting. """ overlaps = burstingColumns * self._stimulusThreshold return overlaps def _adaptSynapses(self, inputVector, activeColumns, predictedCells): """ The primary method in charge of learning. Adapts the permanence values of the synapses based on the input vector, and the chosen columns after inhibition round. The following rules applies to synapse adaptation: For active cells: 1. synapses connected to input bits that are on are increased by synPermActiveInc 2. synapses connected to input bits that are off are decreased by synPermInactiveDec 3. synapses connected to inputs bits that are on due to predicted inputs are increased by synPredictedInc. For inactive cells: 4. synapses connected to input bits that are on are decreased by synPermActiveInactiveDec Parameters: ---------------------------- inputVector: a numpy array of 0's and 1's thata comprises the input to the spatial pooler. There exists an entry in the array for every input bit. activeColumns: an array containing the indices of the columns that survived inhibition. predictedCells: a numpy array with numInputs elements. A 1 indicates that this cell switching from predicted state in the previous time step to active state in the current timestep """ inputIndices = numpy.where(inputVector > 0)[0] predictedIndices = numpy.where(predictedCells > 0)[0] permChanges = numpy.zeros(self._numInputs) # decrement Inactive -> active connections permChanges.fill(-1 * self._synPermInactiveDec) # increment active -> active connections permChanges[inputIndices] = self._synPermActiveInc # increment correctly predicted cells -> active connections permChanges[predictedIndices] = self._synPredictedInc if self._spVerbosity > 4: print "\n============== _adaptSynapses ======" print "Active input indices:",inputIndices print "predicted input indices:",predictedIndices # print "permChanges:" # print formatRow(permChanges, "%9.4f", 20) print "\n============== _adaptSynapses ======\n" for i in activeColumns: # input connections to column i perm = self._permanences.getRow(i) # decremant cached (active->inactive connections) permChangesBinary = self._permanenceDecCache.getRow(i) perm = numpy.where(permChangesBinary>0, perm-self._synPermActiveInactiveDec, perm) self._permanenceDecCache.setRowToZero(i) # only consider connections in potential pool maskPotential = numpy.where(self._potentialPools.getRow(i) > 0)[0] perm[maskPotential] += permChanges[maskPotential] self._updatePermanencesForColumn(perm, i, raisePerm=False) # decrement active -> inactive connections if self._synPermActiveInactiveDec > 0: for i in inputIndices: # go through all active inputs if self._spVerbosity > 5: print "Active Input: ", i print "Current Connection: ", self._connectedSynapses.getRow(i) # push permanance decremant to cache permChangesBinary = numpy.zeros(self._numColumns) permChangesBinary.fill(1) permChangesBinary[activeColumns] = 0 self._permanenceDecCache.setColFromDense(i, permChangesBinary) def printParameters(self): """ Useful for debugging. """ print "------------PY SPTP Parameters ------------------" print "numInputs = ", self.getNumInputs() print "numColumns = ", self.getNumColumns() print "columnDimensions = ", self._columnDimensions print "numActiveColumnsPerInhArea = ", self.getNumActiveColumnsPerInhArea() print "potentialPct = ", self.getPotentialPct() print "globalInhibition = ", self.getGlobalInhibition() print "localAreaDensity = ", self.getLocalAreaDensity() print "stimulusThreshold = ", self.getStimulusThreshold() print "synPermActiveInc = ", self.getSynPermActiveInc() print "synPermInactiveDec = ", self.getSynPermInactiveDec() print "synPermConnected = ", self.getSynPermConnected() print "minPctOverlapDutyCycle = ", self.getMinPctOverlapDutyCycles() print "minPctActiveDutyCycle = ", self.getMinPctActiveDutyCycles() print "dutyCyclePeriod = ", self.getDutyCyclePeriod() print "maxBoost = ", self.getMaxBoost() print "spVerbosity = ", self.getSpVerbosity() print "version = ", self._version def extractInputForTP(self, tm): """ Extract inputs for TP from the state of temporal memory three information are extracted 1. correctly predicted cells 2. all active cells 3. bursting cells (unpredicted input) """ # bursting cells in layer 4 burstingColumns = tm.activeState['t'].sum(axis=1) burstingColumns[ burstingColumns < tm.cellsPerColumn ] = 0 burstingColumns[ burstingColumns == tm.cellsPerColumn ] = 1 # print "Bursting column indices=",burstingColumns.nonzero()[0] # correctly predicted cells in layer 4 correctlyPredictedCells = numpy.zeros(self._inputDimensions).astype(realDType) idx = (tm.predictedState['t-1'] + tm.activeState['t']) == 2 idx = idx.reshape(self._inputDimensions) correctlyPredictedCells[idx] = 1.0 # print "Predicted->active cell indices=",correctlyPredictedCells.nonzero()[0] # all currently active cells in layer 4 spInputVector = tm.learnState['t'].reshape(self._inputDimensions) # spInputVector = tm.activeState['t'].reshape(self._inputDimensions) return (correctlyPredictedCells, spInputVector, burstingColumns)
class ColumnPooler(object): """ This class constitutes a temporary implementation for a cross-column pooler. The implementation goal of this class is to prove basic properties before creating a cleaner implementation. """ def __init__(self, inputWidth, numActiveColumnsPerInhArea=40, synPermProximalInc=0.1, synPermProximalDec=0.001, initialProximalPermanence=0.6, columnDimensions=(2048,), activationThreshold=13, minThreshold=10, initialPermanence=0.41, connectedPermanence=0.50, maxNewSynapseCount=20, permanenceIncrement=0.10, permanenceDecrement=0.10, predictedSegmentDecrement=0.0, maxSegmentsPerCell=255, maxSynapsesPerSegment=255, seed=42): """ This classes uses an ExtendedTemporalMemory internally to keep track of distal segments. Please see ExtendedTemporalMemory for descriptions of constructor parameters not defined below. Parameters: ---------------------------- @param inputWidth (int) The number of proximal inputs into this layer @param numActiveColumnsPerInhArea (int) Target number of active cells @param synPermProximalInc (float) Permanence increment for proximal synapses @param synPermProximalDec (float) Permanence decrement for proximal synapses @param initialProximalPermanence (float) Initial permanence value for proximal segments """ self.inputWidth = inputWidth self.numActiveColumnsPerInhArea = numActiveColumnsPerInhArea self.synPermProximalInc = synPermProximalInc self.synPermProximalDec = synPermProximalDec self.initialProximalPermanence = initialProximalPermanence self.connectedPermanence = connectedPermanence self.maxNewSynapseCount = maxNewSynapseCount self.minThreshold = minThreshold self.activeCells = set() self._random = Random(seed) # Create our own instance of extended temporal memory to handle distal # segments. self.tm = createModel( modelName="extendedCPP", columnDimensions=columnDimensions, cellsPerColumn=1, activationThreshold=activationThreshold, initialPermanence=initialPermanence, connectedPermanence=connectedPermanence, minThreshold=minThreshold, maxNewSynapseCount=maxNewSynapseCount, permanenceIncrement=permanenceIncrement, permanenceDecrement=permanenceDecrement, predictedSegmentDecrement=predictedSegmentDecrement, maxSegmentsPerCell=maxSegmentsPerCell, maxSynapsesPerSegment=maxSynapsesPerSegment, seed=seed, learnOnOneCell=False, ) # These sparse matrices will hold the synapses for each proximal segment. # # proximalPermanences - SparseMatrix with permanence values # proximalConnections - SparseBinaryMatrix of connected synapses self.proximalPermanences = SparseMatrix(self.numberOfColumns(), inputWidth) self.proximalConnections = SparseBinaryMatrix(inputWidth) self.proximalConnections.resize(self.numberOfColumns(), inputWidth) def compute(self, feedforwardInput=None, activeExternalCells=None, learn=True): """ Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param activeExternalCells (set) Indices of active cells that will form connections to distal segments. @param learn (bool) If True, we are learning a new object """ if activeExternalCells is None: activeExternalCells = set() if learn: self._computeLearningMode(feedforwardInput=feedforwardInput, lateralInput=activeExternalCells) else: self._computeInferenceMode(feedforwardInput=feedforwardInput, lateralInput=activeExternalCells) def _computeLearningMode(self, feedforwardInput, lateralInput): """ Learning mode: we are learning a new object. If there is no prior activity, we randomly activate 2% of cells and create connections to incoming input. If there was prior activity, we maintain it. These cells will represent the object and learn distal connections to lateral cortical columns. Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param lateralInput (set) Indices of active cells from neighboring columns. """ # If there are no previously active cells, select random subset of cells if len(self.activeCells) == 0: self.activeCells = set(self._random.shuffle( numpy.array(range(self.numberOfCells()), dtype="uint32"))[0:self.numActiveColumnsPerInhArea]) # else we maintain previous activity, nothing to do. # Those cells that remain active will learn on their proximal and distal # dendrites as long as there is some input. If there are no # cells active, no learning happens. This only happens in the very # beginning if there has been no bottom up activity at all. if len(self.activeCells) > 0: # Learn on proximal dendrite if appropriate if len(feedforwardInput) > 0: self._learnProximal(feedforwardInput, self.activeCells, self.maxNewSynapseCount, self.proximalPermanences, self.proximalConnections, self.initialProximalPermanence, self.synPermProximalInc, self.synPermProximalDec, self.connectedPermanence) # Learn on distal dendrites if appropriate self.tm.compute(activeColumns=self.activeCells, activeExternalCells=lateralInput, formInternalConnections=False, learn=True) def _computeInferenceMode(self, feedforwardInput, lateralInput): """ Inference mode: if there is some feedforward activity, perform spatial pooling on it to recognize previously known objects. If there is no feedforward activity, maintain previous activity. Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param lateralInput (list of lists) A list of list of active cells from neighboring columns. len(lateralInput) == number of connected neighboring cortical columns. """ # Figure out which cells are active due to feedforward proximal inputs # In order to form unions, we keep all cells that are over threshold inputVector = numpy.zeros(self.numberOfInputs(), dtype=realDType) inputVector[list(feedforwardInput)] = 1 overlaps = numpy.zeros(self.numberOfColumns(), dtype=realDType) self.proximalConnections.rightVecSumAtNZ_fast(inputVector.astype(realDType), overlaps) overlaps[overlaps < self.minThreshold] = 0 bottomUpActivity = set(overlaps.nonzero()[0]) # If there is insufficient current bottom up activity, we incorporate all # previous activity. We set their overlaps so they are sure to win. if len(bottomUpActivity) < self.numActiveColumnsPerInhArea: bottomUpActivity = bottomUpActivity.union(self.activeCells) maxOverlap = overlaps.max() overlaps[self.getActiveCells()] = maxOverlap+1 # Narrow down list of active cells based on lateral activity self.activeCells = self._winnersBasedOnLateralActivity( bottomUpActivity, self.getPredictiveCells(), overlaps, self.numActiveColumnsPerInhArea ) # Update predictive cells for next time step self.tm.compute(activeColumns=self.activeCells, activeExternalCells=lateralInput, formInternalConnections=False, learn=False) def numberOfInputs(self): """ Returns the number of inputs into this layer """ return self.inputWidth def numberOfColumns(self): """ Returns the number of columns in this layer. @return (int) Number of columns """ return self.tm.numberOfColumns() def numberOfCells(self): """ Returns the number of cells in this layer. @return (int) Number of cells """ return self.tm.numberOfCells() def getActiveCells(self): """ Returns the indices of the active cells. @return (set) Indices of active cells. """ return self.getCellIndices(self.activeCells) @classmethod def getCellIndices(cls, cells): return [cls.getCellIndex(c) for c in cells] @staticmethod def getCellIndex(cell): return cell def numberOfConnectedSynapses(self, cells=None): """ Returns the number of proximal connected synapses on these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells. If None return count for all cells. """ if cells is None: cells = xrange(self.numberOfCells()) n = 0 for cell in cells: n += self.proximalConnections.nNonZerosOnRow(cell) return n def numberOfSynapses(self, cells=None): """ Returns the number of proximal synapses with permanence>0 on these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells. If None return count for all cells. """ if cells is None: cells = xrange(self.numberOfCells()) n = 0 for cell in cells: n += self.proximalPermanences.nNonZerosOnRow(cell) return n def numberOfDistalSegments(self, cells): """ Returns the total number of distal segments for these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells """ n = 0 for cell in cells: n += len(self.tm.connections.segmentsForCell(cell)) return n def numberOfDistalSynapses(self, cells): """ Returns the total number of distal synapses for these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells """ n = 0 for cell in cells: segments = self.tm.connections.segmentsForCell(cell) for segment in segments: n += len(self.tm.connections.synapsesForSegment(segment)) return n def reset(self): """ Reset internal states. When learning this signifies we are to learn a unique new object. """ self.activeCells = set() self.tm.reset() def getPredictiveCells(self): """ Get the set of distally predictive cells as a set. @return (list) A list containing indices of the current distally predicted cells. """ return self.tm.getPredictiveCells() def getPredictedActiveCells(self): """ Get the set of cells that were predicted previously then became active @return (set) A set containing indices. """ return self.tm.predictedActiveCellsIndices() def getConnections(self): """ Get the Connections structure associated with our TM. Beware of using this as it is implementation specific and may change. @return (object) A Connections object """ return self.tm.connections def _learnProximal(self, activeInputs, activeCells, maxNewSynapseCount, proximalPermanences, proximalConnections, initialPermanence, synPermProximalInc, synPermProximalDec, connectedPermanence): """ Learn on proximal dendrites of active cells. Updates proximalPermanences """ for cell in activeCells: cellPermanencesDense = proximalPermanences.getRow(cell) cellNonZeroIndices, _ = proximalPermanences.rowNonZeros(cell) cellNonZeroIndices = list(cellNonZeroIndices) # Get new and existing connections for this segment newInputs, existingInputs = self._pickProximalInputsToLearnOn( maxNewSynapseCount, activeInputs, cellNonZeroIndices ) # Adjust existing connections appropriately # First we decrement all existing permanences if len(cellNonZeroIndices) > 0: cellPermanencesDense[cellNonZeroIndices] -= synPermProximalDec # Then we add inc + dec to existing active synapses if len(existingInputs) > 0: cellPermanencesDense[existingInputs] += synPermProximalInc + synPermProximalDec # Add new connections if len(newInputs) > 0: cellPermanencesDense[newInputs] += initialPermanence # Update proximalPermanences and proximalConnections proximalPermanences.setRowFromDense(cell, cellPermanencesDense) newConnected = numpy.where(cellPermanencesDense >= connectedPermanence)[0] proximalConnections.replaceSparseRow(cell, newConnected) def _pickProximalInputsToLearnOn(self, newSynapseCount, activeInputs, cellNonZeros): """ Pick inputs to form proximal connections to a particular cell. We just randomly subsample from activeInputs, regardless of whether they are already connected. We return a list of up to newSynapseCount input indices from activeInputs that are valid new connections for this cell. We also return a list containing all inputs in activeInputs that are already connected to this cell. Parameters: ---------------------------- @param newSynapseCount (int) Number of inputs to pick @param cell (int) Cell index @param activeInputs (set) Indices of active inputs @param cellNonZeros (list) Indices of inputs input this cell with non-zero permanences. @return (list, list) Indices of new inputs to connect to, inputs already connected """ candidates = [] alreadyConnected = [] # Collect inputs that already have synapses and list of new candidate inputs for inputIdx in activeInputs: if inputIdx in cellNonZeros: alreadyConnected += [inputIdx] else: candidates += [inputIdx] # Select min(newSynapseCount, len(candidates)) new inputs to connect to if newSynapseCount >= len(candidates): return candidates, alreadyConnected else: # Pick newSynapseCount cells randomly # TODO: we could maybe implement this more efficiently with shuffle. inputs = [] for _ in range(newSynapseCount): i = self._random.getUInt32(len(candidates)) inputs += [candidates[i]] candidates.remove(candidates[i]) return inputs, alreadyConnected def _winnersBasedOnLateralActivity(self, activeCells, predictiveCells, overlaps, targetActiveCells): """ Given the set of cells active due to feedforward input, narrow down the list of active cells based on predictions due to previous lateralInput. Parameters: ---------------------------- @param activeCells (set) Indices of cells activated by bottom-up input. @param predictiveCells (set) Indices of cells that are laterally predicted. @param overlaps (numpy array) Bottom up overlap scores for each proximal segment. This is used to select additional cells if the narrowed down list contains less than targetActiveCells. @param targetActiveCells (int) The number of active cells we want to have active. @return (set) List of new winner cell indices """ # No TM accessors that return set so access internal member directly predictedActiveCells = activeCells.intersection(predictiveCells) # If predicted cells don't intersect at all with active cells, we go with # bottom up input. In these cases we can stick with existing active cells # and skip the overlap sorting if len(predictedActiveCells) == 0: predictedActiveCells = activeCells # We want to keep all cells that were predicted and currently active due to # feedforward input. This set could be larger than our target number of # active cells due to unions, which is ok. However if there are insufficient # cells active after this intersection, we fill in with those currently # active cells that have highest overlap. elif len(predictedActiveCells) < targetActiveCells: # Don't want to consider cells already chosen overlaps[list(predictedActiveCells)] = 0 # Add in the desired number of cells with highest activity numActive = targetActiveCells - len(predictedActiveCells) winnerIndices = numpy.argsort(overlaps, kind='mergesort') sortedWinnerIndices = winnerIndices[-numActive:][::-1] predictedActiveCells = predictedActiveCells.union(set(sortedWinnerIndices)) return predictedActiveCells
class ColumnPooler(object): """ This class constitutes a temporary implementation for a cross-column pooler. The implementation goal of this class is to prove basic properties before creating a cleaner implementation. """ def __init__(self, inputWidth, lateralInputWidth, numActiveColumnsPerInhArea=40, synPermProximalInc=0.1, synPermProximalDec=0.001, initialProximalPermanence=0.6, columnDimensions=(2048,), minThresholdProximal=10, activationThresholdDistal=13, minThresholdDistal=10, initialPermanence=0.41, connectedPermanence=0.50, maxNewProximalSynapseCount=20, maxNewDistalSynapseCount=20, permanenceIncrement=0.10, permanenceDecrement=0.10, predictedSegmentDecrement=0.0, maxSegmentsPerCell=255, maxSynapsesPerProximalSegment=255, maxSynapsesPerDistalSegment=255, seed=42): """ This classes uses an ExtendedTemporalMemory internally to keep track of distal segments. Please see ExtendedTemporalMemory for descriptions of constructor parameters not defined below. Parameters: ---------------------------- @param inputWidth (int) The number of proximal inputs into this layer @param lateralInputWidth (int) The number of lateral inputs into this layer @param numActiveColumnsPerInhArea (int) Target number of active cells @param synPermProximalInc (float) Permanence increment for proximal synapses @param synPermProximalDec (float) Permanence decrement for proximal synapses @param initialProximalPermanence (float) Initial permanence value for proximal segments """ self.inputWidth = inputWidth self.lateralInputWidth = lateralInputWidth self.numActiveColumnsPerInhArea = numActiveColumnsPerInhArea self.synPermProximalInc = synPermProximalInc self.synPermProximalDec = synPermProximalDec self.initialProximalPermanence = initialProximalPermanence self.connectedPermanence = connectedPermanence self.maxNewProximalSynapseCount = maxNewProximalSynapseCount self.maxNewDistalSynapseCount = maxNewDistalSynapseCount self.minThresholdProximal = minThresholdProximal self.minThresholdDistal = minThresholdDistal self.maxSynapsesPerProximalSegment = maxSynapsesPerProximalSegment self.activeCells = set() self._random = Random(seed) # Create our own instance of extended temporal memory to handle distal # segments. self.tm = createModel( modelName="etm_cpp", columnDimensions=columnDimensions, basalInputDimensions=(lateralInputWidth,), apicalInputDimensions=(), cellsPerColumn=1, activationThreshold=activationThresholdDistal, initialPermanence=initialPermanence, connectedPermanence=connectedPermanence, minThreshold=minThresholdDistal, maxNewSynapseCount=maxNewDistalSynapseCount, permanenceIncrement=permanenceIncrement, permanenceDecrement=permanenceDecrement, predictedSegmentDecrement=predictedSegmentDecrement, formInternalBasalConnections=False, learnOnOneCell=False, maxSegmentsPerCell=maxSegmentsPerCell, maxSynapsesPerSegment=maxSynapsesPerDistalSegment, seed=seed, ) # These sparse matrices will hold the synapses for each proximal segment. # # proximalPermanences - SparseMatrix with permanence values # proximalConnections - SparseBinaryMatrix of connected synapses self.proximalPermanences = SparseMatrix(self.numberOfColumns(), inputWidth) self.proximalConnections = SparseBinaryMatrix(inputWidth) self.proximalConnections.resize(self.numberOfColumns(), inputWidth) def depolarizeCells(self, activeExternalCells, learn=True): """ Parameters: ---------------------------- @param activeExternalCells (set) Indices of active cells that will form connections to distal segments. @param learn (bool) If true, distal segment activations will be recorded. This information is used during segment cleanup. """ self.tm.depolarizeCells(activeCellsExternalBasal=activeExternalCells, learn=learn) def activateCells(self, feedforwardInput=(), reinforceCandidatesExternal=(), growthCandidatesExternal=(), learn=True): """ @param feedforwardInput (set) Indices of active input bits @param reinforceCandidatesExternal (set) Indices of active cells that will reinforce synapses to distal segments. @param growthCandidatesExternal (set) Indices of active cells that will grow synapses to distal segments. @param learn (bool) If True, we are learning a new object """ if learn: self._activateCellsLearningMode(feedforwardInput, reinforceCandidatesExternal, growthCandidatesExternal) else: self._activateCellsInferenceMode(feedforwardInput) def compute(self, feedforwardInput=(), lateralInput=(), learn=True): """ Runs one time step of the column pooler algorithm. This method assumes: - Lateral input should trigger predictions for this time step, i.e. for this feedforward input. - During learning, all lateral input is eligible for growth and reinforcement. If these are bad assumptions, use depolarizeCells and activateCells directly. @param feedforwardInput (set) Indices of active feedforward input bits @param lateralInput (set) Indices of active lateral input bits @param learn (bool) If True, we are learning a new object """ self.depolarizeCells(lateralInput, learn) self.activateCells(feedforwardInput, lateralInput, lateralInput, learn) def _activateCellsLearningMode(self, feedforwardInput, reinforceCandidatesExternal, growthCandidatesExternal): """ Learning mode: we are learning a new object. If there is no prior activity, we randomly activate 2% of cells and create connections to incoming input. If there was prior activity, we maintain it. These cells will represent the object and learn distal connections to lateral cortical columns. Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param lateralInput (set) Indices of active cells from neighboring columns. """ # If there are no previously active cells, select random subset of cells if len(self.activeCells) == 0: self.activeCells = set(self._random.shuffle( numpy.array(range(self.numberOfCells()), dtype="uint32"))[0:self.numActiveColumnsPerInhArea]) # else we maintain previous activity, nothing to do. # Those cells that remain active will learn on their proximal and distal # dendrites as long as there is some input. If there are no # cells active, no learning happens. This only happens in the very # beginning if there has been no bottom up activity at all. if len(self.activeCells) > 0: # Learn on proximal dendrite if appropriate if len(feedforwardInput) > 0: self._learnProximal(feedforwardInput, self.activeCells, self.maxNewProximalSynapseCount, self.proximalPermanences, self.proximalConnections, self.initialProximalPermanence, self.synPermProximalInc, self.synPermProximalDec, self.connectedPermanence) # Learn on distal dendrites if appropriate self.tm.activateCells( activeColumns=sorted(self.activeCells), reinforceCandidatesExternalBasal=sorted(reinforceCandidatesExternal), growthCandidatesExternalBasal=sorted(growthCandidatesExternal), learn=True) def _activateCellsInferenceMode(self, feedforwardInput): """ Inference mode: if there is some feedforward activity, perform spatial pooling on it to recognize previously known objects. If there is no feedforward activity, maintain previous activity. Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits """ # Figure out which cells are active due to feedforward proximal inputs # In order to form unions, we keep all cells that are over threshold inputVector = numpy.zeros(self.numberOfInputs(), dtype=realDType) inputVector[list(feedforwardInput)] = 1 overlaps = numpy.zeros(self.numberOfColumns(), dtype=realDType) self.proximalConnections.rightVecSumAtNZ_fast(inputVector.astype(realDType), overlaps) overlaps[overlaps < self.minThresholdProximal] = 0 bottomUpActivity = set(overlaps.nonzero()[0]) # If there is insufficient current bottom up activity, we incorporate all # previous activity. We set their overlaps so they are sure to win. if len(bottomUpActivity) < self.numActiveColumnsPerInhArea: bottomUpActivity = bottomUpActivity.union(self.activeCells) maxOverlap = overlaps.max() overlaps[self.getActiveCells()] = maxOverlap+1 # Narrow down list of active cells based on lateral activity self.activeCells = self._winnersBasedOnLateralActivity( bottomUpActivity, self.getPredictiveCells(), overlaps, self.numActiveColumnsPerInhArea ) # Update the active cells in the TM. Without learning and without internal # basal connections, this has no effect on column pooler output. self.tm.activateCells(activeColumns=sorted(self.activeCells), learn=False) def numberOfInputs(self): """ Returns the number of inputs into this layer """ return self.inputWidth def numberOfColumns(self): """ Returns the number of columns in this layer. @return (int) Number of columns """ return self.tm.numberOfColumns() def numberOfCells(self): """ Returns the number of cells in this layer. @return (int) Number of cells """ return self.tm.numberOfCells() def getActiveCells(self): """ Returns the indices of the active cells. @return (list) Indices of active cells. """ return list(self.activeCells) def numberOfConnectedSynapses(self, cells=None): """ Returns the number of proximal connected synapses on these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells. If None return count for all cells. """ if cells is None: cells = xrange(self.numberOfCells()) n = 0 for cell in cells: n += self.proximalConnections.nNonZerosOnRow(cell) return n def numberOfSynapses(self, cells=None): """ Returns the number of proximal synapses with permanence>0 on these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells. If None return count for all cells. """ if cells is None: cells = xrange(self.numberOfCells()) n = 0 for cell in cells: n += self.proximalPermanences.nNonZerosOnRow(cell) return n def numberOfDistalSegments(self, cells): """ Returns the total number of distal segments for these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells """ n = 0 for cell in cells: n += self.tm.basalConnections.numSegments(cell) return n def numberOfDistalSynapses(self, cells): """ Returns the total number of distal synapses for these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells """ n = 0 for cell in cells: segments = self.tm.basalConnections.segmentsForCell(cell) for segment in segments: n += self.tm.basalConnections.numSynapses(segment) return n def reset(self): """ Reset internal states. When learning this signifies we are to learn a unique new object. """ self.activeCells = set() self.tm.reset() def getPredictiveCells(self): """ Get the set of distally predictive cells as a set. @return (list) A list containing indices of the current distally predicted cells. """ return self.tm.getPredictiveCells() def getPredictedActiveCells(self): """ Get the set of cells that were predicted previously then became active @return (set) A set containing indices. """ return self.tm.predictedActiveCellsIndices() def getConnections(self): """ Get the Connections structure associated with our TM. Beware of using this as it is implementation specific and may change. @return (object) A Connections object """ return self.tm.basalConnections def _learnProximal(self, activeInputs, activeCells, maxNewSynapseCount, proximalPermanences, proximalConnections, initialPermanence, synPermProximalInc, synPermProximalDec, connectedPermanence): """ Learn on proximal dendrites of active cells. Updates proximalPermanences """ for cell in activeCells: cellPermanencesDense = proximalPermanences.getRow(cell) cellNonZeroIndices, _ = proximalPermanences.rowNonZeros(cell) cellNonZeroIndices = set(cellNonZeroIndices) # Find the synapses that should be reinforced, punished, and grown. reinforce = list(activeInputs & cellNonZeroIndices) punish = list(cellNonZeroIndices - activeInputs) growthCandidates = activeInputs - cellNonZeroIndices newSynapseCount = min(len(growthCandidates), maxNewSynapseCount) grow = _sample(growthCandidates, newSynapseCount, self._random) # Make the changes. cellPermanencesDense[punish] -= synPermProximalDec cellPermanencesDense[reinforce] += synPermProximalInc cellPermanencesDense[grow] = initialPermanence # Update proximalPermanences and proximalConnections. proximalPermanences.setRowFromDense(cell, cellPermanencesDense) newConnected = numpy.where(cellPermanencesDense >= connectedPermanence)[0] proximalConnections.replaceSparseRow(cell, newConnected) def _winnersBasedOnLateralActivity(self, activeCells, predictiveCells, overlaps, targetActiveCells): """ Given the set of cells active due to feedforward input, narrow down the list of active cells based on predictions due to previous lateralInput. Parameters: ---------------------------- @param activeCells (set) Indices of cells activated by bottom-up input. @param predictiveCells (set) Indices of cells that are laterally predicted. @param overlaps (numpy array) Bottom up overlap scores for each proximal segment. This is used to select additional cells if the narrowed down list contains less than targetActiveCells. @param targetActiveCells (int) The number of active cells we want to have active. @return (set) List of new winner cell indices """ # No TM accessors that return set so access internal member directly predictedActiveCells = activeCells.intersection(predictiveCells) # If predicted cells don't intersect at all with active cells, we go with # bottom up input. In these cases we can stick with existing active cells # and skip the overlap sorting if len(predictedActiveCells) == 0: predictedActiveCells = activeCells # We want to keep all cells that were predicted and currently active due to # feedforward input. This set could be larger than our target number of # active cells due to unions, which is ok. However if there are insufficient # cells active after this intersection, we fill in with those currently # active cells that have highest overlap. elif len(predictedActiveCells) < targetActiveCells: # Don't want to consider cells already chosen overlaps[list(predictedActiveCells)] = 0 # Add in the desired number of cells with highest activity numActive = targetActiveCells - len(predictedActiveCells) winnerIndices = numpy.argsort(overlaps, kind='mergesort') sortedWinnerIndices = winnerIndices[-numActive:][::-1] predictedActiveCells = predictedActiveCells.union(set(sortedWinnerIndices)) return predictedActiveCells
class ColumnPooler(ExtendedTemporalMemory): """ This class constitutes a temporary implementation for a cross-column pooler. The implementation goal of this class is to prove basic properties before creating a cleaner implementation. """ def __init__(self, inputWidth, numActiveColumnsPerInhArea=40, synPermProximalInc=0.1, synPermProximalDec=0.001, initialProximalPermanence=0.6, **kwargs): """ Please see ExtendedTemporalMemory for descriptions of common constructor parameters. Parameters: ---------------------------- @param inputWidth (int) The number of proximal inputs into this layer @param numActiveColumnsPerInhArea (int) Number of active cells @param synPermProximalInc (float) Permanence increment for proximal synapses @param synPermProximalDec (float) Permanence decrement for proximal synapses @param initialProximalPermanence (float) Initial permanence value for proximal segments """ # Override: we only support one cell per column for now kwargs['cellsPerColumn'] = 1 super(ColumnPooler, self).__init__(**kwargs) self.inputWidth = inputWidth self.numActiveColumnsPerInhArea = numActiveColumnsPerInhArea self.synPermProximalInc = synPermProximalInc self.synPermProximalDec = synPermProximalDec self.initialProximalPermanence = initialProximalPermanence self.previousOverlaps = None # These sparse matrices will hold the synapses for each proximal segment. # # proximalPermanences - SparseMatrix with permanence values # proximalConnections - SparseBinaryMatrix of connected synapses self.proximalPermanences = SparseMatrix(self.numberOfColumns(), inputWidth) self.proximalConnections = SparseBinaryMatrix(inputWidth) self.proximalConnections.resize(self.numberOfColumns(), inputWidth) def compute(self, feedforwardInput=None, activeExternalCells=None, activeApicalCells=None, formInternalConnections=True, learn=True): """ Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param activeExternalCells (set) Indices of active cells that will form connections to distal segments. @param activeApicalCells (set) Indices of active cells that will form connections to apical segments. @param formInternalConnections (bool) If True, cells will form @param learn If True, we are learning a new object """ if activeExternalCells is None: activeExternalCells = set() if activeApicalCells is None: activeApicalCells = set() if learn: self._computeLearningMode(feedforwardInput=feedforwardInput, lateralInput=activeExternalCells) else: self._computeInferenceMode(feedforwardInput=feedforwardInput, lateralInput=activeExternalCells) def _computeLearningMode(self, feedforwardInput, lateralInput): """ Learning mode: we are learning a new object. If there is no prior activity, we randomly activate 2% of cells and create connections to incoming input. If there was prior activity, we maintain it. These cells will represent the object and learn distal connections to lateral cortical columns. Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param lateralInput (list of lists) A list of list of active cells from neighboring columns. len(lateralInput) == number of connected neighboring cortical columns. """ # If there are no previously active cells, select random subset of cells if len(self.activeCells) == 0: self.activeCells = set(self._random.shuffle( numpy.array(range(self.numberOfCells()), dtype="uint32"))[0:self.numActiveColumnsPerInhArea]) # else we maintain previous activity, nothing to do. # Incorporate distal segment activity and update list of active cells self.activeCells = self._winnersBasedOnLateralActivity( self.activeCells, lateralInput, self.minThreshold ) # Those cells that remain active will learn on their proximal and distal # dendrites as long as there is some input. If there are no # cells active, no learning happens. if len(self.activeCells) > 0: # Learn on proximal dendrite if appropriate if len(feedforwardInput) > 0: self._learnProximal(feedforwardInput, self.activeCells, self.maxNewSynapseCount, self.proximalPermanences, self.proximalConnections, self.initialProximalPermanence, self.synPermProximalInc, self.synPermProximalDec, self.connectedPermanence) # Learn on distal dendrites if appropriate if len(lateralInput) > 0: self._learnDistal(lateralInput, self.activeCells) def _computeInferenceMode(self, feedforwardInput, lateralInput): """ Inference mode: if there is some feedforward activity, perform spatial pooling on it to recognize previously known objects. If there is no feedforward activity, maintain previous activity. Parameters: ---------------------------- @param feedforwardInput (set) Indices of active input bits @param lateralInput (list of lists) A list of list of active cells from neighboring columns. len(lateralInput) == number of connected neighboring cortical columns. """ # Figure out which cells are active due to feedforward proximal inputs inputVector = numpy.zeros(self.numberOfInputs(), dtype=realDType) inputVector[list(feedforwardInput)] = 1 overlaps = numpy.zeros(self.numberOfColumns(), dtype=realDType) self.proximalConnections.rightVecSumAtNZ_fast(inputVector.astype(realDType), overlaps) overlaps[overlaps < self.minThreshold] = 0 # If there isn't enough bottom up activity, do nothing and maintain previous # activity. if overlaps.sum() > 0.0: # In order to form unions, we keep all cells that are over threshold self.activeCells = set(overlaps.nonzero()[0]) def numberOfInputs(self): """ Returns the number of inputs into this layer """ return self.inputWidth def numberOfConnectedSynapses(self, cells): """ Returns the number of connected synapses on these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells """ n = 0 for cell in cells: n += self.proximalConnections.nNonZerosOnRow(cell) return n def numberOfSynapses(self, cells): """ Returns the number of synapses with permanence>0 on these cells. Parameters: ---------------------------- @param cells (set or list) Indices of the cells """ n = 0 for cell in cells: n += self.proximalPermanences.nNonZerosOnRow(cell) return n def _learnProximal(self, activeInputs, activeCells, maxNewSynapseCount, proximalPermanences, proximalConnections, initialPermanence, synPermProximalInc, synPermProximalDec, connectedPermanence): """ Learn on proximal dendrites of active cells. Updates proximalPermanences """ for cell in activeCells: cellPermanencesDense = proximalPermanences.getRow(cell) cellNonZeroIndices, _ = proximalPermanences.rowNonZeros(cell) cellNonZeroIndices = list(cellNonZeroIndices) # Get new and existing connections for this segment newInputs, existingInputs = self._pickProximalInputsToLearnOn( maxNewSynapseCount, activeInputs, cellNonZeroIndices ) # Adjust existing connections appropriately # First we decrement all existing permanences if len(cellNonZeroIndices) > 0: cellPermanencesDense[cellNonZeroIndices] -= synPermProximalDec # Then we add inc + dec to existing active synapses if len(existingInputs) > 0: cellPermanencesDense[existingInputs] += synPermProximalInc + synPermProximalDec # Add new connections if len(newInputs) > 0: cellPermanencesDense[newInputs] += initialPermanence # Update proximalPermanences and proximalConnections proximalPermanences.setRowFromDense(cell, cellPermanencesDense) newConnected = numpy.where(cellPermanencesDense >= connectedPermanence)[0] proximalConnections.replaceSparseRow(cell, newConnected) cellNonZeroIndices, _ = proximalPermanences.rowNonZeros(cell) def _winnersBasedOnLateralActivity(self, activeCells, lateralInput, minThreshold): """ Incorporate effect of lateral activity, if any, and update the set of winners. UNIMPLEMENTED @return (set) list of new winner cell indices """ if len(lateralInput) == 0: return activeCells sortedWinnerIndices = activeCells # Figure out distal input into active cells # TODO: Reconcile and select the cells with sufficient bottom up activity # plus maximal lateral activity # Calculate winners using stable sort algorithm (mergesort) # for compatibility with C++ # if overlaps.max() >= minThreshold: # winnerIndices = numpy.argsort(overlaps, kind='mergesort') # sortedWinnerIndices = winnerIndices[ # -self.numActiveColumnsPerInhArea:][::-1] # sortedWinnerIndices = set(sortedWinnerIndices) return sortedWinnerIndices def _pickProximalInputsToLearnOn(self, newSynapseCount, activeInputs, cellNonZeros): """ Pick inputs to form proximal connections to. We just randomly subsample from activeInputs, regardless of whether they are already connected. We return a list of up to newSynapseCount input indices from activeInputs that are valid new connections for this cell. We also return a list containing all inputs in activeInputs that are already connected to this cell. Parameters: ---------------------------- @param newSynapseCount (int) Number of inputs to pick @param cell (int) Cell index @param activeInputs (set) Indices of active inputs @param proximalPermanences (sparse) The matrix of proximal connections @return (list, list) Indices of new inputs to connect to, inputs already connected """ candidates = [] alreadyConnected = [] # Collect inputs that already have synapses and list of new candidate inputs for inputIdx in activeInputs: if inputIdx in cellNonZeros: alreadyConnected += [inputIdx] else: candidates += [inputIdx] # Select min(newSynapseCount, len(candidates)) new inputs to connect to if newSynapseCount >= len(candidates): return candidates, alreadyConnected else: # Pick newSynapseCount cells randomly # TODO: we could maybe implement this more efficiently with shuffle. inputs = [] for _ in range(newSynapseCount): i = self._random.getUInt32(len(candidates)) inputs += [candidates[i]] candidates.remove(candidates[i]) # print "number of new candidates:",len(inputs) return inputs, alreadyConnected def _learnDistal(self, lateralInput, activeCells): pass