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

MNIST params #533

Merged
merged 13 commits into from
Jul 6, 2019
61 changes: 42 additions & 19 deletions src/examples/mnist/MNIST_SP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,32 @@
#include <htm/algorithms/SpatialPooler.hpp>
#include <htm/algorithms/SDRClassifier.hpp>
#include <htm/utils/SdrMetrics.hpp>
#include <htm/os/Timer.hpp>

#include <mnist/mnist_reader.hpp> // MNIST data itself + read methods, namespace mnist::
#include <mnist/mnist_utils.hpp> // mnist::binarize_dataset

namespace examples {

using namespace std;
using namespace htm;

class MNIST {
/**
* RESULTS:
*
* Order : score : column dim : #pass : time(s): git commit : comment
* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
* 1/Score: 97.11% (289 / 10000 wrong) : 28x28x16 : 4 : 557 : 1f0187fc6 : epochs help, at cost of time
*
* 2/Score: 96.56% (344 / 10000 wrong) : 28x28x16 : 1 : 142 : 3ccadc6d6
*
* 3/Score: 96.1% (390 / 10000 wrong). : 28x28x30 : 1 : 256 : 454f7a9d8
*
* others/
* Score: 95.35% (465 / 10000 wrong) : 28x28x16 : 2 : 125 : : smaller boosting (2.0)
* -- this will be my working model, reasonable performance/speed ratio
*
*/

private:
SpatialPooler sp;
Expand All @@ -51,31 +67,31 @@ class MNIST {

public:
UInt verbosity = 1;
const UInt train_dataset_iterations = 1u;
const UInt train_dataset_iterations = 2u; //epochs somewhat help, at linear time


void setup() {

input.initialize({28 * 28});
columns.initialize({10 * 1000});
input.initialize({28, 28,1});
columns.initialize({28, 28, 8}); //1D vs 2D no big difference, 2D seems more natural for the problem. Speed-----, Results+++++++++; #columns HIGHEST impact.
sp.initialize(
/* inputDimensions */ input.dimensions,
/* columnDimensions */ columns.dimensions,
/* potentialRadius */ 999999u, // No topology, all to all connections.
/* potentialPct */ 0.65f,
/* globalInhibition */ true,
/* localAreaDensity */ 0.05f, // % active bits
/* potentialRadius */ 7, // with 2D, 7 results in 15x15 area, which is cca 25% for the input area. Slightly improves than 99999 aka "no topology, all to all connections"
/* potentialPct */ 0.1f, //we have only 10 classes, and << #columns. So we want to force each col to specialize. Cca 0.3 w "7" above, or very small (0.1) for "no topology". Cannot be too small due to internal checks. Speed++
/* globalInhibition */ true, //Speed+++++++; SDR quality-- (global does have active nearby cols, which we want to avoid (local)); Results+-0
/* localAreaDensity */ 0.1f, // % active bits
/* numActiveColumnsPerInhArea */ -1,
/* stimulusThreshold */ 6u,
/* synPermInactiveDec */ 0.005f,
/* synPermActiveInc */ 0.014f,
/* synPermConnected */ 0.1f,
/* minPctOverlapDutyCycles */ 0.001f,
/* synPermInactiveDec */ 0.002f, //FIXME inactive decay permanence plays NO role, investigate! (slightly better w/o 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.

I never get to decay play a role!?

/* synPermActiveInc */ 0.14f, //takes upto 5x steps to get dis/connected
/* synPermConnected */ 0.5f, //no difference, let's leave at 0.5 in the middle
/* minPctOverlapDutyCycles */ 0.2f, //speed of re-learning?
/* dutyCyclePeriod */ 1402,
/* boostStrength */ 7.8f, // Boosting does help
/* seed */ 93u,
/* boostStrength */ 2.0f, // Boosting does help, but entropy is high, on MNIST it does not matter, for learning with TM prefer boosting off (=0.0), or "neutral"=1.0
/* seed */ 4u,
/* spVerbosity */ 1u,
/* wrapAround */ false); // No topology, turn off wrapping
/* wrapAround */ true); // does not matter (helps slightly)

// Save the connections to file for postmortem analysis.
ofstream dump("mnist_sp_initial.connections", ofstream::binary | ofstream::trunc | ofstream::out);
Expand All @@ -99,6 +115,8 @@ void train() {
Metrics inputStats(input, 1402);
Metrics columnStats(columns, 1402);

Timer tTrain(true);

for(auto epoch = 0u; epoch < train_dataset_iterations; epoch++) {
NTA_INFO << "epoch " << epoch;
// Shuffle the training data.
Expand All @@ -120,11 +138,15 @@ void train() {
if( verbosity && (++i % 1000 == 0) ) cout << "." << flush;
}
if( verbosity ) cout << endl;
}

cout << "epoch ended" << endl;
cout << "inputStats " << inputStats << endl;
cout << "columnStats " << columnStats << endl;
cout << sp << endl;
}

tTrain.stop();
cout << "MNIST train time: " << tTrain.getElapsed() << endl;

// Save the connections to file for postmortem analysis.
ofstream dump("mnist_sp_learned.connections", ofstream::binary | ofstream::trunc | ofstream::out);
Expand Down Expand Up @@ -153,14 +175,15 @@ void test() {
if( verbosity && i % 1000 == 0 ) cout << "." << flush;
}
if( verbosity ) cout << endl;
cout << "Score: " << 100.0 * score / n_samples << "% " << endl;
cout << "===========RESULTs=================" << endl;
cout << "Score: " << 100.0 * score / n_samples << "% ("<< (n_samples - score) << " / " << n_samples << " wrong). " << endl;
cout << "SDR example: " << columns << endl;
}

}; // End class MNIST
} // End namespace examples

int main(int argc, char **argv) {
examples::MNIST m;
MNIST m;
m.setup();
m.train();
m.test();
Expand Down
28 changes: 21 additions & 7 deletions src/htm/algorithms/SpatialPooler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ void SpatialPooler::initialize(
overlapDutyCycles_.assign(numColumns_, 0);
activeDutyCycles_.assign(numColumns_, 0);
minOverlapDutyCycles_.assign(numColumns_, 0.0);
boostFactors_.assign(numColumns_, 1);
boostFactors_.assign(numColumns_, 1.0); //1 is neutral value for boosting
overlaps_.resize(numColumns_);
overlapsPct_.resize(numColumns_);
boostedOverlaps_.resize(numColumns_);
Expand Down Expand Up @@ -500,6 +500,11 @@ void SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) {

void SpatialPooler::boostOverlaps_(const vector<SynapseIdx> &overlaps, //TODO use Eigen sparse vector here
vector<Real> &boosted) const {
if(boostStrength_ < htm::Epsilon) {
const auto begin = static_cast<const SynapseIdx*>(overlaps.data());
boosted.assign(begin, begin + overlaps.size());
return; //boost ~ 0.0, we can skip these computations.
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 probably changed the results for the test

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you be a little more certain of this? Why exactly did the unit tests change?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nevermind, I see how your unit test works.

Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpick: boosted.assign(overlaps.begin(), overlaps.end())

}
for (UInt i = 0; i < numColumns_; i++) {
boosted[i] = overlaps[i] * boostFactors_[i];
}
Expand Down Expand Up @@ -755,6 +760,16 @@ void SpatialPooler::updateBoostFactors_() {
}


void applyBoosting_(const UInt i,
const Real targetDensity,
const vector<Real>& actualDensity,
const Real boost,
vector<Real>& output) {
if(boost < htm::Epsilon) return; //skip for disabled boosting
output[i] = exp((targetDensity - actualDensity[i]) * boost); //TODO doc this code
Copy link
Member Author

Choose a reason for hiding this comment

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

where is a doc for this exp based boosting, please?

}


void SpatialPooler::updateBoostFactorsGlobal_() {
Real targetDensity;
if (numActiveColumnsPerInhArea_ > 0) {
Expand All @@ -767,9 +782,9 @@ void SpatialPooler::updateBoostFactorsGlobal_() {
} else {
targetDensity = localAreaDensity_;
}

for (UInt i = 0; i < numColumns_; ++i) {
boostFactors_[i] = exp((targetDensity - activeDutyCycles_[i]) * boostStrength_);
for (UInt i = 0; i < numColumns_; ++i) {
applyBoosting_(i, targetDensity, activeDutyCycles_, boostStrength_, boostFactors_);
}
}

Expand All @@ -792,8 +807,7 @@ void SpatialPooler::updateBoostFactorsLocal_() {
}

const Real targetDensity = localActivityDensity / numNeighbors;
boostFactors_[i] =
exp((targetDensity - activeDutyCycles_[i]) * boostStrength_);
applyBoosting_(i, targetDensity, activeDutyCycles_, boostStrength_, boostFactors_);
}
}

Expand Down Expand Up @@ -828,7 +842,7 @@ void SpatialPooler::calculateOverlapPct_(const vector<SynapseIdx> &overlaps,


void SpatialPooler::inhibitColumns_(const vector<Real> &overlaps,
vector<UInt> &activeColumns) const {
vector<CellIdx> &activeColumns) const {
Real density = localAreaDensity_;
if (numActiveColumnsPerInhArea_ > 0) {
UInt inhibitionArea =
Expand Down
9 changes: 5 additions & 4 deletions src/htm/algorithms/SpatialPooler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class SpatialPooler : public Serializable
that each column can potentially be connected to every input
bit. This parameter defines a square (or hyper square) area: a
column will have a max square potential pool with sides of
length (2 * potentialRadius + 1).
length `(2 * potentialRadius + 1)`, rounded to fit into each dimension.

@param potentialPct The percent of the inputs, within a column's
potential radius, that a column can be connected to. If set to
Expand Down Expand Up @@ -185,8 +185,9 @@ class SpatialPooler : public Serializable
boost. Shorter values make it potentially more unstable and
likely to oscillate.

@param boostStrength A number greater or equal than 0, used to
control boosting strength. No boosting is applied if it is set to 0.
@param boostStrength A number greater or equal than 1.0 or exactly 0.0, used to
breznak marked this conversation as resolved.
Show resolved Hide resolved
control boosting strength.
No boosting is applied if it is set to 0.0, (runs faster due to skipped code).
The strength of boosting increases as a function of boostStrength.
Boosting encourages columns to have similar activeDutyCycles as their
neighbors, which will lead to more efficient use of columns. However,
Expand Down Expand Up @@ -920,7 +921,7 @@ class SpatialPooler : public Serializable
columns.
*/
void inhibitColumns_(const vector<Real> &overlaps,
vector<UInt> &activeColumns) const;
vector<CellIdx> &activeColumns) const;

/**
Perform global inhibition.
Expand Down
9 changes: 6 additions & 3 deletions src/htm/algorithms/TemporalMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,13 +427,13 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) {
const auto columnForSegment = [&](Segment segment) {
return connections.cellForSegment(segment) / cellsPerColumn_;
};
const auto identity = [](const UInt a) {return a;}; //TODO use std::identity when c++20
const auto identity = [](const ElemSparse a) {return a;}; //TODO use std::identity when c++20

for (auto &&columnData : groupBy( //group by columns, and convert activeSegments & matchingSegments to cols.
sparse, identity,
activeSegments_, columnForSegment,
matchingSegments_, columnForSegment)) {
UInt column;
CellIdx column;
vector<Segment>::const_iterator activeColumnsBegin, activeColumnsEnd,
columnActiveSegmentsBegin, columnActiveSegmentsEnd,
columnMatchingSegmentsBegin, columnMatchingSegmentsEnd;
Expand Down Expand Up @@ -598,9 +598,12 @@ SDR TemporalMemory::cellsToColumns(const SDR& cells) const {
auto correctDims = getColumnDimensions(); //nD column dimensions (eg 10x100)
correctDims.push_back(static_cast<CellIdx>(getCellsPerColumn())); //add n+1-th dimension for cellsPerColumn (eg. 10x100x8)

NTA_CHECK(cells.dimensions == correctDims)
NTA_CHECK(cells.dimensions.size() == correctDims.size())
<< "cells.dimensions must match TM's (column dims x cellsPerColumn) ";

for(size_t i = 0; i<correctDims.size(); i++)
NTA_CHECK(correctDims[i] == cells.dimensions[i]);

SDR cols(getColumnDimensions());
auto& dense = cols.getDense();
for(const auto cell : cells.getSparse()) {
Expand Down
2 changes: 1 addition & 1 deletion src/htm/types/Sdr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ namespace htm {
NTA_CHECK( ( 1 + fractionNoise) * getSparsity() <= 1. );

const UInt num_move_bits = (UInt) std::round( fractionNoise * getSum() );
const vector<UInt> turn_off = rng.sample(getSparse(), num_move_bits);
const auto& turn_off = rng.sample(getSparse(), num_move_bits);

auto& dns = getDense();

Expand Down
2 changes: 1 addition & 1 deletion src/htm/utils/GroupBy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static KeyType minFrontKey(KeyType frontrunner, Iterator0 begin0,
KeyType ret = frontrunner;

if (begin0 != end0) {
ret = std::min(ret, keyFn0(*begin0));
ret = std::min(ret, static_cast<KeyType>(keyFn0(*begin0)));
}

if (begin1 != end1) {
Expand Down
21 changes: 18 additions & 3 deletions src/test/unit/algorithms/SpatialPoolerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1718,20 +1718,35 @@ TEST(SpatialPoolerTest, getOverlaps) {

vector<Real> boostFactors = {1.0f, 2.0f, 3.0f};
sp.setBoostFactors(boostFactors.data());
sp.setBoostStrength(0.0f); //default, effectively disables boosting

SDR input( {5});
input.setDense(vector<UInt>{1, 1, 1, 1, 1});
SDR activeColumns( {3} );
activeColumns.setDense(vector<UInt>{0, 0, 0});
sp.compute(input, true, activeColumns);

//overlaps (not boosted)
const auto &overlaps = sp.getOverlaps();
const vector<SynapseIdx> expectedOverlaps = {0, 3, 5};
EXPECT_EQ(expectedOverlaps, overlaps);

const vector<Real> &boostedOverlaps = sp.getBoostedOverlaps();
const vector<Real> expectedBoostedOverlaps = {0.0f, 6.0f, 15.0f};
EXPECT_EQ(expectedBoostedOverlaps, boostedOverlaps);
//boosted overlaps, but boost strength=0.0
const auto& boostedOverlaps = sp.getBoostedOverlaps();
const vector<Real> expectedBoostedOverlaps = {0.0f, 3.0f, 5.0f}; //same as orig above (but float)
EXPECT_EQ(expectedBoostedOverlaps, boostedOverlaps) << "SP with boost strength " << sp.getBoostStrength() << " must not change boosting ";

//boosted overlaps, but boost strength=2.0
//recompute
sp.setBoostFactors(boostFactors.data());
sp.setBoostStrength(2.0f);

activeColumns.setDense(vector<UInt>{0, 0, 0});
sp.compute(input, true, activeColumns);

const auto& boostedOverlaps2 = sp.getBoostedOverlaps();
const vector<Real> expectedBoostedOverlaps2 = {0.0f, 6.0f, 15.0f};
EXPECT_EQ(expectedBoostedOverlaps2, boostedOverlaps2) << "SP with boost strength " << sp.getBoostStrength() << " must change boosting ";
}

TEST(SpatialPoolerTest, ZeroOverlap_NoStimulusThreshold_GlobalInhibition) {
Expand Down