../_images/quickstart.png

Network API

See the Network API Guide 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).

import json
import os
import yaml

from nupic.engine import Network
from nupic.encoders import MultiEncoder
from nupic.data.file_record_stream import FileRecordStream

_NUM_RECORDS = 3000
_EXAMPLE_DIR = os.path.dirname(os.path.abspath(__file__))
_INPUT_FILE_PATH = os.path.join(_EXAMPLE_DIR, os.pardir, "data", "gymdata.csv")
_PARAMS_PATH = os.path.join(_EXAMPLE_DIR, os.pardir, "params", "model.yaml")



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 createResetLink(network, sensorRegionName, regionName):
  """Create a reset link from a sensor region: sensorRegionName -> regionName"""
  network.link(sensorRegionName, regionName, "UniformLink", "",
               srcOutput="resetOut", destInput="resetIn")


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")


def createEncoder(encoderParams):
  """Create a multi-encoder from params."""
  encoder = MultiEncoder()
  encoder.addMultipleEncoders(encoderParams)
  return encoder


def createNetwork(dataSource):
  """Create and initialize a network."""
  with open(_PARAMS_PATH, "r") as f:
    modelParams = yaml.safe_load(f)["modelParams"]

  # Create a network that will hold the regions.
  network = Network()

  # Add a sensor region.
  network.addRegion("sensor", "py.RecordSensor", '{}')

  # Set the encoder and data source of the sensor region.
  sensorRegion = network.regions["sensor"].getSelf()
  sensorRegion.encoder = createEncoder(modelParams["sensorParams"]["encoders"])
  sensorRegion.dataSource = dataSource

  # Make sure the SP input width matches the sensor region output width.
  modelParams["spParams"]["inputWidth"] = sensorRegion.encoder.getWidth()

  # Add SP and TM regions.
  network.addRegion("SP", "py.SPRegion", json.dumps(modelParams["spParams"]))
  network.addRegion("TM", "py.TMRegion", json.dumps(modelParams["tmParams"]))

  # Add a classifier region.
  clName = "py.%s" % modelParams["clParams"].pop("regionName")
  network.addRegion("classifier", clName, json.dumps(modelParams["clParams"]))

  # Add all links
  createSensorToClassifierLinks(network, "sensor", "classifier")
  createDataOutLink(network, "sensor", "SP")
  createFeedForwardLink(network, "SP", "TM")
  createFeedForwardLink(network, "TM", "classifier")
  # Reset links are optional, since the sensor region does not send resets.
  createResetLink(network, "sensor", "SP")
  createResetLink(network, "sensor", "TM")

  # Make sure all objects are initialized.
  network.initialize()

  return network


def getPredictionResults(network, clRegionName):
  """Get prediction 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


def runHotgym(numRecords):
  """Run the Hot Gym example."""

  # Create a data source for the network.
  dataSource = FileRecordStream(streamID=_INPUT_FILE_PATH)
  numRecords = min(numRecords, dataSource.getDataRowCount())
  network = createNetwork(dataSource)

  # Set predicted field
  network.regions["sensor"].setParameter("predictedField", "consumption")

  # 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)

  results = []
  N = 1  # Run the network, N iterations at a time.
  for iteration in range(0, numRecords, N):
    network.run(N)

    predictionResults = getPredictionResults(network, "classifier")
    oneStep = predictionResults[1]["predictedValue"]
    oneStepConfidence = predictionResults[1]["predictionConfidence"]
    fiveStep = predictionResults[5]["predictedValue"]
    fiveStepConfidence = predictionResults[5]["predictionConfidence"]

    result = (oneStep, oneStepConfidence * 100,
              fiveStep, fiveStepConfidence * 100)
    print "1-step: {:16} ({:4.4}%)\t 5-step: {:16} ({:4.4}%)".format(*result)
    results.append(result)

  return results

if __name__ == "__main__":
  runHotgym(_NUM_RECORDS)

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:

import yaml

_PARAMS_PATH = "/path/to/model.yaml"

with open(_PARAMS_PATH, "r") as f:
  modelParams = yaml.safe_load(f)["modelParams"]

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 Network:

from nupic.engine import Network

# A network that will hold the regions.
network = Network()

Now we need to add several regions to this network:

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 RecordSensor.

network.addRegion("sensor", "py.RecordSensor", '{}')

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 FileRecordStream.

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

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:

# 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

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 RandomDistributedScalarEncoder. The next two values represent two different aspects of time using the 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 HTMPredictionModel will combine these encodings using the 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:

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)

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.

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))

Add a Temporal Memory Region

tmParams = modelParams["tmParams"]
network.addRegion("TM", "py.TMRegion", json.dumps(tmParams))

Add a Classifier Region

clParams = modelParams["clParams"]
network.addRegion("classifier", "py.SDRClassifierRegion", json.dumps(clParams))

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.

network.regions["sensor"].setParameter("predictedField", "consumption")

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.

# 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)

Run the Network

Before running the network, you need to initialize it. After that, you can run the network N iterations at a time.

# 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)

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 getOutputData() to get output predictions from the classifier region. In our case, we are interested in:

actualValues = classifierRegion.getOutputData("actualValues")
probabilities = classifierRegion.getOutputData("probabilities")

Refer to the documentation of 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:

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

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!