Skip to content
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

TM anomaly #406

Merged
merged 42 commits into from
May 5, 2019
Merged

TM anomaly #406

merged 42 commits into from
May 5, 2019

Conversation

breznak
Copy link
Member

@breznak breznak commented Apr 15, 2019

Anomaly is computed in TM directly, not in Anomaly class

TODO:

  • finish implementation :)
  • move AnomalyTests to TM.anomalyTests
    • new AnomalyLikelihood tests -> must be fixed in separate PR.
  • remove Anomaly class
    • remove computeRawAnomalyScore() vector version, keep only SDR
  • bindings for TM::getAnomalyScore()
  • serialization
  • doc
  • waiting for TM activeCells_ is a SDR #442 to resolve issue with extraActive, causing SDR to fail. Triggered by "testExtraActive " test

Fixes #347

Anomaly is computed in TM directly, not in Anomaly class
@breznak breznak added anomaly TM code code enhancement, optimization, cleanup..programmer stuff labels Apr 15, 2019
@breznak breznak self-assigned this Apr 15, 2019
src/nupic/algorithms/TemporalMemory.cpp Outdated Show resolved Hide resolved
src/nupic/algorithms/TemporalMemory.cpp Outdated Show resolved Hide resolved
src/nupic/algorithms/TemporalMemory.cpp Outdated Show resolved Hide resolved
breznak added 13 commits May 1, 2019 16:19
problem was in cellsToColumns() which expects a SDR,
but onfortunately (known err) does not fail if vector is passed!
all code related to anomaly computation in TM now moved to
internal struct TMAnomaly, with public
TM.anomaly.score
serialize the new functionality in TMAnomaly
deprecated, use:
- TM.anomaly.score for "normal" TM's anomaly,
- computeRawAnomalyScore from Anomaly.hpp
- AnomalyLikelihood
rework to split
update() called after activateDendrites()

and getScore() called when needed
not working, need to be fixed later
@breznak
Copy link
Member Author

breznak commented May 3, 2019

Update: I came with a workaround for

Still waiting for #442 to resolve the issue. SDR failing to create when activateDendrites is used with extraActive

now, TM does not support anomaly for cases when extra inputs (param extra>0) are used. Otherwise anomaly runs just fine.

Copy link
Collaborator

@ctrl-z-9000-times ctrl-z-9000-times left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like these changes. I think there is a much simpler solution to this problem. Instead of removing the anomaly class and adding a different class, just reuse the existing anomaly class. The TM should keep an instance of the existing anomaly class and call Anomaly::compute inside of TM::compute, in between the calls to activateDendrites and activateCells which ensures that the TM always has valid predictions as well as the current feed forward input.

// Calculate and return percent of active columns that were not predicted.
SDR both(active.dimensions);
both.intersection(active, predicted);

return (active.getSum() - both.getSum()) / Real(active.getSum());
}

Real computeRawAnomalyScore(vector<UInt>& active,
vector<UInt>& predicted)
Copy link
Collaborator

@ctrl-z-9000-times ctrl-z-9000-times May 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep this overload?
This is where I keep my notes about how the SDR class should be doing set intersections.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where I keep my notes about how the SDR class should be doing set intersections.

We could keep it,
or keep the code just as a commet, if you're not actively developing on that?
And ideally move the comment to SDR.intersection() ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I we can drop this method, but the rest of my criticism stands.

  • Friend classes are confusing and usually not necessary.
  • This doesn't include anomaly likelihood.
  • External predictive inputs don't work? I read through the code and it looks like they should just work, if you remove the checks that disable it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of removing the anomaly class and adding a different class, just reuse the existing anomaly class.

I thought you wanted me to get rid of the old Anomaly class and replace it with as simple as possibleimplementation in TM (?), which I ended up liking.

  1. I'm OK to revive the old Anomaly class, with a few changes *).
  2. Or I can just move the code TMAnomaly to Anomaly.hpp and be done with it.
    Which of those would you prefer?

Friend classes are confusing and usually not necessary.

I'll avoid 'friend access' when moving to a separate new/old file.

This doesn't include anomaly likelihood.

Yes, and it is intended. And about other refactoring in Anomaly class *):

  • I've implemented Anomaly class in the past to simplify various options with anomaly (thresholds, likelihood, moving average,...)
  • now with later usage experience, I don;t think it's a good design, and as those features can be implemented as "one liners" with current code, I don't think it should be provided with anomaly class. "Let TM.getAnomalyScore() be the simple raw anomaly" (#A), just conveniently computed. And let the user do what they need later with it.
    • thereshold: simple if(score > XXX) return 1 else return 0;
    • likelihood:
      • (probably even does not truly work)
      • in Anomaly had a FIXME note that it's not truly configurable. And should be separated.
      • I could use the approach with abstract class AbstractAnomaly::compute(SDR, SDR), but it's complicating things, see argument #A
      • AnomalyLikelihood is not even a true anomaly (computed from 2 SDRs), but a likelihood of anomalies, thus computed AFTER and from anomaly score. So score = TM.getAnomalyScore(); Likelihood::anomalyProbability(score, timestep);
    • moving average: again, simple MovingAverage ma(5); ma.add(score); ma.get();

TL;DR: the proper configuration and ordering is too complicated in a wrapper Anomaly class, while relatively easy hand-tailored by user (with tools we provide).

External predictive inputs don't work? I read through the code and it looks like they should just work, if you remove the checks that disable it.

Externals break it in the way the they add "cell indices out of scope of the current TM" (idx in <numberOfCells() , numOfCells + extra>).
See for yourself in SDR::setSparseInplace(), you must use Debug build to trigger, that's why it's failing only on the OSX CI. I'm proposing to proceed as is, and discuss the fixes on external inputs in a separate issue or #442

Copy link
Collaborator

@ctrl-z-9000-times ctrl-z-9000-times May 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you prefer?

My goal is to have convenient way to print out everything interesting about the TM to a human readable string, and from it to determine if the TM is working. I see the anomaly metric as a diagnostic tool.

  • I want to know the mean & standard deviation of the anomaly. The anomaly likelihood also uses this info.
  • It should be enabled by default.

The proper configuration and ordering is too complicated.

Agreed,

  • The user should be responsible for discarding anomaly readings when they haven't finished training the network.
  • The user should be responsible for applying the threshold.
  • I'm skeptical that the WEIGHTED mode can outperform the likelyhood mode. I don't understand the reasoning behind it, and it's not mentioned in the published literature. Maybe we could drop this too.
  • AnomalyLikelihood needs 1 parameter to do what it does, and it has 4 or 5.

@ctrl-z-9000-times
Copy link
Collaborator

I would also like to use the anomaly likelyhood code in the TM class. The original publication of NAB (https://arxiv.org/pdf/1510.03336.pdf) uses the likelihood code.

We could create a new API for this, like:

class AbstractAnomaly{
    virtual Real compute(const sdr::SDR& active, 
                         const sdr::SDR& predicted) = nullptr;
};

Or

typedef std::function<Real (const SDR&, const SDR&)> AbstractAnomaly;

And then let the user specify the function (Anomaly or AnomalyLikelihood) and its parameters.

Alternatively, if one of the three methods (Anomaly, AnomalyLikelihood, Weighted) is objectively the best then we should bake it into the TM, and not worry the users about it.

Copy link

@dkeeney dkeeney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no problems with these changes.

tm_->getActiveCells(sdr);
tm_->getActiveCells(sdr); //active cells
if (args_.orColumnOutputs) { //output as columns
sdr = tm_->cellsToColumns(sdr);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add another output to TMRegion to provide access to anomaly scores, etc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can decide this later when discussing "anomaly region", but yes, anomaly score would be moved to TM class, so it'll end up as a new part of existing TMRegion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkeeney if you have time, would you mind pushing a commit that adds a field "float anomalyScore" to TMRegion, and is computed from TM.getAnomalyScore(), please?
I could do it but Regions are still not my cup of coffee.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understand. Yes, I can do this.
But rather than a field I suggest that it be a new output. That way, the Anomaly score can be passed to some other module after each iteration. It would be cool if we had a plot region to send it to.

If we just make it a field that can be queried then the user needs to add a callback to get it at each iteration. We can provide that as well but it would not be as useful.

I will take a look at the old .py code to see what the old python Anomaly Region did. I assume there was one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

But rather than a field I suggest that it be a new output. That way, the Anomaly score can be passed to some other module after each iteration.

Ok, this is the better solution. Would it be a problem that the output is different? (1 integer, instead of TM::getColumnDimensions(), or TM::getColumnDims + cellsPerColumn?

I will take a look at the old .py code to see what the old python Anomaly Region did. I assume there was one.

there wasn't any AnomalyRegion

need to save/load anomaly_ member
as its a part of TM now, tAnLikely still exists
for normal computation, constructor must always specify
columnDimensions,
only expection is TM tm; for deserialization, but that should never be
used for compute()
removes old alternative with vectors
Copy link
Member Author

@breznak breznak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review, this finishes the new TM.anomaly implementation from #445
and has some further fixes to TM.

@@ -180,6 +180,9 @@ using namespace nupic::algorithms::connections;

py_HTM.def_property_readonly("extra", [](const HTM_t &self) { return self.extra; } );

py_HTM.def_property_readonly("anomaly", [](const HTM_t &self) { return self.anomaly; },
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TM.anomaly py binding

@@ -1064,7 +1044,8 @@ bool TemporalMemory::operator==(const TemporalMemory &other) {
winnerCells_ != other.winnerCells_ ||
maxSegmentsPerCell_ != other.maxSegmentsPerCell_ ||
maxSynapsesPerSegment_ != other.maxSynapsesPerSegment_ ||
iteration_ != other.iteration_) {
iteration_ != other.iteration_ ||
anomaly_ != other.anomaly_ ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed operator ==, and op!= which was broken. added test

*
* See TM::compute() for details of the parameters.
*
*/
void activateDendrites(const bool learn = true,
const vector<UInt> &extraActive = {std::numeric_limits<UInt>::max()},
const vector<UInt> &extraWinners = {std::numeric_limits<UInt>::max()});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the vector version of activateDendrites. All TM's public API now uses SDR

@@ -472,6 +474,7 @@ using namespace nupic::algorithms::connections;
CEREAL_NVP(activeCells_),
CEREAL_NVP(winnerCells_),
CEREAL_NVP(segmentsValid_),
CEREAL_NVP(anomaly_),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed serialization for new TM.anomaly

Copy link
Collaborator

@ctrl-z-9000-times ctrl-z-9000-times left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me!

@breznak breznak merged commit 41f6e08 into master May 5, 2019
@breznak breznak deleted the tm_anomaly branch May 5, 2019 14:15
@breznak
Copy link
Member Author

breznak commented May 5, 2019

Thank you, David! I'm quite happy with the TM.anomaly as it is now 😄

Two remaining issues:

  • anomaly output for TMRegion
  • anomaly works with TM::compute() but breaks with activateCells.
    • I'd prefer moving activateCells to protected.
    • or we could track TM.anomaly with some iteration number, and NTA_THROW if mismatched

@dkeeney
Copy link

dkeeney commented May 5, 2019

adds a field "float anomalyScore" to TMRegion, and is computed from TM.getAnomalyScore()

I assume you still want this new function.
But we did not decide if you also want anomaly to be a Region output.

@breznak
Copy link
Member Author

breznak commented May 5, 2019

yes, but now it's called just TM.anomaly

But we did not decide if you also want anomaly to be a Region output.

I agree with your suggestion that output would be a better choice.

@dkeeney
Copy link

dkeeney commented May 5, 2019

Ok, I am creating a new PR to do this.

@dkeeney
Copy link

dkeeney commented May 5, 2019

For Outputs we currently have:

  • bottomUpOut which is from TM::getActiveCells() which is an SDR of either numberOfCols if orColumnOutputs is set or number of cells by default.
  • activeCells (which is always an SDR of number of cells)
  • predictedActiveCells which are the Winners
  • anomaly (which is the new output)

With all of the recent changes, is this still the expected outputs from TMRegion.

@breznak
Copy link
Member Author

breznak commented May 5, 2019

With all of the recent changes, is this still the expected outputs from TMRegion.

yes.

  • anomaly would be 1x1 scalar

predictedActiveCells which are the Winners

might change name to winnerCells are winners

  • might add predictiveCells populated with tm.getPredictiveCells()

  • probably all of the *Cells should use the orColumnOutputs logic (rename that to asColumns)

@ctrl-z-9000-times
Copy link
Collaborator

predictedActiveCells which are the Winners

This is not 100% correct. When a mini-column bursts because it was unpredicted (this is an anomaly!), then a winner cell is arbitrarily chosen.

@dkeeney
Copy link

dkeeney commented May 5, 2019

Changing the name of predictedActiveCells will break the API. But we are changing so many other things, now is probably a good time to change this as well.

I was going to get this out in just a few min but but with these name changes this will take longer. My wife says I need to go wash the windows...company coming next week. I will try to finish this PR later today.

@breznak
Copy link
Member Author

breznak commented May 5, 2019

Changing the name of predictedActiveCells will break the API.

hmm, we wanted to keep NetworkAPI as stable as possible.

I'm not sure what @ctrl-z-9000-times meant with the comment

This is not 100% correct. When a mini-column bursts because it was unpredicted (this is an anomaly!), then a winner cell is arbitrarily chosen.

if it's just the comment about name vs winner cells, we should keep the old output name, and add this note to the description.

@dkeeney
Copy link

dkeeney commented May 5, 2019

Ok, windows are washed. On to more interesting things...

  • Lets see, a second output called predictiveCells
  • orColumnOutputs, lets not change the field name
  • PredictedActiveCells will remain that name. Comments already say its the Winners.

@dkeeney
Copy link

dkeeney commented May 5, 2019

What comment should I add to the predictiveCells output? Is "The cells that were predicted" or is this "The cells that predict the next set of active cells".

Also, The vector overload of activateDendrites( ) does not appear to be implemented.

  void activateDendrites(const bool learn = true,
                         const vector<UInt> &extraActive  = {std::numeric_limits<UInt>::max()},
                         const vector<UInt> &extraWinners = {std::numeric_limits<UInt>::max()});

@breznak
Copy link
Member Author

breznak commented May 5, 2019

"Cells predicted to be active in the next step" ?

Also, The vector overload of activateDendrites( ) does not appear to be implemented.

yes, there's only SDR version now. activateDendrites(bool), or activateDendrites(bool, SDR, SDR)

@dkeeney
Copy link

dkeeney commented May 5, 2019

oh, just needed to pull from master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
anomaly API_BREAKING_CHANGE code code enhancement, optimization, cleanup..programmer stuff ready TM
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Anomaly removal, cleanup
3 participants