diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index e38ccee3c2..5270c1e3c2 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -379,7 +379,7 @@ void Connections::reset() currentUpdates_.clear(); } -vector Connections::computeActivity(const vector &activePresynapticCells, const bool learn) { +vector Connections::computeActivity(const vector &activePresynapticCells, const bool learn) const { vector numActiveConnectedSynapsesForSegment(segments_.size(), 0); if(learn) iteration_++; @@ -406,7 +406,7 @@ vector Connections::computeActivity(const vector &activePre vector Connections::computeActivity( vector &numActivePotentialSynapsesForSegment, const vector &activePresynapticCells, - const bool learn) { + const bool learn) const { NTA_ASSERT(numActivePotentialSynapsesForSegment.size() == segments_.size()); // Iterate through all connected synapses. diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 745bf93286..e0863ad6c8 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -441,10 +441,10 @@ class Connections : public Serializable std::vector computeActivity( std::vector &numActivePotentialSynapsesForSegment, const std::vector &activePresynapticCells, - const bool learn = true); + const bool learn = true) const; std::vector computeActivity(const std::vector &activePresynapticCells, - const bool learn = true); + const bool learn = true) const; /** * The primary method in charge of learning. Adapts the permanence values of @@ -730,8 +730,8 @@ class Connections : public Serializable // These three members should be used when working with highly correlated // data. The vectors store the permanence changes made by adaptSegment. bool timeseries_; - std::vector previousUpdates_; - std::vector currentUpdates_; + mutable std::vector previousUpdates_; //FIXME use lock or async vector (from folly?) + mutable std::vector currentUpdates_; //for prune statistics Synapse prunedSyns_ = 0; //how many synapses have been removed? diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index d529ba2d3f..a0e0e628b4 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -167,18 +167,6 @@ void SpatialPooler::setBoostStrength(Real boostStrength) { boostStrength_ = boostStrength; } -UInt SpatialPooler::getIterationNum() const { return iterationNum_; } - -void SpatialPooler::setIterationNum(UInt iterationNum) { - iterationNum_ = iterationNum; -} - -UInt SpatialPooler::getIterationLearnNum() const { return iterationLearnNum_; } - -void SpatialPooler::setIterationLearnNum(UInt iterationLearnNum) { - iterationLearnNum_ = iterationLearnNum; -} - UInt SpatialPooler::getSpVerbosity() const { return spVerbosity_; } void SpatialPooler::setSpVerbosity(UInt spVerbosity) { @@ -359,10 +347,6 @@ void SpatialPooler::getConnectedCounts(UInt connectedCounts[]) const { } -const vector &SpatialPooler::getBoostedOverlaps() const { - return boostedOverlaps_; -} - void SpatialPooler::initialize( const vector& inputDimensions, const vector& columnDimensions, @@ -422,14 +406,11 @@ void SpatialPooler::initialize( wrapAround_ = wrapAround; updatePeriod_ = 50u; initConnectedPct_ = 0.5f; //FIXME make SP's param, and much lower 0.01 https://discourse.numenta.org/t/spatial-pooler-implementation-for-mnist-dataset/2317/25?u=breznak - iterationNum_ = 0u; - iterationLearnNum_ = 0u; overlapDutyCycles_.assign(numColumns_, 0); //TODO make all these sparse or rm to reduce footprint activeDutyCycles_.assign(numColumns_, 0); minOverlapDutyCycles_.assign(numColumns_, 0.0); boostFactors_.assign(numColumns_, 1.0); //1 is neutral value for boosting - boostedOverlaps_.resize(numColumns_); inhibitionRadius_ = 0; @@ -457,17 +438,16 @@ void SpatialPooler::initialize( } -const vector SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) { +vector SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) const { input.reshape( inputDimensions_ ); active.reshape( columnDimensions_ ); - updateBookeepingVars_(learn); + //now calculate overlaps of input and input synapses const auto& overlaps = connections_.computeActivity(input.getSparse(), learn); - - boostOverlaps_(overlaps, boostedOverlaps_); + const auto& boostedOverlaps = getBoostedOverlaps(overlaps); auto &activeVector = active.getSparse(); - inhibitColumns_(boostedOverlaps_, activeVector); + 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. @@ -484,20 +464,19 @@ const vector SpatialPooler::compute(const SDR &input, const bool lea updateMinDutyCycles_(); } } - return overlaps; } -void SpatialPooler::boostOverlaps_(const vector &overlaps, //TODO use Eigen sparse vector here - vector &boosted) const { +vector SpatialPooler::getBoostedOverlaps(const vector &overlaps) const { //TODO use Eigen sparse vector here + vector boosted(overlaps.begin(), overlaps.end()); //copy if(boostStrength_ < htm::Epsilon) { //boost ~ 0.0, we can skip these computations, just copy the data - boosted.assign(overlaps.begin(), overlaps.end()); - return; + return boosted; } for (UInt i = 0; i < numColumns_; i++) { - boosted[i] = overlaps[i] * boostFactors_[i]; + boosted[i] = overlaps[i] * boostFactors_[i]; //TODO vectorize boosted * factors } + return boosted; } @@ -646,7 +625,7 @@ void SpatialPooler::updateDutyCycles_(const vector &overlaps, } newOverlap.setSparse( overlapsSparseVec ); - const UInt period = std::min(dutyCyclePeriod_, iterationNum_); + const UInt period = dutyCyclePeriod_; updateDutyCyclesHelper_(overlapDutyCycles_, newOverlap, period); updateDutyCyclesHelper_(activeDutyCycles_, active, period); @@ -792,14 +771,6 @@ void SpatialPooler::updateBoostFactorsLocal_() { } -void SpatialPooler::updateBookeepingVars_(bool learn) { - iterationNum_++; - if (learn) { - iterationLearnNum_++; - } -} - - void SpatialPooler::inhibitColumns_(const vector &overlaps, vector &activeColumns) const { const Real density = localAreaDensity_; @@ -909,7 +880,7 @@ void SpatialPooler::inhibitColumnsLocal_(const vector &overlaps, bool SpatialPooler::isUpdateRound_() const { - return (iterationNum_ % updatePeriod_) == 0; + return (rng_.getReal64() < 1.0/updatePeriod_); //approx every updatePeriod steps } namespace htm { @@ -927,9 +898,7 @@ std::ostream& operator<< (std::ostream& stream, const SpatialPooler& self) // Print the main SP creation parameters void SpatialPooler::printParameters(std::ostream& out) const { - out << "------------CPP SpatialPooler Parameters ------------------\n"; - out << "iterationNum = " << getIterationNum() << std::endl - << "iterationLearnNum = " << getIterationLearnNum() << std::endl + out << "------------CPP SpatialPooler Parameters ------------------\n" << "numInputs = " << getNumInputs() << std::endl << "numColumns = " << getNumColumns() << std::endl << std::endl @@ -987,8 +956,6 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ if (inhibitionRadius_ != o.inhibitionRadius_) return false; if (dutyCyclePeriod_ != o.dutyCyclePeriod_) return false; if (boostStrength_ != o.boostStrength_) return false; - if (iterationNum_ != o.iterationNum_) return false; - if (iterationLearnNum_ != o.iterationLearnNum_) return false; if (spVerbosity_ != o.spVerbosity_) return false; if (updatePeriod_ != o.updatePeriod_) return false; if (synPermInactiveDec_ != o.synPermInactiveDec_) return false; diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index b27eed0226..a7b137419f 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -230,18 +230,21 @@ class SpatialPooler : public Serializable will be removed from activeVector. TODO: we may want to keep boosting on even when learning is off. - @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). - - @return overlap - 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. - Replaces: SP.calculateOverlaps_(), SP.getOverlaps() + @return param `active` is filled with active output columns. + + @return overlaps dense vector of number of connected synapses for each column `active`. + Determines each column's overlap with the current input vector. + The overlap of a column is the number of synapses for that column + that are connected (permanence value is greater than + 'synPermConnected') to input bits which are turned on. + 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. + This replaces deprecated method `SP.getOverlaps()` */ - virtual const vector compute(const SDR &input, const bool learn, SDR &active); + virtual std::vector compute(const SDR &input, + const bool learn, + SDR &active) const; /** @@ -467,33 +470,6 @@ class SpatialPooler : public Serializable */ void setBoostStrength(Real boostStrength); - /** - Returns the iteration number. - - @returns integer number of iteration number. - */ - UInt getIterationNum() const; - - /** - Sets the iteration number. - - @param iterationNum integer number of iteration number. - */ - void setIterationNum(UInt iterationNum); - - /** - Returns the learning iteration number. - - @returns integer of the learning iteration number. - */ - UInt getIterationLearnNum() const; - - /** - Sets the learning iteration number. - - @param iterationLearnNum integer of learning iteration number. - */ - void setIterationLearnNum(UInt iterationLearnNum); /** Returns the verbosity level. @@ -744,9 +720,19 @@ class SpatialPooler : public Serializable /** - Returns the boosted overlap score for each column. + * Apply boosting (see param @ref `boostStrength` in constructor) to the overlap. + * + * @param overlaps vector returns from @ref `compute()`. Signifies overlap of the SP with + * the input, aka. activation. + * + * @deprecate replaces deprecated `SP.getBoostedOverlaps_()`; now use + * `auto over = compute(..); auto boosted = sp.getBoostedOverlaps(over);` + * Also replaces `SP.boostOverlaps_()` + * + * @return boosted overlaps for each input. + * */ - const vector &getBoostedOverlaps() const; + const vector& getBoostedOverlaps(const vector overlaps) const; /////////////////////////////////////////////////////////// // @@ -754,8 +740,6 @@ class SpatialPooler : public Serializable // NOT part of the public API - void boostOverlaps_(const vector &overlaps, vector &boostedOverlaps) const; - /** Maps a column to its respective input index, keeping to the topology of the region. It takes the index of the column as an argument and determines @@ -853,6 +837,10 @@ class SpatialPooler : public Serializable void clip_(vector &perm) const; + void raisePermanencesToThreshold_(vector &perm, + const vector &potential) const; + + /** Performs inhibition. This method calculates the necessary values needed to actually perform inhibition and then delegates the task of picking the diff --git a/src/htm/utils/Random.cpp b/src/htm/utils/Random.cpp index 83d3933c13..6b6e9919a9 100644 --- a/src/htm/utils/Random.cpp +++ b/src/htm/utils/Random.cpp @@ -57,37 +57,7 @@ Random::Random(UInt64 seed) { steps_ = 0; } - namespace htm { -std::ostream &operator<<(std::ostream &outStream, const Random &r) { - outStream << "random-v2" << " "; - outStream << r.seed_ << " "; - outStream << r.steps_ << " "; - outStream << "endrandom-v2" << " "; - return outStream; -} - - -std::istream &operator>>(std::istream &inStream, Random &r) { - std::string version; - - inStream >> version; - NTA_CHECK(version == "random-v2") << "Random() deserializer -- found unexpected version string '" - << version << "'"; - inStream >> r.seed_; - r.gen.seed(static_cast(r.seed_)); //reseed - inStream >> r.steps_; - r.gen.discard(r.steps_); //advance n steps - //FIXME we could de/serialize directly RNG gen, it should be multi-platform according to standard, - //but on OSX CI it wasn't (25/11/2018). So "hacking" the above instead. - std::string endtag; - inStream >> endtag; - NTA_CHECK(endtag == "endrandom-v2") << "Random() deserializer -- found unexpected end tag '" << endtag << "'"; - inStream.ignore(1); - - return inStream; -} - // helper function for seeding RNGs across the plugin barrier UInt32 GetRandomSeed() { return htm::Random().getUInt32(); diff --git a/src/htm/utils/Random.hpp b/src/htm/utils/Random.hpp index 03c79c00e3..bacebc218e 100644 --- a/src/htm/utils/Random.hpp +++ b/src/htm/utils/Random.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -94,7 +95,7 @@ class Random : public Serializable { //main API methods: /** return a value (uniformly) distributed between [0,max) */ - inline UInt32 getUInt32(const UInt32 max = MAX32) { + inline UInt32 getUInt32(const UInt32 max = MAX32) const { NTA_ASSERT(max > 0); steps_++; return gen() % max; //uniform_int_distribution(gen) replaced, as is not same on all platforms! @@ -103,9 +104,10 @@ class Random : public Serializable { /** return a double uniformly distributed on [0,1.0) * May not be cross-platform (but currently is to our experience) */ - inline Real64 getReal64() { + inline Real64 getReal64() const { //FIXME may break with parallel steps_++; - return gen() / static_cast(max()); + const auto randValue = gen(); + return randValue / static_cast(max()); } // populate choices with a random selection of nChoices elements from @@ -113,7 +115,7 @@ class Random : public Serializable { // templated functions must be defined in header //TODO replace with std::sample in c++17 : https://en.cppreference.com/w/cpp/algorithm/sample template - std::vector sample(const std::vector& population, UInt nChoices) { + std::vector sample(const std::vector& population, const UInt nChoices) const { if (nChoices == 0) { return std::vector{}; } @@ -128,7 +130,7 @@ class Random : public Serializable { /** * return random from range [from, to) */ - Real realRange(Real from, Real to) { + Real realRange(const Real from, const Real to) const { NTA_ASSERT(from <= to); const Real split = to - from; return from + static_cast(split * getReal64()); @@ -137,26 +139,28 @@ class Random : public Serializable { // randomly shuffle the elements template - void shuffle(RandomAccessIterator first, RandomAccessIterator last) { + void shuffle(RandomAccessIterator first, RandomAccessIterator last) const { //std::shuffle(first, last, gen); //not platform independent results :( platform_independent_shuffle(first, last); } + // for STL compatibility - UInt32 operator()(UInt32 n = MAX32) { + UInt32 operator()(const UInt32 n = MAX32) const { NTA_ASSERT(n > 0); return getUInt32(n); } + // normally used for debugging only UInt64 getSeed() const { return seed_; } // for STL - typedef unsigned long argument_type; - typedef unsigned long result_type; + using argument_type = unsigned long; + using result_type = unsigned long; result_type max() const { return gen.max(); } result_type min() const { return gen.min(); } - static const UInt32 MAX32 = std::numeric_limits::max(); + static const constexpr UInt32 MAX32 = std::numeric_limits::max(); protected: friend class RandomTest; @@ -165,9 +169,9 @@ class Random : public Serializable { friend UInt32 GetRandomSeed(); private: UInt64 seed_; - UInt64 steps_ = 0; //step counter, used in serialization. It is important that steps_ is in sync with number of + mutable UInt64 steps_ = 0; //step counter, used in serialization. It is important that steps_ is in sync with number of // calls to RNG - std::mt19937 gen; //Standard mersenne_twister_engine 64bit seeded with seed_ + mutable std::mt19937 gen; //Standard mersenne_twister_engine 64bit seeded with seed_ // std::random_device rd; //HW random for random seed cases, undeterministic -> problems with op= and copy-constructor, therefore disabled // our reimpementation of std::shuffle, @@ -175,13 +179,11 @@ class Random : public Serializable { // resuting in differences between impementations (OS, stdlib,...) :( // https://en.cppreference.com/w/cpp/algorithm/random_shuffle template - void platform_independent_shuffle(RandomIt first, RandomIt last) + void platform_independent_shuffle(RandomIt first, RandomIt last) const { - typename std::iterator_traits::difference_type i, n; - n = last - first; - for (i = n-1; i > 0; --i) { - using std::swap; - swap(first[i], first[this->getUInt32(static_cast(i+1))]); + const auto n = last - first; + for (auto i = n-1; i > 0; --i) { + std::swap(first[i], first[this->getUInt32(static_cast(i+1))]); } } }; diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 667a40dd3b..8fbe82d732 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -135,8 +135,6 @@ void check_spatial_eq(const SpatialPooler& sp1, const SpatialPooler& sp2) { ASSERT_TRUE(sp1.getStimulusThreshold() == sp2.getStimulusThreshold()); ASSERT_TRUE(sp1.getDutyCyclePeriod() == sp2.getDutyCyclePeriod()); ASSERT_TRUE(almost_eq(sp1.getBoostStrength(), sp2.getBoostStrength())); - ASSERT_TRUE(sp1.getIterationNum() == sp2.getIterationNum()); - ASSERT_TRUE(sp1.getIterationLearnNum() == sp2.getIterationLearnNum()); ASSERT_TRUE(sp1.getSpVerbosity() == sp2.getSpVerbosity()); ASSERT_TRUE(sp1.getWrapAround() == sp2.getWrapAround()); ASSERT_TRUE(sp1.getUpdatePeriod() == sp2.getUpdatePeriod()); @@ -521,7 +519,6 @@ TEST(SpatialPoolerTest, testUpdateDutyCycles) { overlaps.assign(overlapNewVal1, overlapNewVal1 + numColumns); active.setDense(vector({0, 0, 0, 0, 0})); - sp.setIterationNum(2); sp.updateDutyCycles_(overlaps, active); Real resultOverlapArr1[5]; @@ -531,7 +528,6 @@ TEST(SpatialPoolerTest, testUpdateDutyCycles) { ASSERT_TRUE(check_vector_eq(resultOverlapArr1, trueOverlapArr1, numColumns)); sp.setOverlapDutyCycles(initOverlapArr1); - sp.setIterationNum(2000); sp.setUpdatePeriod(1000); sp.updateDutyCycles_(overlaps, active); @@ -1032,20 +1028,6 @@ TEST(SpatialPoolerTest, testUpdateBoostFactors) { } -TEST(SpatialPoolerTest, testUpdateBookeepingVars) { - SpatialPooler sp; - sp.setIterationNum(5); - sp.setIterationLearnNum(3); - sp.updateBookeepingVars_(true); - ASSERT_TRUE(6 == sp.getIterationNum()); - ASSERT_TRUE(4 == sp.getIterationLearnNum()); - - sp.updateBookeepingVars_(false); - ASSERT_TRUE(7 == sp.getIterationNum()); - ASSERT_TRUE(4 == sp.getIterationLearnNum()); -} - - TEST(SpatialPoolerTest, testCalculateOverlap) { SpatialPooler sp; UInt numInputs = 10; @@ -1084,6 +1066,7 @@ TEST(SpatialPoolerTest, testCalculateOverlap) { SDR input({numInputs}); SDR output(sp.getColumnDimensions()); input.setDense(SDR_dense_t(inputs[i], inputs[i] + numInputs)); + //former SP.calculateOverlap_() const auto overlaps = sp.compute(input, false, output); ASSERT_TRUE(check_vector_eq(trueOverlaps[i], overlaps)); } @@ -1362,31 +1345,13 @@ TEST(SpatialPoolerTest, testInhibitColumnsLocal) { TEST(SpatialPoolerTest, testIsUpdateRound) { SpatialPooler sp; - sp.setUpdatePeriod(50); - sp.setIterationNum(1); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(39); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(50); - ASSERT_TRUE(sp.isUpdateRound_()); - sp.setIterationNum(1009); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(1250); - ASSERT_TRUE(sp.isUpdateRound_()); - - sp.setUpdatePeriod(125); - sp.setIterationNum(0); - ASSERT_TRUE(sp.isUpdateRound_()); - sp.setIterationNum(200); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(249); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(1330); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(1249); - ASSERT_TRUE(!sp.isUpdateRound_()); - sp.setIterationNum(1375); - ASSERT_TRUE(sp.isUpdateRound_()); + sp.setUpdatePeriod(5); + UInt count = 0; + for (int i=0; i< 100; i++) { + if(sp.isUpdateRound_()) count++; //should be every 50th step, we use probability, so almost every 1/50 chance -> approx 20x in 100. + } + ASSERT_TRUE(sp.getUpdatePeriod()-2 <= count and + sp.getUpdatePeriod()+2 >= count); //can fail, but should not too ofthen } @@ -1698,7 +1663,7 @@ TEST(SpatialPoolerTest, getOverlaps) { EXPECT_EQ(expectedOverlaps, overlaps); //boosted overlaps, but boost strength=0.0 - const auto& boostedOverlaps = sp.getBoostedOverlaps(); + const auto& boostedOverlaps = sp.getBoostedOverlaps(overlaps); const vector 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 "; @@ -1708,9 +1673,9 @@ TEST(SpatialPoolerTest, getOverlaps) { sp.setBoostStrength(2.0f); activeColumns.setDense(vector{0, 0, 0}); - sp.compute(input, true, activeColumns); + overlaps = sp.compute(input, true, activeColumns); - const auto& boostedOverlaps2 = sp.getBoostedOverlaps(); + const auto& boostedOverlaps2 = sp.getBoostedOverlaps(overlaps); const vector expectedBoostedOverlaps2 = {0.0f, 6.0f, 15.0f}; EXPECT_EQ(expectedBoostedOverlaps2, boostedOverlaps2) << "SP with boost strength " << sp.getBoostStrength() << " must change boosting "; }