From 9067e81b46787c1964d00b1b3c23ccc10e6693b6 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 24 Jul 2019 18:47:45 +0200 Subject: [PATCH 1/6] SP.spatial_anomaly implementation from NAB spatial anomaly ported from NAB --- src/htm/algorithms/SpatialPooler.hpp | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index 16b3f9f162..93908f7850 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -1209,6 +1209,65 @@ class SpatialPooler : public Serializable Random rng_; public: + /** + * holds together functionality for computing + * `spatial anomaly`. This is used in NAB. + */ + struct spatial_anomaly { + + /** Fraction outside of the range of values seen so far that will be considered + * a spatial anomaly regardless of the anomaly likelihood calculation. + * This accounts for the human labelling bias for spatial values larger than what + * has been seen so far. + */ + const Real SPATIAL_TOLERANCE = 0.05; + + /** + * toggle if we should compute spatial anomaly + */ + bool enabled = true; + + /** + * compute if the current input `value` is considered spatial-anomaly. + * update internal variables. + * + * @param value Real, input #TODO currently handles only 1 variable input, and requires value passed to compute! + * # later remove, and implement using SP's internal state. But for now we are compatible with NAB's implementation. + * + * @return nothing, but updates internal variable `anomalyScore_`, which is either 0.0f (no anomaly), + * or exactly 0.9995947141f (spatial anomaly). This can be accessed by public @ref `SP.anomaly` + */ + void compute(const Real value) { + anomalyScore_ = 0.0f; + if(minVal_ != maxVal_) { + const Real tolerance = (maxVal_ - minVal_) * SPATIAL_TOLERANCE; + const Real maxExpected = maxVal_ + tolerance; + const Real minExpected = minVal_ - tolerance; + + if(value > maxExpected or value < minExpected) { //spatial anomaly + anomalyScore_ = 0.9995947141f; //almost 1.0 = max anomaly. Encodes value specific to spatial anomaly (so this can be recognized on results), + // "5947141" would translate in l33t speech to "spatial" :) + } + } + if(value > maxVal_) maxVal_ = value; + if(value < minVal_) minVal_ = value; + } + + private: + Real minVal_ = std::numeric_limits::max(); //TODO fix serialization + Real maxVal_ = std::numeric_limits::min(); + public: + Real anomalyScore_ = 0.0f; //default score = no anomaly + } spAnomaly; + + /** + * spatial anomaly + * + * updated on each @ref `compute()`. + * + * @return either 0.0f (no anomaly), or exactly 0.9995947141f (spatial anomaly). This specific value can be recognized in results. + */ + const Real& anomaly = spAnomaly.anomalyScore_; const Connections &connections = connections_; }; From e4b4c78254b5cb054276e5d621b76b00c9dd9444 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 24 Jul 2019 20:17:37 +0200 Subject: [PATCH 2/6] SP uses spatial anomaly & tests had to exptend SP.compute(..., value=Real) with an optional argument --- src/examples/hotgym/HelloSPTP.cpp | 14 ++-- src/htm/algorithms/SpatialPooler.cpp | 11 +++- src/htm/algorithms/SpatialPooler.hpp | 34 +++++++--- .../unit/algorithms/SpatialPoolerTest.cpp | 64 +++++++++++++++++++ 4 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index 757f691ed5..13e8cb14a4 100644 --- a/src/examples/hotgym/HelloSPTP.cpp +++ b/src/examples/hotgym/HelloSPTP.cpp @@ -106,8 +106,9 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool //Encode tEnc.start(); x+=0.01f; //step size for fn(x) - enc.encode(sin(x), input); //model sin(x) function //TODO replace with CSV data -// cout << x << "\n" << sin(x) << "\n" << input << "\n\n"; + const Real value = sin(x); + enc.encode(value, input); //model sin(x) function //TODO replace with CSV data +// cout << x << "\n" << value << "\n" << input << "\n\n"; tEnc.stop(); tRng.start(); @@ -117,13 +118,13 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool //SP (global x local) if(useSPlocal) { tSPloc.start(); - spLocal.compute(input, true, outSPlocal); + spLocal.compute(input, true, outSPlocal, value /* optional for spatial anomaly*/); tSPloc.stop(); } if(useSPglobal) { tSPglob.start(); - spGlobal.compute(input, true, outSPglobal); + spGlobal.compute(input, true, outSPglobal, value /* optional for spatial anomaly */); tSPglob.stop(); } outSP = outSPglobal; //toggle if local/global SP is used further down the chain (TM, Anomaly) @@ -168,6 +169,7 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool // output values cout << "Epoch = " << e << endl; cout << "Anomaly = " << an << endl; + cout << "Anomaly (spatial) = " << spGlobal.anomaly << endl; cout << "Anomaly (avg) = " << avgAnom10.getCurrentAvg() << endl; cout << "Anomaly (Likelihood) = " << anLikely << endl; cout << "SP (g)= " << outSP << endl; @@ -217,10 +219,12 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool if(useSPglobal) { NTA_CHECK(outSPglobal == goldSP) << "Deterministic output of SP (g) failed!\n" << outSP << "should be:\n" << goldSP; } if(useSPlocal) { NTA_CHECK(outSPlocal == goldSPlocal) << "Deterministic output of SP (l) failed!\n" << outSPlocal << "should be:\n" << goldSPlocal; } if(useTM) { NTA_CHECK(outTM == goldTM) << "Deterministic output of TM failed!\n" << outTM << "should be:\n" << goldTM; } - NTA_CHECK(static_cast(an *10000.0f) == static_cast(goldAn *10000.0f)) //compare to 4 decimal places + // anomalies + NTA_CHECK(static_cast(an *10000.0f) == static_cast(goldAn *10000.0f)) //compare to 4 decimal places << "Deterministic output of Anomaly failed! " << an << "should be: " << goldAn; NTA_CHECK(static_cast(avgAnom10.getCurrentAvg() * 10000.0f) == static_cast(goldAnAvg * 10000.0f)) << "Deterministic average anom score failed:" << avgAnom10.getCurrentAvg() << " should be: " << goldAnAvg; + if(useSPglobal) { NTA_CHECK(0.0f == spGlobal.anomaly) << "Deterministic spatial anomaly mismatch!" << spGlobal.anomaly; } } // check runtime speed diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index fa22e53eba..9a825e6697 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -459,7 +459,11 @@ void SpatialPooler::initialize( } -void SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) { +void SpatialPooler::compute(const SDR &input, + const bool learn, + SDR &active, + const Real spatialAnomalyInputValue) { + input.reshape( inputDimensions_ ); active.reshape( columnDimensions_ ); updateBookeepingVars_(learn); @@ -485,6 +489,11 @@ void SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) { updateMinDutyCycles_(); } } + + //update spatial anomaly + if(this->spAnomaly.enabled) { + spAnomaly.compute(spatialAnomalyInputValue); + } } diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index 93908f7850..7b9f605a57 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -233,8 +233,15 @@ class SpatialPooler : public Serializable @param active An SDR representing the winning columns after inhibition. The size of the SDR is equal to the number of columns (also returned by the method getNumColumns). + + @param spatialAnomalyInputValue (optional) `Real` used for computing "spatial anomaly", see @ref `this.spatial_anomaly.compute()`, + obtained by @ref `SP.anomaly` + */ - virtual void compute(const SDR &input, const bool learn, SDR &active); + virtual void compute(const SDR &input, + const bool learn, + SDR &active, + const Real spatialAnomalyInputValue = std::numeric_limits::min()); /** @@ -1220,7 +1227,7 @@ class SpatialPooler : public Serializable * This accounts for the human labelling bias for spatial values larger than what * has been seen so far. */ - const Real SPATIAL_TOLERANCE = 0.05; + Real SPATIAL_TOLERANCE = 0.05; /** * toggle if we should compute spatial anomaly @@ -1234,30 +1241,39 @@ class SpatialPooler : public Serializable * @param value Real, input #TODO currently handles only 1 variable input, and requires value passed to compute! * # later remove, and implement using SP's internal state. But for now we are compatible with NAB's implementation. * - * @return nothing, but updates internal variable `anomalyScore_`, which is either 0.0f (no anomaly), - * or exactly 0.9995947141f (spatial anomaly). This can be accessed by public @ref `SP.anomaly` + * @return nothing, but updates internal variable `anomalyScore_`, which is either `NO_ANOMALY` (0.0f) , + * or `SPATIAL_ANOMALY` (exactly 0.9995947141f). Accessed by public @ref `SP.anomaly`. + * */ void compute(const Real value) { - anomalyScore_ = 0.0f; - if(minVal_ != maxVal_) { + anomalyScore_ = NO_ANOMALY; + if(not enabled) return; + NTA_ASSERT(SPATIAL_TOLERANCE > 0.0f and SPATIAL_TOLERANCE <= 1.0f); + + if(minVal_ != std::numeric_limits::max() and + maxVal_ != std::numeric_limits::min() ) { const Real tolerance = (maxVal_ - minVal_) * SPATIAL_TOLERANCE; const Real maxExpected = maxVal_ + tolerance; const Real minExpected = minVal_ - tolerance; if(value > maxExpected or value < minExpected) { //spatial anomaly - anomalyScore_ = 0.9995947141f; //almost 1.0 = max anomaly. Encodes value specific to spatial anomaly (so this can be recognized on results), - // "5947141" would translate in l33t speech to "spatial" :) + anomalyScore_ = SPATIAL_ANOMALY; } } if(value > maxVal_) maxVal_ = value; if(value < minVal_) minVal_ = value; + NTA_ASSERT(anomalyScore_ == NO_ANOMALY or anomalyScore_ == SPATIAL_ANOMALY) << "Out of bounds of acceptable values"; } private: Real minVal_ = std::numeric_limits::max(); //TODO fix serialization Real maxVal_ = std::numeric_limits::min(); + public: - Real anomalyScore_ = 0.0f; //default score = no anomaly + const Real NO_ANOMALY = 0.0f; + const Real SPATIAL_ANOMALY = 0.9995947141f; //almost 1.0 = max anomaly. Encodes value specific to spatial anomaly (so this can be recognized on results), + // "5947141" would translate in l33t speech to "spatial" :) + Real anomalyScore_ = NO_ANOMALY; //default score = no anomaly } spAnomaly; /** diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index af58932c6b..a6b5a0305d 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace testing { @@ -2103,5 +2104,68 @@ TEST(SpatialPoolerTest, ExactOutput) { ASSERT_EQ( columns, gold_sdr ); } +TEST(SpatialPoolerTest, spatialAnomaly) { + SDR inputs({ 1000 }); + SDR columns({ 200 }); + SpatialPooler sp({inputs.dimensions}, {columns.dimensions}, + /*potentialRadius*/ 99999, + /*potentialPct*/ 0.5f, + /*globalInhibition*/ true, + /*localAreaDensity*/ 0.05f, + /*stimulusThreshold*/ 3u, + /*synPermInactiveDec*/ 0.008f, + /*synPermActiveInc*/ 0.05f, + /*synPermConnected*/ 0.1f, + /*minPctOverlapDutyCycles*/ 0.001f, + /*dutyCyclePeriod*/ 200, + /*boostStrength*/ 10.0f, + /*seed*/ 42, + /*spVerbosity*/ 0, + /*wrapAround*/ true); + + + // test too large threshold +#ifdef NTA_ASSERTIONS_ON //only for Debug + sp.spAnomaly.SPATIAL_TOLERANCE = 1.2345f; //out of bounds, will crash! + EXPECT_ANY_THROW(sp.compute(inputs, false, columns, 0.1f /*whatever, fails on TOLERANCE */)) << "Spatial anomaly should fail if SPATIAL_TOLERANCE is out of bounds!"; + sp.spAnomaly.SPATIAL_TOLERANCE = 0.01f; //within bounds, OK + EXPECT_NO_THROW(sp.compute(inputs, false, columns, 0.1f /*whatever */)); +#endif + + //test spatial anomaly computation + sp.spAnomaly.SPATIAL_TOLERANCE = 0.2f; //threshold 20% + ScalarEncoderParameters params; + params.minimum = 0.0f; + params.maximum = 100.0f; + params.size = 1000; + params.sparsity = 0.3f; + ScalarEncoder enc(params); + + Real val; + + val = 0.0f; + enc.encode(val, inputs); //TODO can SDR hold .origValue = Real which encoders would set? Classifier,Predictor, and spatia_anomaly would use that + sp.compute(inputs, true, columns, val); + EXPECT_EQ(0.0f, sp.anomaly); + EXPECT_EQ(sp.spAnomaly.NO_ANOMALY, sp.anomaly) << "should be the same as above"; + + val = 10.0f; + enc.encode(val, inputs); + sp.compute(inputs, true, columns, val); + EXPECT_EQ(sp.spAnomaly.NO_ANOMALY, sp.anomaly); + + val = 11.99f; //(10-0) * 0.2 == 2 -> <-2, +12> is not anomalous + enc.encode(val, inputs); + sp.compute(inputs, true, columns, val); + EXPECT_EQ(0.0f, sp.anomaly) << "This shouldn't be an anomaly!"; + + val = 100.0f; //(12-0) * 0.2 == ~2.2 -> <-2.2, +14.2> is not anomalous, but 100 is! + enc.encode(val, inputs); + sp.compute(inputs, true, columns, val); + EXPECT_EQ(0.9995947141f, sp.anomaly) << "This should be an anomaly!"; + EXPECT_EQ(sp.spAnomaly.SPATIAL_ANOMALY, sp.anomaly) << "Should be same as above!"; + + +} } // end anonymous namespace From 183173fc6ab3aaf43efaf2019314f652edd1b2d3 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 24 Jul 2019 20:51:41 +0200 Subject: [PATCH 3/6] SP spatial anomaly python bindings --- .../bindings/algorithms/py_SpatialPooler.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp b/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp index 0b6dd29e5c..cd2541bb64 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp @@ -225,6 +225,14 @@ Argument wrapAround boolean value that determines whether or not inputs py_SpatialPooler.def("getMinPctOverlapDutyCycles", &SpatialPooler::getMinPctOverlapDutyCycles); py_SpatialPooler.def("setMinPctOverlapDutyCycles", &SpatialPooler::setMinPctOverlapDutyCycles); + py_SpatialPooler.def("anomaly", [](const SpatialPooler& self) { + return self.anomaly; + }); + py_SpatialPooler.def("anomalyThreshold", [](const SpatialPooler& self) { + return &self.spAnomaly.SPATIAL_TOLERANCE; + }); + + // loadFromString py_SpatialPooler.def("loadFromString", [](SpatialPooler& self, const py::bytes& inString) { @@ -245,8 +253,8 @@ Argument wrapAround boolean value that determines whether or not inputs }); // compute - py_SpatialPooler.def("compute", [](SpatialPooler& self, SDR& input, bool learn, SDR& output) - { self.compute( input, learn, output ); }, + py_SpatialPooler.def("compute", [](SpatialPooler& self, const SDR& input, const bool learn, SDR& output, const Real spatialAnomalyInputValue) + { self.compute( input, learn, output, spatialAnomalyInputValue ); }, R"( This is the main workhorse method of the SpatialPooler class. This method takes an input SDR and computes the set of output active columns. If 'learn' is @@ -269,10 +277,15 @@ Argument learn A boolean value indicating whether learning should be Argument output An SDR representing the winning columns after inhibition. The size of the SDR is equal to the number of columns (also returned by the method getNumColumns). + +Argument spatialAnomalyInputValue (optional) A `Real` value of the original input + that was given to encoder. Used only for spatial anomaly computation. + See @ref `SP.anomaly` )", py::arg("input"), py::arg("learn") = true, - py::arg("output") + py::arg("output"), + py::arg("spatialAnomalyInputValue") = std::numeric_limits::min() ); // setBoostFactors From e978009e8bd17e4612d73adf482f12f8d912483a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 25 Jul 2019 01:31:30 +0200 Subject: [PATCH 4/6] SP.anomaly fix serialization & equals --- src/htm/algorithms/SpatialPooler.cpp | 4 +++ src/htm/algorithms/SpatialPooler.hpp | 42 +++++++++++++++++++++------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 9a825e6697..c5475429e0 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -1027,6 +1027,10 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ //Random if (rng_ != o.rng_) return false; + + //spatial anomaly + if (spAnomaly != o.spAnomaly) return false; + return true; } diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index 7b9f605a57..c6f9daef11 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -283,7 +283,11 @@ class SpatialPooler : public Serializable CEREAL_NVP(synPermBelowStimulusInc_), CEREAL_NVP(synPermConnected_), CEREAL_NVP(minPctOverlapDutyCycles_), - CEREAL_NVP(wrapAround_)); + CEREAL_NVP(wrapAround_), + CEREAL_NVP(spAnomaly.minVal_), + CEREAL_NVP(spAnomaly.maxVal_), + CEREAL_NVP(spAnomaly.anomalyScore_) + ); ar(CEREAL_NVP(boostFactors_)); ar(CEREAL_NVP(overlapDutyCycles_)); ar(CEREAL_NVP(activeDutyCycles_)); @@ -316,7 +320,11 @@ class SpatialPooler : public Serializable CEREAL_NVP(synPermBelowStimulusInc_), CEREAL_NVP(synPermConnected_), CEREAL_NVP(minPctOverlapDutyCycles_), - CEREAL_NVP(wrapAround_)); + CEREAL_NVP(wrapAround_), + CEREAL_NVP(spAnomaly.minVal_), + CEREAL_NVP(spAnomaly.maxVal_), + CEREAL_NVP(spAnomaly.anomalyScore_) + ); ar(CEREAL_NVP(boostFactors_)); ar(CEREAL_NVP(overlapDutyCycles_)); ar(CEREAL_NVP(activeDutyCycles_)); @@ -1226,11 +1234,13 @@ class SpatialPooler : public Serializable * a spatial anomaly regardless of the anomaly likelihood calculation. * This accounts for the human labelling bias for spatial values larger than what * has been seen so far. + * Default value 0.05f aka 5%, as used in NAB. */ Real SPATIAL_TOLERANCE = 0.05; /** - * toggle if we should compute spatial anomaly + * toggle whether we should compute spatial anomaly + * Default true. */ bool enabled = true; @@ -1248,10 +1258,9 @@ class SpatialPooler : public Serializable void compute(const Real value) { anomalyScore_ = NO_ANOMALY; if(not enabled) return; - NTA_ASSERT(SPATIAL_TOLERANCE > 0.0f and SPATIAL_TOLERANCE <= 1.0f); + NTA_ASSERT(SPATIAL_TOLERANCE >= 0.0f and SPATIAL_TOLERANCE <= 1.0f); - if(minVal_ != std::numeric_limits::max() and - maxVal_ != std::numeric_limits::min() ) { + if(minVal_ != maxVal_) { const Real tolerance = (maxVal_ - minVal_) * SPATIAL_TOLERANCE; const Real maxExpected = maxVal_ + tolerance; const Real minExpected = minVal_ - tolerance; @@ -1265,15 +1274,28 @@ class SpatialPooler : public Serializable NTA_ASSERT(anomalyScore_ == NO_ANOMALY or anomalyScore_ == SPATIAL_ANOMALY) << "Out of bounds of acceptable values"; } + bool operator==(const spatial_anomaly& o) const noexcept { + return minVal_ == o.minVal_ and + maxVal_ == o.maxVal_ and + anomalyScore_ == o.anomalyScore_ and + enabled == o.enabled and + SPATIAL_TOLERANCE == o.SPATIAL_TOLERANCE; + } + + inline bool operator!=(const spatial_anomaly& o) const noexcept { + return !this->operator==(o); + } + private: - Real minVal_ = std::numeric_limits::max(); //TODO fix serialization + friend class SpatialPooler; + Real minVal_ = std::numeric_limits::max(); Real maxVal_ = std::numeric_limits::min(); + Real anomalyScore_ = NO_ANOMALY; //default score = no anomaly public: - const Real NO_ANOMALY = 0.0f; - const Real SPATIAL_ANOMALY = 0.9995947141f; //almost 1.0 = max anomaly. Encodes value specific to spatial anomaly (so this can be recognized on results), + static const constexpr Real NO_ANOMALY = 0.0f; + static const constexpr Real SPATIAL_ANOMALY = 0.9995947141f; //almost 1.0 = max anomaly. Encodes value specific to spatial anomaly (so this can be recognized on results), // "5947141" would translate in l33t speech to "spatial" :) - Real anomalyScore_ = NO_ANOMALY; //default score = no anomaly } spAnomaly; /** From 4a1ea051bdfaf532ce7c9f3aefb692c95af8f2d7 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 25 Jul 2019 01:51:27 +0200 Subject: [PATCH 5/6] SP.anomaly fix test for 1st computation --- src/htm/algorithms/SpatialPooler.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index c6f9daef11..6ced77cb0f 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -1269,8 +1269,8 @@ class SpatialPooler : public Serializable anomalyScore_ = SPATIAL_ANOMALY; } } - if(value > maxVal_) maxVal_ = value; - if(value < minVal_) minVal_ = value; + if(value > maxVal_ or maxVal_ == INF_) maxVal_ = value; + if(value < minVal_ or minVal_ == INF_) minVal_ = value; NTA_ASSERT(anomalyScore_ == NO_ANOMALY or anomalyScore_ == SPATIAL_ANOMALY) << "Out of bounds of acceptable values"; } @@ -1288,8 +1288,9 @@ class SpatialPooler : public Serializable private: friend class SpatialPooler; - Real minVal_ = std::numeric_limits::max(); - Real maxVal_ = std::numeric_limits::min(); + static const constexpr Real INF_ = std::numeric_limits::infinity(); + Real minVal_ = INF_; + Real maxVal_ = INF_; Real anomalyScore_ = NO_ANOMALY; //default score = no anomaly public: From 83d2c6959b41a631b02f963655dd3eb69b5467e9 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 25 Jul 2019 03:20:45 +0200 Subject: [PATCH 6/6] SP fixup test --- src/htm/algorithms/SpatialPooler.hpp | 2 +- .../unit/algorithms/SpatialPoolerTest.cpp | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index 6ced77cb0f..fd16475f8a 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -1258,7 +1258,7 @@ class SpatialPooler : public Serializable void compute(const Real value) { anomalyScore_ = NO_ANOMALY; if(not enabled) return; - NTA_ASSERT(SPATIAL_TOLERANCE >= 0.0f and SPATIAL_TOLERANCE <= 1.0f); + NTA_CHECK(SPATIAL_TOLERANCE >= 0.0f and SPATIAL_TOLERANCE <= 1.0f); if(minVal_ != maxVal_) { const Real tolerance = (maxVal_ - minVal_) * SPATIAL_TOLERANCE; diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index a6b5a0305d..7aca35c229 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -2107,6 +2107,17 @@ TEST(SpatialPoolerTest, ExactOutput) { TEST(SpatialPoolerTest, spatialAnomaly) { SDR inputs({ 1000 }); SDR columns({ 200 }); + + { + SpatialPooler sp(inputs.dimensions, columns.dimensions); + // test too large threshold + sp.spAnomaly.SPATIAL_TOLERANCE = 1.2345f; //out of bounds, will crash! + EXPECT_ANY_THROW(sp.compute(inputs, false, columns, 0.1f /*whatever, fails on TOLERANCE */)) << "Spatial anomaly should fail if SPATIAL_TOLERANCE is out of bounds!"; + sp.spAnomaly.SPATIAL_TOLERANCE = 0.01f; //within bounds, OK + EXPECT_NO_THROW(sp.compute(inputs, false, columns, 0.1f /*whatever */)); + } + + { SpatialPooler sp({inputs.dimensions}, {columns.dimensions}, /*potentialRadius*/ 99999, /*potentialPct*/ 0.5f, @@ -2123,15 +2134,6 @@ TEST(SpatialPoolerTest, spatialAnomaly) { /*spVerbosity*/ 0, /*wrapAround*/ true); - - // test too large threshold -#ifdef NTA_ASSERTIONS_ON //only for Debug - sp.spAnomaly.SPATIAL_TOLERANCE = 1.2345f; //out of bounds, will crash! - EXPECT_ANY_THROW(sp.compute(inputs, false, columns, 0.1f /*whatever, fails on TOLERANCE */)) << "Spatial anomaly should fail if SPATIAL_TOLERANCE is out of bounds!"; - sp.spAnomaly.SPATIAL_TOLERANCE = 0.01f; //within bounds, OK - EXPECT_NO_THROW(sp.compute(inputs, false, columns, 0.1f /*whatever */)); -#endif - //test spatial anomaly computation sp.spAnomaly.SPATIAL_TOLERANCE = 0.2f; //threshold 20% ScalarEncoderParameters params; @@ -2164,7 +2166,7 @@ TEST(SpatialPoolerTest, spatialAnomaly) { sp.compute(inputs, true, columns, val); EXPECT_EQ(0.9995947141f, sp.anomaly) << "This should be an anomaly!"; EXPECT_EQ(sp.spAnomaly.SPATIAL_ANOMALY, sp.anomaly) << "Should be same as above!"; - + } }