diff --git a/VERSION b/VERSION index 026aa641ea..b78946bb14 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.22 \ No newline at end of file +v0.2.3 \ No newline at end of file diff --git a/src/examples/mnist/MNIST_SP.cpp b/src/examples/mnist/MNIST_SP.cpp index 8bd2d4677b..7976614982 100644 --- a/src/examples/mnist/MNIST_SP.cpp +++ b/src/examples/mnist/MNIST_SP.cpp @@ -90,7 +90,7 @@ void setup() { /* synPermConnected */ 0.5f, //no difference, let's leave at 0.5 in the middle /* minPctOverlapDutyCycles */ 0.2f, //speed of re-learning? /* dutyCyclePeriod */ 1402, - /* 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 + /* boostStrength */ 7.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 */ true); // does not matter (helps slightly) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 0843383d1f..643c568af6 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -385,9 +385,9 @@ vector Connections::computeActivity(const vector &activePre } // Iterate through all connected synapses. - for (const auto& cell : activePresynapticCells) { + for (const auto cell : activePresynapticCells) { if (connectedSegmentsForPresynapticCell_.count(cell)) { - for(const auto& segment : connectedSegmentsForPresynapticCell_.at(cell)) { + for(const auto segment : connectedSegmentsForPresynapticCell_.at(cell)) { ++numActiveConnectedSynapsesForSegment[segment]; } } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index d8bb35e8a5..28c414ce1b 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -435,7 +435,7 @@ class Connections : public Serializable * An output vector for active potential synapse counts per segment. * * @param activePresynapticCells - * Active cells in the input. + * Active cells in the input as a sparse indices. * * @param bool learn : enable learning updates (default true) * diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 594d84b6ad..ff28cdc4f4 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -441,7 +441,7 @@ void SpatialPooler::initialize( connections_.raisePermanencesToThreshold( (Segment)i, stimulusThreshold_ ); } - updateInhibitionRadius_(); + inhibitionRadius_ = updateInhibitionRadius_(); if (spVerbosity_ > 0) { printParameters(); @@ -453,14 +453,22 @@ void SpatialPooler::initialize( const vector SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) { input.reshape( inputDimensions_ ); active.reshape( columnDimensions_ ); + updateBookeepingVars_(learn); + //boosting + //must be done before inhibition const auto& overlaps = connections_.computeActivity(input.getSparse(), learn); + boostOverlaps_(overlaps, boostedOverlaps_); // @1 - boostOverlaps_(overlaps, boostedOverlaps_); - - auto &activeVector = active.getSparse(); + //inhibition + //update inhibition radius if it's time, only changes in local inh + auto& activeVector = active.getSparse(); + if(!globalInhibition_ and isUpdateRound_() and learn) { + inhibitionRadius_ = updateInhibitionRadius_(); + } inhibitColumns_(boostedOverlaps_, activeVector); + // Notify the active SDR that its internal data vector has changed. Always // call SDR's setter methods even if when modifying the SDR's own data // inplace. @@ -469,13 +477,9 @@ const vector SpatialPooler::compute(const SDR &input, const bool lea if (learn) { adaptSynapses_(input, active); - updateDutyCycles_(overlaps, active); - bumpUpWeakColumns_(); - updateBoostFactors_(); - if (isUpdateRound_()) { - updateInhibitionRadius_(); - updateMinDutyCycles_(); - } + //boosting + bumpUpWeakColumns_(overlaps); //TODO suggest removal, low impact, long time + updateBoostFactors_(active); //helper for @1, computed after inh } return overlaps; @@ -565,11 +569,9 @@ vector SpatialPooler::initPermanence_(const vector &potential, //TOD } -void SpatialPooler::updateInhibitionRadius_() { - if (globalInhibition_) { - inhibitionRadius_ = - *max_element(columnDimensions_.cbegin(), columnDimensions_.cend()); - return; +UInt SpatialPooler::updateInhibitionRadius_() const { + if (globalInhibition_) { //always const for global inh + return *max_element(columnDimensions_.cbegin(), columnDimensions_.cend()); } Real connectedSpan = 0.0f; @@ -581,7 +583,8 @@ void SpatialPooler::updateInhibitionRadius_() { const Real diameter = connectedSpan * columnsPerInput; Real radius = (diameter - 1) / 2.0f; radius = max((Real)1.0, radius); - inhibitionRadius_ = UInt(round(radius)); + + return UInt(round(radius)); } @@ -626,26 +629,6 @@ void SpatialPooler::updateMinDutyCyclesLocal_() { } -void SpatialPooler::updateDutyCycles_(const vector &overlaps, - SDR &active) { - - // Turn the overlaps array into an SDR. Convert directly to flat-sparse to - // avoid copies and type convertions. - SDR newOverlap({ numColumns_ }); - auto &overlapsSparseVec = newOverlap.getSparse(); - for (UInt i = 0; i < numColumns_; i++) { - if( overlaps[i] != 0 ) - overlapsSparseVec.push_back( i ); - } - newOverlap.setSparse( overlapsSparseVec ); - - const UInt period = std::min(dutyCyclePeriod_, iterationNum_); - - updateDutyCyclesHelper_(overlapDutyCycles_, newOverlap, period); - updateDutyCyclesHelper_(activeDutyCycles_, active, period); -} - - Real SpatialPooler::avgColumnsPerInput_() const { const size_t numDim = max(columnDimensions_.size(), inputDimensions_.size()); Real columnsPerInput = 0.0f; @@ -702,13 +685,40 @@ void SpatialPooler::adaptSynapses_(const SDR &input, } -void SpatialPooler::bumpUpWeakColumns_() { +void SpatialPooler::bumpUpWeakColumns_(const std::vector& overlaps) { for (UInt i = 0; i < numColumns_; i++) { + // skip columns (segments) that are already performing OK if (overlapDutyCycles_[i] >= minOverlapDutyCycles_[i]) { continue; } + //bump the weak connections_.bumpSegment( i, synPermBelowStimulusInc_ ); } + + //do updates: + + // update overlap duty cycles (each round) + updateDutyCyclesOverlaps_(overlaps); + + //update minOverlapDutyCycles_ (on update round only) + if (isUpdateRound_()) { + updateMinDutyCycles_(); + } +} + + +void SpatialPooler::updateDutyCyclesOverlaps_(const vector& overlaps) { + SDR newOverlap({ numColumns_ }); + auto &overlapsSparseVec = newOverlap.getSparse(); + + for (UInt i = 0; i < numColumns_; i++) { + if( overlaps[i] > Epsilon ) + overlapsSparseVec.push_back( i ); + } + newOverlap.setSparse( overlapsSparseVec ); + + const UInt period = std::min(dutyCyclePeriod_, iterationNum_); + updateDutyCyclesHelper_(overlapDutyCycles_, newOverlap, period); } @@ -734,7 +744,19 @@ void SpatialPooler::updateDutyCyclesHelper_(vector &dutyCycles, } -void SpatialPooler::updateBoostFactors_() { +void SpatialPooler::updateBoostFactors_(const SDR& active) { + if(boostStrength_ < htm::Epsilon) return; //skip for disabled boosting + + /** + Updates the duty cycles for each column. The ACTIVITY duty cycles is + a moving average of the frequency of activation for each column. + + @param active A SDR of active columns which survived inhibition + @param period + */ + const UInt period = std::min(dutyCyclePeriod_, iterationNum_); + updateDutyCyclesHelper_(activeDutyCycles_, active, period); + if (globalInhibition_) { updateBoostFactorsGlobal_(); } else { @@ -749,15 +771,14 @@ void applyBoosting_(const UInt i, const Real boost, vector& output) { if(boost < htm::Epsilon) return; //skip for disabled boosting - output[i] = exp((targetDensity - actualDensity[i]) * boost); //TODO doc this code + output[i] = exp((targetDensity - actualDensity[i]) * boost); //exponential boosting, default for Numenta + //output[i] = log(actualDensity[i]) / log(targetDensity); //log boosting } void SpatialPooler::updateBoostFactorsGlobal_() { - const Real targetDensity = localAreaDensity_; - for (UInt i = 0; i < numColumns_; ++i) { - applyBoosting_(i, targetDensity, activeDutyCycles_, boostStrength_, boostFactors_); + applyBoosting_(i, localAreaDensity_, activeDutyCycles_, boostStrength_, boostFactors_); } } @@ -797,9 +818,7 @@ void SpatialPooler::inhibitColumns_(const vector &overlaps, vector &activeColumns) const { const Real density = localAreaDensity_; - if (globalInhibition_ || - inhibitionRadius_ > - *max_element(columnDimensions_.begin(), columnDimensions_.end())) { + if (globalInhibition_) { inhibitColumnsGlobal_(overlaps, density, activeColumns); } else { inhibitColumnsLocal_(overlaps, density, activeColumns); @@ -808,30 +827,38 @@ void SpatialPooler::inhibitColumns_(const vector &overlaps, void SpatialPooler::inhibitColumnsGlobal_(const vector &overlaps, - Real density, - vector &activeColumns) const { + const Real density, + SDR_sparse_t &activeColumns) const { NTA_ASSERT(!overlaps.empty()); NTA_ASSERT(density > 0.0f && density <= 1.0f); activeColumns.clear(); - const UInt numDesired = (UInt)(density * numColumns_); + const UInt numDesired = static_cast(density * numColumns_); NTA_CHECK(numDesired > 0) << "Not enough columns (" << numColumns_ << ") " << "for desired density (" << density << ")."; // Sort the columns by the amount of overlap. First make a list of all of the // column indexes. activeColumns.reserve(numColumns_); + + int same_overlap = 0; for(UInt i = 0; i < numColumns_; i++) activeColumns.push_back(i); // Compare the column indexes by their overlap. - auto compare = [&overlaps](const UInt &a, const UInt &b) -> bool - {return (overlaps[a] == overlaps[b]) ? a > b : overlaps[a] > overlaps[b];}; //for determinism if overlaps match (tieBreaker does not solve that), - //otherwise we'd return just `return overlaps[a] > overlaps[b]`. + auto compare = [&overlaps, &same_overlap](const UInt &a, const UInt &b) -> bool + { + if (overlaps[a] == overlaps[b]) { + same_overlap++; + return a > b; //but we also need this for deterministic results + } else { + return overlaps[a] > overlaps[b]; //this is the main "sort columns by overlaps" + } + }; // Do a partial sort to divide the winners from the losers. This sort is // faster than a regular sort because it stops after it partitions the // elements about the Nth element, with all elements on their correct side of // the Nth element. - std::nth_element( + std::nth_element( activeColumns.begin(), activeColumns.begin() + numDesired, activeColumns.end(), @@ -844,16 +871,18 @@ void SpatialPooler::inhibitColumnsGlobal_(const vector &overlaps, while( !activeColumns.empty() && overlaps[activeColumns.back()] < stimulusThreshold_) activeColumns.pop_back(); + //FIXME not numDesired } void SpatialPooler::inhibitColumnsLocal_(const vector &overlaps, - Real density, + const Real density, vector &activeColumns) const { + activeColumns.clear(); // Tie-breaking: when overlaps are equal, columns that have already been - // selected are treated as "bigger". + // selected are treated as "bigger". //TODO move this idea to the sort/comparison logic vector activeColumnsDense(numColumns_, false); for (UInt column = 0; column < numColumns_; column++) { @@ -903,8 +932,8 @@ void SpatialPooler::inhibitColumnsLocal_(const vector &overlaps, } } - const UInt numActive = (UInt)(0.5f + (density * (numNeighbors + 1))); - if (numBigger < numActive) { + const UInt numDesired = static_cast(std::ceil(density * (numNeighbors + 1))); + if (numBigger < numDesired) { activeColumns.push_back(column); activeColumnsDense[column] = true; } diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index fffd29ff87..df61940e39 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -884,7 +884,8 @@ class SpatialPooler : public Serializable @param activeColumns an int array containing the indices of the active columns. */ - void inhibitColumnsGlobal_(const vector &overlaps, Real density, + void inhibitColumnsGlobal_(const vector &overlaps, + const Real density, vector &activeColumns) const; /** @@ -913,7 +914,8 @@ class SpatialPooler : public Serializable @param activeColumns an int array containing the indices of the active columns. */ - void inhibitColumnsLocal_(const vector &overlaps, Real density, + void inhibitColumnsLocal_(const vector &overlaps, + const Real density, vector &activeColumns) const; /** @@ -941,7 +943,7 @@ class SpatialPooler : public Serializable overlap duty cycle that drops too much below those of their peers. The permanence values for such columns are increased. */ - void bumpUpWeakColumns_(); + void bumpUpWeakColumns_(const std::vector& overlaps); /** Update the inhibition radius. The inhibition radius is a meausre of the @@ -950,10 +952,12 @@ class SpatialPooler : public Serializable determine this quantity by first figuring out how many *inputs* a column is connected to, and then multiplying it by the total number of columns that exist for each input. For multiple dimension the aforementioned - calculations are averaged over all dimensions of inputs and columns. This - value is meaningless if global inhibition is enabled. + calculations are averaged over all dimensions of inputs and columns. + This value is meaningless if global inhibition is enabled. + + @return update value for `inhibitionRadius_` */ - void updateInhibitionRadius_(); + UInt updateInhibitionRadius_() const; /** REturns the average number of columns per input, taking into account the @@ -1030,21 +1034,18 @@ class SpatialPooler : public Serializable const SDR &newValues, const UInt period); - /** - Updates the duty cycles for each column. The OVERLAP duty cycle is a moving - average of the number of inputs which overlapped with the each column. The - ACTIVITY duty cycles is a moving average of the frequency of activation for - each column. - @param overlaps an int vector containing the overlap score for each - column. The overlap score for a column is defined as the number of synapses in - a "connected state" (connected synapses) that are connected to input bits - which are turned on. + /** + Updates the duty cycles for each column. The OVERLAP duty cycle is a moving + average of the number of inputs which overlapped with the each column. - @param activeArray An int array containing the indices of the active columns, - the sprase set of columns which survived inhibition + @param overlaps an int vector containing the overlap score for each + column. The overlap score for a column is defined as the number of synapses in + a "connected state" (connected synapses) that are connected to input bits + which are turned on. */ - void updateDutyCycles_(const vector &overlaps, SDR &active); + void updateDutyCyclesOverlaps_(const vector& overlaps); + /** Update the boost factors for all columns. The boost factors are used to @@ -1074,8 +1075,10 @@ class SpatialPooler : public Serializable | targetDensity @endverbatim + + @param active SDR with active columns from compute(), after inhibition & learning */ - void updateBoostFactors_(); + void updateBoostFactors_(const SDR& active); /** Update boost factors when local inhibition is enabled. In this case, diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 0b02ffdf0f..e396e86b3d 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -232,7 +232,9 @@ void setup(SpatialPooler& sp, UInt numIn, UInt numCols, Real sparsity = 0.5f) { TEST(SpatialPoolerTest, testUpdateInhibitionRadius) { SpatialPooler sp; vector colDim, inputDim; - colDim.push_back(57); + + //test for global inhibition, this is trivial + colDim.push_back(57); //max SP dimension colDim.push_back(31); colDim.push_back(2); inputDim.push_back(1); @@ -241,7 +243,7 @@ TEST(SpatialPoolerTest, testUpdateInhibitionRadius) { EXPECT_NO_THROW(sp.initialize(inputDim, colDim)); sp.setGlobalInhibition(true); - ASSERT_EQ(sp.getInhibitionRadius(), 57u); + ASSERT_EQ(sp.getInhibitionRadius(), 57u) << "In global inh radius must match max dimension"; //test 2 - local inhibition radius colDim.clear(); @@ -259,14 +261,11 @@ TEST(SpatialPoolerTest, testUpdateInhibitionRadius) { Real permArr[] = {1, 1, 1}; sp.setPermanence(i, permArr); } - UInt trueInhibitionRadius = 6; // ((3 * 4) - 1)/2 => round up - sp.updateInhibitionRadius_(); - ASSERT_EQ(trueInhibitionRadius, sp.getInhibitionRadius()); + auto updated = sp.updateInhibitionRadius_(); + ASSERT_EQ(6u, updated); //test 3 - colDim.clear(); - inputDim.clear(); // avgColumnsPerInput = 1.2 // avgConnectedSpanForColumn = 0.5 numInputs = 5; @@ -282,14 +281,10 @@ TEST(SpatialPoolerTest, testUpdateInhibitionRadius) { } sp.setPermanence(i, permArr); } - trueInhibitionRadius = 1; - sp.updateInhibitionRadius_(); - ASSERT_EQ(trueInhibitionRadius, sp.getInhibitionRadius()); - + updated = sp.updateInhibitionRadius_(); + ASSERT_EQ(1u, updated); //test 4 - colDim.clear(); - inputDim.clear(); // avgColumnsPerInput = 2.4 // avgConnectedSpanForColumn = 2 numInputs = 5; @@ -302,12 +297,12 @@ TEST(SpatialPoolerTest, testUpdateInhibitionRadius) { Real permArr[] = {1, 1, 0, 0, 0}; sp.setPermanence(i, permArr); } - trueInhibitionRadius = 2; // ((2.4 * 2) - 1)/2 => round up - sp.updateInhibitionRadius_(); - ASSERT_EQ(trueInhibitionRadius, sp.getInhibitionRadius()); + updated = sp.updateInhibitionRadius_(); + ASSERT_EQ(2u, updated); } + TEST(SpatialPoolerTest, testUpdateMinDutyCycles) { SpatialPooler sp; UInt numColumns = 10; @@ -506,16 +501,14 @@ TEST(SpatialPoolerTest, testUpdateDutyCycles) { UInt numColumns = 5; setup(sp, numInputs, numColumns); vector overlaps; - SDR active({numColumns}); Real initOverlapArr1[] = {1, 1, 1, 1, 1}; sp.setOverlapDutyCycles(initOverlapArr1); UInt overlapNewVal1[] = {1, 5, 7, 0, 0}; overlaps.assign(overlapNewVal1, overlapNewVal1 + numColumns); - active.setDense(vector({0, 0, 0, 0, 0})); sp.setIterationNum(2); - sp.updateDutyCycles_(overlaps, active); + sp.updateDutyCyclesOverlaps_(overlaps); Real resultOverlapArr1[5]; sp.getOverlapDutyCycles(resultOverlapArr1); @@ -526,7 +519,7 @@ TEST(SpatialPoolerTest, testUpdateDutyCycles) { sp.setOverlapDutyCycles(initOverlapArr1); sp.setIterationNum(2000); sp.setUpdatePeriod(1000); - sp.updateDutyCycles_(overlaps, active); + sp.updateDutyCyclesOverlaps_(overlaps); Real resultOverlapArr2[5]; sp.getOverlapDutyCycles(resultOverlapArr2); @@ -912,12 +905,14 @@ TEST(SpatialPoolerTest, testBumpUpWeakColumns) { sp.setPermanence(i, permArr[i]); } - sp.bumpUpWeakColumns_(); + SDR in({numInputs}); + SDR out({numColumns}); + auto overlaps = sp.compute(in, true, out); //calls internally sp.bumpUpWeakColumns_() for (UInt i = 0; i < numColumns; i++) { const auto& perm = sp.getPermanence(i); for(UInt z = 0; z < numInputs; z++) - ASSERT_FLOAT_EQ( truePermArr[i][z], perm[z] ); + ASSERT_NEAR( truePermArr[i][z], perm[z], 0.0001f ); } } @@ -960,6 +955,8 @@ TEST(SpatialPoolerTest, testUpdateDutyCyclesHelper) { TEST(SpatialPoolerTest, testUpdateBoostFactors) { SpatialPooler sp; setup(sp, 5, 6); + SDR dummy({6}); + dummy.setDense(SDR_dense_t{1,1,1,1,1,1}); //"full" SDR Real32 initActiveDutyCycles1[] = {0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f}; Real32 initBoostFactors1[] = {0, 0, 0, 0, 0, 0}; @@ -969,7 +966,7 @@ TEST(SpatialPoolerTest, testUpdateBoostFactors) { sp.setBoostStrength(10); sp.setBoostFactors(initBoostFactors1); sp.setActiveDutyCycles(initActiveDutyCycles1); - sp.updateBoostFactors_(); + sp.updateBoostFactors_(dummy); sp.getBoostFactors(resultBoostFactors1.data()); ASSERT_TRUE(check_vector_eq(trueBoostFactors1, resultBoostFactors1)); @@ -982,7 +979,7 @@ TEST(SpatialPoolerTest, testUpdateBoostFactors) { sp.setBoostStrength(10); sp.setBoostFactors(initBoostFactors2); sp.setActiveDutyCycles(initActiveDutyCycles2); - sp.updateBoostFactors_(); + sp.updateBoostFactors_(dummy); sp.getBoostFactors(resultBoostFactors2.data()); ASSERT_TRUE(check_vector_eq(trueBoostFactors2, resultBoostFactors2)); @@ -998,7 +995,7 @@ TEST(SpatialPoolerTest, testUpdateBoostFactors) { sp.setInhibitionRadius(5); sp.setBoostFactors(initBoostFactors3); sp.setActiveDutyCycles(initActiveDutyCycles3); - sp.updateBoostFactors_(); + sp.updateBoostFactors_(dummy); sp.getBoostFactors(resultBoostFactors3.data()); ASSERT_TRUE(check_vector_eq(trueBoostFactors3, resultBoostFactors3)); @@ -1013,10 +1010,10 @@ TEST(SpatialPoolerTest, testUpdateBoostFactors) { sp.setInhibitionRadius(3); sp.setBoostFactors(initBoostFactors4); sp.setActiveDutyCycles(initActiveDutyCycles4); - sp.updateBoostFactors_(); + sp.updateBoostFactors_(dummy); sp.getBoostFactors(resultBoostFactors4.data()); - ASSERT_TRUE(check_vector_eq(trueBoostFactors3, resultBoostFactors3)); + ASSERT_TRUE(check_vector_eq(trueBoostFactors4, resultBoostFactors4)); } @@ -1279,7 +1276,7 @@ TEST(SpatialPoolerTest, testInhibitColumnsLocal) { sp.setInhibitionRadius(inhibitionRadius); sp.inhibitColumnsLocal_(overlaps, density, active); - ASSERT_TRUE(active.size() == 4); + ASSERT_EQ(active.size(), (Size)4); ASSERT_TRUE(check_vector_eq(trueActive3, active)); }