diff --git a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp index 77dd8c5f81..e3b13981e2 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp @@ -47,6 +47,13 @@ Example usage: TODO )"); + py::enum_(m, "ANMode") + .value("DISABLED", TemporalMemory::ANMode::DISABLED) + .value("RAW", TemporalMemory::ANMode::RAW) + .value("LIKELIHOOD", TemporalMemory::ANMode::LIKELIHOOD) + .value("LOGLIKELIHOOD", TemporalMemory::ANMode::LOGLIKELIHOOD) + .export_values(); + py_HTM.def(py::init<>()); py_HTM.def(py::init , CellIdx @@ -62,7 +69,8 @@ Example usage: , SegmentIdx , SynapseIdx , bool - , UInt>(), + , UInt + , TemporalMemory::ANMode>(), R"(Initialize the temporal memory (TM) using the given parameters. Argument columnDimensions @@ -123,6 +131,10 @@ Argument externalPredictiveInputs TemporalMemory. If this is given (and greater than 0) then the active cells and winner cells of these external inputs must be given to methods TM.compute and TM.activateDendrites + +Argument anomalyMode (optional, default ANMode::RAW) selects mode for `TM.anomaly`. + Options are ANMode {DISABLED, RAW, LIKELIHOOD, LOGLIKELIHOOD} + )" , py::arg("columnDimensions") , py::arg("cellsPerColumn") = 32 @@ -139,7 +151,8 @@ Argument externalPredictiveInputs , py::arg("maxSynapsesPerSegment") = 255 , py::arg("checkInputs") = true , py::arg("externalPredictiveInputs") = 0u - ); + , py::arg("anomalyMode") = TemporalMemory::ANMode::RAW + ); py_HTM.def("printParameters", [](const HTM_t& self) diff --git a/src/htm/algorithms/AnomalyLikelihood.cpp b/src/htm/algorithms/AnomalyLikelihood.cpp index 48d202bcc6..6ee7834fe5 100644 --- a/src/htm/algorithms/AnomalyLikelihood.cpp +++ b/src/htm/algorithms/AnomalyLikelihood.cpp @@ -27,6 +27,7 @@ AnomalyLikelihood::AnomalyLikelihood(UInt learningPeriod, UInt estimationSamples iteration_ = 0; NTA_CHECK(historicWindowSize >= estimationSamples); // cerr << "estimationSamples exceeds historicWindowSize"; NTA_CHECK(aggregationWindow < reestimationPeriod && reestimationPeriod < historicWindowSize); + NTA_WARN << "C++ AnomalyLikelihood may still need some testing."; } diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 2362d1672f..38d7b7cb8c 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -66,12 +66,14 @@ TemporalMemory::TemporalMemory( SegmentIdx maxSegmentsPerCell, SynapseIdx maxSynapsesPerSegment, bool checkInputs, - UInt externalPredictiveInputs) { + UInt externalPredictiveInputs, + ANMode anomalyMode) { + initialize(columnDimensions, cellsPerColumn, activationThreshold, initialPermanence, connectedPermanence, minThreshold, maxNewSynapseCount, permanenceIncrement, permanenceDecrement, predictedSegmentDecrement, seed, maxSegmentsPerCell, - maxSynapsesPerSegment, checkInputs, externalPredictiveInputs); + maxSynapsesPerSegment, checkInputs, externalPredictiveInputs, anomalyMode); } TemporalMemory::~TemporalMemory() {} @@ -91,7 +93,8 @@ void TemporalMemory::initialize( SegmentIdx maxSegmentsPerCell, SynapseIdx maxSynapsesPerSegment, bool checkInputs, - UInt externalPredictiveInputs) { + UInt externalPredictiveInputs, + ANMode anomalyMode) { // Validate all input parameters NTA_CHECK(columnDimensions.size() > 0) << "Number of column dimensions must be greater than 0"; @@ -132,6 +135,8 @@ void TemporalMemory::initialize( maxSegmentsPerCell_ = maxSegmentsPerCell; maxSynapsesPerSegment_ = maxSynapsesPerSegment; + tmAnomaly_.mode_ = anomalyMode; + reset(); } @@ -483,15 +488,43 @@ void TemporalMemory::compute(const SDR &activeColumns, activateDendrites(learn, externalPredictiveInputsActive, externalPredictiveInputsWinners); // Update Anomaly Metric. The anomaly is the percent of active columns that - // were not predicted. - anomaly_ = computeRawAnomalyScore( - activeColumns, - cellsToColumns( getPredictiveCells() )); + // were not predicted. + // Must be computed here, between `activateDendrites()` and `activateCells()`. + switch(tmAnomaly_.mode_) { + + case ANMode::DISABLED: { + tmAnomaly_.anomaly_ = 0.5f; + } break; + + case ANMode::RAW: { + tmAnomaly_.anomaly_ = computeRawAnomalyScore( + activeColumns, + cellsToColumns( getPredictiveCells() )); + } break; + + case ANMode::LIKELIHOOD: { + const Real raw = computeRawAnomalyScore( + activeColumns, + cellsToColumns( getPredictiveCells() )); + tmAnomaly_.anomaly_ = tmAnomaly_.anomalyLikelihood_.anomalyProbability(raw); + } break; + + case ANMode::LOGLIKELIHOOD: { + const Real raw = computeRawAnomalyScore( + activeColumns, + cellsToColumns( getPredictiveCells() )); + const Real like = tmAnomaly_.anomalyLikelihood_.anomalyProbability(raw); + const Real log = tmAnomaly_.anomalyLikelihood_.computeLogLikelihood(like); + tmAnomaly_.anomaly_ = log; + } break; // TODO: Update mean & standard deviation of anomaly here. + }; + NTA_ASSERT(tmAnomaly_.anomaly_ >= 0.0f and tmAnomaly_.anomaly_ <= 1.0f) << "TM.anomaly is out-of-bounds!"; activateCells(activeColumns, learn); } + void TemporalMemory::compute(const SDR &activeColumns, const bool learn) { SDR externalPredictiveInputsActive({ externalPredictiveInputs_ }); SDR externalPredictiveInputsWinners({ externalPredictiveInputs_ }); @@ -504,7 +537,7 @@ void TemporalMemory::reset(void) { activeSegments_.clear(); matchingSegments_.clear(); segmentsValid_ = false; - anomaly_ = -1.0f; + tmAnomaly_.anomaly_ = -1.0f; //TODO reset rather to 0.5 as default (undecided) anomaly } // ============================== @@ -713,7 +746,9 @@ bool TemporalMemory::operator==(const TemporalMemory &other) const { winnerCells_ != other.winnerCells_ || maxSegmentsPerCell_ != other.maxSegmentsPerCell_ || maxSynapsesPerSegment_ != other.maxSynapsesPerSegment_ || - anomaly_ != other.anomaly_ ) { + tmAnomaly_.anomaly_ != other.tmAnomaly_.anomaly_ || + tmAnomaly_.mode_ != other.tmAnomaly_.mode_ || + tmAnomaly_.anomalyLikelihood_ != other.tmAnomaly_.anomalyLikelihood_ ) { return false; } diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index 06861f0425..4eedb4f8e2 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -27,6 +27,8 @@ #include #include #include +#include + #include @@ -35,6 +37,7 @@ namespace htm { using namespace std; using namespace htm; + /** * Temporal Memory implementation in C++. * @@ -53,6 +56,7 @@ using namespace htm; class TemporalMemory : public Serializable { public: + enum class ANMode { DISABLED = 0, RAW = 1, LIKELIHOOD = 2, LOGLIKELIHOOD = 3}; TemporalMemory(); /** @@ -124,6 +128,10 @@ class TemporalMemory : public Serializable * TemporalMemory. If this is given (and greater than 0) then the active * cells and winner cells of these external inputs must be given to methods * TM.compute and TM.activateDendrites + * + * @param anomalyMode (optional, default `ANMode::RAW`)from enum ANMode, how is + * `TM.anomaly` computed. Options ANMode {DISABLED, RAW, LIKELIHOOD, LOGLIKELIHOOD} + * */ TemporalMemory( vector columnDimensions, @@ -140,13 +148,15 @@ class TemporalMemory : public Serializable SegmentIdx maxSegmentsPerCell = 255, SynapseIdx maxSynapsesPerSegment = 255, bool checkInputs = true, - UInt externalPredictiveInputs = 0); + UInt externalPredictiveInputs = 0, + ANMode anomalyMode = ANMode::RAW + ); virtual void initialize( - vector columnDimensions = {2048}, - CellIdx cellsPerColumn = 32, - SynapseIdx activationThreshold = 13, + vector columnDimensions = {2048}, + CellIdx cellsPerColumn = 32, + SynapseIdx activationThreshold = 13, Permanence initialPermanence = 0.21, Permanence connectedPermanence = 0.50, SynapseIdx minThreshold = 10, @@ -158,7 +168,9 @@ class TemporalMemory : public Serializable SegmentIdx maxSegmentsPerCell = 255, SynapseIdx maxSynapsesPerSegment = 255, bool checkInputs = true, - UInt externalPredictiveInputs = 0); + UInt externalPredictiveInputs = 0, + ANMode anomalyMode = ANMode::RAW + ); virtual ~TemporalMemory(); @@ -231,6 +243,10 @@ class TemporalMemory : public Serializable * the TemporalMemory via its compute method ensures that you'll always * be able to call getActiveCells at the end of the time step. * + * Additionaly, this method computes anomaly for `TM.anomaly&`, if you + * use other learning methods (activateCells(), activateDendrites()) your + * anomaly scores will be off. + * * @param activeColumns * Sorted SDR of active columns. * @@ -467,7 +483,9 @@ class TemporalMemory : public Serializable CEREAL_NVP(activeCells_), CEREAL_NVP(winnerCells_), CEREAL_NVP(segmentsValid_), - CEREAL_NVP(anomaly_), + CEREAL_NVP(tmAnomaly_.anomaly_), + CEREAL_NVP(tmAnomaly_.mode_), + CEREAL_NVP(tmAnomaly_.anomalyLikelihood_), CEREAL_NVP(connections)); cereal::size_type numActiveSegments = activeSegments_.size(); @@ -522,7 +540,9 @@ class TemporalMemory : public Serializable CEREAL_NVP(activeCells_), CEREAL_NVP(winnerCells_), CEREAL_NVP(segmentsValid_), - CEREAL_NVP(anomaly_), + CEREAL_NVP(tmAnomaly_.anomaly_), + CEREAL_NVP(tmAnomaly_.mode_), + CEREAL_NVP(tmAnomaly_.anomalyLikelihood_), CEREAL_NVP(connections)); numActiveConnectedSynapsesForSegment_.assign(connections.segmentFlatListLength(), 0); @@ -646,13 +666,24 @@ class TemporalMemory : public Serializable vector numActiveConnectedSynapsesForSegment_; vector numActivePotentialSynapsesForSegment_; - Real anomaly_; - Random rng_; + /** + * holds logic and data for TM's anomaly + */ + struct anomaly_tm { + protected: + friend class TemporalMemory; + Real anomaly_ = 0.5f; //default value + ANMode mode_ = ANMode::RAW; + AnomalyLikelihood anomalyLikelihood_; //TODO provide default/customizable params here + }; + public: Connections connections; const UInt &externalPredictiveInputs = externalPredictiveInputs_; + + anomaly_tm tmAnomaly_; /* * anomaly score computed for the current inputs * (auto-updates after each call to TM::compute()) @@ -660,7 +691,9 @@ class TemporalMemory : public Serializable * @return a float value from computeRawAnomalyScore() * from Anomaly.hpp */ - const Real &anomaly = anomaly_; +const Real &anomaly = tmAnomaly_.anomaly_; //this is position dependant, the struct anomaly_tm must be defined before this use, +// otherwise this surprisingly compiles, but a call to `tmAnomaly_.anomaly` segfaults! + }; } // namespace htm diff --git a/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp b/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp index 2aead360f4..e4ffef21be 100644 --- a/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp +++ b/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp @@ -70,10 +70,11 @@ float runTemporalMemoryTest(UInt numColumns, UInt w, int numSequences, //TODO // learn for (int i = 0; i < 5; i++) { - for (auto sequence : sequences) { - for (auto sdr : sequence) { + for (const auto& sequence : sequences) { + for (const auto& sdr : sequence) { tm.compute(sdr, true); - avgAnomAfter = anom10.compute(tm.anomaly); //average anomaly score + const Real an = tm.anomaly; + avgAnomAfter = anom10.compute(an); //average anomaly score } tm.reset(); }