diff --git a/requirements.txt b/requirements.txt index 1e53b6ae34..dabc6c5d44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,5 +18,5 @@ prettytable==0.7.2 # When updating nupic.bindings, also update any shared dependencies to keep # versions in sync. -nupic.bindings==1.0.3 +nupic.bindings==1.0.4 numpy==1.12.1 diff --git a/src/nupic/regions/AnomalyLikelihoodRegion.capnp b/src/nupic/algorithms/anomaly_likelihood.capnp similarity index 94% rename from src/nupic/regions/AnomalyLikelihoodRegion.capnp rename to src/nupic/algorithms/anomaly_likelihood.capnp index 40e1d757f6..7eb826249b 100644 --- a/src/nupic/regions/AnomalyLikelihoodRegion.capnp +++ b/src/nupic/algorithms/anomaly_likelihood.capnp @@ -1,6 +1,6 @@ @0x8602f38429407eb0; -struct AnomalyLikelihoodRegionProto { +struct AnomalyLikelihoodProto { iteration @0 :UInt64; historicalScores @1 :List(Score); distribution @2 :Distribution; diff --git a/src/nupic/algorithms/anomaly_likelihood.py b/src/nupic/algorithms/anomaly_likelihood.py index faf35f8fbb..0f8e6966ae 100644 --- a/src/nupic/algorithms/anomaly_likelihood.py +++ b/src/nupic/algorithms/anomaly_likelihood.py @@ -122,6 +122,12 @@ from nupic.serializable import Serializable from nupic.utils import MovingAverage +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.algorithms.anomaly_likelihood_capnp import AnomalyLikelihoodProto class AnomalyLikelihood(Serializable): """ @@ -252,12 +258,16 @@ def _calcSkipRecords(numIngested, windowSize, learningPeriod): return min(numIngested, max(0, learningPeriod - numShiftedOut)) + @classmethod + def getSchema(cls): + return AnomalyLikelihoodProto + @classmethod def read(cls, proto): """ capnp deserialization method for the anomaly likelihood object :param proto: (Object) capnp proto object specified in - nupic.regions.AnomalyLikelihoodRegion.capnp + nupic.regions.anomaly_likelihood.capnp :returns: (Object) the deserialized AnomalyLikelihood object """ @@ -303,7 +313,7 @@ def write(self, proto): """ capnp serialization method for the anomaly likelihood object :param proto: (Object) capnp proto object specified in - nupic.regions.AnomalyLikelihoodRegion.capnp + nupic.regions.anomaly_likelihood.capnp """ proto.iteration = self._iteration @@ -315,27 +325,28 @@ def write(self, proto): record.value = float(value) record.anomalyScore = float(anomalyScore) - proto.distribution.name = self._distribution["distribution"]["name"] - proto.distribution.mean = float(self._distribution["distribution"]["mean"]) - proto.distribution.variance = float(self._distribution["distribution"]["variance"]) - proto.distribution.stdev = float(self._distribution["distribution"]["stdev"]) + if self._distribution: + proto.distribution.name = self._distribution["distribution"]["name"] + proto.distribution.mean = float(self._distribution["distribution"]["mean"]) + proto.distribution.variance = float(self._distribution["distribution"]["variance"]) + proto.distribution.stdev = float(self._distribution["distribution"]["stdev"]) - proto.distribution.movingAverage.windowSize = float(self._distribution["movingAverage"]["windowSize"]) + proto.distribution.movingAverage.windowSize = float(self._distribution["movingAverage"]["windowSize"]) - historicalValues = self._distribution["movingAverage"]["historicalValues"] - pHistValues = proto.distribution.movingAverage.init( - "historicalValues", len(historicalValues)) - for i, value in enumerate(historicalValues): - pHistValues[i] = float(value) + historicalValues = self._distribution["movingAverage"]["historicalValues"] + pHistValues = proto.distribution.movingAverage.init( + "historicalValues", len(historicalValues)) + for i, value in enumerate(historicalValues): + pHistValues[i] = float(value) - #proto.distribution.movingAverage.historicalValues = self._distribution["movingAverage"]["historicalValues"] - proto.distribution.movingAverage.total = float(self._distribution["movingAverage"]["total"]) + #proto.distribution.movingAverage.historicalValues = self._distribution["movingAverage"]["historicalValues"] + proto.distribution.movingAverage.total = float(self._distribution["movingAverage"]["total"]) - historicalLikelihoods = self._distribution["historicalLikelihoods"] - pHistLikelihoods = proto.distribution.init("historicalLikelihoods", - len(historicalLikelihoods)) - for i, likelihood in enumerate(historicalLikelihoods): - pHistLikelihoods[i] = float(likelihood) + historicalLikelihoods = self._distribution["historicalLikelihoods"] + pHistLikelihoods = proto.distribution.init("historicalLikelihoods", + len(historicalLikelihoods)) + for i, likelihood in enumerate(historicalLikelihoods): + pHistLikelihoods[i] = float(likelihood) proto.probationaryPeriod = self._probationaryPeriod proto.learningPeriod = self._learningPeriod diff --git a/src/nupic/algorithms/backtracking_tm.capnp b/src/nupic/algorithms/backtracking_tm.capnp index 30b2f0467b..b1fe6663ea 100644 --- a/src/nupic/algorithms/backtracking_tm.capnp +++ b/src/nupic/algorithms/backtracking_tm.capnp @@ -32,6 +32,7 @@ struct SegmentUpdateProto { weaklyPredicting @7 :Bool; } +# Next ID: 61 struct BacktrackingTMProto { version @0 :UInt16; random @1 :RandomProto; @@ -69,7 +70,11 @@ struct BacktrackingTMProto { lrnIterationIdx @28 :UInt32; iterationIdx @29 :UInt32; segID @30 :UInt32; - currentOutput @31 :List(List(Bool)); + + currentOutput :union { + none @60 :Void; + list @31 :List(List(Bool)); + } pamCounter @32 :UInt32; collectSequenceStats @33 :Bool; diff --git a/src/nupic/algorithms/backtracking_tm.py b/src/nupic/algorithms/backtracking_tm.py index 124356b158..7b18fd2a88 100644 --- a/src/nupic/algorithms/backtracking_tm.py +++ b/src/nupic/algorithms/backtracking_tm.py @@ -470,8 +470,10 @@ def write(self, proto): proto.lrnIterationIdx = self.lrnIterationIdx proto.iterationIdx = self.iterationIdx proto.segID = self.segID - if self.currentOutput is not None: - proto.currentOutput = self.currentOutput.tolist() + if self.currentOutput is None: + proto.currentOutput.none = None + else: + proto.currentOutput.list = self.currentOutput.tolist() proto.pamCounter = self.pamCounter proto.collectSequenceStats = self.collectSequenceStats proto.resetCalled = self.resetCalled @@ -595,7 +597,11 @@ def read(cls, proto): # Initialize various structures obj._initEphemerals() - obj.currentOutput = numpy.array(proto.currentOutput, dtype='float32') + if proto.currentOutput.which() == "none": + obj.currentOutput = None + else: + obj.currentOutput = numpy.array(proto.currentOutput.list, + dtype='float32') for pattern in proto.prevLrnPatterns: obj.prevLrnPatterns.append([v for v in pattern]) diff --git a/src/nupic/algorithms/backtracking_tm_cpp.py b/src/nupic/algorithms/backtracking_tm_cpp.py index 6a24083661..747754a989 100644 --- a/src/nupic/algorithms/backtracking_tm_cpp.py +++ b/src/nupic/algorithms/backtracking_tm_cpp.py @@ -126,7 +126,7 @@ def __init__(self, # If set to False, Cells4 will *not* be treated as an ephemeral member # and full BacktrackingTMCPP pickling is possible. This is useful for testing # pickle/unpickle without saving Cells4 to an external file - self.makeCells4Ephemeral = True + self.makeCells4Ephemeral = False #--------------------------------------------------------------------------------- # Store the seed for constructing Cells4 @@ -162,6 +162,8 @@ def __init__(self, outputType = outputType, ) + if not self.makeCells4Ephemeral: + self._initCells4() @classmethod def getSchema(cls): @@ -211,6 +213,36 @@ def read(cls, proto): return obj + def _initCells4(self): + self.cells4 = Cells4(self.numberOfCols, + self.cellsPerColumn, + self.activationThreshold, + self.minThreshold, + self.newSynapseCount, + self.segUpdateValidDuration, + self.initialPerm, + self.connectedPerm, + self.permanenceMax, + self.permanenceDec, + self.permanenceInc, + self.globalDecay, + self.doPooling, + self.seed, + self.allocateStatesInCPP, + self.checkSynapseConsistency) + + self.cells4.setVerbosity(self.verbosity) + self.cells4.setPamLength(self.pamLength) + self.cells4.setMaxAge(self.maxAge) + self.cells4.setMaxInfBacktrack(self.maxInfBacktrack) + self.cells4.setMaxLrnBacktrack(self.maxLrnBacktrack) + self.cells4.setMaxSeqLength(self.maxSeqLength) + self.cells4.setMaxSegmentsPerCell(self.maxSegmentsPerCell) + self.cells4.setMaxSynapsesPerCell(self.maxSynapsesPerSegment) + + # Reset internal C++ pointers to states + self._setStatePointers() + def __setstate__(self, state): """ @@ -218,34 +250,7 @@ def __setstate__(self, state): """ super(BacktrackingTMCPP, self).__setstate__(state) if self.makeCells4Ephemeral: - self.cells4 = Cells4(self.numberOfCols, - self.cellsPerColumn, - self.activationThreshold, - self.minThreshold, - self.newSynapseCount, - self.segUpdateValidDuration, - self.initialPerm, - self.connectedPerm, - self.permanenceMax, - self.permanenceDec, - self.permanenceInc, - self.globalDecay, - self.doPooling, - self.seed, - self.allocateStatesInCPP, - self.checkSynapseConsistency) - - self.cells4.setVerbosity(self.verbosity) - self.cells4.setPamLength(self.pamLength) - self.cells4.setMaxAge(self.maxAge) - self.cells4.setMaxInfBacktrack(self.maxInfBacktrack) - self.cells4.setMaxLrnBacktrack(self.maxLrnBacktrack) - self.cells4.setMaxSeqLength(self.maxSeqLength) - self.cells4.setMaxSegmentsPerCell(self.maxSegmentsPerCell) - self.cells4.setMaxSynapsesPerCell(self.maxSynapsesPerSegment) - - # Reset internal C++ pointers to states - self._setStatePointers() + self._initCells4() def _getEphemeralMembers(self): @@ -276,33 +281,7 @@ def _initEphemerals(self): self.retrieveLearningStates = False if self.makeCells4Ephemeral: - self.cells4 = Cells4(self.numberOfCols, - self.cellsPerColumn, - self.activationThreshold, - self.minThreshold, - self.newSynapseCount, - self.segUpdateValidDuration, - self.initialPerm, - self.connectedPerm, - self.permanenceMax, - self.permanenceDec, - self.permanenceInc, - self.globalDecay, - self.doPooling, - self.seed, - self.allocateStatesInCPP, - self.checkSynapseConsistency) - - self.cells4.setVerbosity(self.verbosity) - self.cells4.setPamLength(self.pamLength) - self.cells4.setMaxAge(self.maxAge) - self.cells4.setMaxInfBacktrack(self.maxInfBacktrack) - self.cells4.setMaxLrnBacktrack(self.maxLrnBacktrack) - self.cells4.setMaxSeqLength(self.maxSeqLength) - self.cells4.setMaxSegmentsPerCell(self.maxSegmentsPerCell) - self.cells4.setMaxSynapsesPerCell(self.maxSynapsesPerSegment) - - self._setStatePointers() + self._initCells4() def saveToFile(self, filePath): """ @@ -315,6 +294,7 @@ def loadFromFile(self, filePath): """ Load Cells4 state from a file saved with :meth:`saveToFile`. """ + self._setStatePointers() self.cells4.loadFromFile(filePath) @@ -336,7 +316,7 @@ def __getattr__(self, name): """ try: - return super(BacktrackingTM, self).__getattr__(name) + return super(BacktrackingTMCPP, self).__getattr__(name) except AttributeError: raise AttributeError("'TM' object has no attribute '%s'" % name) @@ -428,8 +408,8 @@ def _copyAllocatedStates(self): assert False (activeT, activeT1, predT, predT1, colConfidenceT, colConfidenceT1, confidenceT, confidenceT1) = self.cells4.getStates() - self.confidence['t-1'] = confidenceT1.reshape((self.numberOfCols, self.cellsPerColumn)) - self.confidence['t'] = confidenceT.reshape((self.numberOfCols, self.cellsPerColumn)) + self.cellConfidence['t'] = confidenceT.reshape((self.numberOfCols, self.cellsPerColumn)) + self.cellConfidence['t-1'] = confidenceT1.reshape((self.numberOfCols, self.cellsPerColumn)) self.colConfidence['t'] = colConfidenceT.reshape(self.numberOfCols) self.colConfidence['t-1'] = colConfidenceT1.reshape(self.numberOfCols) self.infActiveState['t-1'] = activeT1.reshape((self.numberOfCols, self.cellsPerColumn)) diff --git a/src/nupic/algorithms/backtracking_tm_shim.py b/src/nupic/algorithms/backtracking_tm_shim.py index c9f49602b1..de47cc95c9 100644 --- a/src/nupic/algorithms/backtracking_tm_shim.py +++ b/src/nupic/algorithms/backtracking_tm_shim.py @@ -33,7 +33,30 @@ class MonitoredTemporalMemory(TemporalMemoryMonitorMixin, - TemporalMemory): pass + TemporalMemory): + def __init__(self, *args, **kwargs): + TemporalMemoryMonitorMixin.__init__(self, *args, **kwargs) + TemporalMemory.__init__(self, *args, **kwargs) + + @classmethod + def read(cls, proto): + """ + Intercepts TemporalMemory deserialization request in order to initialize + `TemporalMemoryMonitorMixin` state + + @param proto (DynamicStructBuilder) Proto object + + @return (TemporalMemory) TemporalMemory shim instance + """ + tm = super(TemporalMemoryMonitorMixin, cls).read(proto) + + # initialize `TemporalMemoryMonitorMixin` attributes + tm.mmName = None + tm._mmTraces = None + tm._mmData = None + tm.mmClearHistory() + tm._mmResetActive = True + return tm @@ -215,6 +238,20 @@ def __init__(self, self.infActiveState = {"t": None} + @classmethod + def read(cls, proto): + """ + Intercepts TemporalMemory deserialization request in order to initialize + `self.infActiveState` + + @param proto (DynamicStructBuilder) Proto object + + @return (TemporalMemory) TemporalMemory shim instance + """ + tm = super(MonitoredTMShim, cls).read(proto) + tm.infActiveState = {"t": None} + return tm + def compute(self, bottomUpInput, enableLearn, computeInfOutput=None): """ diff --git a/src/nupic/algorithms/connections.py b/src/nupic/algorithms/connections.py index 76f7ec66e2..6e2ae9e9e8 100644 --- a/src/nupic/algorithms/connections.py +++ b/src/nupic/algorithms/connections.py @@ -23,6 +23,12 @@ from collections import defaultdict from nupic.serializable import Serializable +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.proto.ConnectionsProto_capnp import ConnectionsProto EPSILON = 0.00001 # constant error threshold to check equality of permanences to # other floats @@ -107,6 +113,12 @@ class CellData(object): def __init__(self): self._segments = [] + def __eq__(self, other): + return self._segments == other._segments + + def __ne__(self, other): + return not self.__eq__(other) + def binSearch(arr, val): @@ -443,6 +455,11 @@ def write(self, proto): protoSynapses[k].permanence = synapse.permanence + @classmethod + def getSchema(cls): + return ConnectionsProto + + @classmethod def read(cls, proto): """ diff --git a/src/nupic/algorithms/knn_classifier.capnp b/src/nupic/algorithms/knn_classifier.capnp index f4b0f6725c..2b7edfb2cc 100644 --- a/src/nupic/algorithms/knn_classifier.capnp +++ b/src/nupic/algorithms/knn_classifier.capnp @@ -2,7 +2,7 @@ using import "/nupic/proto/SparseMatrixProto.capnp".SparseMatrixProto; -# Next ID: 31 +# Next ID: 34 struct KNNClassifierProto { # Public fields version @0 :Int32; @@ -18,7 +18,11 @@ struct KNNClassifierProto { relativeThreshold @10 :Bool; numWinners @11 :Int32; numSVDSamples @12 :Int32; - numSVDDims @13 :Text; + numSVDDims :union { + none @32 :Void; + numSVDDimsText @13 :Text; + numSVDDimsInt @31: Int32; + } fractionOfMax @14 :Float32; verbosity @15 :Int8; maxStoredPatterns @16 :Int32; @@ -28,6 +32,7 @@ struct KNNClassifierProto { # Private State memory :union { + none @33 :Void; ndarray @20 :List(List(Float64)); # NearestNeighbor inherits from SparseMatrix and shares the same schema nearestNeighbor @30 :SparseMatrixProto; diff --git a/src/nupic/algorithms/knn_classifier.py b/src/nupic/algorithms/knn_classifier.py index 760cd23925..fb4e1eeca4 100755 --- a/src/nupic/algorithms/knn_classifier.py +++ b/src/nupic/algorithms/knn_classifier.py @@ -40,6 +40,8 @@ g_debugPrefix = "KNN" KNNCLASSIFIER_VERSION = 1 +EPSILON = 0.00001 # constant error threshold to check equality of floats +EPSILON_ROUND = 5 # Used to round floats def _labeledInput(activeInputs, cellsPerCol=32): @@ -182,7 +184,7 @@ def __init__(self, k=1, sparseThreshold=0.1, relativeThreshold=False, numWinners=0, - numSVDSamples=None, + numSVDSamples=0, numSVDDims=None, fractionOfMax=None, verbosity=0, @@ -206,7 +208,7 @@ def __init__(self, k=1, self.sparseThreshold = sparseThreshold self.relativeThreshold = relativeThreshold self.numWinners = numWinners - self.numSVDSamples = numSVDSamples + self.numSVDSamples = numSVDSamples or 0 self.numSVDDims = numSVDDims self.fractionOfMax = fractionOfMax if self.numSVDDims=="adaptive": @@ -519,7 +521,7 @@ def learn(self, inputPattern, inputCategory, partitionId=None, isSparse=0, # If the input was given in sparse form, convert it to dense if necessary if isSparse > 0 and (self._vt is not None or self.distThreshold > 0 \ - or self.numSVDDims is not None or self.numSVDSamples is not None \ + or self.numSVDDims is not None or self.numSVDSamples > 0 \ or self.numWinners > 0): denseInput = numpy.zeros(isSparse) denseInput[inputPattern] = 1.0 @@ -613,9 +615,7 @@ def learn(self, inputPattern, inputCategory, partitionId=None, isSparse=0, self._categoryRecencyList.pop(leastRecentlyUsedPattern) self._numPatterns -= 1 - - - if self.numSVDDims is not None and self.numSVDSamples is not None \ + if self.numSVDDims is not None and self.numSVDSamples > 0 \ and self._numPatterns == self.numSVDSamples: self.computeSVD() @@ -1035,7 +1035,7 @@ def finishLearning(self): self.computeSVD() - def computeSVD(self, numSVDSamples=None, finalize=True): + def computeSVD(self, numSVDSamples=0, finalize=True): """ Compute the singular value decomposition (SVD). The SVD is a factorization of a real or complex matrix. It factors the matrix `a` as @@ -1062,7 +1062,7 @@ def computeSVD(self, numSVDSamples=None, finalize=True): :returns: (array) The singular values for every matrix, sorted in descending order. """ - if numSVDSamples is None: + if numSVDSamples == 0: numSVDSamples = self._numPatterns if not self.useSparseMemory: @@ -1204,14 +1204,25 @@ def read(cls, proto): knn.distanceMethod = proto.distanceMethod knn.distThreshold = proto.distThreshold knn.doBinarization = proto.doBinarization - knn.binarizationThreshold = proto.binarizationThreshold + knn.binarizationThreshold = round(proto.binarizationThreshold, + EPSILON_ROUND) knn.useSparseMemory = proto.useSparseMemory - knn.sparseThreshold = proto.sparseThreshold + knn.sparseThreshold = round(proto.sparseThreshold, EPSILON_ROUND) knn.relativeThreshold = proto.relativeThreshold knn.numWinners = proto.numWinners knn.numSVDSamples = proto.numSVDSamples - knn.numSVDDims = proto.numSVDDims - knn.fractionOfMax = proto.fractionOfMax + which = proto.numSVDDims.which() + if which == "none": + knn.numSVDDims = None + elif which == "numSVDDimsText": + knn.numSVDDims = proto.numSVDDims.numSVDDimsText + else: + knn.numSVDDims = proto.numSVDDims.numSVDDimsInt + if proto.fractionOfMax != 0: + knn.fractionOfMax = round(proto.fractionOfMax, EPSILON_ROUND) + else: + knn.fractionOfMax = None + knn.verbosity = proto.verbosity knn.maxStoredPatterns = proto.maxStoredPatterns knn.replaceDuplicates = proto.replaceDuplicates @@ -1225,17 +1236,18 @@ def read(cls, proto): # Read private state knn.clear() - if proto.memory is not None: - which = proto.memory.which() - if which == "ndarray": - knn._Memory = numpy.array(proto.memory.ndarray, dtype=numpy.float64) - elif which == "nearestNeighbor": - knn._Memory = NearestNeighbor() - knn._Memory.read(proto.memory.nearestNeighbor) + which = proto.memory.which() + if which == "ndarray": + knn._Memory = numpy.array(proto.memory.ndarray, dtype=numpy.float64) + elif which == "nearestNeighbor": + knn._Memory = NearestNeighbor() + knn._Memory.read(proto.memory.nearestNeighbor) + else: + knn._Memory = None knn._numPatterns = proto.numPatterns - if proto.m is not None: + if len(proto.m) > 0: knn._M = numpy.array(proto.m, dtype=numpy.float64) if proto.categoryList is not None: @@ -1248,13 +1260,13 @@ def read(cls, proto): knn._iterationIdx = proto.iterationIdx knn._finishedLearning = proto.finishedLearning - if proto.s is not None: + if len(proto.s) > 0: knn._s = numpy.array(proto.s, dtype=numpy.float32) - if proto.vt is not None: + if len(proto.vt) > 0: knn._vt = numpy.array(proto.vt, dtype=numpy.float32) - if proto.mean is not None: + if len(proto.mean): knn._mean = numpy.array(proto.mean, dtype=numpy.float32) return knn @@ -1267,13 +1279,23 @@ def write(self, proto): proto.exact = bool(self.exact) proto.distanceNorm = self.distanceNorm proto.distanceMethod = self.distanceMethod - proto.distThreshold = self.distThreshold + proto.distThreshold = round(self.distThreshold, EPSILON_ROUND) proto.doBinarization = bool(self.doBinarization) - proto.binarizationThreshold = self.binarizationThreshold + proto.binarizationThreshold = round(self.binarizationThreshold, + EPSILON_ROUND) proto.useSparseMemory = bool(self.useSparseMemory) - proto.sparseThreshold = self.sparseThreshold + proto.sparseThreshold = round(self.sparseThreshold, EPSILON_ROUND) proto.relativeThreshold = bool(self.relativeThreshold) proto.numWinners = self.numWinners + proto.numSVDSamples = self.numSVDSamples + if self.numSVDDims is None: + proto.numSVDDims.none = None + elif isinstance(self.numSVDDims, int): + proto.numSVDDims.numSVDDimsInt = self.numSVDDims + else: + proto.numSVDDims.numSVDDimsText = self.numSVDDims + if self.fractionOfMax is not None: + proto.fractionOfMax = round(self.fractionOfMax, EPSILON_ROUND) proto.verbosity = self.verbosity proto.maxStoredPatterns = self.maxStoredPatterns proto.replaceDuplicates = bool(self.replaceDuplicates) @@ -1281,12 +1303,13 @@ def write(self, proto): proto.minSparsity = self.minSparsity # Write private state - if self._Memory is not None: - if isinstance(self._Memory, numpy.ndarray): - proto.memory.ndarray = self._Memory.tolist() - else: - proto.memory.init("nearestNeighbor") - self._Memory.write(proto.memory.nearestNeighbor) + if self._Memory is None: + proto.memory.none = None + elif isinstance(self._Memory, numpy.ndarray): + proto.memory.ndarray = self._Memory.tolist() + else: + proto.memory.init("nearestNeighbor") + self._Memory.write(proto.memory.nearestNeighbor) proto.numPatterns = self._numPatterns diff --git a/src/nupic/algorithms/sdr_classifier.py b/src/nupic/algorithms/sdr_classifier.py index 2054811d3c..6019c4f643 100644 --- a/src/nupic/algorithms/sdr_classifier.py +++ b/src/nupic/algorithms/sdr_classifier.py @@ -103,7 +103,7 @@ class SDRClassifier(Serializable): def __init__(self, - steps=(1,), + steps=[1], alpha=0.001, actValueAlpha=0.3, verbosity=0): diff --git a/src/nupic/algorithms/spatial_pooler.py b/src/nupic/algorithms/spatial_pooler.py index 7669601651..f1fbcd07e9 100644 --- a/src/nupic/algorithms/spatial_pooler.py +++ b/src/nupic/algorithms/spatial_pooler.py @@ -40,7 +40,7 @@ VERSION = 3 PERMANENCE_EPSILON = 0.000001 - +EPSILON_ROUND = 5 class InvalidSPParamValueError(ValueError): @@ -1826,17 +1826,22 @@ def read(cls, proto): instance._columnDimensions = numpy.array(proto.columnDimensions) instance._inputDimensions = numpy.array(proto.inputDimensions) instance._potentialRadius = proto.potentialRadius - instance._potentialPct = proto.potentialPct + instance._potentialPct = round(proto.potentialPct, + EPSILON_ROUND) instance._inhibitionRadius = proto.inhibitionRadius instance._globalInhibition = proto.globalInhibition instance._numActiveColumnsPerInhArea = proto.numActiveColumnsPerInhArea instance._localAreaDensity = proto.localAreaDensity instance._stimulusThreshold = proto.stimulusThreshold - instance._synPermInactiveDec = proto.synPermInactiveDec - instance._synPermActiveInc = proto.synPermActiveInc - instance._synPermBelowStimulusInc = proto.synPermBelowStimulusInc - instance._synPermConnected = proto.synPermConnected - instance._minPctOverlapDutyCycles = proto.minPctOverlapDutyCycles + instance._synPermInactiveDec = round(proto.synPermInactiveDec, + EPSILON_ROUND) + instance._synPermActiveInc = round(proto.synPermActiveInc, EPSILON_ROUND) + instance._synPermBelowStimulusInc = round(proto.synPermBelowStimulusInc, + EPSILON_ROUND) + instance._synPermConnected = round(proto.synPermConnected, + EPSILON_ROUND) + instance._minPctOverlapDutyCycles = round(proto.minPctOverlapDutyCycles, + EPSILON_ROUND) instance._dutyCyclePeriod = proto.dutyCyclePeriod instance._boostStrength = proto.boostStrength instance._wrapAround = proto.wrapAround @@ -1844,7 +1849,8 @@ def read(cls, proto): instance._synPermMin = proto.synPermMin instance._synPermMax = proto.synPermMax - instance._synPermTrimThreshold = proto.synPermTrimThreshold + instance._synPermTrimThreshold = round(proto.synPermTrimThreshold, + EPSILON_ROUND) # TODO: These two overlaps attributes aren't currently saved. instance._overlaps = numpy.zeros(numColumns, dtype=realDType) diff --git a/src/nupic/algorithms/temporal_memory.py b/src/nupic/algorithms/temporal_memory.py index 1235d3c910..937c5cf976 100644 --- a/src/nupic/algorithms/temporal_memory.py +++ b/src/nupic/algorithms/temporal_memory.py @@ -32,8 +32,16 @@ from nupic.serializable import Serializable from nupic.support.group_by import groupby2 +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.proto.TemporalMemoryProto_capnp import TemporalMemoryProto + EPSILON = 0.00001 # constant error threshold to check equality of permanences to # other floats +EPSILON_ROUND = 5 # Used to round floats @@ -1133,12 +1141,12 @@ def write(self, proto): proto.columnDimensions = list(self.columnDimensions) proto.cellsPerColumn = self.cellsPerColumn proto.activationThreshold = self.activationThreshold - proto.initialPermanence = self.initialPermanence - proto.connectedPermanence = self.connectedPermanence + proto.initialPermanence = round(self.initialPermanence, EPSILON_ROUND) + proto.connectedPermanence = round(self.connectedPermanence, EPSILON_ROUND) proto.minThreshold = self.minThreshold proto.maxNewSynapseCount = self.maxNewSynapseCount - proto.permanenceIncrement = self.permanenceIncrement - proto.permanenceDecrement = self.permanenceDecrement + proto.permanenceIncrement = round(self.permanenceIncrement, EPSILON_ROUND) + proto.permanenceDecrement = round(self.permanenceDecrement, EPSILON_ROUND) proto.predictedSegmentDecrement = self.predictedSegmentDecrement proto.maxSegmentsPerCell = self.maxSegmentsPerCell @@ -1189,6 +1197,11 @@ def write(self, proto): protoLastUsedIteration[i].number = lastUsed + @classmethod + def getSchema(cls): + return TemporalMemoryProto + + @classmethod def read(cls, proto): """ @@ -1206,13 +1219,14 @@ def read(cls, proto): tm.columnDimensions = tuple(proto.columnDimensions) tm.cellsPerColumn = int(proto.cellsPerColumn) tm.activationThreshold = int(proto.activationThreshold) - tm.initialPermanence = proto.initialPermanence - tm.connectedPermanence = proto.connectedPermanence + tm.initialPermanence = round(proto.initialPermanence, EPSILON_ROUND) + tm.connectedPermanence = round(proto.connectedPermanence, EPSILON_ROUND) tm.minThreshold = int(proto.minThreshold) tm.maxNewSynapseCount = int(proto.maxNewSynapseCount) - tm.permanenceIncrement = proto.permanenceIncrement - tm.permanenceDecrement = proto.permanenceDecrement - tm.predictedSegmentDecrement = proto.predictedSegmentDecrement + tm.permanenceIncrement = round(proto.permanenceIncrement, EPSILON_ROUND) + tm.permanenceDecrement = round(proto.permanenceDecrement, EPSILON_ROUND) + tm.predictedSegmentDecrement = round(proto.predictedSegmentDecrement, + EPSILON_ROUND) tm.maxSegmentsPerCell = int(proto.maxSegmentsPerCell) tm.maxSynapsesPerSegment = int(proto.maxSynapsesPerSegment) diff --git a/src/nupic/algorithms/temporal_memory_shim.capnp b/src/nupic/algorithms/temporal_memory_shim.capnp new file mode 100644 index 0000000000..edffd2fb9e --- /dev/null +++ b/src/nupic/algorithms/temporal_memory_shim.capnp @@ -0,0 +1,11 @@ +@0xbf2d8ef193440c2f + +using import "/nupic/algorithms/backtracking_tm_cpp_capnp".BacktrackingTMCppProto; +using import "/nupic/proto/ConnectionsProto_capnp".ConnectionsProto; + +# Next ID: 3 +struct TemporalMemoryShimProto { + baseTM @0 :BacktrackingTMCppProto; + predictiveCells @1 :List(UInt32); + connections @2 :ConnectionsProto; +} diff --git a/src/nupic/algorithms/temporal_memory_shim.py b/src/nupic/algorithms/temporal_memory_shim.py index f1a5563c84..176ac5b57e 100644 --- a/src/nupic/algorithms/temporal_memory_shim.py +++ b/src/nupic/algorithms/temporal_memory_shim.py @@ -30,6 +30,13 @@ from nupic.algorithms.backtracking_tm_cpp import BacktrackingTMCPP from nupic.algorithms.connections import Connections from nupic.math import GetNTAReal +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.algorithms.temporal_memory_shim_capnp import ( + TemporalMemoryShimProto) @@ -94,3 +101,29 @@ def compute(self, activeColumns, learn=True): predictedState = self.getPredictedState() self.predictiveCells = set(numpy.flatnonzero(predictedState)) + + + @classmethod + def getSchema(cls): + return TemporalMemoryShimProto + + + @classmethod + def read(cls, proto): + """Deserialize from proto instance. + + :param proto: (TemporalMemoryShimProto) the proto instance to read from + """ + tm = super(TemporalMemoryShim, cls).read(proto.baseTM) + tm.predictiveCells = set(proto.predictedState) + tm.connections = Connections.read(proto.conncetions) + + + def write(self, proto): + """Populate serialization proto instance. + + :param proto: (TemporalMemoryShimProto) the proto instance to populate + """ + super(TemporalMemoryShim, self).write(proto.baseTM) + proto.connections.write(self.connections) + proto.predictiveCells = self.predictiveCells diff --git a/src/nupic/encoders/adaptive_scalar.py b/src/nupic/encoders/adaptive_scalar.py index 0dbc19e138..142dba12f3 100644 --- a/src/nupic/encoders/adaptive_scalar.py +++ b/src/nupic/encoders/adaptive_scalar.py @@ -26,6 +26,13 @@ from nupic.encoders.scalar import ScalarEncoder from nupic.utils import MovingAverage +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.adaptive_scalar_capnp import AdaptiveScalarEncoderProto + class AdaptiveScalarEncoder(ScalarEncoder): @@ -211,6 +218,10 @@ def __str__(self): string += " padding: {padding}".format(padding = self.padding) return string + @classmethod + def getSchema(cls): + return AdaptiveScalarEncoderProto + @classmethod def read(cls, proto): encoder = super(AdaptiveScalarEncoder, cls).read(proto) diff --git a/src/nupic/encoders/category.py b/src/nupic/encoders/category.py index 3f0e5836dc..6144ac453b 100644 --- a/src/nupic/encoders/category.py +++ b/src/nupic/encoders/category.py @@ -26,6 +26,12 @@ from nupic.encoders.base import Encoder, EncoderResult from nupic.encoders.scalar import ScalarEncoder +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.category_capnp import CategoryEncoderProto UNKNOWN = "" @@ -222,6 +228,10 @@ def topDownCompute(self, encoded): encoding=encoderResult.encoding) + @classmethod + def getSchema(cls): + return CategoryEncoderProto + @classmethod def read(cls, proto): encoder = object.__new__(cls) @@ -238,7 +248,9 @@ def read(cls, proto): in encoder.indexToCategory.items() if category != UNKNOWN} encoder._topDownMappingM = None + encoder.ncategories = len(proto.indexToCategory) encoder._bucketValues = None + encoder.encoders = None return encoder diff --git a/src/nupic/encoders/coordinate.py b/src/nupic/encoders/coordinate.py index 4f234b670f..128a4f03e3 100644 --- a/src/nupic/encoders/coordinate.py +++ b/src/nupic/encoders/coordinate.py @@ -27,6 +27,13 @@ from nupic.encoders.base import Encoder +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.coordinate_capnp import CoordinateEncoderProto + class CoordinateEncoder(Encoder): """ @@ -188,6 +195,10 @@ def __str__(self): string += "\n n: {n}".format(n=self.n) return string + @classmethod + def getSchema(cls): + return CoordinateEncoderProto + @classmethod def read(cls, proto): @@ -196,6 +207,7 @@ def read(cls, proto): encoder.n = proto.n encoder.verbosity = proto.verbosity encoder.name = proto.name + encoder.encoders = None return encoder diff --git a/src/nupic/encoders/date.py b/src/nupic/encoders/date.py index 2bc234376a..f7dd5c8a0e 100644 --- a/src/nupic/encoders/date.py +++ b/src/nupic/encoders/date.py @@ -26,6 +26,12 @@ from nupic.data import SENTINEL_VALUE_FOR_MISSING_DATA from nupic.encoders.base import Encoder from nupic.encoders.scalar import ScalarEncoder +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.date_capnp import DateEncoderProto @@ -399,6 +405,9 @@ def encodeIntoArray(self, input, output): def getDescription(self): return self.description + @classmethod + def getSchema(cls): + return DateEncoderProto @classmethod def read(cls, proto): diff --git a/src/nupic/encoders/delta.capnp b/src/nupic/encoders/delta.capnp index f608061215..aeb98c3b0b 100644 --- a/src/nupic/encoders/delta.capnp +++ b/src/nupic/encoders/delta.capnp @@ -2,6 +2,7 @@ using import "/nupic/encoders/adaptive_scalar.capnp".AdaptiveScalarEncoderProto; +# Next ID: 8 struct DeltaEncoderProto { width @0 :UInt32; name @1 :Text; @@ -10,4 +11,5 @@ struct DeltaEncoderProto { prevAbsolute @4 :Float32; prevDelta @5 :Float32; stateLock @6 :Bool; + learningEnabled @7: Bool; } diff --git a/src/nupic/encoders/delta.py b/src/nupic/encoders/delta.py index 70adf3c5f9..e0fb2e478d 100644 --- a/src/nupic/encoders/delta.py +++ b/src/nupic/encoders/delta.py @@ -25,6 +25,13 @@ from nupic.encoders.adaptive_scalar import AdaptiveScalarEncoder from nupic.encoders.base import EncoderResult +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.delta_capnp import DeltaEncoderProto + class DeltaEncoder(AdaptiveScalarEncoder): """ @@ -109,6 +116,11 @@ def topDownCompute(self, encoded): return ret + @classmethod + def getSchema(cls): + return DeltaEncoderProto + + @classmethod def read(cls, proto): encoder = object.__new__(cls) @@ -118,9 +130,12 @@ def read(cls, proto): encoder._adaptiveScalarEnc = ( AdaptiveScalarEncoder.read(proto.adaptiveScalarEnc) ) - encoder._prevAbsolute = proto.prevAbsolute - encoder._prevDelta = proto.prevDelta + encoder._prevAbsolute = None if proto.prevAbsolute == 0 else proto.prevAbsolute + encoder._prevDelta = None if proto.prevDelta == 0 else proto.prevDelta encoder._stateLock = proto.stateLock + encoder._learningEnabled = proto.learningEnabled + encoder.description = [] + encoder.encoders = None return encoder @@ -129,6 +144,9 @@ def write(self, proto): proto.name = self.name or "" proto.n = self.n self._adaptiveScalarEnc.write(proto.adaptiveScalarEnc) - proto.prevAbsolute = self._prevAbsolute - proto.prevDelta = self._prevDelta + if self._prevAbsolute: + proto.prevAbsolute = self._prevAbsolute + if self._prevDelta: + proto.prevDelta = self._prevDelta proto.stateLock = self._stateLock + proto.learningEnabled = self._learningEnabled diff --git a/src/nupic/encoders/geospatial_coordinate.py b/src/nupic/encoders/geospatial_coordinate.py index baf9e06ba9..56d48f967c 100644 --- a/src/nupic/encoders/geospatial_coordinate.py +++ b/src/nupic/encoders/geospatial_coordinate.py @@ -25,6 +25,13 @@ from pyproj import Proj, transform from nupic.encoders import CoordinateEncoder +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.geospatial_coordinate_capnp import GeospatialCoordinateEncoderProto + # From http://spatialreference.org/ref/epsg/popular-visualisation-crs-mercator/ @@ -135,6 +142,11 @@ def __str__(self): return string + @classmethod + def getSchema(cls): + return GeospatialCoordinateEncoderProto + + @classmethod def read(cls, proto): encoder = super(GeospatialCoordinateEncoder, cls).read(proto) diff --git a/src/nupic/encoders/logarithm.py b/src/nupic/encoders/logarithm.py index c73eba22a7..37e5c3af3a 100644 --- a/src/nupic/encoders/logarithm.py +++ b/src/nupic/encoders/logarithm.py @@ -26,7 +26,14 @@ from nupic.data.field_meta import FieldMetaType from nupic.encoders.base import Encoder, EncoderResult from nupic.encoders import ScalarEncoder +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.logarithm_capnp import LogEncoderProto +EPSILON_ROUND = 7 # Used to round floats class LogEncoder(Encoder): """ @@ -282,20 +289,25 @@ def closenessScores(self, expValues, actValues, fractional=True): return numpy.array([closeness]) + @classmethod + def getSchema(cls): + return LogEncoderProto + @classmethod def read(cls, proto): encoder = object.__new__(cls) encoder.verbosity = proto.verbosity - encoder.minScaledValue = proto.minScaledValue - encoder.maxScaledValue = proto.maxScaledValue + encoder.minScaledValue = round(proto.minScaledValue, EPSILON_ROUND) + encoder.maxScaledValue = round(proto.maxScaledValue, EPSILON_ROUND) encoder.clipInput = proto.clipInput - encoder.minval = proto.minval - encoder.maxval = proto.maxval + encoder.minval = round(proto.minval, EPSILON_ROUND) + encoder.maxval = round(proto.maxval, EPSILON_ROUND) encoder.encoder = ScalarEncoder.read(proto.encoder) encoder.name = proto.name encoder.width = encoder.encoder.getWidth() encoder.description = [(encoder.name, 0)] encoder._bucketValues = None + encoder.encoders = None return encoder diff --git a/src/nupic/encoders/multi.py b/src/nupic/encoders/multi.py index 2e891fbd66..68d06dff7f 100644 --- a/src/nupic/encoders/multi.py +++ b/src/nupic/encoders/multi.py @@ -33,6 +33,12 @@ GeospatialCoordinateEncoder, RandomDistributedScalarEncoder) +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.multi_capnp import MultiEncoderProto # Map class to Cap'n Proto schema union attribute _CLASS_ATTR_MAP = { @@ -70,6 +76,8 @@ def __init__(self, encoderDefinitions=None): self.encoders = [] self.description = [] self.name = '' + self._flattenedEncoderList = None + self._flattenedFieldTypeList = None if encoderDefinitions is not None: self.addMultipleEncoders(encoderDefinitions) @@ -91,8 +99,6 @@ def addEncoder(self, name, encoder): self.description.append((d[0], d[1] + self.width)) self.width += encoder.getWidth() - self._flattenedEncoderList = None - self._flattenedFieldTypeList = None def encodeIntoArray(self, obj, output): @@ -177,11 +183,17 @@ def addMultipleEncoders(self, fieldEncodings): "that were provided are: %s" % (encoderName, fieldParams)) raise + @classmethod + def getSchema(cls): + return MultiEncoderProto + @classmethod def read(cls, proto): encoder = object.__new__(cls) + encoder._flattenedEncoderList = None + encoder._flattenedFieldTypeList = None encoder.encoders = [None] * len(proto.encoders) encoder.width = 0 @@ -201,7 +213,8 @@ def read(cls, proto): encoder.width += encoder.encoders[index][1].getWidth() # Derive description from encoder list - encoder.description = [(enc[1].name, enc[2]) for enc in encoder.encoders] + encoder.description = [(enc[1].name, int(enc[2])) + for enc in encoder.encoders] encoder.name = proto.name return encoder diff --git a/src/nupic/encoders/pass_through.py b/src/nupic/encoders/pass_through.py index e974fc67da..a37f011d61 100644 --- a/src/nupic/encoders/pass_through.py +++ b/src/nupic/encoders/pass_through.py @@ -24,6 +24,13 @@ from nupic.encoders.base import Encoder +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.pass_through_capnp import PassThroughEncoderProto + class PassThroughEncoder(Encoder): """ @@ -143,6 +150,10 @@ def closenessScores(self, expValues, actValues, **kwargs): return numpy.array([r]) + @classmethod + def getSchema(cls): + return PassThroughEncoderProto + @classmethod def read(cls, proto): diff --git a/src/nupic/encoders/random_distributed_scalar.capnp b/src/nupic/encoders/random_distributed_scalar.capnp index 931e768346..ed36f099d4 100644 --- a/src/nupic/encoders/random_distributed_scalar.capnp +++ b/src/nupic/encoders/random_distributed_scalar.capnp @@ -2,23 +2,27 @@ using import "/nupic/proto/RandomProto.capnp".RandomProto; -# Next ID: 10 +# Next ID: 13 struct RandomDistributedScalarEncoderProto { resolution @0 :Float32; w @1 :UInt32; n @2 :UInt32; name @3 :Text; - offset @4 :Float32; + offset :union { + none @12 :Void; + value @4 :Float32; + } random @5 :RandomProto; verbosity @6 :UInt8; minIndex @7 :UInt32; maxIndex @8 :UInt32; bucketMap @9 :List(BucketMapping); + maxOverlap @10 :UInt32; + numTries @11: UInt32; # Next ID: 2 struct BucketMapping { key @0 :UInt32; value @1 :List(UInt32); } - } diff --git a/src/nupic/encoders/random_distributed_scalar.py b/src/nupic/encoders/random_distributed_scalar.py index 4c289073b8..5728fef7fa 100644 --- a/src/nupic/encoders/random_distributed_scalar.py +++ b/src/nupic/encoders/random_distributed_scalar.py @@ -30,6 +30,13 @@ from nupic.encoders.base import Encoder from nupic.bindings.math import Random as NupicRandom +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.random_distributed_scalar_capnp import ( + RandomDistributedScalarEncoderProto) INITIAL_BUCKETS = 1000 @@ -456,6 +463,10 @@ def __str__(self): string += str(self.bucketMap) return string + @classmethod + def getSchema(cls): + return RandomDistributedScalarEncoderProto + @classmethod def read(cls, proto): encoder = object.__new__(cls) @@ -463,7 +474,10 @@ def read(cls, proto): encoder.w = proto.w encoder.n = proto.n encoder.name = proto.name - encoder._offset = proto.offset + if proto.offset.which() == "none": + encoder._offset = None + else: + encoder._offset = proto.offset.value encoder.random = NupicRandom() encoder.random.read(proto.random) encoder.resolution = proto.resolution @@ -472,6 +486,8 @@ def read(cls, proto): encoder.maxIndex = proto.maxIndex encoder.encoders = None encoder._maxBuckets = INITIAL_BUCKETS + encoder._maxOverlap = proto.maxOverlap or 0 + encoder.numTries = proto.numTries or 0 encoder.bucketMap = {x.key: numpy.array(x.value, dtype=numpy.uint32) for x in proto.bucketMap} @@ -483,10 +499,15 @@ def write(self, proto): proto.w = self.w proto.n = self.n proto.name = self.name - proto.offset = self._offset + if self._offset is None: + proto.offset.none = None + else: + proto.offset.value = self._offset self.random.write(proto.random) proto.verbosity = self.verbosity proto.minIndex = self.minIndex proto.maxIndex = self.maxIndex proto.bucketMap = [{"key": key, "value": value.tolist()} for key, value in self.bucketMap.items()] + proto.numTries = self.numTries + proto.maxOverlap = self._maxOverlap diff --git a/src/nupic/encoders/scalar.py b/src/nupic/encoders/scalar.py index 99ed3abc98..4ae7947880 100644 --- a/src/nupic/encoders/scalar.py +++ b/src/nupic/encoders/scalar.py @@ -29,6 +29,12 @@ from nupic.bindings.math import SM32, GetNTAReal from nupic.encoders.base import Encoder, EncoderResult +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.scalar_capnp import ScalarEncoderProto DEFAULT_RADIUS = 0 @@ -720,6 +726,11 @@ def __str__(self): return string + @classmethod + def getSchema(cls): + return ScalarEncoderProto + + @classmethod def read(cls, proto): if proto.n is not None: diff --git a/src/nupic/encoders/sdr_category.capnp b/src/nupic/encoders/sdr_category.capnp index ca6f04f0b8..a753ad1549 100644 --- a/src/nupic/encoders/sdr_category.capnp +++ b/src/nupic/encoders/sdr_category.capnp @@ -2,7 +2,7 @@ using import "/nupic/proto/RandomProto.capnp".RandomProto; -# Next ID: 7 +# Next ID: 8 struct SDRCategoryEncoderProto { n @0 :UInt32; w @1 :UInt32; @@ -11,4 +11,5 @@ struct SDRCategoryEncoderProto { name @4 :Text; categories @5 :List(Text); sdrs @6 :List(List(UInt8)); + learningEnabled @7: Bool; } diff --git a/src/nupic/encoders/sdr_category.py b/src/nupic/encoders/sdr_category.py index 62a3269ca8..1c6f97f9bc 100644 --- a/src/nupic/encoders/sdr_category.py +++ b/src/nupic/encoders/sdr_category.py @@ -27,6 +27,12 @@ from nupic.encoders.base import Encoder, EncoderResult from nupic.bindings.math import SM32, GetNTAReal, Random as NupicRandom +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.encoders.sdr_category_capnp import SDRCategoryEncoderProto class SDRCategoryEncoder(Encoder): @@ -357,6 +363,10 @@ def closenessScores(self, expValues, actValues, fractional=True): return numpy.array([closeness]) + @classmethod + def getSchema(cls): + return SDRCategoryEncoderProto + @classmethod def read(cls, proto): encoder = object.__new__(cls) @@ -375,8 +385,11 @@ def read(cls, proto): for index, category in enumerate(encoder.categories)} encoder.ncategories = len(encoder.categories) - encoder._learningEnabled = False + encoder._learningEnabled = proto.learningEnabled encoder._initOverlap() + encoder._topDownMappingM = None + encoder._topDownValues = None + encoder.encoders = None return encoder @@ -389,3 +402,4 @@ def write(self, proto): proto.name = self.name proto.categories = self.categories proto.sdrs = self.sdrs.tolist() + proto.learningEnabled = self._learningEnabled diff --git a/src/nupic/engine/__init__.py b/src/nupic/engine/__init__.py index cc09187a5b..a634714be3 100644 --- a/src/nupic/engine/__init__.py +++ b/src/nupic/engine/__init__.py @@ -284,8 +284,10 @@ def values(self): def items(self): return zip(self.keys(), self.values()) - def __cmp__(self, other): + def __eq__(self, other): return self.collection == other.collection + def __ne__(self, other): + return not self.__eq__(other) def __hash__(self): return hash(self.collection) @@ -480,10 +482,14 @@ def __hash__(self): """Hash a region""" return self._region.__hash__() - def __cmp__(self, other): + def __eq__(self, other): """Compare regions""" return self._region == other._region + def __ne__(self, other): + """Compare regions""" + return self._region != other._region + def _getParameterMethods(self, paramName): """Returns functions to set/get the parameter. These are the strongly typed functions get/setParameterUInt32, etc. diff --git a/src/nupic/frameworks/opf/HTMPredictionModelProto.capnp b/src/nupic/frameworks/opf/HTMPredictionModelProto.capnp index 52a0b9d388..331f33625a 100644 --- a/src/nupic/frameworks/opf/HTMPredictionModelProto.capnp +++ b/src/nupic/frameworks/opf/HTMPredictionModelProto.capnp @@ -4,11 +4,27 @@ using import "/nupic/proto/NetworkProto.capnp".NetworkProto; using import "/nupic/frameworks/opf/model.capnp".ModelProto; using import "/nupic/frameworks/opf/opf_utils.capnp".InferenceType; -# Next ID: 5 +# Next ID: 15 struct HTMPredictionModelProto { modelBase @0 :ModelProto; numRunCalls @1 :UInt32; minLikelihoodThreshold @2 :Float32; maxPredictionsPerStep @3 :UInt32; network @4 :NetworkProto; + spLearningEnabled @5 :Bool; + tpLearningEnabled @6 :Bool; + predictedFieldIdx :union { + none @12 :Void; + value @7 :UInt32; + } + predictedFieldName :union { + none @13 :Void; + value @8 :Text; + } + trainSPNetOnlyIfRequested @9: Bool; + finishedLearning @10: Bool; + numFields :union { + none @14 :Void; + value @11 :UInt32; + } } diff --git a/src/nupic/frameworks/opf/htm_prediction_model.py b/src/nupic/frameworks/opf/htm_prediction_model.py index 1b9c52315a..970d4fd77b 100644 --- a/src/nupic/frameworks/opf/htm_prediction_model.py +++ b/src/nupic/frameworks/opf/htm_prediction_model.py @@ -65,6 +65,7 @@ DEFAULT_ANOMALY_THRESHOLD = 1.1 DEFAULT_ANOMALY_CACHESIZE = 10000 +EPSILON_ROUND = 7 def requireAnomalyModel(func): """ @@ -101,6 +102,12 @@ def __repr__(self): return "NetworkInfo(net=%r, statsCollectors=%r)" % ( self.net, self.statsCollectors) + def __eq__(self, other): + return self.net == other.net and \ + self.statsCollectors == other.statsCollectors + + def __ne__(self, other): + return not self.__eq__(other) class HTMPredictionModel(Model): @@ -1329,6 +1336,22 @@ def write(self, proto): proto.maxPredictionsPerStep = self._maxPredictionsPerStep self._netInfo.net.write(proto.network) + proto.spLearningEnabled = self.__spLearningEnabled + proto.tpLearningEnabled = self.__tpLearningEnabled + if self._predictedFieldIdx is None: + proto.predictedFieldIdx.none = None + else: + proto.predictedFieldIdx.value = self._predictedFieldIdx + if self._predictedFieldName is None: + proto.predictedFieldName.none = None + else: + proto.predictedFieldName.value = self._predictedFieldName + if self._numFields is None: + proto.numFields.none = None + else: + proto.numFields.value = self._numFields + proto.trainSPNetOnlyIfRequested = self.__trainSPNetOnlyIfRequested + proto.finishedLearning = self.__finishedLearning @classmethod @@ -1336,28 +1359,55 @@ def read(cls, proto): """ :param proto: capnp HTMPredictionModelProto message reader """ - network = Network.read(proto.network) - spEnable = ("SP" in network.regions) - tmEnable = ("TM" in network.regions) - clEnable = ("Classifier" in network.regions) + obj = object.__new__(cls) + # model.capnp + super(HTMPredictionModel, obj).__init__(proto=proto.modelBase) + + # HTMPredictionModelProto.capnp + obj._minLikelihoodThreshold = round(proto.minLikelihoodThreshold, + EPSILON_ROUND) + obj._maxPredictionsPerStep = proto.maxPredictionsPerStep - model = cls(spEnable=spEnable, - tmEnable=tmEnable, - clEnable=clEnable, - network=network, - baseProto=proto.modelBase) + network = Network.read(proto.network) + obj._hasSP = ("SP" in network.regions) + obj._hasTP = ("TM" in network.regions) + obj._hasCL = ("Classifier" in network.regions) + obj._netInfo = NetworkInfo(net=network, statsCollectors=[]) + + obj.__spLearningEnabled = bool(proto.spLearningEnabled) + obj.__tpLearningEnabled = bool(proto.tpLearningEnabled) + obj.__numRunCalls = proto.numRunCalls + + obj._classifierInputEncoder = None + if proto.predictedFieldIdx.which() == "none": + obj._predictedFieldIdx = None + else: + obj._predictedFieldIdx = proto.predictedFieldIdx.value + if proto.predictedFieldName.which() == "none": + obj._predictedFieldName = None + else: + obj._predictedFieldName = proto.predictedFieldName.value + obj._numFields = proto.numFields + if proto.numFields.which() == "none": + obj._numFields = None + else: + obj._numFields = proto.numFields.value + obj.__trainSPNetOnlyIfRequested = proto.trainSPNetOnlyIfRequested + obj.__finishedLearning = proto.finishedLearning + obj._input = None + sensor = network.regions['sensor'].getSelf() + sensor.dataSource = DataBuffer() + network.initialize() - model.__numRunCalls = proto.numRunCalls - model._minLikelihoodThreshold = proto.minLikelihoodThreshold - model._maxPredictionsPerStep = proto.maxPredictionsPerStep - model._getSensorRegion().getSelf().dataSource = DataBuffer() - model._netInfo.net.initialize() + obj.__logger = initLogger(obj) + obj.__logger.debug("Instantiating %s." % obj.__myClassName) # Mark end of restoration from state - model.__restoringFromState = False + obj.__restoringFromState = False + obj.__restoringFromV1 = False - return model + return obj def _serializeExtraData(self, extraDataDir): diff --git a/src/nupic/frameworks/opf/previous_value_model.py b/src/nupic/frameworks/opf/previous_value_model.py index ffc2327b86..d060bb4c33 100644 --- a/src/nupic/frameworks/opf/previous_value_model.py +++ b/src/nupic/frameworks/opf/previous_value_model.py @@ -144,7 +144,8 @@ def write(self, proto): proto.fieldNames = self._fieldNames proto.fieldTypes = self._fieldTypes - proto.predictedField = self._predictedField + if self._predictedField: + proto.predictedField = self._predictedField proto.predictionSteps = self._predictionSteps @@ -161,8 +162,10 @@ def read(cls, proto): super(PreviousValueModel, instance).__init__(proto=proto.modelBase) instance._logger = opf_utils.initLogger(instance) - - instance._predictedField = proto.predictedField + if len(proto.predictedField): + instance._predictedField = proto.predictedField + else: + instance._predictedField = None instance._fieldNames = list(proto.fieldNames) instance._fieldTypes = list(proto.fieldTypes) instance._predictionSteps = list(proto.predictionSteps) diff --git a/src/nupic/frameworks/opf/two_gram_model.py b/src/nupic/frameworks/opf/two_gram_model.py index 17e4cd02c9..b9ebc9c41c 100644 --- a/src/nupic/frameworks/opf/two_gram_model.py +++ b/src/nupic/frameworks/opf/two_gram_model.py @@ -59,7 +59,7 @@ def __init__(self, inferenceType=InferenceType.TemporalNextStep, self._learningEnabled = True self._encoder = encoders.MultiEncoder(encoderParams) self._fieldNames = self._encoder.getScalarNames() - self._prevValues = [None] * len(self._fieldNames) + self._prevValues = [] * len(self._fieldNames) self._twoGramDicts = [dict() for _ in xrange(len(self._fieldNames))] def run(self, inputRecord): diff --git a/src/nupic/regions/SDRClassifierRegion.capnp b/src/nupic/regions/SDRClassifierRegion.capnp index 74ce1bb709..1f63e9e6e6 100644 --- a/src/nupic/regions/SDRClassifierRegion.capnp +++ b/src/nupic/regions/SDRClassifierRegion.capnp @@ -2,7 +2,7 @@ using import "/nupic/proto/SdrClassifier.capnp".SdrClassifierProto; -# Next ID: 6 +# Next ID: 9 struct SDRClassifierRegionProto { implementation @0 :Text; sdrClassifier @1 :SdrClassifierProto; @@ -10,4 +10,7 @@ struct SDRClassifierRegionProto { alpha @3 :Float32; verbosity @4 :UInt32; maxCategoryCount @5 :UInt32; + learningMode @6 :Bool; + inferenceMode @7 :Bool; + recordNum @8 :UInt32; } diff --git a/src/nupic/regions/anomaly_likelihood_region.py b/src/nupic/regions/anomaly_likelihood_region.py index 40b8f0cc9a..cf0b450b46 100644 --- a/src/nupic/regions/anomaly_likelihood_region.py +++ b/src/nupic/regions/anomaly_likelihood_region.py @@ -126,6 +126,9 @@ def __eq__(self, other): def __ne__(self, other): return not self == other + @classmethod + def getSchema(cls): + return AnomalyLikelihood.getSchema() @classmethod def read(cls, proto): diff --git a/src/nupic/regions/anomaly_region.py b/src/nupic/regions/anomaly_region.py index 4e228cd23b..98d87fa3eb 100644 --- a/src/nupic/regions/anomaly_region.py +++ b/src/nupic/regions/anomaly_region.py @@ -27,6 +27,12 @@ from nupic.bindings.regions.PyRegion import PyRegion from nupic.serializable import Serializable +try: + import capnp +except ImportError: + capnp = None +if capnp: + from nupic.regions.AnomalyRegion_capnp import AnomalyRegionProto class AnomalyRegion(PyRegion, Serializable): @@ -101,15 +107,20 @@ def __ne__(self, other): return not self == other + @classmethod + def getSchema(cls): + return AnomalyRegionProto + @classmethod def read(cls, proto): anomalyRegion = object.__new__(cls) - anomalyRegion.prevPredictedColumns = numpy.array(proto.prevPredictedColumns) + anomalyRegion.prevPredictedColumns = numpy.array(proto.prevPredictedColumns, + dtype=numpy.float32) return anomalyRegion def write(self, proto): - proto.prevPredictedColumns = self.prevPredictedColumns.tolist() + proto.prevPredictedColumns = self.prevPredictedColumns.ravel().tolist() def initialize(self): @@ -122,6 +133,7 @@ def compute(self, inputs, outputs): rawAnomalyScore = anomaly.computeRawAnomalyScore( activeColumns, self.prevPredictedColumns) - self.prevPredictedColumns = inputs["predictedColumns"].nonzero()[0] + self.prevPredictedColumns = numpy.array( + inputs["predictedColumns"].nonzero()[0], dtype=numpy.float32) outputs["rawAnomalyScore"][0] = rawAnomalyScore diff --git a/src/nupic/regions/knn_anomaly_classifier_region.py b/src/nupic/regions/knn_anomaly_classifier_region.py index 8edb9296aa..fda3cb0b57 100644 --- a/src/nupic/regions/knn_anomaly_classifier_region.py +++ b/src/nupic/regions/knn_anomaly_classifier_region.py @@ -841,7 +841,8 @@ def writeToProto(self, proto): proto.activeColumnCount = self._activeColumnCount if self._prevPredictedColumns is not None: proto.prevPredictedColumns = self._prevPredictedColumns.tolist() - proto.anomalyVectorLength = self._anomalyVectorLength + if self._anomalyVectorLength is not None: + proto.anomalyVectorLength = self._anomalyVectorLength proto.classificationMaxDist = self._classificationMaxDist proto.iteration = self._iteration proto.trainRecords = self.trainRecords diff --git a/src/nupic/regions/sdr_classifier_region.py b/src/nupic/regions/sdr_classifier_region.py index 2f9f3bcd18..fc2a4420c1 100755 --- a/src/nupic/regions/sdr_classifier_region.py +++ b/src/nupic/regions/sdr_classifier_region.py @@ -336,6 +336,9 @@ def writeToProto(self, proto): proto.alpha = self.alpha proto.verbosity = self.verbosity proto.maxCategoryCount = self.maxCategoryCount + proto.learningMode = self.learningMode + proto.inferenceMode = self.inferenceMode + proto.recordNum = self.recordNum self._sdrClassifier.write(proto.sdrClassifier) @@ -351,12 +354,17 @@ def readFromProto(cls, proto): instance.implementation = proto.implementation instance.steps = proto.steps + instance.stepsList = [int(i) for i in proto.steps.split(",")] instance.alpha = proto.alpha instance.verbosity = proto.verbosity instance.maxCategoryCount = proto.maxCategoryCount instance._sdrClassifier = SDRClassifierFactory.read(proto) + instance.learningMode = proto.learningMode + instance.inferenceMode = proto.inferenceMode + instance.recordNum = proto.recordNum + return instance diff --git a/tests/unit/nupic/regions/anomaly_likelihood_region_test.py b/tests/unit/nupic/regions/anomaly_likelihood_region_test.py index c20d36e364..dbf708f20b 100644 --- a/tests/unit/nupic/regions/anomaly_likelihood_region_test.py +++ b/tests/unit/nupic/regions/anomaly_likelihood_region_test.py @@ -30,8 +30,8 @@ except ImportError: capnp = None if capnp: - from nupic.regions.AnomalyLikelihoodRegion_capnp import\ - AnomalyLikelihoodRegionProto + from nupic.algorithms.anomaly_likelihood_capnp import\ + AnomalyLikelihoodProto as AnomalyLikelihoodRegionProto from nupic.regions.anomaly_likelihood_region import AnomalyLikelihoodRegion from nupic.algorithms.anomaly_likelihood import AnomalyLikelihood diff --git a/tests/unit/nupic/serializable_test.py b/tests/unit/nupic/serializable_test.py index c20c7d8fd0..038c41a8be 100644 --- a/tests/unit/nupic/serializable_test.py +++ b/tests/unit/nupic/serializable_test.py @@ -19,8 +19,13 @@ # http://numenta.org/licenses/ # ---------------------------------------------------------------------- +import importlib +import inspect import os +import pkgutil +import tempfile import unittest +import numpy try: import capnp @@ -29,59 +34,272 @@ # Ignore for platforms in which capnp is not available, e.g. windows capnp = None -from nupic.serializable import Serializable +import nupic +from nupic.frameworks.opf.common_models.cluster_params import ( + getScalarMetricWithTimeOfDayAnomalyParams) + +from nupic.serializable import Serializable + +MODEL_PARAMS = getScalarMetricWithTimeOfDayAnomalyParams([0], + minVal=23.42, + maxVal=23.420001) + +SERIALIZABLE_SUBCLASSES = { + "MovingAverage": { + "params": {"windowSize": 1} + }, + "AnomalyLikelihood": {}, + "BacktrackingTM": {}, + "Connections": {"params": {"numCells": 1}}, + "TemporalMemory": {}, + "KNNClassifier": {}, + "SDRClassifier": {}, + "SpatialPooler": { + "params": {"inputDimensions": (2, 2), "columnDimensions": (4, 4)} + }, + "Encoder": {}, + "Model": {}, + "AnomalyLikelihoodRegion": {}, + "AnomalyRegion": {}, + "TestRegion": { + "skip": True + }, + "BacktrackingTMCPP": {}, + "TemporalMemoryShim": {}, + "MonitoredTemporalMemory": { + "volatile": ["mmName", "_mmTransitionTracesStale", "_mmTraces", "_mmData", + "_mmResetActive"] + }, + "TMShim": {}, + "MonitoredTMShim": { + "volatile": ["mmName", "_mmTraces", "_mmData", "_mmResetActive"] + }, + "ScalarEncoder": { + "params": {"w": 21, "n": 1024, "minval": 0, "maxval": 100} + }, + "RandomDistributedScalarEncoder": { + "params": {"resolution": 1} + }, + "DateEncoder": {}, + "LogEncoder": { + "params": {"w": 21, "n": 100} + }, + "CategoryEncoder": { + "params": {"w": 21, "categoryList": ["a", "b", "c"]} + }, + "SDRCategoryEncoder": { + "params": {"n": 100, "w": 21} + }, + "ScalarSpaceEncoder": {}, + "CoordinateEncoder": {}, + "PassThroughEncoder": { + "params": {"n": 100, "w": 21} + }, + "MultiEncoder": {}, + "AdaptiveScalarEncoder": { + "params": {"w": 21, "n": 1024, "minval": 0, "maxval": 100} + }, + "DeltaEncoder": { + "params": {"w": 21, "n": 1024, "minval": 0, "maxval": 100} + }, + "GeospatialCoordinateEncoder": { + "params": {"scale": 1, "timestep": 1} + }, + "SparsePassThroughEncoder": { + "params": {"n": 100, "w": 21} + }, + "HTMPredictionModel": { + "params": MODEL_PARAMS['modelConfig']['modelParams'] + }, + "TwoGramModel": { + "params": {"encoderParams": {"blah": {"fieldname": "blah", "maxval": 9, + "minval": 0, "n": 10, "w": 1, + "clipInput": True, "forced": True, + "type": "ScalarEncoder"}}} + }, + "PreviousValueModel": {} +} + + + +def _allSubclasses(cls): + """ + Get all subclasses + :param cls: The class to get subclasses from + :return: list with all subclasses + """ + return cls.__subclasses__() + [ + g for s in cls.__subclasses__() for g in _allSubclasses(s) + ] + + + +def _getAttributes(obj): + """ + Get all attributes of the given object + """ + if isinstance(obj, dict): + attrs = obj + elif hasattr(obj, "__slots__"): + attrs = {attr: getattr(obj, attr) for attr in obj.__slots__} + elif hasattr(obj, "__dict__"): + attrs = obj.__dict__ + + # Ignore volatile fields when comparing field values + testParams = SERIALIZABLE_SUBCLASSES[obj.__class__.__name__] + if "volatile" in testParams: + for f in testParams["volatile"]: + if f in attrs: + del attrs[f] + + return attrs + + + +def _remove(fname): + """ + Clean up function used to delete files created by the test + :param fname: File to be deleted + :return: + """ + if os.path.isfile(fname): + os.remove(fname) @unittest.skipUnless(capnp, "Capnp not available.") class SerializableTest(unittest.TestCase): + # pylint: disable=R0201,W0223 + def customAssertArrayEquals(self, a1, a2, msg=None): + """ + Function used by `addTypeEqualityFunc` comparing numpy arrays + """ + numpy.testing.assert_equal(a1, a2, msg) + + def customAssertSequenceEquals(self, l1, l2, msg=None): + """ + Function used by `addTypeEqualityFunc` comparing sequences + """ + self.assertEquals(len(l1), len(l2), msg) + for i in xrange(len(l1)): + first = l1[i] + second = l2[i] + if type(first).__name__ in SERIALIZABLE_SUBCLASSES: + first = _getAttributes(first) + second = _getAttributes(second) + self.assertEquals(first, second, msg) + + def customAssertDictEquals(self, d1, d2, msg=None): + """ + Function used by `addTypeEqualityFunc` comparing dicts + """ + self.assertIsInstance(d1, dict, 'First argument is not a dictionary') + self.assertIsInstance(d2, dict, 'Second argument is not a dictionary') + self.assertEquals(len(d1), len(d2), msg + str(d1) + ' != ' + str(d2)) + + for k, _ in d1.items(): + if k not in d2: + raise AssertionError(repr(k)) + first = d1[k] + second = d2[k] + if type(first).__name__ in SERIALIZABLE_SUBCLASSES: + first = _getAttributes(first) + second = _getAttributes(second) + + self.assertEquals(first, second, 'key=%r\n%s' % (k, msg)) def testABCProtocolEnforced(self): + # pylint: disable=E0110 class Foo(Serializable): - pass # read(), write(), getCapnpSchema() not implemented here + pass # read(), write(), getCapnpSchema() not implemented here with self.assertRaises(TypeError): Foo() - def testReadFromAndWriteToFile(self): """ Test generic usage of serializable mixin class """ class Bar(object): pass - class Foo(Bar, Serializable): - def __init__(self, bar): self.bar = bar - @classmethod def getSchema(cls): return serializable_test_capnp.Foo - @classmethod def read(cls, proto): foo = object.__new__(cls) foo.bar = proto.bar return foo - def write(self, proto): proto.bar = self.bar - def _remove(fname): - if os.path.isfile(fname): - os.remove(fname) - - self.addCleanup(_remove, "foo.data") + filename = tempfile.mktemp() + self.addCleanup(_remove, filename) - with open("foo.data", "wb") as outp: + with open(filename, "wb") as outp: Foo("bar").writeToFile(outp) - with open("foo.data", "rb") as inp: + with open(filename, "rb") as inp: self.assertEqual(Foo.readFromFile(inp).bar, "bar") + + def testAllSubClasses(self): + """ + Test all Serializable subclasses making sure all the fields are initialized + """ + self.addTypeEqualityFunc(numpy.ndarray, self.customAssertArrayEquals) + self.addTypeEqualityFunc(tuple, self.customAssertSequenceEquals) + self.addTypeEqualityFunc(list, self.customAssertSequenceEquals) + self.addTypeEqualityFunc(dict, self.customAssertDictEquals) + + # Import all nupic modules to find Serializable subclasses + packages = pkgutil.walk_packages(path=nupic.__path__, + prefix=nupic.__name__ + ".") + for _, modname, ispkg in packages: + if not ispkg: + try: + importlib.import_module(modname) + except: # pylint: disable=W0702 + pass # Ignore deprecated modules + + # Check every Serializable subclass + for klass in _allSubclasses(Serializable): + if inspect.isabstract(klass): + continue + + # Make sure all serializable classes are accounted for + self.assertIn(klass.__name__, SERIALIZABLE_SUBCLASSES) + print klass.__name__ + testParams = SERIALIZABLE_SUBCLASSES[klass.__name__] + + # Skip test class + if "skip" in testParams: + continue + + # Instantiate class with test parameters + if "params" in testParams: + original = klass(**(testParams["params"])) + else: + original = klass() + + # Test read/write + filename = tempfile.mktemp() + self.addCleanup(_remove, filename) + with open(filename, "wb") as outp: + original.writeToFile(outp) + + with open(filename, "rb") as inp: + serialized = klass.readFromFile(inp) + + expected = _getAttributes(original) + actual = _getAttributes(serialized) + + # Make sure all fields were initialized + self.assertEquals(actual, expected, klass.__name__)