diff --git a/docs/examples/network/example-add-classifier.py b/docs/examples/network/example-add-classifier.py new file mode 100644 index 0000000000..11ee8e3658 --- /dev/null +++ b/docs/examples/network/example-add-classifier.py @@ -0,0 +1,2 @@ +clParams = modelParams["clParams"] +network.addRegion("classifier", "py.SDRClassifierRegion", json.dumps(clParams)) \ No newline at end of file diff --git a/docs/examples/network/example-add-sensor-region.py b/docs/examples/network/example-add-sensor-region.py new file mode 100644 index 0000000000..970db69b42 --- /dev/null +++ b/docs/examples/network/example-add-sensor-region.py @@ -0,0 +1 @@ +network.addRegion("sensor", "py.RecordSensor", '{}') \ No newline at end of file diff --git a/docs/examples/network/example-add-sp.py b/docs/examples/network/example-add-sp.py new file mode 100644 index 0000000000..155c4913d1 --- /dev/null +++ b/docs/examples/network/example-add-sp.py @@ -0,0 +1,7 @@ +spParams = modelParams["spParams"] + +# Make sure the SP input width matches the sensor output width. +spParams["inputWidth"] = sensorRegion.encoder.getWidth() + +# Add SP region. +network.addRegion("SP", "py.SPRegion", json.dumps(spParams)) \ No newline at end of file diff --git a/docs/examples/network/example-add-tm.py b/docs/examples/network/example-add-tm.py new file mode 100644 index 0000000000..2d85787764 --- /dev/null +++ b/docs/examples/network/example-add-tm.py @@ -0,0 +1,2 @@ +tmParams = modelParams["tmParams"] +network.addRegion("TM", "py.TPRegion", json.dumps(tmParams)) \ No newline at end of file diff --git a/docs/examples/network/example-create-encoder.py b/docs/examples/network/example-create-encoder.py new file mode 100644 index 0000000000..8ed4bd53db --- /dev/null +++ b/docs/examples/network/example-create-encoder.py @@ -0,0 +1,13 @@ +from nupic.encoders import MultiEncoder + +def createEncoder(encoderParams): + encoder = MultiEncoder() + encoder.addMultipleEncoders(encoderParams) + return encoder + +# Use the same modelParams extracted from the YAML file earlier. +encoderParams = modelParams["sensorParams"]["encoders"] + +# Add encoder to the sensor region. +sensorRegion = network.regions["sensor"].getSelf() +sensorRegion.encoder = createEncoder(encoderParams) \ No newline at end of file diff --git a/docs/examples/network/example-create-network.py b/docs/examples/network/example-create-network.py new file mode 100644 index 0000000000..289536db02 --- /dev/null +++ b/docs/examples/network/example-create-network.py @@ -0,0 +1,4 @@ +from nupic.engine import Network + +# A network that will hold the regions. +network = Network() \ No newline at end of file diff --git a/docs/examples/network/example-create-regions.py b/docs/examples/network/example-create-regions.py new file mode 100644 index 0000000000..6fcabd766a --- /dev/null +++ b/docs/examples/network/example-create-regions.py @@ -0,0 +1,49 @@ +import json + +# Add a sensor region, set its encoder and data source. +network.addRegion("sensor", "py.RecordSensor", json.dumps({"verbosity": 0})) + +# Make sure the SP input width matches the sensor region output width. +model_params["spParams"]["inputWidth"] = sensorRegion.encoder.getWidth() + +# Add the SP and TM regions. +network.addRegion("SP", "py.SPRegion", json.dumps(model_params["spParams"])) +network.addRegion("TM", "py.TPRegion", json.dumps(model_params["tmParams"])) + +# Add the classifier region. +clName = "py.%s" % model_params[] +network.addRegion("classifier", , json.dumps(model_params["clParams"])) + + + +# Add all links +createSensorToClassifierLinks(network, "sensor", "classifier") + +# Link the sensor region to the SP region so that it can pass it data. +createDataOutLink(network, "sensor", "SP") + +# Create feed-forward links between regions. +createFeedForwardLink(network, "SP", "TM") +createFeedForwardLink(network, "TM", "classifier") + +# Propagate reset signals to SP and TM regions. +# Optional if you know that your sensor regions does not send resets. +createResetLink(network, "sensor", "SP") +createResetLink(network, "sensor", "TM") + + + +# Set the data source to the sensor region +sensorRegion = network.regions["sensor"].getSelf() +sensorRegion.dataSource = dataSource + +# Set the encoder to the sensor region +sensorRegion.encoder = createEncoder(model_params["sensorParams"]["encoders"]) + + + +# Make sure all objects are initialized. +network.initialize() + + + diff --git a/docs/examples/network/example-data-source.py b/docs/examples/network/example-data-source.py new file mode 100644 index 0000000000..ee9df8f650 --- /dev/null +++ b/docs/examples/network/example-data-source.py @@ -0,0 +1,8 @@ +from nupic.data.file_record_stream import FileRecordStream + +_INPUT_FILE_PATH = "/path/to/your/data.csv" +dataSource = FileRecordStream(streamID=_INPUT_FILE_PATH) + +# Add the data source to the sensor region. +sensorRegion = network.regions["sensor"].getSelf() +sensorRegion.dataSource = dataSource \ No newline at end of file diff --git a/docs/examples/network/example-enable-learning-and-inference.py b/docs/examples/network/example-enable-learning-and-inference.py new file mode 100644 index 0000000000..69ba45c1fa --- /dev/null +++ b/docs/examples/network/example-enable-learning-and-inference.py @@ -0,0 +1,9 @@ +# Enable learning for all regions. +network.regions["SP"].setParameter("learningMode", 1) +network.regions["TM"].setParameter("learningMode", 1) +network.regions["classifier"].setParameter("learningMode", 1) + +# Enable inference for all regions. +network.regions["SP"].setParameter("inferenceMode", 1) +network.regions["TM"].setParameter("inferenceMode", 1) +network.regions["classifier"].setParameter("inferenceMode", 1) diff --git a/docs/examples/network/example-extract-results.py b/docs/examples/network/example-extract-results.py new file mode 100644 index 0000000000..a4fb72cfad --- /dev/null +++ b/docs/examples/network/example-extract-results.py @@ -0,0 +1,20 @@ +def getPredictionResults(network, clRegionName): + """Helper function to extract results for all prediction steps.""" + + + classifierRegion = network.regions[clRegionName] + actualValues = classifierRegion.getOutputData("actualValues") + probabilities = classifierRegion.getOutputData("probabilities") + steps = classifierRegion.getSelf().stepsList + + N = classifierRegion.getSelf().maxCategoryCount + results = {step: {} for step in steps} + for i in range(len(steps)): + # stepProbabilities are probabilities for this prediction step only. + stepProbabilities = probabilities[i * N:(i + 1) * N - 1] + mostLikelyCategoryIdx = stepProbabilities.argmax() + predictedValue = actualValues[mostLikelyCategoryIdx] + predictionConfidence = stepProbabilities[mostLikelyCategoryIdx] + results[steps[i]]["predictedValue"] = predictedValue + results[steps[i]]["predictionConfidence"] = predictionConfidence + return results \ No newline at end of file diff --git a/docs/examples/network/example-link-all.py b/docs/examples/network/example-link-all.py new file mode 100644 index 0000000000..2d544db098 --- /dev/null +++ b/docs/examples/network/example-link-all.py @@ -0,0 +1,32 @@ +def createDataOutLink(network, sensorRegionName, regionName): + """Link sensor region to other region so that it can pass it data.""" + network.link(sensorRegionName, regionName, "UniformLink", "", + srcOutput="dataOut", destInput="bottomUpIn") + + +def createFeedForwardLink(network, regionName1, regionName2): + """Create a feed-forward link between 2 regions: regionName1 -> regionName2""" + network.link(regionName1, regionName2, "UniformLink", "", + srcOutput="bottomUpOut", destInput="bottomUpIn") + + +def createSensorToClassifierLinks(network, sensorRegionName, classifierRegionName): + """Create required links from a sensor region to a classifier region.""" + network.link(sensorRegionName, classifierRegionName, "UniformLink", "", + srcOutput="bucketIdxOut", destInput="bucketIdxIn") + network.link(sensorRegionName, classifierRegionName, "UniformLink", "", + srcOutput="actValueOut", destInput="actValueIn") + network.link(sensorRegionName, classifierRegionName, "UniformLink", "", + srcOutput="categoryOut", destInput="categoryIn") + + +# 1. Add data link between sensor and SP. +createDataOutLink(network, "sensor", "SP") + +# 2. Add feed forward links. +createFeedForwardLink(network, "SP", "TM") +createFeedForwardLink(network, "TM", "classifier") + +# 3. Add links between sensor and classifier. +createSensorToClassifierLinks(network, "sensor", "classifier") + diff --git a/docs/examples/network/example-run-network.py b/docs/examples/network/example-run-network.py new file mode 100644 index 0000000000..57826e7d43 --- /dev/null +++ b/docs/examples/network/example-run-network.py @@ -0,0 +1,6 @@ +# Make sure all objects are initialized. +network.initialize() + +N = 1 # Run the network, N iterations at a time. +for iteration in range(0, numRecords, N): + network.run(N) diff --git a/docs/examples/network/example-set-predicted-field.py b/docs/examples/network/example-set-predicted-field.py new file mode 100644 index 0000000000..e7ae8bbc9c --- /dev/null +++ b/docs/examples/network/example-set-predicted-field.py @@ -0,0 +1,3 @@ +predictedIdx = dataSource.getFieldNames().index("consumption") + +network.regions["sensor"].setParameter("predictedFieldIdx", predictedIdx) \ No newline at end of file diff --git a/docs/examples/network/example-yaml-import.py b/docs/examples/network/example-yaml-import.py new file mode 100644 index 0000000000..ff5d737cf4 --- /dev/null +++ b/docs/examples/network/example-yaml-import.py @@ -0,0 +1,6 @@ +import yaml + +_PARAMS_PATH = "/path/to/model.yaml" + +with open(_PARAMS_PATH, "r") as f: + modelParams = yaml.safe_load(f)["modelParams"] \ No newline at end of file diff --git a/docs/examples/opf/example-model-param-encoders.py b/docs/examples/opf/example-model-param-encoders.py deleted file mode 100644 index 1ea50eff12..0000000000 --- a/docs/examples/opf/example-model-param-encoders.py +++ /dev/null @@ -1,18 +0,0 @@ -'encoders': { - u'consumption': { - 'fieldname': u'consumption', - 'resolution': 0.88, - 'seed': 1, - 'name': u'consumption', - 'type': 'RandomDistributedScalarEncoder', - }, - - 'timestamp_timeOfDay': { 'fieldname': u'timestamp', - 'name': u'timestamp_timeOfDay', - 'timeOfDay': (21, 1), - 'type': 'DateEncoder'}, - 'timestamp_weekend': { 'fieldname': u'timestamp', - 'name': u'timestamp_weekend', - 'type': 'DateEncoder', - 'weekend': 21} -} diff --git a/docs/examples/opf/example-model-param-encoders.yaml b/docs/examples/opf/example-model-param-encoders.yaml new file mode 100644 index 0000000000..a8462ef21c --- /dev/null +++ b/docs/examples/opf/example-model-param-encoders.yaml @@ -0,0 +1,18 @@ +# List of encoders and their parameters. +encoders: + consumption: + fieldname: consumption + name: consumption + resolution: 0.88 + seed: 1 + type: RandomDistributedScalarEncoder + timestamp_timeOfDay: + fieldname: timestamp + name: timestamp_timeOfDay + timeOfDay: [21, 1] + type: DateEncoder + timestamp_weekend: + fieldname: timestamp + name: timestamp_weekend + type: DateEncoder + weekend: 21 \ No newline at end of file diff --git a/docs/source/quick-start/network.rst b/docs/source/quick-start/network.rst index b95b1664d0..827a49d940 100644 --- a/docs/source/quick-start/network.rst +++ b/docs/source/quick-start/network.rst @@ -3,4 +3,212 @@ Network API ----------- -**UNDER CONSTRUCTION** +See the `Network API Guide <../guides/network.html>`_ for an overview of this API. + +Here is the complete program we are going to use as an example. In sections +below, we'll break it down into parts and explain what is happening (without +some of the plumbing details). + +.. literalinclude:: ../../examples/network/complete-example.py + +Network Parameters +^^^^^^^^^^^^^^^^^^ + +Before you can create an HTM network, you need to have model (or network) parameters +defined in a file. These model parameters contain many details about how the HTM +network will be constructed, what encoder configurations will be used, and +individual algorithm parameters that can drastically affect how a model +operates. The model parameters we're using in this Quick Start +`can be found here `_. + +To use model parameters, they can be written to a file and imported into +your script. In this example, our model parameters existing in a +`YAML `_ file called ``params.yaml`` and are identical to +those `linked above `_. + + +To import the model params from a YAML file: + +.. literalinclude:: ../../examples/network/example-yaml-import.py + +The dictionary ``modelParams`` is what you will use to parametrize your +HTM network. We'll do that in the next sections of this example. + +Create a Network +^^^^^^^^^^^^^^^^ + +Create an HTM network with :class:`.Network`: + +.. literalinclude:: ../../examples/network/example-create-network.py + +Now we need to add several regions to this network: + +- Sensor Region +- Spatial Pooler Region +- Temporal Memory Region +- Classifier Region + +The regions will be linked serially. In the next sections, we'll cover how to +create regions with the right parameters and how to link them. + + +Add a Sensor Region +^^^^^^^^^^^^^^^^^^^ + +Let's add a region of type :class:`.RecordSensor`. + +.. literalinclude:: ../../examples/network/example-add-sensor-region.py + +This region is in charge of sensing the input data. It does not require any +particular parameters (hence the ``'{}'``) but it does need a Data Source +as well as an Encoder. We'll add that next. + + +Add a Data Source to the Sensor Region +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A data source is in charge of producing the data that will be fed to the HTM +network. You can create a data source that reads from a CSV file +with :class:`.FileRecordStream`. + +.. literalinclude:: ../../examples/network/example-data-source.py + +.. note:: + The input CSV needs to have specific headers. + More details `here `_. + + +Add an Encoder to the Sensor Region +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Our `model parameters `_ define how this data will be +encoded in the ``encoders`` section: + +.. literalinclude:: ../../examples/opf/example-model-param-encoders.yaml + +Notice that three semantic values are being encoded into the input space. The +first is the scalar energy ``consumption`` value, which is being encoded with +the :class:`.RandomDistributedScalarEncoder`. The next two values represent two +different aspects of time using the :class:`.DateEncoder`. The encoder called +``timestamp_timeOfDay`` encodes the time of day, while the ``timestamp_weekend`` +encoder will output different representations for weekends vs weekdays. The +:class:`.HTMPredictionModel` will combine these encodings using the +:class:`.MultiEncoder`. + + For details about encoding and how these encoders work, see the + `HTM School `_ episodes on encoders. + +Let's create an encoder and add it the Sensor Region: + + .. literalinclude:: ../../examples/network/example-create-encoder.py + + +Add a Spatial Pooler Region +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When creating a region, we always need to make sure that the output width of +the previous region matches the input width of the following region. In the +case of the Spatial Pooler region, the input width was not specified in the +``modelParams``, so we need to set it. + + .. literalinclude:: ../../examples/network/example-add-sp.py + +Add a Temporal Memory Region +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. literalinclude:: ../../examples/network/example-add-tm.py + + +Add a Classifier Region +^^^^^^^^^^^^^^^^^^^^^^^ + + .. literalinclude:: ../../examples/network/example-add-classifier.py + + +Link all Regions +^^^^^^^^^^^^^^^^ + +Now that we have created all regions, we need to link them together. Links +are responsible for passing information from one region to the next. + +First, we'll add the link in charge of passing the data from ``sensorRegion`` +to ``spRegion``. Then we'll create 2 ``feedForward`` links: one from the +SP to the TM and another from the TM to the Classifier. Finally, we'll add a +couple of special links between ``sensorRegion`` and ``classifierRegion``. +These links make it possible for the classifier to map predicted cell states +to actual values learned from the input data. + + +.. literalinclude:: ../../examples/network/example-link-all.py + + +Set the Predicted Field Index +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To be able to make predictions, the network needs to know what field we want +to predict. So let's set the predicted field index. + + +.. literalinclude:: ../../examples/network/example-set-predicted-field.py + +Make sure that the predicted field index that you are passing to the classifier +region is using the same indexing as the data source. This is the role of the +first line in the code snippet above. + + +Enable Learning and Inference +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If you want the network to learn on the input data, you need to enable learning. +Note that enabling learning is independent from enabling inference. So if you +want the network to output predictions, you need to enable inference as well. +Let's enable both in our case, because we want the network to learn and make predictions. + +.. literalinclude:: ../../examples/network/example-enable-learning-and-inference.py + + +Run the Network +^^^^^^^^^^^^^^^ +Before running the network, you need to initialize it. After that, you can run +the network ``N`` iterations at a time. + +.. literalinclude:: ../../examples/network/example-run-network.py + +Here we run the network ``1`` iteration at a time because we want to extract +the prediction results at each time step. + + +Getting Predictions +^^^^^^^^^^^^^^^^^^^ + +In the classifier configuration of our `model parameters `_, +identified as ``modelParams.clParams``, the ``steps`` value tells the model how +many steps into the future to predict. In this case, we are predicting both one +and five steps into the future as shown by the value ``1,5``. + +You can use the method ``getOuputData()`` to get output predictions from the +classifier region. In our case, we are interested in: + +.. code-block:: python + + actualValues = classifierRegion.getOutputData("actualValues") + probabilities = classifierRegion.getOutputData("probabilities") + + +Refer to the documentation of :class:`~nupic.regions.SDRClassifierRegion.SDRClassifierRegion` for +more information about output values and their structure. + +We'll use the helper function below to extract predictions more easily from +the classifier region: + +.. literalinclude:: ../../examples/network/example-extract-results.py + +Once you put all this together and run the `full example `_, +you can see both predictions and their confidences in the console output, +which should look something like this: + +:: + + 1-step: 45.6100006104 (96.41%) 5-step: 0.0 (0.1883%) + 1-step: 43.4000015259 (3.969%) 5-step: 0.0 (0.1883%) + 1-step: 43.4000015259 (4.125%) 5-step: 0.0 (0.1883%) +**Congratulations! You've got HTM predictions for a scalar data stream!** diff --git a/docs/source/quick-start/opf.rst b/docs/source/quick-start/opf.rst index 4c3d4248cb..ec9d945f13 100644 --- a/docs/source/quick-start/opf.rst +++ b/docs/source/quick-start/opf.rst @@ -44,7 +44,7 @@ The raw input data file is described `here `_ in detail. Our `model parameters `_ define how this data will be encoded in the ``encoders`` section: -.. literalinclude:: ../../examples/opf/example-model-param-encoders.py +.. literalinclude:: ../../examples/opf/example-model-param-encoders.yaml Notice that three semantic values are being encoded into the input space. The first is the scalar energy ``consumption`` value, which is being encoded with