From 34dd874896b22da217c5bede494f910faf6efb99 Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Mon, 5 Jun 2017 11:21:24 -0700 Subject: [PATCH 1/8] Switch predictedFieldIdx to predictedField and add classifier links to HTMPredictionModel --- .../network/complete-network-example.py | 5 +-- .../network/example-set-predicted-field.py | 4 +- examples/network/hierarchy_network_demo.py | 5 +-- .../frameworks/opf/htm_prediction_model.py | 11 +++++ src/nupic/regions/record_sensor.py | 45 +++++++++---------- .../regions/record_sensor_region_test.py | 3 +- .../regions/sdr_classifier_region_test.py | 3 +- 7 files changed, 37 insertions(+), 39 deletions(-) diff --git a/docs/examples/network/complete-network-example.py b/docs/examples/network/complete-network-example.py index a937354cd1..3adaae4f14 100644 --- a/docs/examples/network/complete-network-example.py +++ b/docs/examples/network/complete-network-example.py @@ -118,9 +118,8 @@ def runHotgym(numRecords): numRecords = min(numRecords, dataSource.getDataRowCount()) network = createNetwork(dataSource) - # Set predicted field index. It needs to be the same index as the data source. - predictedIdx = dataSource.getFieldNames().index("consumption") - network.regions["sensor"].setParameter("predictedFieldIdx", predictedIdx) + # Set predicted field + network.regions["sensor"].setParameter("predictedField", "consumption") # Enable learning for all regions. network.regions["SP"].setParameter("learningMode", 1) diff --git a/docs/examples/network/example-set-predicted-field.py b/docs/examples/network/example-set-predicted-field.py index e7ae8bbc9c..dcc5de19ef 100644 --- a/docs/examples/network/example-set-predicted-field.py +++ b/docs/examples/network/example-set-predicted-field.py @@ -1,3 +1 @@ -predictedIdx = dataSource.getFieldNames().index("consumption") - -network.regions["sensor"].setParameter("predictedFieldIdx", predictedIdx) \ No newline at end of file +network.regions["sensor"].setParameter("predictedField", "consumption") diff --git a/examples/network/hierarchy_network_demo.py b/examples/network/hierarchy_network_demo.py index 0df4894ba2..2ab9ba1989 100755 --- a/examples/network/hierarchy_network_demo.py +++ b/examples/network/hierarchy_network_demo.py @@ -149,10 +149,7 @@ def createRecordSensor(network, name, dataSource): sensorRegion.encoder = createEncoder() # Specify which sub-encoder should be used for "actValueOut" - predictedFieldIdx = dataSource.getFieldNames().index("consumption") - network.regions[name].setParameter("predictedFieldIdx", - numpy.array([predictedFieldIdx], - dtype="uint32")) + network.regions[name].setParameter("predictedField", "consumption") # Specify the dataSource as a file record stream instance sensorRegion.dataSource = dataSource diff --git a/src/nupic/frameworks/opf/htm_prediction_model.py b/src/nupic/frameworks/opf/htm_prediction_model.py index 1bfa89faec..20a43426a2 100644 --- a/src/nupic/frameworks/opf/htm_prediction_model.py +++ b/src/nupic/frameworks/opf/htm_prediction_model.py @@ -309,6 +309,13 @@ def setFieldStatistics(self, fieldStats): encoder.setFieldStats('',fieldStats) + def enableInference(self, inferenceArgs=None): + super(HTMPredictionModel, self).enableInference(inferenceArgs) + if inferenceArgs is not None and "predictedField" in inferenceArgs: + self._getSensorRegion().setParameter("predictedField", + inferenceArgs["predictedField"]) + + def enableLearning(self): super(HTMPredictionModel, self).enableLearning() self.setEncoderLearning(True) @@ -1165,6 +1172,10 @@ def __createCLANetwork(self, sensorParams, spEnable, spParams, tmEnable, clParams)) n.addRegion("Classifier", "py.%s" % str(clRegionName), json.dumps(clParams)) + n.link("sensor", "Classifier", "UniformLink", "", srcOutput="actValueOut", + destInput="actValueIn") + n.link("sensor", "Classifier", "UniformLink", "", srcOutput="bucketIdxOut", + destInput="bucketIdxIn") n.link("sensor", "Classifier", "UniformLink", "", srcOutput="categoryOut", destInput="categoryIn") diff --git a/src/nupic/regions/record_sensor.py b/src/nupic/regions/record_sensor.py index 431e05e88c..28df62844f 100644 --- a/src/nupic/regions/record_sensor.py +++ b/src/nupic/regions/record_sensor.py @@ -111,7 +111,7 @@ def getSpec(cls): actValueOut=dict( description="Actual value of the field to predict. The index of the " "field to predict must be specified via the parameter " - "predictedFieldIdx. If this parameter is not set, then " + "predictedField. If this parameter is not set, then " "actValueOut won't be populated.", dataType="Real32", count=0, @@ -121,7 +121,7 @@ def getSpec(cls): description="Active index of the encoder bucket for the " "actual value of the field to predict. The index of the " "field to predict must be specified via the parameter " - "predictedFieldIdx. If this parameter is not set, then " + "predictedField. If this parameter is not set, then " "actValueOut won't be populated.", dataType="UInt64", count=0, @@ -206,15 +206,13 @@ def getSpec(cls): accessMode="ReadWrite", count=1, constraints=""), - predictedFieldIdx=dict( - description="Index of the field to be predicted. Needs to be " - "consistent with the data source indexing. " - "Default value is < 0 which means that no particular " - "field is selected. This will result in the outputs " - "actValueOut and bucketIdxOut not being populated.", - dataType="UInt32", + predictedField=dict( + description="The field to be predicted. This will result in the " + "outputs actValueOut and bucketIdxOut not being " + "populated.", + dataType="Byte", accessMode="ReadWrite", - count=1, + count=0, defaultValue=-1, constraints=""), topDownMode=dict( @@ -246,10 +244,10 @@ def __init__(self, verbosity=0, numCategories=1): self.numCategories = numCategories self._iterNum = 0 - # Optional index of the field for which we want to populate bucketIdxOut - # and actValueOut. If predictedFieldIdx < 0, then bucketIdxOut and + # Optional field for which we want to populate bucketIdxOut + # and actValueOut. If predictedField is None, then bucketIdxOut and # actValueOut won't be populated. - self.predictedFieldIdx = -1 + self.predictedField = None # lastRecord is the last record returned. Used for debugging only self.lastRecord = None @@ -380,14 +378,8 @@ def compute(self, inputs, outputs): self.encoder.encodeIntoArray(data, outputs["dataOut"]) # If there is a field to predict, set bucketIdxOut and actValueOut. - if self.predictedFieldIdx >= 0: - fields = self.dataSource.getFieldNames() - if self.predictedFieldIdx >= len(fields): - raise ValueError("predictedFieldIdx (%s) must be strictly less than " - "the number of fields (%s). Fields: %s." - % (self.predictedFieldIdx, len(fields), fields)) - predictedField = fields[self.predictedFieldIdx] - encoders = [e for e in self.encoder.encoders if e[0] == predictedField] + if self.predictedField is not None: + encoders = [e for e in self.encoder.encoders if e[0] == self.predictedField] if len(encoders) == 0: raise ValueError("There is no encoder for set for the predicted " "field: %s" % predictedField) @@ -397,9 +389,12 @@ def compute(self, inputs, outputs): else: encoder = encoders[0][1] - actualValue = data[predictedField] + actualValue = data[self.predictedField] outputs["bucketIdxOut"][:] = encoder.getBucketIndices(actualValue) - outputs["actValueOut"][:] = actualValue + if isinstance(actualValue, str): + outputs["actValueOut"][:] = encoder.getBucketIndices(actualValue) + else: + outputs["actValueOut"][:] = actualValue # Write out the scalar values obtained from they data source. outputs["sourceOut"][:] = self.encoder.getScalars(data) @@ -594,8 +589,8 @@ def setParameter(self, parameterName, index, parameterValue): """ if parameterName == 'topDownMode': self.topDownMode = parameterValue - elif parameterName == 'predictedFieldIdx': - self.predictedFieldIdx = parameterValue + elif parameterName == 'predictedField': + self.predictedField = parameterValue else: raise Exception('Unknown parameter: ' + parameterName) diff --git a/tests/unit/nupic/regions/record_sensor_region_test.py b/tests/unit/nupic/regions/record_sensor_region_test.py index 34d7ddfbe8..f975d26fb4 100644 --- a/tests/unit/nupic/regions/record_sensor_region_test.py +++ b/tests/unit/nupic/regions/record_sensor_region_test.py @@ -54,8 +54,7 @@ def _createNetwork(): sensorRegion.dataSource = dataSource # Get and set what field index we want to predict. - predictedIdx = dataSource.getFieldNames().index('consumption') - network.regions['sensor'].setParameter('predictedFieldIdx', predictedIdx) + network.regions['sensor'].setParameter('predictedField', 'consumption') return network diff --git a/tests/unit/nupic/regions/sdr_classifier_region_test.py b/tests/unit/nupic/regions/sdr_classifier_region_test.py index 6e7e866cad..4d2b288ee1 100644 --- a/tests/unit/nupic/regions/sdr_classifier_region_test.py +++ b/tests/unit/nupic/regions/sdr_classifier_region_test.py @@ -56,8 +56,7 @@ def _createNetwork(): sensorRegion.dataSource = dataSource # Get and set what field index we want to predict. - predictedIdx = dataSource.getFieldNames().index('consumption') - network.regions['sensor'].setParameter('predictedFieldIdx', predictedIdx) + network.regions['sensor'].setParameter('predictedField', 'consumption') return network From 755acba594fd5d1687739e4f2f1273848a30b669 Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Mon, 5 Jun 2017 16:42:27 -0700 Subject: [PATCH 2/8] Remove CLAClassifier, derivatives, tests, and references --- docs/source/api/network/regions.rst | 7 - docs/source/contributing/cpp-style-guide.md | 2 +- scripts/run_experiment_classifier_diff.py | 13 +- src/nupic/algorithms/cla_classifier.py | 673 ------------------ src/nupic/algorithms/cla_classifier_diff.py | 97 --- .../algorithms/cla_classifier_factory.py | 65 -- src/nupic/algorithms/sdr_classifier.py | 3 - src/nupic/engine/__init__.py | 1 - .../frameworks/opf/exp_description_api.py | 7 - .../frameworks/opf/htm_prediction_model.py | 20 +- src/nupic/regions/CLAClassifierRegion.capnp | 13 - src/nupic/regions/cla_classifier_region.py | 498 ------------- src/nupic/support/nupic-default.xml | 8 - .../algorithms/cla_classifier_diff_test.py | 49 -- .../nupic/algorithms/cla_classifier_test.py | 552 -------------- .../algorithms/fast_cla_classifier_test.py | 48 -- ...mpredictionmodel_classifier_helper_test.py | 2 +- .../frameworks/opf/htmpredictionmodel_test.py | 2 +- 18 files changed, 19 insertions(+), 2041 deletions(-) delete mode 100644 src/nupic/algorithms/cla_classifier.py delete mode 100755 src/nupic/algorithms/cla_classifier_diff.py delete mode 100644 src/nupic/algorithms/cla_classifier_factory.py delete mode 100644 src/nupic/regions/CLAClassifierRegion.capnp delete mode 100755 src/nupic/regions/cla_classifier_region.py delete mode 100755 tests/unit/nupic/algorithms/cla_classifier_diff_test.py delete mode 100755 tests/unit/nupic/algorithms/cla_classifier_test.py delete mode 100755 tests/unit/nupic/algorithms/fast_cla_classifier_test.py diff --git a/docs/source/api/network/regions.rst b/docs/source/api/network/regions.rst index 5772d5c4f7..c6fae10b7c 100644 --- a/docs/source/api/network/regions.rst +++ b/docs/source/api/network/regions.rst @@ -37,13 +37,6 @@ AnomalyLikelihoodRegion :members: :show-inheritance: -CLAClassifierRegion -^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: nupic.regions.cla_classifier_region.CLAClassifierRegion - :members: - :show-inheritance: - KNNAnomalyClassifierRegion ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/contributing/cpp-style-guide.md b/docs/source/contributing/cpp-style-guide.md index 9979aa345d..c9ab7ebbf1 100644 --- a/docs/source/contributing/cpp-style-guide.md +++ b/docs/source/contributing/cpp-style-guide.md @@ -60,7 +60,7 @@ Filenames should not contain characters that could cause problems on different p C++ header files should have the suffix ".hpp". C header files should have the suffix ".h". -C/C++ (`.h`, `.c`, `.hpp` and `.cpp`) and related SWIG (`.i`) file names should be **UpperCamelCase**. Initials and two-letter acronyms should be capitalized (e.g. **RegionIO.cpp**, **OSUnix.cpp**), longer acronyms treated as words (e.g. **FastClaClassifier.cpp**). A few non-code files (e.g. **README.md**, **cmake_install.cmake**) are uppercase or snake_case by standard convention. +C/C++ (`.h`, `.c`, `.hpp` and `.cpp`) and related SWIG (`.i`) file names should be **UpperCamelCase**. Initials and two-letter acronyms should be capitalized (e.g. **RegionIO.cpp**, **OSUnix.cpp**), longer acronyms treated as words (e.g. **SdrClassifier.cpp**). A few non-code files (e.g. **README.md**, **cmake_install.cmake**) are uppercase or snake_case by standard convention. ### Naming Conventions diff --git a/scripts/run_experiment_classifier_diff.py b/scripts/run_experiment_classifier_diff.py index 416585c3cc..8c4c3676e3 100755 --- a/scripts/run_experiment_classifier_diff.py +++ b/scripts/run_experiment_classifier_diff.py @@ -20,14 +20,13 @@ # ---------------------------------------------------------------------- """This script is a command-line client of Online Prediction Framework (OPF). -It executes a single experiment and diffs the results for the CLAClassifier and -FastCLAClassifier. +It executes a single experiment and diffs the results for the SDRClassifier. """ import sys -from nupic.algorithms.cla_classifier_diff import CLAClassifierDiff -from nupic.algorithms.cla_classifier_factory import CLAClassifierFactory +from nupic.algorithms.sdr_classifier_diff import SDRClassifierDiff +from nupic.algorithms.sdr_classifier_factory import SDRClassifierFactory from nupic.frameworks.opf.experiment_runner import (runExperiment, initExperimentPrng) from nupic.support import initLogging @@ -41,12 +40,12 @@ def main(): # Initialize PRNGs initExperimentPrng() - # Mock out the creation of the CLAClassifier. + # Mock out the creation of the SDRClassifier. @staticmethod def _mockCreate(*args, **kwargs): kwargs.pop('implementation', None) - return CLAClassifierDiff(*args, **kwargs) - CLAClassifierFactory.create = _mockCreate + return SDRClassifierDiff(*args, **kwargs) + SDRClassifierFactory.create = _mockCreate # Run it! runExperiment(sys.argv[1:]) diff --git a/src/nupic/algorithms/cla_classifier.py b/src/nupic/algorithms/cla_classifier.py deleted file mode 100644 index 825e981f9d..0000000000 --- a/src/nupic/algorithms/cla_classifier.py +++ /dev/null @@ -1,673 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -"""This file implements the CLAClassifier.""" - -import array -from collections import deque -import itertools - -import numpy - - -# This determines how large one of the duty cycles must get before each of the -# duty cycles are updated to the current iteration. -# This must be less than float32 size since storage is float32 size -DUTY_CYCLE_UPDATE_INTERVAL = numpy.finfo(numpy.float32).max / (2 ** 20) - -g_debugPrefix = "cla_classifier" - - -def _pFormatArray(array_, fmt="%.2f"): - """Return a string with pretty-print of a numpy array using the given format - for each element""" - return "[ " + " ".join(fmt % x for x in array_) + " ]" - - -class BitHistory(object): - """Class to store an activationPattern bit history.""" - - __slots__ = ("_classifier", "_id", "_stats", "_lastTotalUpdate", - "_learnIteration", "_version") - - __VERSION__ = 2 - - - def __init__(self, classifier, bitNum, nSteps): - """Constructor for bit history. - - Parameters: - --------------------------------------------------------------------- - classifier: instance of the CLAClassifier that owns us - bitNum: activation pattern bit number this history is for, - used only for debug messages - nSteps: number of steps of prediction this history is for, used - only for debug messages - """ - # Store reference to the classifier - self._classifier = classifier - - # Form our "id" - self._id = "%d[%d]" % (bitNum, nSteps) - - # Dictionary of bucket entries. The key is the bucket index, the - # value is the dutyCycle, which is the rolling average of the duty cycle - self._stats = array.array("f") - - # lastUpdate is the iteration number of the last time it was updated. - self._lastTotalUpdate = None - - # The bit's learning iteration. This is updated each time store() gets - # called on this bit. - self._learnIteration = 0 - - # Set the version to the latest version. - # This is used for serialization/deserialization - self._version = BitHistory.__VERSION__ - - - def store(self, iteration, bucketIdx): - """Store a new item in our history. - - This gets called for a bit whenever it is active and learning is enabled - - Parameters: - -------------------------------------------------------------------- - iteration: the learning iteration number, which is only incremented - when learning is enabled - bucketIdx: the bucket index to store - - Save duty cycle by normalizing it to the same iteration as - the rest of the duty cycles which is lastTotalUpdate. - - This is done to speed up computation in inference since all of the duty - cycles can now be scaled by a single number. - - The duty cycle is brought up to the current iteration only at inference and - only when one of the duty cycles gets too large (to avoid overflow to - larger data type) since the ratios between the duty cycles are what is - important. As long as all of the duty cycles are at the same iteration - their ratio is the same as it would be for any other iteration, because the - update is simply a multiplication by a scalar that depends on the number of - steps between the last update of the duty cycle and the current iteration. - """ - - # If lastTotalUpdate has not been set, set it to the current iteration. - if self._lastTotalUpdate is None: - self._lastTotalUpdate = iteration - # Get the duty cycle stored for this bucket. - statsLen = len(self._stats) - 1 - if bucketIdx > statsLen: - self._stats.extend(itertools.repeat(0.0, bucketIdx - statsLen)) - - # Update it now. - # duty cycle n steps ago is dc{-n} - # duty cycle for current iteration is (1-alpha)*dc{-n}*(1-alpha)**(n)+alpha - dc = self._stats[bucketIdx] - - # To get the duty cycle from n iterations ago that when updated to the - # current iteration would equal the dc of the current iteration we simply - # divide the duty cycle by (1-alpha)**(n). This results in the formula - # dc'{-n} = dc{-n} + alpha/(1-alpha)**n where the apostrophe symbol is used - # to denote that this is the new duty cycle at that iteration. This is - # equivalent to the duty cycle dc{-n} - denom = ((1.0 - self._classifier.alpha) ** - (iteration - self._lastTotalUpdate)) - if denom > 0: - dcNew = dc + (self._classifier.alpha / denom) - - # This is to prevent errors associated with inf rescale if too large - if denom == 0 or dcNew > DUTY_CYCLE_UPDATE_INTERVAL: - exp = ((1.0 - self._classifier.alpha) ** - (iteration - self._lastTotalUpdate)) - for (bucketIdxT, dcT) in enumerate(self._stats): - dcT *= exp - self._stats[bucketIdxT] = dcT - - # Reset time since last update - self._lastTotalUpdate = iteration - - # Add alpha since now exponent is 0 - dc = self._stats[bucketIdx] + self._classifier.alpha - else: - dc = dcNew - - self._stats[bucketIdx] = dc - if self._classifier.verbosity >= 2: - print "updated DC for %s, bucket %d to %f" % (self._id, bucketIdx, dc) - - - def infer(self, votes): - """Look up and return the votes for each bucketIdx for this bit. - - Parameters: - -------------------------------------------------------------------- - votes: a numpy array, initialized to all 0's, that should be filled - in with the votes for each bucket. The vote for bucket index N - should go into votes[N]. - """ - # Place the duty cycle into the votes and update the running total for - # normalization - total = 0 - for (bucketIdx, dc) in enumerate(self._stats): - # Not updating to current iteration since we are normalizing anyway - if dc > 0.0: - votes[bucketIdx] = dc - total += dc - - # Experiment... try normalizing the votes from each bit - if total > 0: - votes /= total - if self._classifier.verbosity >= 2: - print "bucket votes for %s:" % (self._id), _pFormatArray(votes) - - - def __getstate__(self): - return dict((elem, getattr(self, elem)) for elem in self.__slots__) - - - def __setstate__(self, state): - version = 0 - if "_version" in state: - version = state["_version"] - - # Migrate from version 0 to version 1 - if version == 0: - stats = state.pop("_stats") - assert isinstance(stats, dict) - maxBucket = max(stats.iterkeys()) - self._stats = array.array("f", itertools.repeat(0.0, maxBucket + 1)) - for (index, value) in stats.iteritems(): - self._stats[index] = value - elif version == 1: - state.pop("_updateDutyCycles", None) - elif version == 2: - pass - else: - raise Exception("Error while deserializing %s: Invalid version %s" - % (self.__class__, version)) - - for (attr, value) in state.iteritems(): - setattr(self, attr, value) - - self._version = BitHistory.__VERSION__ - - - def write(self, proto): - proto.id = self._id - - statsProto = proto.init("stats", len(self._stats)) - for (bucketIdx, dutyCycle) in enumerate(self._stats): - statsProto[bucketIdx].index = bucketIdx - statsProto[bucketIdx].dutyCycle = dutyCycle - - proto.lastTotalUpdate = self._lastTotalUpdate - proto.learnIteration = self._learnIteration - - - @classmethod - def read(cls, proto): - bitHistory = object.__new__(cls) - - bitHistory._id = proto.id - - for statProto in proto.stats: - statsLen = len(bitHistory._stats) - 1 - if statProto.index > statsLen: - bitHistory._stats.extend( - itertools.repeat(0.0, statProto.index - statsLen)) - bitHistory._stats[statProto.index] = statProto.dutyCycle - - bitHistory._lastTotalUpdate = proto.lastTotalUpdate - bitHistory._learnIteration = proto.learnIteration - - return bitHistory - - - -class CLAClassifier(object): - """ - A CLA classifier accepts a binary input from the level below (the - "activationPattern") and information from the sensor and encoders (the - "classification") describing the input to the system at that time step. - - When learning, for every bit in activation pattern, it records a history of - the classification each time that bit was active. The history is weighted so - that more recent activity has a bigger impact than older activity. The alpha - parameter controls this weighting. - - For inference, it takes an ensemble approach. For every active bit in the - activationPattern, it looks up the most likely classification(s) from the - history stored for that bit and then votes across these to get the resulting - classification(s). - - This classifier can learn and infer a number of simultaneous classifications - at once, each representing a shift of a different number of time steps. For - example, say you are doing multi-step prediction and want the predictions for - 1 and 3 time steps in advance. The CLAClassifier would learn the associations - between the activation pattern for time step T and the classifications for - time step T+1, as well as the associations between activation pattern T and - the classifications for T+3. The 'steps' constructor argument specifies the - list of time-steps you want. - - """ - - __VERSION__ = 2 - - - def __init__(self, steps=(1,), alpha=0.001, actValueAlpha=0.3, verbosity=0): - """Constructor for the CLA classifier. - - Parameters: - --------------------------------------------------------------------- - steps: Sequence of the different steps of multi-step predictions to learn - alpha: The alpha used to compute running averages of the bucket duty - cycles for each activation pattern bit. A lower alpha results - in longer term memory. - verbosity: verbosity level, can be 0, 1, or 2 - """ - # Save constructor args - self.steps = steps - self.alpha = alpha - self.actValueAlpha = actValueAlpha - self.verbosity = verbosity - - # Init learn iteration index - self._learnIteration = 0 - - # This contains the offset between the recordNum (provided by caller) and - # learnIteration (internal only, always starts at 0). - self._recordNumMinusLearnIteration = None - - # Max # of steps of prediction we need to support - maxSteps = max(self.steps) + 1 - - # History of the last _maxSteps activation patterns. We need to keep - # these so that we can associate the current iteration's classification - # with the activationPattern from N steps ago - self._patternNZHistory = deque(maxlen=maxSteps) - - # These are the bit histories. Each one is a BitHistory instance, stored in - # this dict, where the key is (bit, nSteps). The 'bit' is the index of the - # bit in the activation pattern and nSteps is the number of steps of - # prediction desired for that bit. - self._activeBitHistory = dict() - - # This contains the value of the highest bucket index we've ever seen - # It is used to pre-allocate fixed size arrays that hold the weights of - # each bucket index during inference - self._maxBucketIdx = 0 - - # This keeps track of the actual value to use for each bucket index. We - # start with 1 bucket, no actual value so that the first infer has something - # to return - self._actualValues = [None] - - # Set the version to the latest version. - # This is used for serialization/deserialization - self._version = CLAClassifier.__VERSION__ - - - def compute(self, recordNum, patternNZ, classification, learn, infer): - """ - Process one input sample. - This method is called by outer loop code outside the nupic-engine. We - use this instead of the nupic engine compute() because our inputs and - outputs aren't fixed size vectors of reals. - - Parameters: - -------------------------------------------------------------------- - recordNum: Record number of this input pattern. Record numbers should - normally increase sequentially by 1 each time unless there - are missing records in the dataset. Knowing this information - insures that we don't get confused by missing records. - patternNZ: List of the active indices from the output below. - - When the input is from TemporalMemory, this list should be the - indices of the active cells. - classification: dict of the classification information: - bucketIdx: index of the encoder bucket - actValue: actual value going into the encoder - learn: if true, learn this sample - infer: if true, perform inference - - retval: dict containing inference results, there is one entry for each - step in self.steps, where the key is the number of steps, and - the value is an array containing the relative likelihood for - each bucketIdx starting from bucketIdx 0. - - There is also an entry containing the average actual value to - use for each bucket. The key is 'actualValues'. - - for example: - {1 : [0.1, 0.3, 0.2, 0.7], - 4 : [0.2, 0.4, 0.3, 0.5], - 'actualValues': [1.5, 3,5, 5,5, 7.6], - } - """ - - # Save the offset between recordNum and learnIteration if this is the first - # compute - if self._recordNumMinusLearnIteration is None: - self._recordNumMinusLearnIteration = recordNum - self._learnIteration - - # Update the learn iteration - self._learnIteration = recordNum - self._recordNumMinusLearnIteration - - if self.verbosity >= 1: - print "\n%s: compute" % g_debugPrefix - print " recordNum:", recordNum - print " learnIteration:", self._learnIteration - print " patternNZ (%d):" % len(patternNZ), patternNZ - print " classificationIn:", classification - - # Store pattern in our history - self._patternNZHistory.append((self._learnIteration, patternNZ)) - - # To allow multi-class classification, we need to be able to run learning - # without inference being on. So initialize retval outside - # of the inference block. - retval = None - - # ------------------------------------------------------------------------ - # Inference: - # For each active bit in the activationPattern, get the classification - # votes - if infer: - retval = self.infer(patternNZ, classification) - - # ------------------------------------------------------------------------ - # Learning: - # For each active bit in the activationPattern, store the classification - # info. If the bucketIdx is None, we can't learn. This can happen when the - # field is missing in a specific record. - if learn and classification["bucketIdx"] is not None: - - # Get classification info - bucketIdx = classification["bucketIdx"] - actValue = classification["actValue"] - - # Update maxBucketIndex - self._maxBucketIdx = max(self._maxBucketIdx, bucketIdx) - - # Update rolling average of actual values if it's a scalar. If it's - # not, it must be a category, in which case each bucket only ever - # sees one category so we don't need a running average. - while self._maxBucketIdx > len(self._actualValues) - 1: - self._actualValues.append(None) - if self._actualValues[bucketIdx] is None: - self._actualValues[bucketIdx] = actValue - else: - if isinstance(actValue, int) or isinstance(actValue, float): - self._actualValues[bucketIdx] = ((1.0 - self.actValueAlpha) - * self._actualValues[bucketIdx] - + self.actValueAlpha * actValue) - else: - self._actualValues[bucketIdx] = actValue - - # Train each pattern that we have in our history that aligns with the - # steps we have in self.steps - for nSteps in self.steps: - - # Do we have the pattern that should be assigned to this classification - # in our pattern history? If not, skip it - found = False - for (iteration, learnPatternNZ) in self._patternNZHistory: - if iteration == self._learnIteration - nSteps: - found = True; - break - if not found: - continue - - # Store classification info for each active bit from the pattern - # that we got nSteps time steps ago. - for bit in learnPatternNZ: - - # Get the history structure for this bit and step # - key = (bit, nSteps) - history = self._activeBitHistory.get(key, None) - if history is None: - history = self._activeBitHistory[key] = BitHistory(self, - bitNum=bit, - nSteps=nSteps) - - # Store new sample - history.store(iteration=self._learnIteration, - bucketIdx=bucketIdx) - - # ------------------------------------------------------------------------ - # Verbose print - if infer and self.verbosity >= 1: - print " inference: combined bucket likelihoods:" - print " actual bucket values:", retval["actualValues"] - for (nSteps, votes) in retval.items(): - if nSteps == "actualValues": - continue - print " %d steps: " % (nSteps), _pFormatArray(votes) - bestBucketIdx = votes.argmax() - print (" most likely bucket idx: " - "%d, value: %s" % (bestBucketIdx, - retval["actualValues"][bestBucketIdx])) - print - - return retval - - - def infer(self, patternNZ, classification): - """ - Return the inference value from one input sample. The actual - learning happens in compute(). The method customCompute() is here to - maintain backward compatibility. - - Parameters: - -------------------------------------------------------------------- - patternNZ: list of the active indices from the output below - classification: dict of the classification information: - bucketIdx: index of the encoder bucket - actValue: actual value going into the encoder - - retval: dict containing inference results, one entry for each step in - self.steps. The key is the number of steps, the value is an - array containing the relative likelihood for each bucketIdx - starting from bucketIdx 0. - - for example: - {'actualValues': [0.0, 1.0, 2.0, 3.0] - 1 : [0.1, 0.3, 0.2, 0.7] - 4 : [0.2, 0.4, 0.3, 0.5]} - """ - - # Return value dict. For buckets which we don't have an actual value - # for yet, just plug in any valid actual value. It doesn't matter what - # we use because that bucket won't have non-zero likelihood anyways. - - # NOTE: If doing 0-step prediction, we shouldn't use any knowledge - # of the classification input during inference. - if self.steps[0] == 0: - defaultValue = 0 - else: - defaultValue = classification["actValue"] - actValues = [x if x is not None else defaultValue - for x in self._actualValues] - retval = {"actualValues": actValues} - - # For each n-step prediction... - for nSteps in self.steps: - - # Accumulate bucket index votes and actValues into these arrays - sumVotes = numpy.zeros(self._maxBucketIdx + 1) - bitVotes = numpy.zeros(self._maxBucketIdx + 1) - - # For each active bit, get the votes - for bit in patternNZ: - key = (bit, nSteps) - history = self._activeBitHistory.get(key, None) - if history is None: - continue - - bitVotes.fill(0) - history.infer(votes=bitVotes) - - sumVotes += bitVotes - - # Return the votes for each bucket, normalized - total = sumVotes.sum() - if total > 0: - sumVotes /= total - else: - # If all buckets have zero probability then simply make all of the - # buckets equally likely. There is no actual prediction for this - # timestep so any of the possible predictions are just as good. - if sumVotes.size > 0: - sumVotes = numpy.ones(sumVotes.shape) - sumVotes /= sumVotes.size - - retval[nSteps] = sumVotes - - return retval - - - def __getstate__(self): - return self.__dict__ - - - def __setstate__(self, state): - if "_profileMemory" in state: - state.pop("_profileMemory") - - # Set our state - self.__dict__.update(state) - - # Handle version 0 case (i.e. before versioning code) - if "_version" not in state or state["_version"] < 2: - self._recordNumMinusLearnIteration = None - - # Plug in the iteration number in the old patternNZHistory to make it - # compatible with the new format - historyLen = len(self._patternNZHistory) - for (i, pattern) in enumerate(self._patternNZHistory): - self._patternNZHistory[i] = (self._learnIteration - (historyLen - i), - pattern) - - - elif state["_version"] == 2: - # Version 2 introduced _recordNumMinusLearnIteration - pass - - else: - pass - - self._version = CLAClassifier.__VERSION__ - - - @classmethod - def read(cls, proto): - classifier = object.__new__(cls) - - classifier.steps = [] - for step in proto.steps: - classifier.steps.append(step) - - classifier.alpha = proto.alpha - classifier.actValueAlpha = proto.actValueAlpha - classifier._learnIteration = proto.learnIteration - classifier._recordNumMinusLearnIteration = ( - proto.recordNumMinusLearnIteration) - - classifier._patternNZHistory = deque(maxlen=max(classifier.steps) + 1) - patternNZHistoryProto = proto.patternNZHistory - learnIteration = classifier._learnIteration - len(patternNZHistoryProto) + 1 - for i in xrange(len(patternNZHistoryProto)): - classifier._patternNZHistory.append((learnIteration, - list(patternNZHistoryProto[i]))) - learnIteration += 1 - - classifier._activeBitHistory = dict() - activeBitHistoryProto = proto.activeBitHistory - for i in xrange(len(activeBitHistoryProto)): - stepBitHistories = activeBitHistoryProto[i] - nSteps = stepBitHistories.steps - for indexBitHistoryProto in stepBitHistories.bitHistories: - bit = indexBitHistoryProto.index - bitHistory = BitHistory.read(indexBitHistoryProto.history) - classifier._activeBitHistory[(bit, nSteps)] = bitHistory - - classifier._maxBucketIdx = proto.maxBucketIdx - - classifier._actualValues = [] - for actValue in proto.actualValues: - if actValue == 0: - classifier._actualValues.append(None) - else: - classifier._actualValues.append(actValue) - - classifier._version = proto.version - classifier.verbosity = proto.verbosity - - return classifier - - - def write(self, proto): - stepsProto = proto.init("steps", len(self.steps)) - for i in xrange(len(self.steps)): - stepsProto[i] = self.steps[i] - - proto.alpha = self.alpha - proto.actValueAlpha = self.actValueAlpha - proto.learnIteration = self._learnIteration - proto.recordNumMinusLearnIteration = self._recordNumMinusLearnIteration - - patternNZHistory = [] - for (iteration, learnPatternNZ) in self._patternNZHistory: - patternNZHistory.append(learnPatternNZ) - proto.patternNZHistory = patternNZHistory - - i = 0 - activeBitHistoryProtos = proto.init("activeBitHistory", - len(self._activeBitHistory)) - if len(self._activeBitHistory) > 0: - for nSteps in self.steps: - stepBitHistory = {bit: self._activeBitHistory[(bit, step)] - for (bit, step) in self._activeBitHistory.keys() - if step == nSteps} - stepBitHistoryProto = activeBitHistoryProtos[i] - stepBitHistoryProto.steps = nSteps - indexBitHistoryListProto = stepBitHistoryProto.init("bitHistories", - len(stepBitHistory)) - j = 0 - for indexBitHistory in stepBitHistory: - indexBitHistoryProto = indexBitHistoryListProto[j] - indexBitHistoryProto.index = indexBitHistory - bitHistoryProto = indexBitHistoryProto.history - stepBitHistory[indexBitHistory].write(bitHistoryProto) - j += 1 - i += 1 - - proto.maxBucketIdx = self._maxBucketIdx - - actualValuesProto = proto.init("actualValues", len(self._actualValues)) - for i in xrange(len(self._actualValues)): - if self._actualValues[i] is not None: - actualValuesProto[i] = self._actualValues[i] - else: - actualValuesProto[i] = 0 - - proto.version = self._version - proto.verbosity = self.verbosity diff --git a/src/nupic/algorithms/cla_classifier_diff.py b/src/nupic/algorithms/cla_classifier_diff.py deleted file mode 100755 index 6675908df0..0000000000 --- a/src/nupic/algorithms/cla_classifier_diff.py +++ /dev/null @@ -1,97 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -"""CLA classifier diff tool. - -This class can be used just like versions of the CLA classifier but internally -creates instances of each CLA classifier. Each record is fed to both -classifiers and the results are checked for differences. -""" - -import cPickle as pickle -import numbers - -from nupic.algorithms.cla_classifier import CLAClassifier -from nupic.bindings.algorithms import FastCLAClassifier - - -CALLS_PER_SERIALIZE = 100 - - - -class CLAClassifierDiff(object): - """Classifier-like object that diffs the output from different classifiers. - - Instances of each version of the CLA classifier are created and each call to - compute is passed to each version of the classifier. The results are diffed - to make sure the there are no differences. - - Optionally, the classifiers can be serialized and deserialized after a - specified number of calls to compute to ensure that serialization does not - cause discrepencies between the results. - - TODO: Check internal state as well. - TODO: Provide option to write output to a file. - TODO: Provide record differences without throwing an exception. - """ - - - __VERSION__ = 'CLAClassifierDiffV1' - - - def __init__(self, steps=(1,), alpha=0.001, actValueAlpha=0.3, verbosity=0, - callsPerSerialize=CALLS_PER_SERIALIZE): - self._claClassifier = CLAClassifier(steps, alpha, actValueAlpha, verbosity) - self._fastCLAClassifier = FastCLAClassifier(steps, alpha, actValueAlpha, - verbosity) - self._calls = 0 - self._callsPerSerialize = callsPerSerialize - - - def compute(self, recordNum, patternNZ, classification, learn, infer): - result1 = self._claClassifier.compute(recordNum, patternNZ, classification, - learn, infer) - result2 = self._fastCLAClassifier.compute(recordNum, patternNZ, - classification, learn, infer) - self._calls += 1 - # Check if it is time to serialize and deserialize. - if self._calls % self._callsPerSerialize == 0: - self._claClassifier = pickle.loads(pickle.dumps(self._claClassifier)) - self._fastCLAClassifier = pickle.loads(pickle.dumps( - self._fastCLAClassifier)) - # Assert both results are the same type. - assert type(result1) == type(result2) - # Assert that the keys match. - assert set(result1.keys()) == set(result2.keys()), "diff detected: " \ - "py result=%s, C++ result=%s" % (result1, result2) - # Assert that the values match. - for k, l in result1.iteritems(): - assert type(l) == type(result2[k]) - for i in xrange(len(l)): - if isinstance(classification['actValue'], numbers.Real): - assert abs(float(l[i]) - float(result2[k][i])) < 0.0000001, ( - 'Python CLAClassifier has value %f and C++ FastCLAClassifier has ' - 'value %f.' % (l[i], result2[k][i])) - else: - assert l[i] == result2[k][i], ( - 'Python CLAClassifier has value %s and C++ FastCLAClassifier has ' - 'value %s.' % (str(l[i]), str(result2[k][i]))) - return result1 diff --git a/src/nupic/algorithms/cla_classifier_factory.py b/src/nupic/algorithms/cla_classifier_factory.py deleted file mode 100644 index e72dbb5573..0000000000 --- a/src/nupic/algorithms/cla_classifier_factory.py +++ /dev/null @@ -1,65 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -"""Module providing a factory for instantiating a CLA classifier.""" - -from nupic.algorithms.cla_classifier import CLAClassifier -from nupic.algorithms.cla_classifier_diff import CLAClassifierDiff -from nupic.bindings.algorithms import FastCLAClassifier -from nupic.support.configuration import Configuration - - - -class CLAClassifierFactory(object): - """Factory for instantiating CLA classifiers.""" - - - @staticmethod - def create(*args, **kwargs): - impl = kwargs.pop('implementation', None) - if impl is None: - impl = Configuration.get('nupic.opf.claClassifier.implementation') - if impl == 'py': - return CLAClassifier(*args, **kwargs) - elif impl == 'cpp': - return FastCLAClassifier(*args, **kwargs) - elif impl == 'diff': - return CLAClassifierDiff(*args, **kwargs) - else: - raise ValueError('Invalid classifier implementation (%r). Value must be ' - '"py" or "cpp".' % impl) - - - @staticmethod - def read(proto): - """ - proto: CLAClassifierRegionProto capnproto object - """ - impl = proto.classifierImp - if impl == 'py': - return CLAClassifier.read(proto.claClassifier) - elif impl == 'cpp': - return FastCLAClassifier.read(proto.claClassifier) - elif impl == 'diff': - raise NotImplementedError("CLAClassifierDiff.read not implemented") - else: - raise ValueError('Invalid classifier implementation (%r). Value must be ' - '"py" or "cpp".' % impl) diff --git a/src/nupic/algorithms/sdr_classifier.py b/src/nupic/algorithms/sdr_classifier.py index 913caa05cb..a7da5a9b69 100644 --- a/src/nupic/algorithms/sdr_classifier.py +++ b/src/nupic/algorithms/sdr_classifier.py @@ -49,9 +49,6 @@ class SDRClassifier(object): During learning, the connection weights between input units and output units are adjusted to maximize the likelihood of the model - The SDR Classifier is a variation of the previous CLAClassifier which was - not based on the references below. - Example Usage: .. code-block:: python diff --git a/src/nupic/engine/__init__.py b/src/nupic/engine/__init__.py index 2020a9cbb4..e892b28d7d 100644 --- a/src/nupic/engine/__init__.py +++ b/src/nupic/engine/__init__.py @@ -44,7 +44,6 @@ ("nupic.bindings.regions.TestNode", "TestNode"), ("nupic.regions.AnomalyLikelihoodRegion", "AnomalyLikelihoodRegion"), ("nupic.regions.anomaly_region", "AnomalyRegion"), - ("nupic.regions.cla_classifier_region", "CLAClassifierRegion"), ("nupic.regions.knn_anomaly_classifier_region", "KNNAnomalyClassifierRegion"), ("nupic.regions.knn_classifier_region", "KNNClassifierRegion"), ("nupic.regions.pluggable_encoder_sensor", "PluggableEncoderSensor"), diff --git a/src/nupic/frameworks/opf/exp_description_api.py b/src/nupic/frameworks/opf/exp_description_api.py index 481ddaa94c..e8195f5234 100644 --- a/src/nupic/frameworks/opf/exp_description_api.py +++ b/src/nupic/frameworks/opf/exp_description_api.py @@ -323,13 +323,6 @@ def __getHTMPredictionModelDescription(self): clAlpha = 0.001 clParams['alpha'] = clAlpha clParams['steps'] = config.get('clSteps', '1') - elif config['clRegionName'] == 'CLAClassifierRegion': - # deprecated - clAlpha = config.get('clAlpha', None) - if clAlpha is None: - clAlpha = 0.001 - clParams['alpha'] = clAlpha - clParams['steps'] = config.get('clSteps', '1') if 'clAdvancedParams' in config: clParams.update(config['clAdvancedParams']) diff --git a/src/nupic/frameworks/opf/htm_prediction_model.py b/src/nupic/frameworks/opf/htm_prediction_model.py index 20a43426a2..c20bd398ec 100644 --- a/src/nupic/frameworks/opf/htm_prediction_model.py +++ b/src/nupic/frameworks/opf/htm_prediction_model.py @@ -84,7 +84,7 @@ def _decorator(self, *args, **kwargs): class NetworkInfo(object): """ Data type used as return value type by - HTMPredictionModel.__createCLANetwork() + HTMPredictionModel.__createHTMNetwork() """ def __init__(self, net, statsCollectors): @@ -224,7 +224,7 @@ def __init__(self, self._netInfo = NetworkInfo(net=network, statsCollectors=[]) else: # Create the network - self._netInfo = self.__createCLANetwork( + self._netInfo = self.__createHTMNetwork( sensorParams, spEnable, spParams, tmEnable, tmParams, clEnable, clParams, anomalyParams) @@ -580,10 +580,10 @@ def _multiStepCompute(self, rawInput): "TM, SP, or Sensor regions") inputTSRecordIdx = rawInput.get('_timestampRecordIdx') - return self._handleCLAClassifierMultiStep( - patternNZ=patternNZ, - inputTSRecordIdx=inputTSRecordIdx, - rawInput=rawInput) + return self._handleSDRClassifierMultiStep( + patternNZ=patternNZ, + inputTSRecordIdx=inputTSRecordIdx, + rawInput=rawInput) def _classificationCompute(self): @@ -691,7 +691,7 @@ def _anomalyCompute(self): return inferences - def _handleCLAClassifierMultiStep(self, patternNZ, + def _handleSDRClassifierMultiStep(self, patternNZ, inputTSRecordIdx, rawInput): """ Handle the CLA Classifier compute logic when implementing multi-step @@ -832,7 +832,7 @@ def _handleCLAClassifierMultiStep(self, patternNZ, # Plug in the predictions for each requested time step. for steps in predictionSteps: # From the clResults, compute the predicted actual value. The - # CLAClassifier classifies the bucket index and returns a list of + # SDRClassifier classifies the bucket index and returns a list of # relative likelihoods for each bucket. Let's find the max one # and then look up the actual value from that bucket index likelihoodsVec = clResults[steps] @@ -1074,7 +1074,7 @@ def _getDataSource(self): return self._getSensorRegion().getSelf().dataSource - def __createCLANetwork(self, sensorParams, spEnable, spParams, tmEnable, + def __createHTMNetwork(self, sensorParams, spEnable, spParams, tmEnable, tmParams, clEnable, clParams, anomalyParams): """ Create a CLA network and return it. @@ -1099,7 +1099,7 @@ def __createCLANetwork(self, sensorParams, spEnable, spParams, tmEnable, if classifierOnly: enabledEncoders.pop(name) - # Disabled encoders are encoders that are fed to CLAClassifierRegion but not + # Disabled encoders are encoders that are fed to SDRClassifierRegion but not # SP or TM Regions. This is to handle the case where the predicted field # is not fed through the SP/TM. We typically just have one of these now. disabledEncoders = copy.deepcopy(sensorParams['encoders']) diff --git a/src/nupic/regions/CLAClassifierRegion.capnp b/src/nupic/regions/CLAClassifierRegion.capnp deleted file mode 100644 index 1f833dea7f..0000000000 --- a/src/nupic/regions/CLAClassifierRegion.capnp +++ /dev/null @@ -1,13 +0,0 @@ -@0x86ee045dcbcfbf3f; - -using import "/nupic/proto/ClaClassifier.capnp".ClaClassifierProto; - -# Next ID: 6 -struct CLAClassifierRegionProto { - classifierImp @0 :Text; - claClassifier @1 :ClaClassifierProto; - steps @2 :Text; - alpha @3 :Float32; - verbosity @4 :UInt32; - maxCategoryCount @5 :UInt32; -} diff --git a/src/nupic/regions/cla_classifier_region.py b/src/nupic/regions/cla_classifier_region.py deleted file mode 100755 index f5dbd6556a..0000000000 --- a/src/nupic/regions/cla_classifier_region.py +++ /dev/null @@ -1,498 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013-15, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -""" -This file implements the CLA Classifier region. See the comments in the class -definition of CLAClassifierRegion for a description. -""" - -import warnings - -from nupic.bindings.regions.PyRegion import PyRegion -from nupic.bindings.algorithms import FastCLAClassifier -from nupic.algorithms.cla_classifier_factory import CLAClassifierFactory -from nupic.support.configuration import Configuration - -try: - import capnp -except ImportError: - capnp = None -if capnp: - from nupic.regions.CLAClassifierRegion_capnp import CLAClassifierRegionProto - - - -class CLAClassifierRegion(PyRegion): - """ - CLAClassifierRegion implements a CLA specific classifier that accepts a binary - input from the level below (the "activationPattern") and information from the - sensor and encoders (the "classification") describing the input to the system - at that time step. - - When learning, for every bit in activation pattern, it records a history of - the classification each time that bit was active. The history is bounded by a - maximum allowed age so that old entries are thrown away. - - For inference, it takes an ensemble approach. For every active bit in the - activationPattern, it looks up the most likely classification(s) from the - history stored for that bit and then votes across these to get the resulting - classification(s). - - The caller can choose to tell the region that the classifications for - iteration N+K should be aligned with the activationPattern for iteration N. - This results in the classifier producing predictions for K steps in advance. - Any number of different K's can be specified, allowing the classifier to learn - and infer multi-step predictions for a number of steps in advance. - """ - - - @classmethod - def getSpec(cls): - ns = dict( - description=CLAClassifierRegion.__doc__, - singleNodeOnly=True, - - inputs=dict( - categoryIn=dict( - description='Vector of categories of the input sample', - dataType='Real32', - count=0, - required=True, - regionLevel=True, - isDefaultInput=False, - requireSplitterMap=False), - - bottomUpIn=dict( - description='Belief values over children\'s groups', - dataType='Real32', - count=0, - required=True, - regionLevel=False, - isDefaultInput=True, - requireSplitterMap=False), - - predictedActiveCells=dict( - description="The cells that are active and predicted", - dataType='Real32', - count=0, - required=True, - regionLevel=True, - isDefaultInput=False, - requireSplitterMap=False), - - sequenceIdIn=dict( - description="Sequence ID", - dataType='UInt64', - count=1, - required=False, - regionLevel=True, - isDefaultInput=False, - requireSplitterMap=False), - - ), - - outputs=dict( - categoriesOut=dict( - description='Classification results', - dataType='Real32', - count=0, - regionLevel=True, - isDefaultOutput=False, - requireSplitterMap=False), - - actualValues=dict( - description='Classification results', - dataType='Real32', - count=0, - regionLevel=True, - isDefaultOutput=False, - requireSplitterMap=False), - - probabilities=dict( - description='Classification results', - dataType='Real32', - count=0, - regionLevel=True, - isDefaultOutput=False, - requireSplitterMap=False), - ), - - parameters=dict( - learningMode=dict( - description='Boolean (0/1) indicating whether or not a region ' - 'is in learning mode.', - dataType='UInt32', - count=1, - constraints='bool', - defaultValue=1, - accessMode='ReadWrite'), - - inferenceMode=dict( - description='Boolean (0/1) indicating whether or not a region ' - 'is in inference mode.', - dataType='UInt32', - count=1, - constraints='bool', - defaultValue=0, - accessMode='ReadWrite'), - - maxCategoryCount=dict( - description='The maximal number of categories the ' - 'classifier will distinguish between.', - dataType='UInt32', - required=True, - count=1, - constraints='', - # arbitrarily large value for backward compatibility - defaultValue=1000, - accessMode='Create'), - - steps=dict( - description='Comma separated list of the desired steps of ' - 'prediction that the classifier should learn', - dataType="Byte", - count=0, - constraints='', - defaultValue='0', - accessMode='Create'), - - alpha=dict( - description='The alpha used to compute running averages of the ' - 'bucket duty cycles for each activation pattern bit. A ' - 'lower alpha results in longer term memory', - dataType="Real32", - count=1, - constraints='', - defaultValue=0.001, - accessMode='Create'), - - implementation=dict( - description='The classifier implementation to use.', - accessMode='ReadWrite', - dataType='Byte', - count=0, - constraints='enum: py, cpp'), - - verbosity=dict( - description='An integer that controls the verbosity level, ' - '0 means no verbose output, increasing integers ' - 'provide more verbosity.', - dataType='UInt32', - count=1, - constraints='', - defaultValue=0, - accessMode='ReadWrite'), - - ), - commands=dict() - ) - - return ns - - - def __init__(self, - steps='1', - alpha=0.001, - verbosity=0, - implementation=None, - maxCategoryCount=None - ): - - # Set default implementation - if implementation is None: - implementation = Configuration.get('nupic.opf.claClassifier.implementation') - - # Convert the steps designation to a list - self.classifierImp = implementation - self.steps = steps - self.stepsList = eval("[%s]" % (steps)) - self.alpha = alpha - self.verbosity = verbosity - - # Initialize internal structures - self._claClassifier = CLAClassifierFactory.create( - steps=self.stepsList, - alpha=self.alpha, - verbosity=self.verbosity, - implementation=implementation, - ) - self.learningMode = True - self.inferenceMode = False - self.maxCategoryCount = maxCategoryCount - self.recordNum = 0 - self._initEphemerals() - - # Flag to know if the compute() function is ever called. This is to - # prevent backward compatibilities issues with the customCompute() method - # being called at the same time as the compute() method. Only compute() - # should be called via network.run(). This flag will be removed once we - # get to cleaning up the htm_prediction_model.py file. - self._computeFlag = False - - - def _initEphemerals(self): - pass - - - def initialize(self): - pass - - - def clear(self): - self._claClassifier.clear() - - - def getAlgorithmInstance(self): - """Returns instance of the underlying CLAClassifier algorithm object.""" - return self._claClassifier - - - def getParameter(self, name, index=-1): - """ - Get the value of the parameter. - - @param name -- the name of the parameter to retrieve, as defined - by the Node Spec. - """ - # If any spec parameter name is the same as an attribute, this call - # will get it automatically, e.g. self.learningMode - return PyRegion.getParameter(self, name, index) - - - def setParameter(self, name, index, value): - """ - Set the value of the parameter. - - @param name -- the name of the parameter to update, as defined - by the Node Spec. - @param value -- the value to which the parameter is to be set. - """ - if name == "learningMode": - self.learningMode = bool(int(value)) - elif name == "inferenceMode": - self.inferenceMode = bool(int(value)) - else: - return PyRegion.setParameter(self, name, index, value) - - - @staticmethod - def getProtoType(): - """Return the pycapnp proto type that the class uses for serialization.""" - return CLAClassifierRegionProto - - - def writeToProto(self, proto): - """Write state to proto object. - - proto: CLAClassifierRegionProto capnproto object - """ - proto.classifierImp = self.classifierImp - proto.steps = self.steps - proto.alpha = self.alpha - proto.verbosity = self.verbosity - proto.maxCategoryCount = self.maxCategoryCount - - self._claClassifier.write(proto.claClassifier) - - - @classmethod - def readFromProto(cls, proto): - """Read state from proto object. - - proto: CLAClassifierRegionProto capnproto object - """ - instance = cls() - - instance.classifierImp = proto.classifierImp - instance.steps = proto.steps - instance.alpha = proto.alpha - instance.verbosity = proto.verbosity - instance.maxCategoryCount = proto.maxCategoryCount - - instance._claClassifier = CLAClassifierFactory.read(proto) - - return instance - - - def reset(self): - pass - - - def compute(self, inputs, outputs): - """ - Process one input sample. - This method is called by the runtime engine. - @param inputs -- inputs of the classifier region - @param outputs -- outputs of the classifier region - - """ - - # This flag helps to prevent double-computation, in case the deprecated - # customCompute() method is being called in addition to compute() called - # when network.run() is called - self._computeFlag = True - - # An input can potentially belong to multiple categories. - # If a category value is < 0, it means that the input does not belong to - # that category. - categories = [category for category in inputs["categoryIn"] - if category >= 0] - - activeCells = inputs["bottomUpIn"] - patternNZ = activeCells.nonzero()[0] - - # ========================================================================== - # Allow to train on multiple input categories. - # Do inference first, and then train on all input categories. - - # -------------------------------------------------------------------------- - # 1. Call classifier. Don't train. Just inference. Train after. - - # Dummy classification input, because this param is required. Learning is - # off, so the classifier is not learning this input. Inference only here. - classificationIn = {"actValue": 0, "bucketIdx": 0} - clResults = self._claClassifier.compute(recordNum=self.recordNum, - patternNZ=patternNZ, - classification=classificationIn, - learn=False, - infer=self.inferenceMode) - - for category in categories: - classificationIn = {"bucketIdx": int(category), "actValue": int(category)} - - # ------------------------------------------------------------------------ - # 2. Train classifier, no inference - self._claClassifier.compute(recordNum=self.recordNum, - patternNZ=patternNZ, - classification=classificationIn, - learn=self.learningMode, - infer=False) - - actualValues = clResults["actualValues"] - outputs['actualValues'][:len(actualValues)] = actualValues - for step in self.stepsList: - stepIndex = self.stepsList.index(step) - categoryOut = actualValues[clResults[step].argmax()] - outputs['categoriesOut'][stepIndex] = categoryOut - - # Flatten the rest of the output. For example: - # Original dict {1 : [0.1, 0.3, 0.2, 0.7] - # 4 : [0.2, 0.4, 0.3, 0.5]} - # becomes: [0.1, 0.3, 0.2, 0.7, 0.2, 0.4, 0.3, 0.5] - stepProbabilities = clResults[step] - for categoryIndex in xrange(self.maxCategoryCount): - flatIndex = categoryIndex + stepIndex * self.maxCategoryCount - if categoryIndex < len(stepProbabilities): - outputs['probabilities'][flatIndex] = stepProbabilities[categoryIndex] - else: - outputs['probabilities'][flatIndex] = 0.0 - - self.recordNum += 1 - - - def customCompute(self, recordNum, patternNZ, classification): - """ - Just return the inference value from one input sample. The actual - learning happens in compute() -- if, and only if learning is enabled -- - which is called when you run the network. - - WARNING: The method customCompute() is here to maintain backward - compatibility. This method is deprecated, and will be removed. - Use network.run() instead, which will call the compute() method. - - Parameters: - -------------------------------------------------------------------- - recordNum: Record number of the input sample. - patternNZ: List of the active indices from the output below - classification: Dict of the classification information: - bucketIdx: index of the encoder bucket - actValue: actual value going into the encoder - - retval: dict containing inference results, one entry for each step in - self.steps. The key is the number of steps, the value is an - array containing the relative likelihood for each bucketIdx - starting from bucketIdx 0. - - for example: - {'actualValues': [0.0, 1.0, 2.0, 3.0] - 1 : [0.1, 0.3, 0.2, 0.7] - 4 : [0.2, 0.4, 0.3, 0.5]} - """ - - # If the compute flag has not been initialized (for example if we - # restored a model from an old checkpoint) initialize it to False. - if not hasattr(self, "_computeFlag"): - self._computeFlag = False - - if self._computeFlag: - # Will raise an exception if the deprecated method customCompute() is - # being used at the same time as the compute function. - warnings.simplefilter('error', DeprecationWarning) - warnings.warn("The customCompute() method should not be " - "called at the same time as the compute() " - "method. The compute() method is called " - "whenever network.run() is called.", - DeprecationWarning) - - return self._claClassifier.compute(recordNum, - patternNZ, - classification, - self.learningMode, - self.inferenceMode) - - - def getOutputValues(self, outputName): - """ - Return the dictionary of output values. Note that these are normal Python - lists, rather than numpy arrays. This is to support lists with mixed scalars - and strings, as in the case of records with categorical variables - """ - return self._outputValues[outputName] - - - def getOutputElementCount(self, outputName): - """Returns the width of dataOut.""" - - # Check if classifier has a 'maxCategoryCount' attribute - if not hasattr(self, "maxCategoryCount"): - # Large default value for backward compatibility - self.maxCategoryCount = 1000 - - if outputName == "categoriesOut": - return len(self.stepsList) - elif outputName == "probabilities": - return len(self.stepsList) * self.maxCategoryCount - elif outputName == "actualValues": - return self.maxCategoryCount - else: - raise ValueError("Unknown output {}.".format(outputName)) - - - -if __name__ == "__main__": - from nupic.engine import Network - - n = Network() - classifier = n.addRegion( - 'classifier', - 'py.CLAClassifierRegion', - '{ steps: "1,2", maxAge: 1000}' - ) diff --git a/src/nupic/support/nupic-default.xml b/src/nupic/support/nupic-default.xml index 1229567764..1203f737d4 100644 --- a/src/nupic/support/nupic-default.xml +++ b/src/nupic/support/nupic-default.xml @@ -7,14 +7,6 @@ - - nupic.opf.claClassifier.implementation - cpp - The classifier implementation to use by default. The current - options are 'py', 'cpp', and 'diff'. - - - nupic.opf.sdrClassifier.implementation cpp diff --git a/tests/unit/nupic/algorithms/cla_classifier_diff_test.py b/tests/unit/nupic/algorithms/cla_classifier_diff_test.py deleted file mode 100755 index d928ddb989..0000000000 --- a/tests/unit/nupic/algorithms/cla_classifier_diff_test.py +++ /dev/null @@ -1,49 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -"""Unit tests for the "diff" version of the CLA classifier.""" - -import unittest2 as unittest - -from nupic.algorithms.cla_classifier_diff import CLAClassifierDiff - -import cla_classifier_test - - - -class CLAClassifierDiffTest(cla_classifier_test.CLAClassifierTest): - """CLAClassifierDiff unit tests.""" - - - def setUp(self): - self._classifier = CLAClassifierDiff - - - unittest.skip("The classifier diff fails for this test for some reason. " - "Should be fixed but the diff classifier is just for testing " - "anyway.") - def testComputeCategory2(self): - pass - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/nupic/algorithms/cla_classifier_test.py b/tests/unit/nupic/algorithms/cla_classifier_test.py deleted file mode 100755 index 2a19c199c7..0000000000 --- a/tests/unit/nupic/algorithms/cla_classifier_test.py +++ /dev/null @@ -1,552 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -"""Unit tests for CLAClassifier module.""" - -CL_VERBOSITY = 0 - -import cPickle as pickle -import types -import unittest2 as unittest - -import numpy -import tempfile - -from nupic.algorithms.cla_classifier import CLAClassifier - -try: - import capnp -except ImportError: - capnp = None -if capnp: - from nupic.proto import ClaClassifier_capnp - - - -class CLAClassifierTest(unittest.TestCase): - """Unit tests for CLAClassifier class.""" - - - def setUp(self): - self._classifier = CLAClassifier - - - def testInitialization(self): - c = self._classifier([1], 0.1, 0.1, 0) - self.assertEqual(type(c), self._classifier) - - - def testSingleValue(self): - """Send same value 10 times and expect 100% likelihood for prediction.""" - classifier = self._classifier() - - # Enough times to perform Inference and learn associations - retval = [] - for recordNum in xrange(10): - retval = self._compute(classifier, recordNum, [1, 5], 0, 10) - - self._checkValue(retval, 0, 10, 1.) - - - def testSingleValue0Steps(self): - """Send same value 10 times and expect 100% likelihood for prediction - using 0-step ahead prediction""" - classifier = self._classifier([0]) - - # Enough times to perform Inference and learn associations - retval = [] - for recordNum in xrange(10): - retval = self._compute(classifier, recordNum, [1, 5], 0, 10) - - self.assertEqual(retval['actualValues'][0], 10) - self.assertEqual(retval[0][0], 1.0) - - - def testComputeResultTypes(self): - c = self._classifier([1], 0.1, 0.1, 0) - result = c.compute(recordNum=0, - patternNZ=[1, 5, 9], - classification= {'bucketIdx': 4, 'actValue': 34.7}, - learn=True, - infer=True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertEqual(type(result['actualValues']), list) - self.assertEqual(len(result['actualValues']), 1) - self.assertEqual(type(result['actualValues'][0]), float) - self.assertEqual(type(result[1]), numpy.ndarray) - self.assertEqual(result[1].itemsize, 8) - self.assertAlmostEqual(result['actualValues'][0], 34.7, places=5) - - - def testBucketIdxNumpyInt64Input(self): - c = self._classifier([1], 0.1, 0.1, 0) - result = c.compute(0, [1, 5, 9], - {'bucketIdx': numpy.int64(4), 'actValue': 34.7}, True, - True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertEqual(len(result['actualValues']), 1) - self.assertAlmostEqual(result['actualValues'][0], 34.7, places=5) - - - def testCompute1(self): - c = self._classifier([1], 0.1, 0.1, 0) - result = c.compute(recordNum=0, - patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, - infer=True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertEqual(len(result['actualValues']), 1) - self.assertAlmostEqual(result['actualValues'][0], 34.7, places=5) - - - def testCompute2(self): - c = self._classifier([1], 0.1, 0.1, 0) - c.compute(recordNum=0, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - result = c.compute(recordNum=1, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertAlmostEqual(result['actualValues'][4], 34.7, places=5) - - - def testComputeComplex(self): - c = self._classifier([1], 0.1, 0.1, 0) - recordNum=0 - c.compute(recordNum=recordNum, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - recordNum += 1 - - c.compute(recordNum=recordNum, patternNZ=[0, 6, 9, 11], - classification={'bucketIdx': 5, 'actValue': 41.7}, - learn=True, infer=True) - recordNum += 1 - - c.compute(recordNum=recordNum, patternNZ=[6, 9], - classification={'bucketIdx': 5, 'actValue': 44.9}, - learn=True, infer=True) - recordNum += 1 - - c.compute(recordNum=recordNum, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 42.9}, - learn=True, infer=True) - recordNum += 1 - - result = c.compute(recordNum=recordNum, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - recordNum += 1 - - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertAlmostEqual(result['actualValues'][4], 35.520000457763672, - places=5) - self.assertAlmostEqual(result['actualValues'][5], 42.020000457763672, - places=5) - self.assertEqual(len(result[1]), 6) - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 0.0, places=5) - self.assertAlmostEqual(result[1][2], 0.0, places=5) - self.assertAlmostEqual(result[1][3], 0.0, places=5) - self.assertAlmostEqual(result[1][4], 0.12300123, places=5) - self.assertAlmostEqual(result[1][5], 0.87699877, places=5) - - - def testComputeWithMissingValue(self): - c = self._classifier([1], 0.1, 0.1, 0) - result = c.compute( - recordNum=0, patternNZ=[1, 5, 9], - classification={'bucketIdx': None, 'actValue': None}, learn=True, - infer=True) - - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertEqual(len(result['actualValues']), 1) - self.assertEqual(result['actualValues'][0], None) - - - def testComputeCategory(self): - c = self._classifier([1], 0.1, 0.1, 0) - c.compute(recordNum=0, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 'D'}, - learn=True, infer=True) - result = c.compute(recordNum=1, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 'D'}, - learn=True, infer=True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertEqual(result['actualValues'][4], 'D') - - predictResult = c.compute(recordNum=2, patternNZ=[1, 5, 9], - classification={'bucketIdx': 5, - 'actValue': None}, - learn=True, infer=True) - for value in predictResult['actualValues']: - self.assertIsInstance(value, (types.NoneType, types.StringType)) - - - def testComputeCategory2(self): - c = self._classifier([1], 0.1, 0.1, 0) - c.compute(recordNum=0, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 'D'}, - learn=True, infer=True) - result = c.compute(recordNum=1, patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 'E'}, - learn=True, infer=True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertEqual(result['actualValues'][4], 'D') - - - def testSerialization(self): - c = self._classifier([1], 0.1, 0.1, 0) - c.compute(recordNum=0, - patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - c.compute(recordNum=1, - patternNZ=[0, 6, 9, 11], - classification={'bucketIdx': 5, 'actValue': 41.7}, - learn=True, infer=True) - c.compute(recordNum=2, - patternNZ=[6, 9], - classification={'bucketIdx': 5, 'actValue': 44.9}, - learn=True, infer=True) - c.compute(recordNum=3, - patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 42.9}, - learn=True, infer=True) - serialized = pickle.dumps(c) - c = pickle.loads(serialized) - result = c.compute(recordNum=4, - patternNZ=[1, 5, 9], - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - self.assertSetEqual(set(result.keys()), set(('actualValues', 1))) - self.assertAlmostEqual(result['actualValues'][4], 35.520000457763672, - places=5) - self.assertAlmostEqual(result['actualValues'][5], 42.020000457763672, - places=5) - self.assertEqual(len(result[1]), 6) - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 0.0, places=5) - self.assertAlmostEqual(result[1][2], 0.0, places=5) - self.assertAlmostEqual(result[1][3], 0.0, places=5) - self.assertAlmostEqual(result[1][4], 0.12300123, places=5) - self.assertAlmostEqual(result[1][5], 0.87699877, places=5) - - - @unittest.skipUnless( - capnp, "pycapnp is not installed, skipping serialization test.") - def testWriteRead(self): - c1 = CLAClassifier([1], 0.1, 0.1, 0) - - # Create a vector of input bit indices - input1 = [1, 5, 9] - result = c1.compute(recordNum=0, - patternNZ=input1, - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - - proto1 = ClaClassifier_capnp.ClaClassifierProto.new_message() - c1.write(proto1) - - # Write the proto to a temp file and read it back into a new proto - with tempfile.TemporaryFile() as f: - proto1.write(f) - f.seek(0) - proto2 = ClaClassifier_capnp.ClaClassifierProto.read(f) - - # Load the deserialized proto - c2 = CLAClassifier.read(proto2) - - self.assertEqual(c1.steps, c2.steps) - self.assertAlmostEqual(c1.alpha, c2.alpha) - self.assertAlmostEqual(c1.actValueAlpha, c2.actValueAlpha) - self.assertEqual(c1._learnIteration, c2._learnIteration) - self.assertEqual(c1._recordNumMinusLearnIteration, c2._recordNumMinusLearnIteration) - self.assertEqual(c1._patternNZHistory, c2._patternNZHistory) - self.assertEqual(c1._activeBitHistory.keys(), c2._activeBitHistory.keys()) - for bit, nSteps in c1._activeBitHistory.keys(): - c1BitHistory = c1._activeBitHistory[(bit, nSteps)] - c2BitHistory = c2._activeBitHistory[(bit, nSteps)] - self.assertEqual(c1BitHistory._id, c2BitHistory._id) - self.assertEqual(c1BitHistory._stats, c2BitHistory._stats) - self.assertEqual(c1BitHistory._lastTotalUpdate, c2BitHistory._lastTotalUpdate) - self.assertEqual(c1BitHistory._learnIteration, c2BitHistory._learnIteration) - self.assertEqual(c1._maxBucketIdx, c2._maxBucketIdx) - self.assertEqual(len(c1._actualValues), len(c2._actualValues)) - for i in xrange(len(c1._actualValues)): - self.assertAlmostEqual(c1._actualValues[i], c2._actualValues[i], 5) - self.assertEqual(c1._version, c2._version) - self.assertEqual(c1.verbosity, c2.verbosity) - - result1 = c1.compute(recordNum=1, - patternNZ=input1, - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - result2 = c2.compute(recordNum=1, - patternNZ=input1, - classification={'bucketIdx': 4, 'actValue': 34.7}, - learn=True, infer=True) - - self.assertEqual(result1.keys(), result2.keys()) - for key in result1.keys(): - for i in xrange(len(c1._actualValues)): - self.assertAlmostEqual(result1[key][i], result2[key][i], 5) - - - # Temporarily disabled until David's classifier change is submitted. - def _testUnknownValues(self): - classifier = self._classifier() - - # Single Unknown Value - retval = self._compute(classifier, recordNum=0, pattern=[1, 5], bucket=9, - value=9) - - # Test with single value of 9->9 (should be 9 with 100%) - self._checkValue(retval, 9, 9, 1.0) - - # Second Unknown Value - retval = self._compute(classifier, recordNum=1, pattern=[2, 3], bucket=2, - value=2) - - # Should be both options with 50% - self._checkValue(retval, 9, 9, 0.5) - self._checkValue(retval, 2, 2, 0.5) - - - def testOverlapPattern(self): - classifier = self._classifier() - - _ = self._compute(classifier, recordNum=0, pattern=[1, 5], bucket=9, - value=9) - _ = self._compute(classifier, recordNum=1, pattern=[1, 5], bucket=9, - value=9) - retval = self._compute(classifier, recordNum=2, pattern=[3, 5], bucket=2, - value=2) - - # Since overlap - should be previous with 100% - self._checkValue(retval, 9, 9, 1.0) - - retval = self._compute(classifier, recordNum=3, pattern=[3, 5], bucket=2, - value=2) - # Second example: now new value should be more probable than old - self.assertGreater(retval[1][2], retval[1][9]) - - - # TODO: Disabled because there are no assertions. - def _testScaling(self): - classifier = self._classifier() - recordNum = 0 - for _ in range(100): - _ = self._compute(classifier, recordNum=recordNum, pattern=[1], - bucket=5, value=5) - recordNum += 1 - - for _ in range(1000): - _ = self._compute(classifier, recordNum=recordNum, pattern=[2], - bucket=9, value=9) - recordNum += 1 - - for _ in range(3): - _retval = self._compute(classifier, recordNum=recordNum, pattern=[1, 5], - bucket=6, value=6) - recordNum += 1 - - #print retval - - - def testMultistepSingleValue(self): - classifier = self._classifier(steps=[1, 2]) - - retval = [] - recordNum = 0 - for _ in range(10): - retval = self._compute(classifier, recordNum=recordNum, pattern=[1, 5], - bucket=0, value=10) - recordNum += 1 - - # Only should return one actual value bucket. - self.assertEqual(retval['actualValues'], [10]) - # Should have a probability of 100% for that bucket. - self.assertEqual(retval[1], [1.]) - self.assertEqual(retval[2], [1.]) - - - def testMultistepSimple(self): - classifier = self._classifier(steps=[1, 2]) - - retval = [] - recordNum = 0 - for i in range(100): - retval = self._compute(classifier, recordNum=recordNum, pattern=[i % 10], - bucket=i % 10, value=(i % 10) * 10) - recordNum += 1 - - # Only should return one actual value bucket. - self.assertEqual(retval['actualValues'], - [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]) - self.assertAlmostEqual(retval[1][0], 1.0) - for i in xrange(1, 10): - self.assertAlmostEqual(retval[1][i], 0.0) - self.assertAlmostEqual(retval[2][1], 1.0) - for i in [0] + range(2, 10): - self.assertAlmostEqual(retval[2][i], 0.0) - - - def testMissingRecords(self): - """ Test missing record support. - - Here, we intend the classifier to learn the associations: - [1,3,5] => bucketIdx 1 - [2,4,6] => bucketIdx 2 - [7,8,9] => don't care - - If it doesn't pay attention to the recordNums in this test, it will learn the - wrong associations. - """ - - c = self._classifier([1], 0.1, 0.1, 0) - recordNum = 0 - c.compute(recordNum=recordNum, patternNZ=[1, 3, 5], - classification={'bucketIdx': 0, 'actValue': 0}, - learn=True, infer=True) - recordNum += 1 - - c.compute(recordNum=recordNum, patternNZ=[2, 4, 6], - classification={'bucketIdx': 1, 'actValue': 1}, - learn=True, infer=True) - recordNum += 1 - - c.compute(recordNum=recordNum, patternNZ=[1, 3, 5], - classification={'bucketIdx': 2, 'actValue': 2}, - learn=True, infer=True) - recordNum += 1 - - c.compute(recordNum=recordNum, patternNZ=[2, 4, 6], - classification={'bucketIdx': 1, 'actValue': 1}, - learn=True, infer=True) - recordNum += 1 - - - # ----------------------------------------------------------------------- - # At this point, we should have learned [1,3,5] => bucket 1 - # [2,4,6] => bucket 2 - result = c.compute(recordNum=recordNum, patternNZ=[1, 3, 5], - classification={'bucketIdx': 2, 'actValue': 2}, - learn=True, infer=True) - recordNum += 1 - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 1.0, places=5) - self.assertAlmostEqual(result[1][2], 0.0, places=5) - - result = c.compute(recordNum=recordNum, patternNZ=[2, 4, 6], - classification={'bucketIdx': 1, 'actValue': 1}, - learn=True, infer=True) - recordNum += 1 - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 0.0, places=5) - self.assertAlmostEqual(result[1][2], 1.0, places=5) - - - - # ----------------------------------------------------------------------- - # Feed in records that skip and make sure they don't mess up what we - # learned - # If we skip a record, the CLA should NOT learn that [2,4,6] from - # the previous learn associates with bucket 0 - recordNum += 1 - result = c.compute(recordNum=recordNum, patternNZ=[1, 3, 5], - classification={'bucketIdx': 0, 'actValue': 0}, - learn=True, infer=True) - recordNum += 1 - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 1.0, places=5) - self.assertAlmostEqual(result[1][2], 0.0, places=5) - - # If we skip a record, the CLA should NOT learn that [1,3,5] from - # the previous learn associates with bucket 0 - recordNum += 1 - result = c.compute(recordNum=recordNum, patternNZ=[2, 4, 6], - classification={'bucketIdx': 0, 'actValue': 0}, - learn=True, infer=True) - recordNum += 1 - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 0.0, places=5) - self.assertAlmostEqual(result[1][2], 1.0, places=5) - - # If we skip a record, the CLA should NOT learn that [2,4,6] from - # the previous learn associates with bucket 0 - recordNum += 1 - result = c.compute(recordNum=recordNum, patternNZ=[1, 3, 5], - classification={'bucketIdx': 0, 'actValue': 0}, - learn=True, infer=True) - recordNum += 1 - self.assertAlmostEqual(result[1][0], 0.0, places=5) - self.assertAlmostEqual(result[1][1], 1.0, places=5) - self.assertAlmostEqual(result[1][2], 0.0, places=5) - - - def testMissingRecordInitialization(self): - """ - Test missing record edge TestCase - Test an edge case in the classifier initialization when there is a missing - record in the first n records, where n is the # of prediction steps. - """ - c = self._classifier([2], 0.1, 0.1, 0) - result = c.compute( - recordNum=0, patternNZ=[1, 5, 9], - classification={'bucketIdx': 0, 'actValue': 34.7}, - learn=True, infer=True) - - result = c.compute( - recordNum=2, patternNZ=[1, 5, 9], - classification={'bucketIdx': 0, 'actValue': 34.7}, - learn=True, infer=True) - - self.assertSetEqual(set(result.keys()), set(('actualValues', 2))) - self.assertEqual(len(result['actualValues']), 1) - self.assertAlmostEqual(result['actualValues'][0], 34.7) - - - def test_pFormatArray(self): - from nupic.algorithms.cla_classifier import _pFormatArray - pretty = _pFormatArray(range(10)) - self.assertIsInstance(pretty, basestring) - self.assertEqual(pretty[0], "[") - self.assertEqual(pretty[-1], "]") - self.assertEqual(len(pretty.split(" ")), 12) - - - def _checkValue(self, retval, index, value, probability): - self.assertEqual(retval['actualValues'][index], value) - self.assertEqual(retval[1][index], probability) - - - @staticmethod - def _compute(classifier, recordNum, pattern, bucket, value): - classification = {'bucketIdx': bucket, 'actValue': value} - return classifier.compute(recordNum, pattern, classification, True, True) - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/nupic/algorithms/fast_cla_classifier_test.py b/tests/unit/nupic/algorithms/fast_cla_classifier_test.py deleted file mode 100755 index d634ace21b..0000000000 --- a/tests/unit/nupic/algorithms/fast_cla_classifier_test.py +++ /dev/null @@ -1,48 +0,0 @@ -# ---------------------------------------------------------------------- -# Numenta Platform for Intelligent Computing (NuPIC) -# Copyright (C) 2013, Numenta, Inc. Unless you have an agreement -# with Numenta, Inc., for a separate license for this software code, the -# following terms and conditions apply: -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero Public License version 3 as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License -# along with this program. If not, see http://www.gnu.org/licenses. -# -# http://numenta.org/licenses/ -# ---------------------------------------------------------------------- - -"""Unit tests for the FastCLAClassifier. - -This test extends the test for the Python CLAClassifier to ensure that both -classifiers and their tests stay in sync. -""" - -import unittest2 as unittest - -from nupic.bindings.algorithms import FastCLAClassifier - -# Don't import the CLAClassifierTest directly or the unittest.main() will pick -# it up and run it. -import cla_classifier_test - - - -class FastCLAClassifierTest(cla_classifier_test.CLAClassifierTest): - """Unit tests for FastCLAClassifier class.""" - - - def setUp(self): - self._classifier = FastCLAClassifier - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/nupic/frameworks/opf/htmpredictionmodel_classifier_helper_test.py b/tests/unit/nupic/frameworks/opf/htmpredictionmodel_classifier_helper_test.py index 106be02878..509ac43523 100755 --- a/tests/unit/nupic/frameworks/opf/htmpredictionmodel_classifier_helper_test.py +++ b/tests/unit/nupic/frameworks/opf/htmpredictionmodel_classifier_helper_test.py @@ -145,7 +145,7 @@ -class CLAClassifierHelperTest(unittest.TestCase): +class SDRClassifierHelperTest(unittest.TestCase): """HTMPredictionModelClassifierHelper unit tests.""" def setUp(self): self.helper = HTMPredictionModelClassifierHelper(Mock(spec=HTMPredictionModel)) diff --git a/tests/unit/nupic/frameworks/opf/htmpredictionmodel_test.py b/tests/unit/nupic/frameworks/opf/htmpredictionmodel_test.py index 11afe46c46..799e909e72 100755 --- a/tests/unit/nupic/frameworks/opf/htmpredictionmodel_test.py +++ b/tests/unit/nupic/frameworks/opf/htmpredictionmodel_test.py @@ -99,7 +99,7 @@ def testTemporalAnomalyModelFactory(self): u'clEnable': False, u'clParams': {u'alpha': 0.035828933612158, u'verbosity': 0, - u'regionName': u'CLAClassifierRegion', + u'regionName': u'SDRClassifierRegion', u'steps': u'1'}, u'inferenceType': u'TemporalAnomaly', u'sensorParams': {u'encoders': {u'c0_dayOfWeek': None, From 004149c24f8f7aa848425c31cc75fec707c81dfb Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Mon, 5 Jun 2017 16:43:46 -0700 Subject: [PATCH 3/8] Code review update --- src/nupic/regions/record_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nupic/regions/record_sensor.py b/src/nupic/regions/record_sensor.py index 28df62844f..9ac64020d7 100644 --- a/src/nupic/regions/record_sensor.py +++ b/src/nupic/regions/record_sensor.py @@ -207,8 +207,8 @@ def getSpec(cls): count=1, constraints=""), predictedField=dict( - description="The field to be predicted. This will result in the " - "outputs actValueOut and bucketIdxOut not being " + description="The name of the field to be predicted. This will result " + "in the outputs actValueOut and bucketIdxOut not being " "populated.", dataType="Byte", accessMode="ReadWrite", From 7c386791998626c3eb8eba4fa68a219ba39f61a2 Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Tue, 6 Jun 2017 09:18:13 -0700 Subject: [PATCH 4/8] Ensure predictedField is passed as Python str to C++ --- src/nupic/frameworks/opf/htm_prediction_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nupic/frameworks/opf/htm_prediction_model.py b/src/nupic/frameworks/opf/htm_prediction_model.py index c20bd398ec..3505ce8a32 100644 --- a/src/nupic/frameworks/opf/htm_prediction_model.py +++ b/src/nupic/frameworks/opf/htm_prediction_model.py @@ -313,7 +313,7 @@ def enableInference(self, inferenceArgs=None): super(HTMPredictionModel, self).enableInference(inferenceArgs) if inferenceArgs is not None and "predictedField" in inferenceArgs: self._getSensorRegion().setParameter("predictedField", - inferenceArgs["predictedField"]) + str(inferenceArgs["predictedField"])) def enableLearning(self): From 67fdf57055a32868c3407cd2a96a5bb6860f13d4 Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Tue, 6 Jun 2017 11:26:07 -0700 Subject: [PATCH 5/8] Make sure we include disabled encoders when looking up predicted field --- src/nupic/regions/record_sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nupic/regions/record_sensor.py b/src/nupic/regions/record_sensor.py index 9ac64020d7..b0436f86ec 100644 --- a/src/nupic/regions/record_sensor.py +++ b/src/nupic/regions/record_sensor.py @@ -379,7 +379,8 @@ def compute(self, inputs, outputs): # If there is a field to predict, set bucketIdxOut and actValueOut. if self.predictedField is not None: - encoders = [e for e in self.encoder.encoders if e[0] == self.predictedField] + encoders = [e for e in (self.encoder.encoders + self.disabledEncoder.encoders) + if e[0] == self.predictedField] if len(encoders) == 0: raise ValueError("There is no encoder for set for the predicted " "field: %s" % predictedField) From 2192942c9257776f256615e2d8cb4a1fa7c5c2d1 Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Tue, 6 Jun 2017 11:35:17 -0700 Subject: [PATCH 6/8] Fix case where disabledEncoder is None --- src/nupic/regions/record_sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nupic/regions/record_sensor.py b/src/nupic/regions/record_sensor.py index b0436f86ec..b09f5edc02 100644 --- a/src/nupic/regions/record_sensor.py +++ b/src/nupic/regions/record_sensor.py @@ -379,7 +379,10 @@ def compute(self, inputs, outputs): # If there is a field to predict, set bucketIdxOut and actValueOut. if self.predictedField is not None: - encoders = [e for e in (self.encoder.encoders + self.disabledEncoder.encoders) + allEncoders = list(self.encoder.encoders) + if self.disabledEncoder is not None: + allEncoders.extend(self.disabledEncoder.encoders) + encoders = [e for e in allEncoders if e[0] == self.predictedField] if len(encoders) == 0: raise ValueError("There is no encoder for set for the predicted " From 2619e7a54aab5bad35f1600624864130ab9396c0 Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Tue, 6 Jun 2017 11:35:33 -0700 Subject: [PATCH 7/8] Add SDRClassifierDiff class --- src/nupic/algorithms/sdr_classifier_diff.py | 97 +++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/nupic/algorithms/sdr_classifier_diff.py diff --git a/src/nupic/algorithms/sdr_classifier_diff.py b/src/nupic/algorithms/sdr_classifier_diff.py new file mode 100644 index 0000000000..569c7f7ef6 --- /dev/null +++ b/src/nupic/algorithms/sdr_classifier_diff.py @@ -0,0 +1,97 @@ +# ---------------------------------------------------------------------- +# Numenta Platform for Intelligent Computing (NuPIC) +# Copyright (C) 2013-2017, Numenta, Inc. Unless you have an agreement +# with Numenta, Inc., for a separate license for this software code, the +# following terms and conditions apply: +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero Public License for more details. +# +# You should have received a copy of the GNU Affero Public License +# along with this program. If not, see http://www.gnu.org/licenses. +# +# http://numenta.org/licenses/ +# ---------------------------------------------------------------------- + +"""SDR classifier diff tool. + +This class can be used just like versions of the SDR classifier but internally +creates instances of each SDR classifier. Each record is fed to both +classifiers and the results are checked for differences. +""" + +import cPickle as pickle +import numbers + +from nupic.algorithms.sdr_classifier import SDRClassifier +from nupic.bindings.algorithms import SDRClassifier as SDRClassifierCpp + + +CALLS_PER_SERIALIZE = 100 + + + +class SDRClassifierDiff(object): + """Classifier-like object that diffs the output from different classifiers. + + Instances of each version of the SDR classifier are created and each call to + compute is passed to each version of the classifier. The results are diffed + to make sure the there are no differences. + + Optionally, the classifiers can be serialized and deserialized after a + specified number of calls to compute to ensure that serialization does not + cause discrepencies between the results. + + TODO: Check internal state as well. + TODO: Provide option to write output to a file. + TODO: Provide record differences without throwing an exception. + """ + + + __VERSION__ = 'SDRClassifierDiffV1' + + + def __init__(self, steps=(1,), alpha=0.001, actValueAlpha=0.3, verbosity=0, + callsPerSerialize=CALLS_PER_SERIALIZE): + self._sdrClassifier = SDRClassifier(steps, alpha, actValueAlpha, verbosity) + self._sdrClassifierCpp = SDRClassifierCpp(steps, alpha, actValueAlpha, + verbosity) + self._calls = 0 + self._callsPerSerialize = callsPerSerialize + + + def compute(self, recordNum, patternNZ, classification, learn, infer): + result1 = self._sdrClassifier.compute(recordNum, patternNZ, classification, + learn, infer) + result2 = self._sdrClassifierCpp.compute(recordNum, patternNZ, + classification, learn, infer) + self._calls += 1 + # Check if it is time to serialize and deserialize. + if self._calls % self._callsPerSerialize == 0: + self._sdrClassifier = pickle.loads(pickle.dumps(self._sdrClassifier)) + self._sdrClassifierCpp = pickle.loads(pickle.dumps( + self._sdrClassifierCpp)) + # Assert both results are the same type. + assert type(result1) == type(result2) + # Assert that the keys match. + assert set(result1.keys()) == set(result2.keys()), "diff detected: " \ + "py result=%s, C++ result=%s" % (result1, result2) + # Assert that the values match. + for k, l in result1.iteritems(): + assert type(l) == type(result2[k]) + for i in xrange(len(l)): + if isinstance(classification['actValue'], numbers.Real): + assert abs(float(l[i]) - float(result2[k][i])) < 0.0000001, ( + 'Python SDRClassifier has value %f and C++ SDRClassifierCpp has ' + 'value %f.' % (l[i], result2[k][i])) + else: + assert l[i] == result2[k][i], ( + 'Python SDRClassifier has value %s and C++ SDRClassifierCpp has ' + 'value %s.' % (str(l[i]), str(result2[k][i]))) + return result1 From c1ed31c50b5e292d5c8747f24f40ab0cb1032afe Mon Sep 17 00:00:00 2001 From: Scott Purdy Date: Tue, 6 Jun 2017 13:34:21 -0700 Subject: [PATCH 8/8] Handle different types of classifier regions in OPF --- src/nupic/frameworks/opf/htm_prediction_model.py | 12 ++++++++---- src/nupic/regions/record_sensor.py | 10 ++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/nupic/frameworks/opf/htm_prediction_model.py b/src/nupic/frameworks/opf/htm_prediction_model.py index 3505ce8a32..88d03f3c23 100644 --- a/src/nupic/frameworks/opf/htm_prediction_model.py +++ b/src/nupic/frameworks/opf/htm_prediction_model.py @@ -1172,10 +1172,14 @@ def __createHTMNetwork(self, sensorParams, spEnable, spParams, tmEnable, clParams)) n.addRegion("Classifier", "py.%s" % str(clRegionName), json.dumps(clParams)) - n.link("sensor", "Classifier", "UniformLink", "", srcOutput="actValueOut", - destInput="actValueIn") - n.link("sensor", "Classifier", "UniformLink", "", srcOutput="bucketIdxOut", - destInput="bucketIdxIn") + # SDR Classifier-specific links + if str(clRegionName) == "SDRClassifierRegion": + n.link("sensor", "Classifier", "UniformLink", "", srcOutput="actValueOut", + destInput="actValueIn") + n.link("sensor", "Classifier", "UniformLink", "", srcOutput="bucketIdxOut", + destInput="bucketIdxIn") + + # This applies to all (SDR and KNN) classifiers n.link("sensor", "Classifier", "UniformLink", "", srcOutput="categoryOut", destInput="categoryIn") diff --git a/src/nupic/regions/record_sensor.py b/src/nupic/regions/record_sensor.py index b09f5edc02..d2ff4c742b 100644 --- a/src/nupic/regions/record_sensor.py +++ b/src/nupic/regions/record_sensor.py @@ -386,10 +386,12 @@ def compute(self, inputs, outputs): if e[0] == self.predictedField] if len(encoders) == 0: raise ValueError("There is no encoder for set for the predicted " - "field: %s" % predictedField) - elif len(encoders) > 1: - raise ValueError("There cant' be more than 1 encoder for the " - "predicted field: %s" % predictedField) + "field: %s" % self.predictedField) + # TODO: Figure out why there are sometimes multiple encoders with the + # same name. + #elif len(encoders) > 1: + # raise ValueError("There cant' be more than 1 encoder for the " + # "predicted field: %s" % self.predictedField) else: encoder = encoders[0][1]