
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:
- Sensor Region (
nupic.regions.record_sensor.RecordSensor
) - Spatial Pooler Region (
nupic.regions.sp_region.SPRegion
) - Temporal Memory Region (
nupic.regions.tm_region.TMRegion
) - Classifier Region (
nupic.regions.sdr_classifier_region.SDRClassifierRegion
)
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))
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.
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")
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!