-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spatial anomaly #587
base: master
Are you sure you want to change the base?
Spatial anomaly #587
Changes from 2 commits
9067e81
e4b4c78
183173f
e978009
4a1ea05
83d2c69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Real>::min()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. had to extend SP.compute() with the extra, optional field. It works ok, but I don't like it too much, maybe better is the proposal with "SP carrying orig input value"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the anomaly code could be built into the encoder, since the encoder already has access to the original input value? Alternatively, this piece of code (spatial anomaly detection) could live in its own class? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
makes sense! This is really more a function of an encoder (input). As I was saying, I don't really like the current implementation, but it is what's used in NAB.
later I'm going to implement SP's anomaly detection based on synapses of SP, so
Which way would you suggest, I don't really have a preference now |
||
|
||
|
||
/** | ||
|
@@ -1209,6 +1216,74 @@ 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. | ||
*/ | ||
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 `NO_ANOMALY` (0.0f) , | ||
* or `SPATIAL_ANOMALY` (exactly 0.9995947141f). Accessed by public @ref `SP.anomaly`. | ||
* | ||
*/ | ||
void compute(const Real value) { | ||
anomalyScore_ = NO_ANOMALY; | ||
if(not enabled) return; | ||
NTA_ASSERT(SPATIAL_TOLERANCE > 0.0f and SPATIAL_TOLERANCE <= 1.0f); | ||
|
||
if(minVal_ != std::numeric_limits<Real>::max() and | ||
maxVal_ != std::numeric_limits<Real>::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_ = 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<Real>::max(); //TODO fix serialization | ||
Real maxVal_ = std::numeric_limits<Real>::min(); | ||
|
||
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), | ||
// "5947141" would translate in l33t speech to "spatial" :) | ||
Real anomalyScore_ = NO_ANOMALY; //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_; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. spAnomaly implementation, |
||
const Connections &connections = connections_; | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
#include <htm/types/Types.hpp> | ||
#include <htm/utils/Log.hpp> | ||
#include <htm/os/Timer.hpp> | ||
#include <htm/encoders/ScalarEncoder.hpp> | ||
|
||
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!"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. testing SP.anomaly correctness |
||
EXPECT_EQ(sp.spAnomaly.SPATIAL_ANOMALY, sp.anomaly) << "Should be same as above!"; | ||
|
||
|
||
} | ||
|
||
} // end anonymous namespace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hotgym checks the new spatial anomaly