diff --git a/requirements.txt b/requirements.txt index baf0f310fc..fe232660ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,9 @@ coverage==3.7.1 mock==1.0.1 ordereddict==1.1 psutil==1.0.1 -pytest==2.5.1 -pytest-cov==1.6 -pytest-xdist==1.8 +pytest==3.0.7 +pytest-cov==2.5.0 +pytest-xdist==1.16.0 python-dateutil==2.1 PyYAML==3.10 unittest2==0.5.1 @@ -18,5 +18,5 @@ prettytable==0.7.2 # When updating nupic.bindings, also update any shared dependencies to keep # versions in sync. -nupic.bindings==0.6.1 -numpy==1.11.2 +nupic.bindings==0.6.3 +numpy==1.12.1 diff --git a/setup.py b/setup.py index c94d22881a..a934df33d2 100644 --- a/setup.py +++ b/setup.py @@ -149,7 +149,7 @@ def findRequirements(): extras_require = { # Default requirement based on system type ":platform_system=='Linux' or platform_system=='Darwin'": - ["pycapnp==0.5.8"], + ["pycapnp==0.5.12"], # Superseded by platform_system-conditional requirement, but keeping # empty extra for compatibility as recommended by setuptools doc. diff --git a/src/nupic/algorithms/connections.py b/src/nupic/algorithms/connections.py index a155665719..fc705d5f15 100644 --- a/src/nupic/algorithms/connections.py +++ b/src/nupic/algorithms/connections.py @@ -30,9 +30,9 @@ class Segment(object): """ Class containing minimal information to identify a unique segment """ - __slots__ = ["cell", "flatIdx", "_synapses", "_lastUsedIteration", "_ordinal"] + __slots__ = ["cell", "flatIdx", "_synapses", "_ordinal"] - def __init__(self, cell, flatIdx, lastUsedIteration, ordinal): + def __init__(self, cell, flatIdx, ordinal): """ @param cell (int) Index of the cell that this segment is on. @@ -48,7 +48,6 @@ def __init__(self, cell, flatIdx, lastUsedIteration, ordinal): self.cell = cell self.flatIdx = flatIdx self._synapses = set() - self._lastUsedIteration = lastUsedIteration self._ordinal = ordinal @@ -60,7 +59,6 @@ def __eq__(self, other): """ return (self.cell == other.cell and - self._lastUsedIteration == other._lastUsedIteration and (sorted(self._synapses, key=lambda x: x._ordinal) == sorted(other._synapses, key=lambda x: x._ordinal))) @@ -134,18 +132,11 @@ class Connections(object): """ Class to hold data representing the connectivity of a collection of cells. """ - def __init__(self, - numCells, - maxSegmentsPerCell=255, - maxSynapsesPerSegment=255): + def __init__(self, numCells): """ @param numCells (int) Number of cells in collection """ # Save member variables self.numCells = numCells - assert maxSegmentsPerCell > 0 - assert maxSynapsesPerSegment > 0 - self.maxSegmentsPerCell = maxSegmentsPerCell - self.maxSynapsesPerSegment = maxSynapsesPerSegment self._cells = [CellData() for _ in xrange(numCells)] self._synapsesForPresynapticCell = defaultdict(set) @@ -154,7 +145,6 @@ def __init__(self, self._numSynapses = 0 self._freeFlatIdxs = [] self._nextFlatIdx = 0 - self._iteration = 0 # Whenever creating a new Synapse or Segment, give it a unique ordinal. # These can be used to sort synapses or segments by age. @@ -226,57 +216,6 @@ def getSegment(self, cell, idx): return self._cells[cell]._segments[idx] - def _leastRecentlyUsedSegment(self, cell): - """ Find this cell's segment that was least recently used. - - Implement this explicitly to make sure that tie-breaking is consistent. - When there's a tie, choose the oldest segment. - - @param cell (int) Cell to query. - - @return (Object) Least recently used segment. - - """ - minSegment = None - minIteration = float("inf") - - for segment in self.segmentsForCell(cell): - if segment._lastUsedIteration < minIteration: - minSegment = segment - minIteration = segment._lastUsedIteration - - assert minSegment is not None - - return minSegment - - - def _minPermanenceSynapse(self, segment): - """ Find this segment's synapse with the smallest permanence. - - This method is NOT equivalent to a simple min() call. It uses an EPSILON to - account for floating point differences between C++ and Python. - - @param segment (Object) Segment to query. - - @return (Object) Synapse with the minimal permanence - - Note: On ties it will choose the first occurrence of the minimum permanence. - - """ - minSynapse = None - minPermanence = float("inf") - - for synapse in sorted(self.synapsesForSegment(segment), - key=lambda s: s._ordinal): - if synapse.permanence < minPermanence - EPSILON: - minSynapse = synapse - minPermanence = synapse.permanence - - assert minSynapse is not None - - return minSynapse - - def segmentForFlatIdx(self, flatIdx): """ Get the segment with the specified flatIdx. @@ -313,9 +252,6 @@ def createSegment(self, cell): @return (int) New segment index """ - while self.numSegments(cell) >= self.maxSegmentsPerCell: - self.destroySegment(self._leastRecentlyUsedSegment(cell)) - cellData = self._cells[cell] if len(self._freeFlatIdxs) > 0: @@ -328,7 +264,7 @@ def createSegment(self, cell): ordinal = self._nextSegmentOrdinal self._nextSegmentOrdinal += 1 - segment = Segment(cell, flatIdx, self._iteration, ordinal) + segment = Segment(cell, flatIdx, ordinal) cellData._segments.append(segment) self._segmentForFlatIdx[flatIdx] = segment @@ -366,10 +302,6 @@ def createSynapse(self, segment, presynapticCell, permanence): @return (Object) created Synapse object """ - - while self.numSynapses(segment) >= self.maxSynapsesPerSegment: - self.destroySynapse(self._minPermanenceSynapse(segment)) - idx = len(segment._synapses) synapse = Synapse(segment, presynapticCell, permanence, self._nextSynapseOrdinal) @@ -444,22 +376,6 @@ def computeActivity(self, activePresynapticCells, connectedPermanence): numActivePotentialSynapsesForSegment) - def recordSegmentActivity(self, segment): - """ Record the fact that a segment had some activity. This information is - used during segment cleanup. - - @param segment The segment that had some activity. - """ - segment._lastUsedIteration = self._iteration - - - def startNewIteration(self): - """ Mark the passage of time. This information is used during segment - cleanup. - """ - self._iteration += 1 - - def numSegments(self, cell=None): """ Returns the number of segments. @@ -515,17 +431,10 @@ def write(self, proto): for j, segment in enumerate(segments): synapses = segment._synapses protoSynapses = protoSegments[j].init('synapses', len(synapses)) - protoSegments[j].destroyed = False - protoSegments[j].lastUsedIteration = segment._lastUsedIteration for k, synapse in enumerate(sorted(synapses, key=lambda s: s._ordinal)): protoSynapses[k].presynapticCell = synapse.presynapticCell protoSynapses[k].permanence = synapse.permanence - protoSynapses[k].destroyed = False - - proto.maxSegmentsPerCell = self.maxSegmentsPerCell - proto.maxSynapsesPerSegment = self.maxSynapsesPerSegment - proto.iteration = self._iteration @classmethod @@ -538,9 +447,7 @@ def read(cls, proto): """ #pylint: disable=W0212 protoCells = proto.cells - connections = cls(len(protoCells), - proto.maxSegmentsPerCell, - proto.maxSynapsesPerSegment) + connections = cls(len(protoCells)) for cellIdx, protoCell in enumerate(protoCells): protoCell = protoCells[cellIdx] @@ -549,11 +456,7 @@ def read(cls, proto): segments = connections._cells[cellIdx]._segments for segmentIdx, protoSegment in enumerate(protoSegments): - if protoSegment.destroyed: - continue - segment = Segment(cellIdx, connections._nextFlatIdx, - protoSegment.lastUsedIteration, connections._nextSegmentOrdinal) segments.append(segment) @@ -565,9 +468,6 @@ def read(cls, proto): protoSynapses = protoSegment.synapses for synapseIdx, protoSynapse in enumerate(protoSynapses): - if protoSynapse.destroyed: - continue - presynapticCell = protoSynapse.presynapticCell synapse = Synapse(segment, presynapticCell, protoSynapse.permanence, ordinal=connections._nextSynapseOrdinal) @@ -577,7 +477,6 @@ def read(cls, proto): connections._numSynapses += 1 - connections._iteration = proto.iteration #pylint: enable=W0212 return connections @@ -589,11 +488,6 @@ def __eq__(self, other): @param other (Connections) Connections instance to compare to """ #pylint: disable=W0212 - if self.maxSegmentsPerCell != other.maxSegmentsPerCell: - return False - if self.maxSynapsesPerSegment != other.maxSynapsesPerSegment: - return False - for i in xrange(self.numCells): segments = self._cells[i]._segments otherSegments = other._cells[i]._segments @@ -607,8 +501,6 @@ def __eq__(self, other): synapses = segment._synapses otherSynapses = otherSegment._synapses - if segment._lastUsedIteration != otherSegment._lastUsedIteration: - return False if len(synapses) != len(otherSynapses): return False @@ -644,8 +536,6 @@ def __eq__(self, other): if self._numSynapses != other._numSynapses: return False - if self._iteration != other._iteration: - return False #pylint: enable=W0212 return True diff --git a/src/nupic/algorithms/temporal_memory.py b/src/nupic/algorithms/temporal_memory.py index ef0b151372..6346d0b6d8 100644 --- a/src/nupic/algorithms/temporal_memory.py +++ b/src/nupic/algorithms/temporal_memory.py @@ -90,10 +90,10 @@ def __init__(self, @param seed (int) Seed for the random number generator. - @param maxSegmentsPerCell + @param maxSegmentsPerCell (int) The maximum number of segments per cell. - @param maxSynapsesPerSegment + @param maxSynapsesPerSegment (int) The maximum number of synapses per segment. @@ -128,13 +128,11 @@ def __init__(self, self.permanenceIncrement = permanenceIncrement self.permanenceDecrement = permanenceDecrement self.predictedSegmentDecrement = predictedSegmentDecrement + self.maxSegmentsPerCell = maxSegmentsPerCell + self.maxSynapsesPerSegment = maxSynapsesPerSegment # Initialize member variables - self.connections = self.connectionsFactory( - self.numberOfCells(), - maxSegmentsPerCell=maxSegmentsPerCell, - maxSynapsesPerSegment=maxSynapsesPerSegment - ) + self.connections = self.connectionsFactory(self.numberOfCells()) self._random = Random(seed) self.activeCells = [] self.winnerCells = [] @@ -144,6 +142,9 @@ def __init__(self, self.numActiveConnectedSynapsesForSegment = [] self.numActivePotentialSynapsesForSegment = [] + self.iteration = 0 + self.lastUsedIterationForSegment = [] + @staticmethod @@ -290,8 +291,8 @@ def activateDendrites(self, learn=True): if learn: for segment in self.activeSegments: - self.connections.recordSegmentActivity(segment) - self.connections.startNewIteration() + self.lastUsedIterationForSegment[segment.flatIdx] = self.iteration + self.iteration += 1 def reset(self): @@ -345,7 +346,8 @@ def activatePredictedColumn(self, column, columnActiveSegments, columnActiveSegments, prevActiveCells, prevWinnerCells, self.numActivePotentialSynapsesForSegment, self.maxNewSynapseCount, self.initialPermanence, - self.permanenceIncrement, self.permanenceDecrement, learn) + self.permanenceIncrement, self.permanenceDecrement, + self.maxSynapsesPerSegment, learn) def burstColumn(self, column, columnMatchingSegments, prevActiveCells, @@ -379,11 +381,12 @@ def burstColumn(self, column, columnMatchingSegments, prevActiveCells, cellsForColumn = xrange(start, start + self.cellsPerColumn) return self._burstColumn( - self.connections, self._random, column, columnMatchingSegments, - prevActiveCells, prevWinnerCells, cellsForColumn, - self.numActivePotentialSynapsesForSegment, self.maxNewSynapseCount, - self.initialPermanence, self.permanenceIncrement, - self.permanenceDecrement, learn) + self.connections, self._random, self.lastUsedIterationForSegment, column, + columnMatchingSegments, prevActiveCells, prevWinnerCells, cellsForColumn, + self.numActivePotentialSynapsesForSegment, self.iteration, + self.maxNewSynapseCount, self.initialPermanence, self.permanenceIncrement, + self.permanenceDecrement, self.maxSegmentsPerCell, + self.maxSynapsesPerSegment, learn) def punishPredictedColumn(self, column, columnActiveSegments, @@ -413,6 +416,23 @@ def punishPredictedColumn(self, column, columnActiveSegments, self.predictedSegmentDecrement) + def createSegment(self, cell): + """ + Create a segment on the specified cell. This method calls createSegment + on the underlying connections, and it does some extra bookkeeping. Unit + tests should call this method, and not connections.createSegment(). + + @param cell (int) + Index of cell to create a segment on. + + @return (Segment) + The created segment. + """ + return self._createSegment( + self.connections, self.lastUsedIterationForSegment, cell, self.iteration, + self.maxSegmentsPerCell) + + # ============================== # Helper methods # @@ -430,9 +450,9 @@ def punishPredictedColumn(self, column, columnActiveSegments, def _activatePredictedColumn(cls, connections, random, columnActiveSegments, prevActiveCells, prevWinnerCells, numActivePotentialSynapsesForSegment, - maxNewSynapseCount, - initialPermanence, permanenceIncrement, - permanenceDecrement, learn): + maxNewSynapseCount, initialPermanence, + permanenceIncrement, permanenceDecrement, + maxSynapsesPerSegment, learn): """ @param connections (Object) Connections for the TM. Gets mutated. @@ -465,6 +485,9 @@ def _activatePredictedColumn(cls, connections, random, columnActiveSegments, @permanenceDecrement (float) Amount by which permanences of synapses are decremented during learning. + @param maxSynapsesPerSegment (int) + The maximum number of synapses per segment. + @param learn (bool) If true, grow and reinforce synapses. @@ -498,17 +521,20 @@ def _activatePredictedColumn(cls, connections, random, columnActiveSegments, if nGrowDesired > 0: cls._growSynapses(connections, random, segment, nGrowDesired, - prevWinnerCells, initialPermanence) + prevWinnerCells, initialPermanence, + maxSynapsesPerSegment) return cellsToAdd @classmethod - def _burstColumn(cls, connections, random, column, columnMatchingSegments, - prevActiveCells, prevWinnerCells, cellsForColumn, - numActivePotentialSynapsesForSegment, maxNewSynapseCount, - initialPermanence, permanenceIncrement, - permanenceDecrement, learn): + def _burstColumn(cls, connections, random, lastUsedIterationForSegment, + column, columnMatchingSegments, prevActiveCells, + prevWinnerCells, cellsForColumn, + numActivePotentialSynapsesForSegment, iteration, + maxNewSynapseCount, initialPermanence, permanenceIncrement, + permanenceDecrement, maxSegmentsPerCell, + maxSynapsesPerSegment, learn): """ @param connections (Object) Connections for the TM. Gets mutated. @@ -516,6 +542,10 @@ def _burstColumn(cls, connections, random, column, columnMatchingSegments, @param random (Object) Random number generator. Gets mutated. + @param lastUsedIterationForSegment (list) + Last used iteration for each segment, indexed by the segment's flatIdx. + Gets mutated. + @param column (int) Index of bursting column. @@ -535,6 +565,9 @@ def _burstColumn(cls, connections, random, column, columnMatchingSegments, Number of active potential synapses per segment, indexed by the segment's flatIdx. + @param iteration (int) + The current timestep. + @param maxNewSynapseCount (int) The maximum number of synapses added to a segment during learning. @@ -547,6 +580,12 @@ def _burstColumn(cls, connections, random, column, columnMatchingSegments, @param permanenceDecrement (float) Amount by which permanences of synapses are decremented during learning. + @param maxSegmentsPerCell (int) + The maximum number of segments per cell. + + @param maxSynapsesPerSegment (int) + The maximum number of synapses per segment. + @param learn (bool) Whether or not learning is enabled. @@ -581,15 +620,19 @@ def _burstColumn(cls, connections, random, column, columnMatchingSegments, if nGrowDesired > 0: cls._growSynapses(connections, random, bestMatchingSegment, - nGrowDesired, prevWinnerCells, initialPermanence) + nGrowDesired, prevWinnerCells, initialPermanence, + maxSynapsesPerSegment) else: winnerCell = cls._leastUsedCell(random, cellsForColumn, connections) if learn: nGrowExact = min(maxNewSynapseCount, len(prevWinnerCells)) if nGrowExact > 0: - segment = connections.createSegment(winnerCell) + segment = cls._createSegment(connections, + lastUsedIterationForSegment, winnerCell, + iteration, maxSegmentsPerCell) cls._growSynapses(connections, random, segment, nGrowExact, - prevWinnerCells, initialPermanence) + prevWinnerCells, initialPermanence, + maxSynapsesPerSegment) return cellsForColumn, winnerCell @@ -620,6 +663,67 @@ def _punishPredictedColumn(cls, connections, columnMatchingSegments, -predictedSegmentDecrement, 0.0) + @classmethod + def _createSegment(cls, connections, lastUsedIterationForSegment, cell, + iteration, maxSegmentsPerCell): + """ + Create a segment on the connections, enforcing the maxSegmentsPerCell + parameter. + """ + # Enforce maxSegmentsPerCell. + while connections.numSegments(cell) >= maxSegmentsPerCell: + leastRecentlyUsedSegment = min( + connections.segmentsForCell(cell), + key=lambda segment : lastUsedIterationForSegment[segment.flatIdx]) + + connections.destroySegment(leastRecentlyUsedSegment) + + # Create the segment. + segment = connections.createSegment(cell) + + # Do TM-specific bookkeeping for the segment. + if segment.flatIdx == len(lastUsedIterationForSegment): + lastUsedIterationForSegment.append(iteration) + elif segment.flatIdx < len(lastUsedIterationForSegment): + # A flatIdx was recycled. + lastUsedIterationForSegment[segment.flatIdx] = iteration + else: + raise AssertionError( + "All segments should be created with the TM createSegment method.") + + return segment + + + @classmethod + def _destroyMinPermanenceSynapses(cls, connections, random, segment, + nDestroy, excludeCells): + """ + Destroy nDestroy synapses on the specified segment, but don't destroy + synapses to the "excludeCells". + """ + + destroyCandidates = sorted( + (synapse for synapse in connections.synapsesForSegment(segment) + if synapse.presynapticCell not in excludeCells), + key=lambda s: s._ordinal + ) + + for _ in xrange(nDestroy): + if len(destroyCandidates) == 0: + break + + minSynapse = None + minPermanence = float("inf") + + for synapse in destroyCandidates: + if synapse.permanence < minPermanence - EPSILON: + minSynapse = synapse + minPermanence = synapse.permanence + + connections.destroySynapse(minSynapse) + destroyCandidates.remove(minSynapse) + + @classmethod def _leastUsedCell(cls, random, cells, connections): """ @@ -655,7 +759,7 @@ def _leastUsedCell(cls, random, cells, connections): @classmethod def _growSynapses(cls, connections, random, segment, nDesiredNewSynapes, - prevWinnerCells, initialPermanence): + prevWinnerCells, initialPermanence, maxSynapsesPerSegment): """ Creates nDesiredNewSynapes synapses on the segment passed in if possible, choosing random cells from the previous winner cells that are @@ -679,6 +783,16 @@ def _growSynapses(cls, connections, random, segment, nDesiredNewSynapes, nActual = min(nDesiredNewSynapes, len(candidates)) + # Check if we're going to surpass the maximum number of synapses. + overrun = connections.numSynapses(segment) + nActual - maxSynapsesPerSegment + if overrun > 0: + cls._destroyMinPermanenceSynapses(connections, random, segment, overrun, + prevWinnerCells) + + # Recalculate in case we weren't able to destroy as many synapses as needed. + nActual = min(nActual, + maxSynapsesPerSegment - connections.numSynapses(segment)) + for _ in range(nActual): i = random.getUInt32(len(candidates)) connections.createSynapse(segment, candidates[i], initialPermanence) @@ -989,7 +1103,7 @@ def getMaxSegmentsPerCell(self): Get the maximum number of segments per cell @return (int) max number of segments per cell """ - return self.connections.maxSegmentsPerCell + return self.maxSegmentsPerCell def getMaxSynapsesPerSegment(self): @@ -997,7 +1111,7 @@ def getMaxSynapsesPerSegment(self): Get the maximum number of synapses per segment. @return (int) max number of synapses per segment """ - return self.connections.maxSynapsesPerSegment + return self.maxSynapsesPerSegment def write(self, proto): @@ -1018,30 +1132,52 @@ def write(self, proto): proto.permanenceDecrement = self.permanenceDecrement proto.predictedSegmentDecrement = self.predictedSegmentDecrement + proto.maxSegmentsPerCell = self.maxSegmentsPerCell + proto.maxSynapsesPerSegment = self.maxSynapsesPerSegment + self.connections.write(proto.connections) self._random.write(proto.random) proto.activeCells = list(self.activeCells) proto.winnerCells = list(self.winnerCells) - activeSegmentOverlaps = \ - proto.init('activeSegmentOverlaps', len(self.activeSegments)) + + protoActiveSegments = proto.init("activeSegments", len(self.activeSegments)) for i, segment in enumerate(self.activeSegments): - activeSegmentOverlaps[i].cell = segment.cell + protoActiveSegments[i].cell = segment.cell idx = self.connections.segmentsForCell(segment.cell).index(segment) - activeSegmentOverlaps[i].segment = idx - activeSegmentOverlaps[i].overlap = ( - self.numActiveConnectedSynapsesForSegment[segment.flatIdx] - ) + protoActiveSegments[i].idxOnCell = idx - matchingSegmentOverlaps = \ - proto.init('matchingSegmentOverlaps', len(self.matchingSegments)) + protoMatchingSegments = proto.init("matchingSegments", + len(self.matchingSegments)) for i, segment in enumerate(self.matchingSegments): - matchingSegmentOverlaps[i].cell = segment.cell + protoMatchingSegments[i].cell = segment.cell idx = self.connections.segmentsForCell(segment.cell).index(segment) - matchingSegmentOverlaps[i].segment = idx - matchingSegmentOverlaps[i].overlap = ( - self.numActivePotentialSynapsesForSegment[segment.flatIdx] - ) + protoMatchingSegments[i].idxOnCell = idx + + protoNumActivePotential = proto.init( + "numActivePotentialSynapsesForSegment", + len(self.numActivePotentialSynapsesForSegment)) + for i, numActivePotentialSynapses in enumerate( + self.numActivePotentialSynapsesForSegment): + segment = self.connections.segmentForFlatIdx(i) + if segment is not None: + protoNumActivePotential[i].cell = segment.cell + idx = self.connections.segmentsForCell(segment.cell).index(segment) + protoNumActivePotential[i].idxOnCell = idx + protoNumActivePotential[i].number = numActivePotentialSynapses + + proto.iteration = self.iteration + + protoLastUsedIteration = proto.init( + "lastUsedIterationForSegment", + len(self.numActivePotentialSynapsesForSegment)) + for i, lastUsed in enumerate(self.lastUsedIterationForSegment): + segment = self.connections.segmentForFlatIdx(i) + if segment is not None: + protoLastUsedIteration[i].cell = segment.cell + idx = self.connections.segmentsForCell(segment.cell).index(segment) + protoLastUsedIteration[i].idxOnCell = idx + protoLastUsedIteration[i].number = lastUsed @classmethod @@ -1069,6 +1205,9 @@ def read(cls, proto): tm.permanenceDecrement = proto.permanenceDecrement tm.predictedSegmentDecrement = proto.predictedSegmentDecrement + tm.maxSegmentsPerCell = int(proto.maxSegmentsPerCell) + tm.maxSynapsesPerSegment = int(proto.maxSynapsesPerSegment) + tm.connections = Connections.read(proto.connections) #pylint: disable=W0212 tm._random = Random() @@ -1081,29 +1220,36 @@ def read(cls, proto): flatListLength = tm.connections.segmentFlatListLength() tm.numActiveConnectedSynapsesForSegment = [0] * flatListLength tm.numActivePotentialSynapsesForSegment = [0] * flatListLength + tm.lastUsedIterationForSegment = [0] * flatListLength tm.activeSegments = [] tm.matchingSegments = [] - for i in xrange(len(proto.activeSegmentOverlaps)): - protoSegmentOverlap = proto.activeSegmentOverlaps[i] + for protoSegment in proto.activeSegments: + tm.activeSegments.append( + tm.connections.getSegment(protoSegment.cell, + protoSegment.idxOnCell)) + + for protoSegment in proto.matchingSegments: + tm.matchingSegments.append( + tm.connections.getSegment(protoSegment.cell, + protoSegment.idxOnCell)) - segment = tm.connections.getSegment(protoSegmentOverlap.cell, - protoSegmentOverlap.segment) - tm.activeSegments.append(segment) + for protoSegment in proto.numActivePotentialSynapsesForSegment: + segment = tm.connections.getSegment(protoSegment.cell, + protoSegment.idxOnCell) - overlap = protoSegmentOverlap.overlap - tm.numActiveConnectedSynapsesForSegment[segment.flatIdx] = overlap + tm.numActivePotentialSynapsesForSegment[segment.flatIdx] = ( + int(protoSegment.number)) - for i in xrange(len(proto.matchingSegmentOverlaps)): - protoSegmentOverlap = proto.matchingSegmentOverlaps[i] + tm.iteration = long(proto.iteration) - segment = tm.connections.getSegment(protoSegmentOverlap.cell, - protoSegmentOverlap.segment) - tm.matchingSegments.append(segment) + for protoSegment in proto.lastUsedIterationForSegment: + segment = tm.connections.getSegment(protoSegment.cell, + protoSegment.idxOnCell) - overlap = protoSegmentOverlap.overlap - tm.numActivePotentialSynapsesForSegment[segment.flatIdx] = overlap + tm.lastUsedIterationForSegment[segment.flatIdx] = ( + long(protoSegment.number)) return tm diff --git a/src/nupic/regions/knn_classifier_region.py b/src/nupic/regions/knn_classifier_region.py index 91b0e9056c..919faa0b19 100644 --- a/src/nupic/regions/knn_classifier_region.py +++ b/src/nupic/regions/knn_classifier_region.py @@ -976,13 +976,13 @@ def compute(self, inputs, outputs): # Update the stored confusion matrix. for category in categories: if category >= 0: - dims = max(category+1, len(inference)) + dims = max(int(category)+1, len(inference)) oldDims = len(self.confusion) if oldDims < dims: confusion = numpy.zeros((dims, dims)) confusion[0:oldDims, 0:oldDims] = self.confusion self.confusion = confusion - self.confusion[inference.argmax(), category] += 1 + self.confusion[inference.argmax(), int(category)] += 1 # Calculate the best prototype indices if nPrototypes > 1: diff --git a/tests/integration/nupic/algorithms/knn_classifier_test/classifier_test.py b/tests/integration/nupic/algorithms/knn_classifier_test/classifier_test.py index ecc0d05a5e..b57eb292c5 100755 --- a/tests/integration/nupic/algorithms/knn_classifier_test/classifier_test.py +++ b/tests/integration/nupic/algorithms/knn_classifier_test/classifier_test.py @@ -74,7 +74,7 @@ def runTestKNNClassifier(self, short = 0): patternDict[i]['pattern'] = patterns[i] patternDict[i]['category'] = numpy.random.randint(0, numClasses-1) testDict[i] = copy.deepcopy(patternDict[i]) - testDict[i]['pattern'][:0.02*patternSize] = numpy.random.rand() + testDict[i]['pattern'][:int(0.02*patternSize)] = numpy.random.rand() testDict[i]['category'] = None LOGGER.info("\nTesting KNN Classifier with L2 norm") diff --git a/tests/unit/nupic/algorithms/connections_test.py b/tests/unit/nupic/algorithms/connections_test.py index 0ac553554d..fd3cbc30f6 100755 --- a/tests/unit/nupic/algorithms/connections_test.py +++ b/tests/unit/nupic/algorithms/connections_test.py @@ -46,77 +46,6 @@ def testCreateSegment(self): list(connections.segmentsForCell(10))) - def testCreateSegmentReuse(self): - connections = Connections(1024, 2) - - segment1 = connections.createSegment(42) - connections.createSynapse(segment1, 1, .5) - connections.createSynapse(segment1, 2, .5) - - # Let some time pass. - connections.startNewIteration() - connections.startNewIteration() - connections.startNewIteration() - - # Create a segment with 3 synapse. - segment2 = connections.createSegment(42) - connections.createSynapse(segment2, 1, .5) - connections.createSynapse(segment2, 2, .5) - connections.createSynapse(segment2, 3, .5) - connections.startNewIteration() - - # Give the first segment some activity. - connections.recordSegmentActivity(segment1) - - # Create a new segment with 1 synapse. - segment3 = connections.createSegment(42) - connections.createSynapse(segment3, 1, .5) - - segments = connections.segmentsForCell(42) - self.assertEqual(2, len(segments)) - - # Verify first segment is still there with the same synapses. - self.assertEqual(set([1, 2]), - set(synapse.presynapticCell for synapse in - connections.synapsesForSegment(segments[0]))) - - # Verify second segment has been replaced. - self.assertEqual(set([1]), - set(synapse.presynapticCell for synapse in - connections.synapsesForSegment(segments[1]))) - - # Verify the flatIdxs were properly reused. - self.assertLess(segment1.flatIdx, 2) - self.assertLess(segment3.flatIdx, 2) - self.assertTrue(segment1 is connections.segmentForFlatIdx(segment1.flatIdx)) - self.assertTrue(segment3 is connections.segmentForFlatIdx(segment3.flatIdx)) - - - def testSynapseReuse(self): - """ Creates a synapse over the synapses per segment limit, and verifies - that the lowest permanence synapse is removed to make room for the new - synapse. - """ - connections = Connections(1024, 1024, 2) - segment = connections.createSegment(10) - - synapse1 = connections.createSynapse(segment, 50, .34) - synapse2 = connections.createSynapse(segment, 51, .48) - - synapses = connections.synapsesForSegment(segment) - self.assertEqual(set([synapse1, synapse2]), synapses) - - # Add an additional synapse to force it over the limit of num synapses - # per segment. - connections.createSynapse(segment, 52, .52) - - # Ensure lower permanence synapse was removed. - self.assertEqual(set([51, 52]), - set(synapse.presynapticCell - for synapse in - connections.synapsesForSegment(segment))) - - def testDestroySegment(self): """ Creates a segment, destroys it, and makes sure it got destroyed along with all of its synapses. @@ -255,70 +184,6 @@ def testReuseSegmentWithDestroyedSynapses(self): self.assertEqual(0, len(connections.synapsesForSegment(reincarnated))) - def testDestroySegmentsThenReachLimit(self): - """ Destroy some segments then verify that the maxSegmentsPerCell is still - correctly applied. - """ - connections = Connections(1024, 2, 2) - - segment1 = connections.createSegment(11) - segment2 = connections.createSegment(11) - - self.assertEqual(2, connections.numSegments()) - connections.destroySegment(segment1) - connections.destroySegment(segment2) - self.assertEqual(0, connections.numSegments()) - - connections.createSegment(11) - self.assertEqual(1, connections.numSegments()) - connections.createSegment(11) - self.assertEqual(2, connections.numSegments()) - segment3 = connections.createSegment(11) - self.assertEqual(2, connections.numSegments(11)) - self.assertEqual(2, connections.numSegments()) - - - def testDestroySynapsesThenReachLimit(self): - """ Destroy some synapses then verify that the maxSynapsesPerSegment is - still correctly applied. - """ - connections = Connections(1024, 2, 2) - - segment = connections.createSegment(10) - - synapse1 = connections.createSynapse(segment, 201, .85) - synapse2 = connections.createSynapse(segment, 202, .85) - - self.assertEqual(2, connections.numSynapses()) - connections.destroySynapse(synapse1) - connections.destroySynapse(synapse2) - self.assertEqual(0, connections.numSynapses()) - - connections.createSynapse(segment, 201, .85) - self.assertEqual(1, connections.numSynapses()) - connections.createSynapse(segment, 202, .90) - self.assertEqual(2, connections.numSynapses()) - synapse3 = connections.createSynapse(segment, 203, .8) - self.assertEqual(2, connections.numSynapses()) - - - def testReachSegmentLimitMultipleTimes(self): - """ Hit the maxSynapsesPerSegment threshold multiple times. Make sure it - works more than once. - """ - connections = Connections(1024, 2, 2) - - segment = connections.createSegment(10) - connections.createSynapse(segment, 201, .85) - self.assertEqual(1, connections.numSynapses()) - connections.createSynapse(segment, 202, .9) - self.assertEqual(2, connections.numSynapses()) - connections.createSynapse(segment, 203, .8) - self.assertEqual(2, connections.numSynapses()) - synapse = connections.createSynapse(segment, 204, .8) - self.assertEqual(2, connections.numSynapses()) - - def testUpdateSynapsePermanence(self): """ Creates a synapse and updates its permanence, and makes sure that its data was correctly updated. diff --git a/tests/unit/nupic/algorithms/temporal_memory_test.py b/tests/unit/nupic/algorithms/temporal_memory_test.py index b2eaa234cb..b5c3abb094 100755 --- a/tests/unit/nupic/algorithms/temporal_memory_test.py +++ b/tests/unit/nupic/algorithms/temporal_memory_test.py @@ -69,7 +69,7 @@ def testActivateCorrectlyPredictiveCells(self): previousActiveCells = [0,1,2,3] expectedActiveCells = [4] - activeSegment = tm.connections.createSegment(expectedActiveCells[0]) + activeSegment = tm.createSegment(expectedActiveCells[0]) tm.connections.createSynapse(activeSegment, previousActiveCells[0], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[1], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[2], .5) @@ -121,7 +121,7 @@ def testZeroActiveColumns(self): previousActiveCells = [0, 1, 2, 3] expectedActiveCells = [4] - segment = tm.connections.createSegment(expectedActiveCells[0]) + segment = tm.createSegment(expectedActiveCells[0]) tm.connections.createSynapse(segment, previousActiveCells[0], .5) tm.connections.createSynapse(segment, previousActiveCells[1], .5) tm.connections.createSynapse(segment, previousActiveCells[2], .5) @@ -159,12 +159,12 @@ def testPredictedActiveCellsAreAlwaysWinners(self): previousActiveCells = [0, 1, 2, 3] expectedWinnerCells = [4, 6] - activeSegment1 = tm.connections.createSegment(expectedWinnerCells[0]) + activeSegment1 = tm.createSegment(expectedWinnerCells[0]) tm.connections.createSynapse(activeSegment1, previousActiveCells[0], .5) tm.connections.createSynapse(activeSegment1, previousActiveCells[1], .5) tm.connections.createSynapse(activeSegment1, previousActiveCells[2], .5) - activeSegment2 = tm.connections.createSegment(expectedWinnerCells[1]) + activeSegment2 = tm.createSegment(expectedWinnerCells[1]) tm.connections.createSynapse(activeSegment2, previousActiveCells[0], .5) tm.connections.createSynapse(activeSegment2, previousActiveCells[1], .5) tm.connections.createSynapse(activeSegment2, previousActiveCells[2], .5) @@ -194,7 +194,7 @@ def testReinforceCorrectlyActiveSegments(self): activeColumns = [1] activeCell = 5 - activeSegment = tm.connections.createSegment(activeCell) + activeSegment = tm.createSegment(activeCell) as1 = tm.connections.createSynapse(activeSegment, prevActiveCells[0], .5) as2 = tm.connections.createSynapse(activeSegment, prevActiveCells[1], .5) as3 = tm.connections.createSynapse(activeSegment, prevActiveCells[2], .5) @@ -228,7 +228,7 @@ def testReinforceSelectedMatchingSegmentInBurstingColumn(self): activeColumns = [1] burstingCells = [4,5,6,7] - selectedMatchingSegment = tm.connections.createSegment(burstingCells[0]) + selectedMatchingSegment = tm.createSegment(burstingCells[0]) as1 = tm.connections.createSynapse(selectedMatchingSegment, previousActiveCells[0], .3) as2 = tm.connections.createSynapse(selectedMatchingSegment, @@ -237,7 +237,7 @@ def testReinforceSelectedMatchingSegmentInBurstingColumn(self): previousActiveCells[2], .3) is1 = tm.connections.createSynapse(selectedMatchingSegment, 81, .3) - otherMatchingSegment = tm.connections.createSegment(burstingCells[1]) + otherMatchingSegment = tm.createSegment(burstingCells[1]) tm.connections.createSynapse(otherMatchingSegment, previousActiveCells[0], .3) tm.connections.createSynapse(otherMatchingSegment, @@ -272,7 +272,7 @@ def testNoChangeToNonselectedMatchingSegmentsInBurstingColumn(self): activeColumns = [1] burstingCells = [4,5,6,7] - selectedMatchingSegment = tm.connections.createSegment(burstingCells[0]) + selectedMatchingSegment = tm.createSegment(burstingCells[0]) tm.connections.createSynapse(selectedMatchingSegment, previousActiveCells[0], .3) tm.connections.createSynapse(selectedMatchingSegment, @@ -281,7 +281,7 @@ def testNoChangeToNonselectedMatchingSegmentsInBurstingColumn(self): previousActiveCells[2], .3) tm.connections.createSynapse(selectedMatchingSegment, 81, .3) - otherMatchingSegment = tm.connections.createSegment(burstingCells[1]) + otherMatchingSegment = tm.createSegment(burstingCells[1]) as1 = tm.connections.createSynapse(otherMatchingSegment, previousActiveCells[0], .3) as2 = tm.connections.createSynapse(otherMatchingSegment, @@ -316,20 +316,20 @@ def testNoChangeToMatchingSegmentsInPredictedActiveColumn(self): expectedActiveCells = [4] otherburstingCells = [5,6,7] - activeSegment = tm.connections.createSegment(expectedActiveCells[0]) + activeSegment = tm.createSegment(expectedActiveCells[0]) tm.connections.createSynapse(activeSegment, previousActiveCells[0], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[1], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[2], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[3], .5) - matchingSegmentOnSameCell = tm.connections.createSegment( + matchingSegmentOnSameCell = tm.createSegment( expectedActiveCells[0]) s1 = tm.connections.createSynapse(matchingSegmentOnSameCell, previousActiveCells[0], .3) s2 = tm.connections.createSynapse(matchingSegmentOnSameCell, previousActiveCells[1], .3) - matchingSegmentOnOtherCell = tm.connections.createSegment( + matchingSegmentOnOtherCell = tm.createSegment( otherburstingCells[0]) s3 = tm.connections.createSynapse(matchingSegmentOnOtherCell, previousActiveCells[0], .3) @@ -465,7 +465,7 @@ def testMatchingSegmentAddSynapsesToSubsetOfWinnerCells(self): prevWinnerCells = [0, 1, 2, 3] activeColumns = [4] - matchingSegment = tm.connections.createSegment(4) + matchingSegment = tm.createSegment(4) tm.connections.createSynapse(matchingSegment, 0, .5) tm.compute(previousActiveColumns, True) @@ -502,7 +502,7 @@ def testMatchingSegmentAddSynapsesToAllWinnerCells(self): prevWinnerCells = [0, 1] activeColumns = [4] - matchingSegment = tm.connections.createSegment(4) + matchingSegment = tm.createSegment(4) tm.connections.createSynapse(matchingSegment, 0, .5) tm.compute(previousActiveColumns, True) @@ -545,7 +545,7 @@ def testActiveSegmentGrowSynapsesAccordingToPotentialOverlap(self): prevWinnerCells = [0, 1, 2, 3, 4] activeColumns = [5] - activeSegment = tm.connections.createSegment(5) + activeSegment = tm.createSegment(5) tm.connections.createSynapse(activeSegment, 0, .5) tm.connections.createSynapse(activeSegment, 1, .5) tm.connections.createSynapse(activeSegment, 2, .2) @@ -579,7 +579,7 @@ def testDestroyWeakSynapseOnWrongPrediction(self): activeColumns = [2] expectedActiveCells = [5] - activeSegment = tm.connections.createSegment(expectedActiveCells[0]) + activeSegment = tm.createSegment(expectedActiveCells[0]) tm.connections.createSynapse(activeSegment, previousActiveCells[0], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[1], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[2], .5) @@ -612,7 +612,7 @@ def testDestroyWeakSynapseOnActiveReinforce(self): activeColumns = [2] activeCell = 5 - activeSegment = tm.connections.createSegment(activeCell) + activeSegment = tm.createSegment(activeCell) tm.connections.createSynapse(activeSegment, previousActiveCells[0], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[1], .5) tm.connections.createSynapse(activeSegment, previousActiveCells[2], .5) @@ -639,25 +639,33 @@ def testRecycleWeakestSynapseToMakeRoomForNewSynapse(self): permanenceDecrement=.02, predictedSegmentDecrement=0.0, seed=42, - maxSynapsesPerSegment=3) + maxSynapsesPerSegment=4) - prevActiveColumns = [0, 1, 2] - prevWinnerCells = [0, 1, 2] + prevActiveColumns = [1, 2, 3] + prevWinnerCells = [1, 2, 3] activeColumns = [4] - matchingSegment = tm.connections.createSegment(4) + matchingSegment = tm.createSegment(4) tm.connections.createSynapse(matchingSegment, 81, .6) - weakestSynapse = tm.connections.createSynapse(matchingSegment, 0, .11) + # Create a weak synapse. Make sure it's not so weak that permanenceIncrement + # destroys it. + tm.connections.createSynapse(matchingSegment, 0, .11) + + # Create a synapse that will match. + tm.connections.createSynapse(matchingSegment, 1, .20) + + # Create a synapse with a high permanence + tm.connections.createSynapse(matchingSegment, 31, .60) tm.compute(prevActiveColumns) self.assertEqual(prevWinnerCells, tm.getWinnerCells()) tm.compute(activeColumns) synapses = tm.connections.synapsesForSegment(matchingSegment) - self.assertEqual(3, len(synapses)) + self.assertEqual(4, len(synapses)) presynapticCells = set(synapse.presynapticCell for synapse in synapses) - self.assertFalse(0 in presynapticCells) + self.assertEqual(set([1, 2, 3, 31]), presynapticCells) def testRecycleLeastRecentlyActiveSegmentToMakeRoomForNewSegment(self): @@ -729,7 +737,7 @@ def testDestroySegmentsWithTooFewSynapsesToBeMatching(self): activeColumns = [2] expectedActiveCell = 5 - matchingSegment = tm.connections.createSegment(expectedActiveCell) + matchingSegment = tm.createSegment(expectedActiveCell) tm.connections.createSynapse(matchingSegment, prevActiveCells[0], .015) tm.connections.createSynapse(matchingSegment, prevActiveCells[1], .015) tm.connections.createSynapse(matchingSegment, prevActiveCells[2], .015) @@ -760,7 +768,7 @@ def testPunishMatchingSegmentsInInactiveColumns(self): activeColumns = [1] previousInactiveCell = 81 - activeSegment = tm.connections.createSegment(42) + activeSegment = tm.createSegment(42) as1 = tm.connections.createSynapse(activeSegment, previousActiveCells[0], .5) as2 = tm.connections.createSynapse(activeSegment, @@ -770,7 +778,7 @@ def testPunishMatchingSegmentsInInactiveColumns(self): is1 = tm.connections.createSynapse(activeSegment, previousInactiveCell, .5) - matchingSegment = tm.connections.createSegment(43) + matchingSegment = tm.createSegment(43) as4 = tm.connections.createSynapse(matchingSegment, previousActiveCells[0], .5) as5 = tm.connections.createSynapse(matchingSegment, @@ -813,9 +821,9 @@ def testAddSegmentToCellWithFewestSegments(self): nonMatchingCells = [0, 3] activeCells = [0, 1, 2, 3] - segment1 = tm.connections.createSegment(nonMatchingCells[0]) + segment1 = tm.createSegment(nonMatchingCells[0]) tm.connections.createSynapse(segment1, prevActiveCells[0], .5) - segment2 = tm.connections.createSegment(nonMatchingCells[1]) + segment2 = tm.createSegment(nonMatchingCells[1]) tm.connections.createSynapse(segment2, prevActiveCells[1], .5) tm.compute(prevActiveColumns, True) @@ -877,12 +885,12 @@ def testConnectionsNeverChangeWhenLearningDisabled(self): prevInactiveCell = 81 expectedActiveCells = [4] - correctActiveSegment = tm.connections.createSegment(expectedActiveCells[0]) + correctActiveSegment = tm.createSegment(expectedActiveCells[0]) tm.connections.createSynapse(correctActiveSegment, prevActiveCells[0], .5) tm.connections.createSynapse(correctActiveSegment, prevActiveCells[1], .5) tm.connections.createSynapse(correctActiveSegment, prevActiveCells[2], .5) - wrongMatchingSegment = tm.connections.createSegment(43) + wrongMatchingSegment = tm.createSegment(43) tm.connections.createSynapse(wrongMatchingSegment, prevActiveCells[0], .5) tm.connections.createSynapse(wrongMatchingSegment, prevActiveCells[1], .5) tm.connections.createSynapse(wrongMatchingSegment, prevInactiveCell, .5) @@ -895,6 +903,69 @@ def testConnectionsNeverChangeWhenLearningDisabled(self): self.assertEqual(before, tm.connections) + def testDestroySegmentsThenReachLimit(self): + """ Destroy some segments then verify that the maxSegmentsPerCell is still + correctly applied. + """ + tm = TemporalMemory( + columnDimensions=[32], + cellsPerColumn=1, + activationThreshold=3, + initialPermanence=.50, + connectedPermanence=.50, + minThreshold=2, + maxNewSynapseCount=3, + permanenceIncrement=.02, + permanenceDecrement=.02, + predictedSegmentDecrement=0.0, + seed=42, + maxSegmentsPerCell=2) + + segment1 = tm.createSegment(11) + segment2 = tm.createSegment(11) + + self.assertEqual(2, tm.connections.numSegments()) + tm.connections.destroySegment(segment1) + tm.connections.destroySegment(segment2) + self.assertEqual(0, tm.connections.numSegments()) + + tm.createSegment(11) + self.assertEqual(1, tm.connections.numSegments()) + tm.createSegment(11) + self.assertEqual(2, tm.connections.numSegments()) + segment3 = tm.createSegment(11) + self.assertEqual(2, tm.connections.numSegments(11)) + self.assertEqual(2, tm.connections.numSegments()) + + + def testReachSegmentLimitMultipleTimes(self): + """ Hit the maxSegmentsPerCell threshold multiple times. Make sure it + works more than once. + """ + tm = TemporalMemory( + columnDimensions=[32], + cellsPerColumn=1, + activationThreshold=3, + initialPermanence=.50, + connectedPermanence=.50, + minThreshold=2, + maxNewSynapseCount=3, + permanenceIncrement=.02, + permanenceDecrement=.02, + predictedSegmentDecrement=0.0, + seed=42, + maxSegmentsPerCell=2) + + tm.createSegment(10) + self.assertEqual(1, tm.connections.numSegments()) + tm.createSegment(10) + self.assertEqual(2, tm.connections.numSegments()) + tm.createSegment(10) + self.assertEqual(2, tm.connections.numSegments()) + tm.createSegment(10) + self.assertEqual(2, tm.connections.numSegments()) + + def testColumnForCell1D(self): tm = TemporalMemory( columnDimensions=[2048], @@ -1019,10 +1090,10 @@ def testMaxSynapsesPerSegmentGetter(self): def serializationTestPrepare(self, tm): # Create an active segment and two matching segments. # Destroy a few to exercise the code. - destroyMe1 = tm.connections.createSegment(4) + destroyMe1 = tm.createSegment(4) tm.connections.destroySegment(destroyMe1) - activeSegment = tm.connections.createSegment(4) + activeSegment = tm.createSegment(4) tm.connections.createSynapse(activeSegment, 0, 0.5) tm.connections.createSynapse(activeSegment, 1, 0.5) destroyMe2 = tm.connections.createSynapse(activeSegment, 42, 0.5) @@ -1030,12 +1101,12 @@ def serializationTestPrepare(self, tm): tm.connections.createSynapse(activeSegment, 2, 0.5) tm.connections.createSynapse(activeSegment, 3, 0.5) - matchingSegment1 = tm.connections.createSegment(8) + matchingSegment1 = tm.createSegment(8) tm.connections.createSynapse(matchingSegment1, 0, 0.4) tm.connections.createSynapse(matchingSegment1, 1, 0.4) tm.connections.createSynapse(matchingSegment1, 2, 0.4) - matchingSegment2 = tm.connections.createSegment(9) + matchingSegment2 = tm.createSegment(9) tm.connections.createSynapse(matchingSegment2, 0, 0.4) tm.connections.createSynapse(matchingSegment2, 1, 0.4) tm.connections.createSynapse(matchingSegment2, 2, 0.4)