diff --git a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp index 12093ee6b9..436ef07aba 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp @@ -49,10 +49,14 @@ R"(Compatibility Warning: This classes API is unstable and may change without wa [](const Connections &self) { return self.getConnectedThreshold(); }); py_Connections.def("createSegment", &Connections::createSegment, - py::arg("cell")); + py::arg("cell"), + py::arg("maxSegmentsPerCell") = 0 + ); py_Connections.def("destroySegment", &Connections::destroySegment); + py_Connections.def("iteration", &Connections::iteration); + py_Connections.def("createSynapse", &Connections::createSynapse, py::arg("segment"), py::arg("presynaticCell"), @@ -93,16 +97,18 @@ R"(Compatibility Warning: This classes API is unstable and may change without wa py_Connections.def("reset", &Connections::reset); py_Connections.def("computeActivity", - [](Connections &self, SDR &activePresynapticCells) { + [](Connections &self, SDR &activePresynapticCells, bool learn=true) { // Allocate buffer to return & make a python destructor object for it. auto activeConnectedSynapses = new std::vector( self.segmentFlatListLength(), 0u ); auto destructor = py::capsule( activeConnectedSynapses, [](void *dataPtr) { delete reinterpret_cast*>(dataPtr); }); - // Call the C++ method. - self.computeActivity(*activeConnectedSynapses, activePresynapticCells.getSparse()); - // Wrap vector in numpy array. + + // Call the C++ method. + self.computeActivity(*activeConnectedSynapses, activePresynapticCells.getSparse(), learn); + + // Wrap vector in numpy array. return py::array(activeConnectedSynapses->size(), activeConnectedSynapses->data(), destructor); @@ -110,7 +116,7 @@ R"(Compatibility Warning: This classes API is unstable and may change without wa R"(Returns numActiveConnectedSynapsesForSegment)"); py_Connections.def("computeActivityFull", - [](Connections &self, SDR &activePresynapticCells) { + [](Connections &self, SDR &activePresynapticCells, bool learn=true) { // Allocate buffer to return & make a python destructor object for it. auto activeConnectedSynapses = new std::vector( self.segmentFlatListLength(), 0u ); @@ -123,9 +129,13 @@ R"(Returns numActiveConnectedSynapsesForSegment)"); auto potentialDestructor = py::capsule( activePotentialSynapses, [](void *dataPtr) { delete reinterpret_cast*>(dataPtr); }); - // Call the C++ method. - self.computeActivity(*activeConnectedSynapses, *activePotentialSynapses, - activePresynapticCells.getSparse()); + + // Call the C++ method. + self.computeActivity(*activeConnectedSynapses, + *activePotentialSynapses, + activePresynapticCells.getSparse(), + learn); + // Wrap vector in numpy array. return py::make_tuple( py::array(activeConnectedSynapses->size(), diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index 5d65c2b693..757f691ed5 100644 --- a/src/examples/hotgym/HelloSPTP.cpp +++ b/src/examples/hotgym/HelloSPTP.cpp @@ -29,6 +29,7 @@ #include "htm/types/Sdr.hpp" #include "htm/utils/Random.hpp" #include "htm/utils/MovingAverage.hpp" +#include "htm/utils/SdrMetrics.hpp" namespace examples { @@ -79,6 +80,13 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool SDR outTM(spGlobal.getColumnDimensions()); Real an = 0.0f, anLikely = 0.0f; //for anomaly: MovingAverage avgAnom10(1000); //chose the window large enough so there's (some) periodicity in the patter, so TM can learn something + + //metrics + Metrics statsInput(input, 1000); + Metrics statsSPlocal(outSPlocal, 1000); + Metrics statsSPglobal(outSPglobal, 1000); + Metrics statsTM(outTM, 1000); + /* * For example: fn = sin(x) -> periodic >= 2Pi ~ 6.3 && x+=0.01 -> 630 steps to 1st period -> window >= 630 */ @@ -147,6 +155,17 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool if (e == EPOCHS - 1) { tAll.stop(); + //print connections stats + cout << "\nInput :\n" << statsInput + << "\nSP(local) " << spLocal.connections + << "\nSP(local) " << statsSPlocal + << "\nSP(global) " << spGlobal.connections + << "\nSP(global) " << statsSPglobal + << "\nTM " << tm.connections + << "\nTM " << statsTM + << "\n"; + + // output values cout << "Epoch = " << e << endl; cout << "Anomaly = " << an << endl; cout << "Anomaly (avg) = " << avgAnom10.getCurrentAvg() << endl; @@ -154,6 +173,8 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool cout << "SP (g)= " << outSP << endl; cout << "SP (l)= " << outSPlocal <= minPermanence); NTA_CHECK(connectedThreshold <= maxPermanence); connectedThreshold_ = connectedThreshold - htm::Epsilon; - - // Every time a segment or synapse is created, we assign it an ordinal and - // increment the nextOrdinal. Ordinals are never recycled, so they can be used - // to order segments or synapses by age. - nextSegmentOrdinal_ = 0; - nextSynapseOrdinal_ = 0; + iteration_ = 0; nextEventToken_ = 0; @@ -75,7 +70,26 @@ void Connections::unsubscribe(UInt32 token) { eventHandlers_.erase(token); } -Segment Connections::createSegment(CellIdx cell) { +Segment Connections::createSegment(const CellIdx cell, + const SegmentIdx maxSegmentsPerCell) { + + //limit number of segmets per cell. If exceeded, remove the least recently used ones. + NTA_ASSERT(maxSegmentsPerCell > 0); + while (numSegments(cell) >= maxSegmentsPerCell) { + const auto& destroyCandidates = segmentsForCell(cell); + const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { + if(dataForSegment(a).lastUsed == dataForSegment(b).lastUsed) { + return a < b; //needed for deterministic sort + } + else return dataForSegment(a).lastUsed < dataForSegment(b).lastUsed; //sort segments by access time + }; + const auto leastRecentlyUsedSegment = std::min_element(destroyCandidates.cbegin(), + destroyCandidates.cend(), compareSegmentsByLRU); + + destroySegment(*leastRecentlyUsedSegment); + } + + //proceed to create a new segment Segment segment; if (!destroyedSegments_.empty() ) { //reuse old, destroyed segs segment = destroyedSegments_.back(); @@ -84,17 +98,12 @@ Segment Connections::createSegment(CellIdx cell) { NTA_CHECK(segments_.size() < std::numeric_limits::max()) << "Add segment failed: Range of Segment (data-type) insufficinet size." << (size_t)segments_.size() << " < " << (size_t)std::numeric_limits::max(); segment = static_cast(segments_.size()); - segments_.push_back(SegmentData()); - segmentOrdinals_.push_back(0); + const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); + segments_.push_back(segmentData); } - SegmentData &segmentData = segments_[segment]; - segmentData.numConnected = 0; - segmentData.cell = cell; - CellData &cellData = cells_[cell]; - segmentOrdinals_[segment] = nextSegmentOrdinal_++; - cellData.segments.push_back(segment); + cellData.segments.push_back(segment); //assign the new segment to its mother-cell for (auto h : eventHandlers_) { h.second->onCreateSegment(segment); @@ -103,6 +112,7 @@ Segment Connections::createSegment(CellIdx cell) { return segment; } + Synapse Connections::createSynapse(Segment segment, CellIdx presynapticCell, Permanence permanence) { @@ -116,14 +126,13 @@ Synapse Connections::createSynapse(Segment segment, << synapses_.size() << " < " << (size_t)std::numeric_limits::max(); synapse = static_cast(synapses_.size()); synapses_.push_back(SynapseData()); - synapseOrdinals_.push_back(0); } // Fill in the new synapse's data SynapseData &synapseData = synapses_[synapse]; synapseData.presynapticCell = presynapticCell; synapseData.segment = segment; - synapseOrdinals_[synapse] = nextSynapseOrdinal_++; + synapseData.id = nextSynapseOrdinal_++; //TODO move these to SynData constructor // Start in disconnected state. synapseData.permanence = connectedThreshold_ - 1.0f; synapseData.presynapticMapIndex_ = @@ -144,14 +153,14 @@ Synapse Connections::createSynapse(Segment segment, return synapse; } -bool Connections::segmentExists_(Segment segment) const { +bool Connections::segmentExists_(const Segment segment) const { const SegmentData &segmentData = segments_[segment]; const vector &segmentsOnCell = cells_[segmentData.cell].segments; - return (std::find(segmentsOnCell.begin(), segmentsOnCell.end(), segment) != - segmentsOnCell.end()); + return (std::find(segmentsOnCell.cbegin(), segmentsOnCell.cend(), segment) != + segmentsOnCell.cend()); } -bool Connections::synapseExists_(Synapse synapse) const { +bool Connections::synapseExists_(const Synapse synapse) const { const SynapseData &synapseData = synapses_[synapse]; const vector &synapsesOnSegment = segments_[synapseData.segment].synapses; @@ -181,7 +190,8 @@ void Connections::removeSynapseFromPresynapticMap_( preSegments.pop_back(); } -void Connections::destroySegment(Segment segment) { + +void Connections::destroySegment(const Segment segment) { NTA_ASSERT(segmentExists_(segment)); for (auto h : eventHandlers_) { h.second->onDestroySegment(segment); @@ -196,21 +206,16 @@ void Connections::destroySegment(Segment segment) { CellData &cellData = cells_[segmentData.cell]; - const auto segmentOnCell = - std::lower_bound(cellData.segments.begin(), cellData.segments.end(), - segment, [&](Segment a, Segment b) { - return segmentOrdinals_[a] < segmentOrdinals_[b]; //TODO will this be slow if ordinals moved to SegmentData? - }); - - NTA_ASSERT(segmentOnCell != cellData.segments.end()); + const auto segmentOnCell = std::find(cellData.segments.cbegin(), cellData.segments.cend(), segment); + NTA_ASSERT(segmentOnCell != cellData.segments.cend()) << "Segment to be destroyed not found on the cell!"; NTA_ASSERT(*segmentOnCell == segment); cellData.segments.erase(segmentOnCell); - destroyedSegments_.push_back(segment); } -void Connections::destroySynapse(Synapse synapse) { + +void Connections::destroySynapse(const Synapse synapse) { NTA_ASSERT(synapseExists_(synapse)); for (auto h : eventHandlers_) { h.second->onDestroySynapse(synapse); @@ -246,9 +251,10 @@ void Connections::destroySynapse(Synapse synapse) { } const auto synapseOnSegment = - std::lower_bound(segmentData.synapses.begin(), segmentData.synapses.end(), - synapse, [&](Synapse a, Synapse b) { - return synapseOrdinals_[a] < synapseOrdinals_[b]; + std::lower_bound(segmentData.synapses.cbegin(), segmentData.synapses.cend(), + synapse, + [&](const Synapse a, const Synapse b) -> bool { + return dataForSynapse(a).id < dataForSynapse(b).id; }); NTA_ASSERT(synapseOnSegment != segmentData.synapses.end()); @@ -259,7 +265,8 @@ void Connections::destroySynapse(Synapse synapse) { destroyedSynapses_.push_back(synapse); } -void Connections::updateSynapsePermanence(Synapse synapse, + +void Connections::updateSynapsePermanence(const Synapse synapse, Permanence permanence) { permanence = std::min(permanence, maxPermanence ); permanence = std::max(permanence, minPermanence ); @@ -313,30 +320,15 @@ void Connections::updateSynapsePermanence(Synapse synapse, } } -const vector &Connections::segmentsForCell(CellIdx cell) const { - return cells_[cell].segments; -} - -Segment Connections::getSegment(CellIdx cell, SegmentIdx idx) const { - return cells_[cell].segments[idx]; -} -const vector &Connections::synapsesForSegment(Segment segment) const { - NTA_ASSERT(segment < segments_.size()) << "Segment out of bounds! " << segment; - return segments_[segment].synapses; -} - -CellIdx Connections::cellForSegment(Segment segment) const { - return segments_[segment].cell; -} - -SegmentIdx Connections::idxOnCellForSegment(Segment segment) const { +SegmentIdx Connections::idxOnCellForSegment(const Segment segment) const { const vector &segments = segmentsForCell(cellForSegment(segment)); const auto it = std::find(segments.begin(), segments.end(), segment); NTA_ASSERT(it != segments.end()); return (SegmentIdx)std::distance(segments.begin(), it); } + void Connections::mapSegmentsToCells(const Segment *segments_begin, const Segment *segments_end, CellIdx *cells_begin) const { @@ -349,28 +341,15 @@ void Connections::mapSegmentsToCells(const Segment *segments_begin, } } -Segment Connections::segmentForSynapse(Synapse synapse) const { - return synapses_[synapse].segment; -} - -const SegmentData &Connections::dataForSegment(Segment segment) const { - return segments_[segment]; -} - -const SynapseData &Connections::dataForSynapse(Synapse synapse) const { - return synapses_[synapse]; -} bool Connections::compareSegments(const Segment a, const Segment b) const { const SegmentData &aData = segments_[a]; const SegmentData &bData = segments_[b]; - if (aData.cell < bData.cell) { - return true; - } else if (bData.cell < aData.cell) { - return false; - } else { - return segmentOrdinals_[a] < segmentOrdinals_[b]; - } + // default sort by cell + if (aData.cell == bData.cell) + //fallback to ordinals: + return aData.id < bData.id; + else return aData.cell < bData.cell; } @@ -393,11 +372,14 @@ void Connections::reset() currentUpdates_.clear(); } + void Connections::computeActivity( vector &numActiveConnectedSynapsesForSegment, - const vector &activePresynapticCells) + const vector &activePresynapticCells, + bool learn) { NTA_ASSERT(numActiveConnectedSynapsesForSegment.size() == segments_.size()); + if(learn) iteration_++; if( timeseries_ ) { // Before each cycle of computation move the currentUpdates to the previous @@ -419,14 +401,16 @@ void Connections::computeActivity( void Connections::computeActivity( vector &numActiveConnectedSynapsesForSegment, vector &numActivePotentialSynapsesForSegment, - const vector &activePresynapticCells) { + const vector &activePresynapticCells, + bool learn) { NTA_ASSERT(numActiveConnectedSynapsesForSegment.size() == segments_.size()); NTA_ASSERT(numActivePotentialSynapsesForSegment.size() == segments_.size()); // Iterate through all connected synapses. computeActivity( numActiveConnectedSynapsesForSegment, - activePresynapticCells ); + activePresynapticCells, + learn ); // Iterate through all potential synapses. std::copy( numActiveConnectedSynapsesForSegment.begin(), @@ -445,15 +429,19 @@ void Connections::computeActivity( void Connections::adaptSegment(const Segment segment, const SDR &inputs, const Permanence increment, - const Permanence decrement) + const Permanence decrement, + const bool pruneZeroSynapses) { const auto &inputArray = inputs.getDense(); if( timeseries_ ) { - previousUpdates_.resize( synapses_.size(), 0.0f ); - currentUpdates_.resize( synapses_.size(), 0.0f ); + previousUpdates_.resize( synapses_.size(), minPermanence ); + currentUpdates_.resize( synapses_.size(), minPermanence ); + } - for( const auto synapse : synapsesForSegment(segment) ) { + const auto& synapses = synapsesForSegment(segment); + for( size_t i = 0; i < synapses.size(); i++) { + const auto synapse = synapses[i]; const SynapseData &synapseData = dataForSynapse(synapse); Permanence update; @@ -463,28 +451,35 @@ void Connections::adaptSegment(const Segment segment, update = -decrement; } + //prune permanences that reached zero + if (pruneZeroSynapses and + synapseData.permanence + update < htm::minPermanence + htm::Epsilon) { //new value will disconnect the synapse + destroySynapse(synapse); + prunedSyns_++; //for statistics + i--; // do not advance `i`, as `destroySynapse` just modified inplace the synapses_, so now a `synapses_[i]` + // is the "next" synapse. + continue; + } + + //update synapse, but for TS only if changed + if(timeseries_) { if( update != previousUpdates_[synapse] ) { updateSynapsePermanence(synapse, synapseData.permanence + update); } currentUpdates_[ synapse ] = update; + } else { + updateSynapsePermanence(synapse, synapseData.permanence + update); } } - else { - for( const auto synapse : synapsesForSegment(segment) ) { - const SynapseData &synapseData = dataForSynapse(synapse); - Permanence permanence = synapseData.permanence; - if( inputArray[synapseData.presynapticCell] ) { - permanence += increment; - } else { - permanence -= decrement; - } - - updateSynapsePermanence(synapse, permanence); - } + //destroy segment if it has too few synapses left -> will never be able to connect again + if(pruneZeroSynapses and synapses.size() < connectedThreshold_) { + destroySegment(segment); + prunedSegs_++; //statistics } } + /** * Called for under-performing Segments (can have synapses pruned, etc.). After * the call, Segment will have at least segmentThreshold synapses connected, so @@ -640,6 +635,9 @@ void Connections::destroyMinPermanenceSynapses( namespace htm { +/** + * print statistics in human readable form + */ std::ostream& operator<< (std::ostream& stream, const Connections& self) { stream << "Connections:" << std::endl; @@ -680,9 +678,9 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) for( const auto syn : segData.synapses ) { const auto &synData = self.dataForSynapse( syn ); - if( synData.permanence == minPermanence ) + if( synData.permanence <= minPermanence + Epsilon ) { synapsesDead++; } - else if( synData.permanence == maxPermanence ) + else if( synData.permanence >= maxPermanence - Epsilon ) { synapsesSaturated++; } } } @@ -691,7 +689,7 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) potentialMean = potentialMean / self.numSegments(); connectedMean = connectedMean / self.numSegments(); - stream << " Segments on Cell Min/Mean/Max " + stream << " Segments on Cell Min/Mean/Max " //TODO print std dev too << segmentsMin << " / " << segmentsMean << " / " << segmentsMax << std::endl; stream << " Potential Synapses on Segment Min/Mean/Max " << potentialMin << " / " << potentialMean << " / " << potentialMax << std::endl; @@ -700,6 +698,10 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) stream << " Synapses Dead (" << (Real) synapsesDead / self.numSynapses() << "%) Saturated (" << (Real) synapsesSaturated / self.numSynapses() << "%)" << std::endl; + stream << " Synapses pruned (" << (Real) self.prunedSyns_ / self.numSynapses() + << "%) Segments pruned (" << (Real) self.prunedSegs_ / self.numSegments() << "%)" << std::endl; + stream << " Buffer for destroyed synapses: " << self.destroyedSynapses_.size() << " \t buffer for destr. segments: " + << self.destroyedSegments_.size() << std::endl; return stream; } @@ -711,6 +713,8 @@ bool Connections::operator==(const Connections &other) const { if (cells_.size() != other.cells_.size()) return false; + if(iteration_ != other.iteration_) return false; + for (CellIdx i = 0; i < static_cast(cells_.size()); i++) { const CellData &cellData = cells_[i]; const CellData &otherCellData = other.cells_[i]; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 2a49c1bbc6..f4b37b13e5 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -22,6 +22,7 @@ #ifndef NTA_CONNECTIONS_HPP #define NTA_CONNECTIONS_HPP +#include #include #include #include @@ -46,6 +47,7 @@ constexpr const Permanence minPermanence = 0.0f; constexpr const Permanence maxPermanence = 1.0f; + /** * SynapseData class used in Connections. * @@ -63,6 +65,9 @@ struct SynapseData: public Serializable { Permanence permanence; Segment segment; Synapse presynapticMapIndex_; + Synapse id; + + SynapseData() {} CerealAdapter; template @@ -90,9 +95,13 @@ struct SynapseData: public Serializable { * The cell that this segment is on. */ struct SegmentData { + SegmentData(const CellIdx cell, Segment id, UInt32 lastUsed = 0) : cell(cell), numConnected(0), lastUsed(lastUsed), id(id) {} //default constructor + std::vector synapses; - CellIdx cell; - SynapseIdx numConnected; + CellIdx cell; //mother cell that this segment originates from + SynapseIdx numConnected; //number of permanences from `synapses` that are >= synPermConnected, ie connected synapses + UInt32 lastUsed = 0; //last used time (iteration). Used for segment pruning by "least recently used" (LRU) in `createSegment` + Segment id; }; /** @@ -201,8 +210,9 @@ class Connections : public Serializable * instead of the usual HTM inputs which reliably change every cycle. See * also (Kropff & Treves, 2007. http://dx.doi.org/10.2976/1.2793335). */ - Connections(CellIdx numCells, Permanence connectedThreshold = 0.5f, - bool timeseries = false); + Connections(const CellIdx numCells, + const Permanence connectedThreshold = 0.5f, + const bool timeseries = false); virtual ~Connections() {} @@ -214,17 +224,26 @@ class Connections : public Serializable * disconnecting. * @param timeseries See constructor. */ - void initialize(CellIdx numCells, Permanence connectedThreshold = 0.5f, - bool timeseries = false); + void initialize(const CellIdx numCells, + const Permanence connectedThreshold = 0.5f, + const bool timeseries = false); /** * Creates a segment on the specified cell. * * @param cell Cell to create segment on. * - * @retval Created segment. + * @param maxSegmetsPerCell Optional. Enforce limit on maximum number of segments that can be + * created on a Cell. If the limit is exceeded, call `destroySegment` to remove least used segments + * (ordered by LRU `SegmentData.lastUsed`). Default value is numeric_limits::max() of the data-type, + * so effectively disabled. + * + * @retval Unique ID of the created segment `seg`. Use `dataForSegment(seg)` to obtain the segment's data. + * Use `idxOfSegmentOnCell()` to get SegmentIdx of `seg` on this `cell`. + * */ - Segment createSegment(CellIdx cell); + Segment createSegment(const CellIdx cell, + const SegmentIdx maxSegmentsPerCell = std::numeric_limits::max()); /** * Creates a synapse on the specified segment. @@ -235,8 +254,8 @@ class Connections : public Serializable * * @reval Created synapse. */ - Synapse createSynapse(Segment segment, - CellIdx presynapticCell, + Synapse createSynapse(const Segment segment, + const CellIdx presynapticCell, Permanence permanence); /** @@ -244,14 +263,14 @@ class Connections : public Serializable * * @param segment Segment to destroy. */ - void destroySegment(Segment segment); + void destroySegment(const Segment segment); /** * Destroys synapse. * * @param synapse Synapse to destroy. */ - void destroySynapse(Synapse synapse); + void destroySynapse(const Synapse synapse); /** * Updates a synapse's permanence. @@ -259,7 +278,8 @@ class Connections : public Serializable * @param synapse Synapse to update. * @param permanence New permanence. */ - void updateSynapsePermanence(Synapse synapse, Permanence permanence); + void updateSynapsePermanence(const Synapse synapse, + Permanence permanence); /** * Gets the segments for a cell. @@ -268,7 +288,9 @@ class Connections : public Serializable * * @retval Segments on cell. */ - const std::vector &segmentsForCell(CellIdx cell) const; + const std::vector &segmentsForCell(const CellIdx cell) const { + return cells_[cell].segments; + } /** * Gets the synapses for a segment. @@ -277,7 +299,10 @@ class Connections : public Serializable * * @retval Synapses on segment. */ - const std::vector &synapsesForSegment(Segment segment) const; + const std::vector &synapsesForSegment(const Segment segment) const { + NTA_ASSERT(segment < segments_.size()) << "Segment out of bounds! " << segment; + return segments_[segment].synapses; + } /** * Gets the cell that this segment is on. @@ -286,7 +311,9 @@ class Connections : public Serializable * * @retval Cell that this segment is on. */ - CellIdx cellForSegment(Segment segment) const; + CellIdx cellForSegment(const Segment segment) const { + return segments_[segment].cell; + } /** * Gets the index of this segment on its respective cell. @@ -295,7 +322,7 @@ class Connections : public Serializable * * @retval Index of the segment. */ - SegmentIdx idxOnCellForSegment(Segment segment) const; + SegmentIdx idxOnCellForSegment(const Segment segment) const; /** * Get the cell for each provided segment. @@ -317,7 +344,9 @@ class Connections : public Serializable * * @retval Segment that this synapse is on. */ - Segment segmentForSynapse(Synapse synapse) const; + Segment segmentForSynapse(const Synapse synapse) const { + return synapses_[synapse].segment; + } /** * Gets the data for a segment. @@ -326,7 +355,12 @@ class Connections : public Serializable * * @retval Segment data. */ - const SegmentData &dataForSegment(Segment segment) const; + const SegmentData &dataForSegment(const Segment segment) const { + return segments_[segment]; + } + SegmentData& dataForSegment(const Segment segment) { //editable access, needed by SP + return segments_[segment]; + } /** * Gets the data for a synapse. @@ -335,7 +369,9 @@ class Connections : public Serializable * * @retval Synapse data. */ - const SynapseData &dataForSynapse(Synapse synapse) const; + const SynapseData &dataForSynapse(const Synapse synapse) const { + return synapses_[synapse]; + } /** * Get the segment at the specified cell and offset. @@ -345,7 +381,9 @@ class Connections : public Serializable * * @retval Segment */ - Segment getSegment(CellIdx cell, SegmentIdx idx) const; + Segment getSegment(const CellIdx cell, const SegmentIdx idx) const { + return cells_[cell].segments[idx]; + } /** * Get the vector length needed to use segments as indices. @@ -364,7 +402,7 @@ class Connections : public Serializable * * @retval true if a < b, false otherwise. */ - bool compareSegments(Segment a, Segment b) const; + bool compareSegments(const Segment a, const Segment b) const; /** * Returns the synapses for the source cell that they synapse on. @@ -373,8 +411,7 @@ class Connections : public Serializable * * @return Synapse indices */ - std::vector - synapsesForPresynapticCell(CellIdx presynapticCell) const; + std::vector synapsesForPresynapticCell(const CellIdx presynapticCell) const; /** * For use with time-series datasets. @@ -397,13 +434,18 @@ class Connections : public Serializable * * @param activePresynapticCells * Active cells in the input. + * + * @param bool learn : enable learning updates (default true) + * */ void computeActivity(std::vector &numActiveConnectedSynapsesForSegment, std::vector &numActivePotentialSynapsesForSegment, - const std::vector &activePresynapticCells); + const std::vector &activePresynapticCells, + const bool learn = true); void computeActivity(std::vector &numActiveConnectedSynapsesForSegment, - const std::vector &activePresynapticCells); + const std::vector &activePresynapticCells, + const bool learn = true); /** * The primary method in charge of learning. Adapts the permanence values of @@ -417,11 +459,15 @@ class Connections : public Serializable * @param inputVector An SDR * @param increment Change in permanence for synapses with active presynapses. * @param decrement Change in permanence for synapses with inactive presynapses. + * @param pruneZeroSynapses (default false) If set, synapses that reach minPermanence(aka. "zero") + * are removed. This is used in TemporalMemory. If the segment becomes empty due to these + * removed synapses, we remove the segment (see @ref `destroySegment`). */ void adaptSegment(const Segment segment, const SDR &inputs, const Permanence increment, - const Permanence decrement); + const Permanence decrement, + const bool pruneZeroSynapses = false); /** * Ensures a minimum number of connected synapses. This raises permance @@ -434,6 +480,18 @@ class Connections : public Serializable void raisePermanencesToThreshold(const Segment segment, const UInt segmentThreshold); + + /** + * iteration: ever increasing step count. + * Increases each main call to "compute". Since connections has more + * methods that are called instead of compute (adaptSegment, computeActivity,..) + * this counter is increased in @ref `computeActivity` as it is called by both + * SP & TM. + */ +//! const UInt32& iteration = iteration_; //FIXME cannot construct iteration like this? + UInt32 iteration() const { return iteration_; } + + /** * Ensures that the number of connected synapses is sane. This method * controls the sparsity of the synaptic connections, which is important for @@ -454,6 +512,7 @@ class Connections : public Serializable const SynapseIdx minimumSynapses, const SynapseIdx maximumSynapses); + /** * Modify all permanence on the given segment, uniformly. * @@ -506,6 +565,7 @@ class Connections : public Serializable ar(CEREAL_NVP(connectedThreshold_)); ar(CEREAL_NVP(sizes)); ar(CEREAL_NVP(syndata)); + ar(CEREAL_NVP(iteration_)); } template @@ -530,6 +590,7 @@ class Connections : public Serializable } } } + ar(CEREAL_NVP(iteration_)); } /** @@ -537,9 +598,9 @@ class Connections : public Serializable * * @retval Number of cells. */ - size_t numCells() const { return cells_.size(); } + size_t numCells() const noexcept { return cells_.size(); } - Permanence getConnectedThreshold() const { return connectedThreshold_; } + constexpr Permanence getConnectedThreshold() const noexcept { return connectedThreshold_; } /** * Gets the number of segments. @@ -555,7 +616,9 @@ class Connections : public Serializable * * @retval Number of segments. */ - size_t numSegments(CellIdx cell) const { return cells_[cell].segments.size(); } + size_t numSegments(const CellIdx cell) const { + return cells_[cell].segments.size(); + } /** * Gets the number of synapses. @@ -572,7 +635,9 @@ class Connections : public Serializable * * @retval Number of synapses. */ - size_t numSynapses(Segment segment) const { return segments_[segment].synapses.size(); } + size_t numSynapses(const Segment segment) const { + return segments_[segment].synapses.size(); + } /** * Comparison operator. @@ -612,7 +677,7 @@ class Connections : public Serializable * * @retval True if it's still in its cell's segment list. */ - bool segmentExists_(Segment segment) const; + bool segmentExists_(const Segment segment) const; /** * Check whether this synapse still exists on its segment. @@ -621,14 +686,14 @@ class Connections : public Serializable * * @retval True if it's still in its segment's synapse list. */ - bool synapseExists_(Synapse synapse) const; + bool synapseExists_(const Synapse synapse) const; /** * Remove a synapse from presynaptic maps. * * @param Synapse Index of synapse in presynaptic vector. * - * @param vector synapsesForPresynapticCell must a vector from be + * @param vector ynapsesForPresynapticCell must a vector from be * either potentialSynapsesForPresynapticCell_ or * connectedSynapsesForPresynapticCell_, depending on whether the synapse is * connected or not. @@ -649,6 +714,7 @@ class Connections : public Serializable std::vector synapses_; std::vector destroyedSynapses_; Permanence connectedThreshold_; //TODO make const + UInt32 iteration_ = 0; // Extra bookkeeping for faster computing of segment activity. @@ -659,10 +725,8 @@ class Connections : public Serializable std::unordered_map, identity> potentialSegmentsForPresynapticCell_; std::unordered_map, identity> connectedSegmentsForPresynapticCell_; - std::vector segmentOrdinals_; - std::vector synapseOrdinals_; - Segment nextSegmentOrdinal_; - Synapse nextSynapseOrdinal_; + Segment nextSegmentOrdinal_ = 0; + Synapse nextSynapseOrdinal_ = 0; // These three members should be used when working with highly correlated // data. The vectors store the permanence changes made by adaptSegment. @@ -670,6 +734,11 @@ class Connections : public Serializable std::vector previousUpdates_; std::vector currentUpdates_; + //for prune statistics + Synapse prunedSyns_ = 0; //how many synapses have been removed? + Segment prunedSegs_ = 0; + + //for listeners UInt32 nextEventToken_; std::map eventHandlers_; }; // end class Connections diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index ff170b4227..fa22e53eba 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -352,7 +352,7 @@ void SpatialPooler::getConnectedSynapses(UInt column, } void SpatialPooler::getConnectedCounts(UInt connectedCounts[]) const { - for(UInt seg = 0; seg < numColumns_; seg++) { + for(UInt seg = 0; seg < numColumns_; seg++) { //in SP each column = 1 cell with 1 segment only. const auto &segment = connections_.dataForSegment( seg ); connectedCounts[ seg ] = segment.numConnected; //TODO numConnected only used here, rm from SegmentData and compute for each segment.synapses? } @@ -437,7 +437,7 @@ void SpatialPooler::initialize( connections_.initialize(numColumns_, synPermConnected_); for (Size i = 0; i < numColumns_; ++i) { - connections_.createSegment( (CellIdx)i ); + connections_.createSegment( (CellIdx)i , 1 /* max segments per cell is fixed for SP to 1 */); // Note: initMapPotential_ & initPermanence_ return dense arrays. vector potential = initMapPotential_((UInt)i, wrapAround_); @@ -463,7 +463,7 @@ void SpatialPooler::compute(const SDR &input, const bool learn, SDR &active) { input.reshape( inputDimensions_ ); active.reshape( columnDimensions_ ); updateBookeepingVars_(learn); - calculateOverlap_(input, overlaps_); + calculateOverlap_(input, overlaps_, learn); boostOverlaps_(overlaps_, boostedOverlaps_); @@ -800,9 +800,10 @@ void SpatialPooler::updateBookeepingVars_(bool learn) { void SpatialPooler::calculateOverlap_(const SDR &input, - vector &overlaps) { + vector &overlaps, + const bool learn) { overlaps.assign( numColumns_, 0 ); - connections_.computeActivity(overlaps, input.getSparse()); + connections_.computeActivity(overlaps, input.getSparse(), learn); } diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index 93832aab90..16b3f9f162 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -875,7 +875,7 @@ class SpatialPooler : public Serializable a "connected state" (connected synapses) that are connected to input bits which are turned on. */ - void calculateOverlap_(const SDR &input, vector &overlap); + void calculateOverlap_(const SDR &input, vector &overlap, const bool learn = true); void calculateOverlapPct_(const vector &overlaps, vector &overlapPct) const; /** diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 3c7353aab8..97395c7f65 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -131,31 +131,35 @@ void TemporalMemory::initialize( maxSegmentsPerCell_ = maxSegmentsPerCell; maxSynapsesPerSegment_ = maxSynapsesPerSegment; - iteration_ = 0; reset(); } -static CellIdx getLeastUsedCell(Random &rng, UInt column, //TODO remove static methods, use private instead +///* +static CellIdx getLeastUsedCell(Random &rng, + const UInt column, //TODO remove static methods, use private instead const Connections &connections, - UInt cellsPerColumn) { + const UInt cellsPerColumn) { const CellIdx start = column * cellsPerColumn; const CellIdx end = start + cellsPerColumn; size_t minNumSegments = std::numeric_limits::max(); UInt32 numTiedCells = 0u; + //for all cells in a mini-column for (CellIdx cell = start; cell < end; cell++) { const size_t numSegments = connections.numSegments(cell); + //..find a cell with least segments if (numSegments < minNumSegments) { minNumSegments = numSegments; numTiedCells = 1u; + //..and how many of the cells have only these min segments? number of weakest } else if (numSegments == minNumSegments) { numTiedCells++; } } + //randomly select one of the tie-d cells from the losers const UInt32 tieWinnerIndex = rng.getUInt32(numTiedCells); - UInt32 tieIndex = 0; for (CellIdx cell = start; cell < end; cell++) { if (connections.numSegments(cell) == minNumSegments) { @@ -169,39 +173,7 @@ static CellIdx getLeastUsedCell(Random &rng, UInt column, //TODO remove static m NTA_THROW << "getLeastUsedCell failed to find a cell"; } - -static void adaptSegment(Connections &connections, Segment segment, - const vector &prevActiveCellsDense, - Permanence permanenceIncrement, - Permanence permanenceDecrement) { - const vector &synapses = connections.synapsesForSegment(segment); - - for (SynapseIdx i = 0; i < synapses.size();) { - const SynapseData &synapseData = connections.dataForSynapse(synapses[i]); - - Permanence permanence = synapseData.permanence; - if (prevActiveCellsDense[synapseData.presynapticCell]) { - permanence += permanenceIncrement; - } else { - permanence -= permanenceDecrement; - } - - permanence = min(permanence, (Permanence)1.0); - permanence = max(permanence, (Permanence)0.0); - - if (permanence < htm::Epsilon) { - connections.destroySynapse(synapses[i]); - // Synapses vector is modified in-place, so don't update `i`. - } else { - connections.updateSynapsePermanence(synapses[i], permanence); - i++; - } - } - - if (synapses.size() == 0) { - connections.destroySegment(segment); - } -} +//*/ static void growSynapses(Connections &connections, Random &rng, @@ -218,7 +190,16 @@ static void growSynapses(Connections &connections, vector candidates(prevWinnerCells.begin(), prevWinnerCells.end()); NTA_ASSERT(std::is_sorted(candidates.begin(), candidates.end())); - // Remove cells that are already synapsed on by this segment + // Skip cells that are already synapsed on by this segment + // Biological motivation (?): + // There are structural constraints on the shapes of axons & synapses + // which prevent a large number duplicate of connections. + // + // It's important to prevent cells from growing duplicate synapses onto a segment, + // because otherwise a strong input would be sampled many times and grow many synapses. + // That would give such input a stronger connection. + // Synapses are supposed to have binary effects (0 or 1) but duplicate synapses give + // them (synapses 0/1) varying levels of strength. for (const Synapse& synapse : connections.synapsesForSegment(segment)) { const CellIdx presynapticCell = connections.dataForSynapse(synapse).presynapticCell; const auto already = std::lower_bound(candidates.cbegin(), candidates.cend(), presynapticCell); @@ -239,10 +220,8 @@ static void growSynapses(Connections &connections, const size_t nActualWithMax = std::min(nActual, static_cast(maxSynapsesPerSegment) - connections.numSynapses(segment)); // Pick nActual cells randomly. - for (size_t c = 0; c < nActualWithMax; c++) { - const auto i = rng.getUInt32(static_cast(candidates.size())); - connections.createSynapse(segment, candidates[i], initialPermanence); //TODO createSynapse consider creating a vector of new synapses at once? - candidates.erase(candidates.begin() + i); //TODO this is costly, optimize it (out) + for (const auto syn : rng.sample(candidates, nActualWithMax)) { + connections.createSynapse(segment, syn, initialPermanence); //TODO createSynapse consider creating a vector of new synapses at once? } } @@ -253,7 +232,7 @@ static void activatePredictedColumn( Random &rng, vector::const_iterator columnActiveSegmentsBegin, vector::const_iterator columnActiveSegmentsEnd, - const vector &prevActiveCellsDense, + const SDR &prevActiveCells, const vector &prevWinnerCells, const vector &numActivePotentialSynapsesForSegment, const UInt maxNewSynapseCount, @@ -270,12 +249,12 @@ static void activatePredictedColumn( // This cell might have multiple active segments. do { - if (learn) { - adaptSegment(connections, *activeSegment, prevActiveCellsDense, - permanenceIncrement, permanenceDecrement); + if (learn) { + connections.adaptSegment(*activeSegment, prevActiveCells, + permanenceIncrement, permanenceDecrement, true); const Int32 nGrowDesired = - maxNewSynapseCount - + static_cast(maxNewSynapseCount) - numActivePotentialSynapsesForSegment[*activeSegment]; if (nGrowDesired > 0) { growSynapses(connections, rng, *activeSegment, nGrowDesired, @@ -288,44 +267,18 @@ static void activatePredictedColumn( } while (activeSegment != columnActiveSegmentsEnd); } -static Segment createSegment(Connections &connections, //TODO remove, use TM::createSegment - vector &lastUsedIterationForSegment, - CellIdx cell, UInt64 iteration, - UInt maxSegmentsPerCell) { - while (connections.numSegments(cell) >= maxSegmentsPerCell) { - const vector &destroyCandidates = - connections.segmentsForCell(cell); - - auto leastRecentlyUsedSegment = - std::min_element(destroyCandidates.begin(), destroyCandidates.end(), - [&](Segment a, Segment b) { - return (lastUsedIterationForSegment[a] < - lastUsedIterationForSegment[b]); - }); - - connections.destroySegment(*leastRecentlyUsedSegment); - } - - const Segment segment = connections.createSegment(cell); - lastUsedIterationForSegment.resize(connections.segmentFlatListLength()); - lastUsedIterationForSegment[segment] = iteration; - - return segment; -} static void burstColumn(vector &activeCells, vector &winnerCells, Connections &connections, Random &rng, - vector &lastUsedIterationForSegment, UInt column, vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, - const vector &prevActiveCellsDense, + const SDR &prevActiveCells, const vector &prevWinnerCells, const vector &numActivePotentialSynapsesForSegment, - UInt64 iteration, CellIdx cellsPerColumn, UInt maxNewSynapseCount, const Permanence initialPermanence, @@ -351,7 +304,7 @@ burstColumn(vector &activeCells, const CellIdx winnerCell = (bestMatchingSegment != columnMatchingSegmentsEnd) ? connections.cellForSegment(*bestMatchingSegment) - : getLeastUsedCell(rng, column, connections, cellsPerColumn); + : getLeastUsedCell(rng, column, connections, cellsPerColumn); //TODO replace (with random?) this is extremely costly, removing makes TM 6x faster! winnerCells.push_back(winnerCell); @@ -359,8 +312,8 @@ burstColumn(vector &activeCells, if (learn) { if (bestMatchingSegment != columnMatchingSegmentsEnd) { // Learn on the best matching segment. - adaptSegment(connections, *bestMatchingSegment, prevActiveCellsDense, - permanenceIncrement, permanenceDecrement); + connections.adaptSegment(*bestMatchingSegment, prevActiveCells, + permanenceIncrement, permanenceDecrement, true); const Int32 nGrowDesired = maxNewSynapseCount - @@ -378,8 +331,7 @@ burstColumn(vector &activeCells, std::min(maxNewSynapseCount, (UInt32)prevWinnerCells.size()); if (nGrowExact > 0) { const Segment segment = - createSegment(connections, lastUsedIterationForSegment, winnerCell, - iteration, maxSegmentsPerCell); + connections.createSegment(winnerCell, maxSegmentsPerCell); growSynapses(connections, rng, segment, nGrowExact, prevWinnerCells, initialPermanence, maxSynapsesPerSegment); @@ -393,13 +345,13 @@ static void punishPredictedColumn( Connections &connections, vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, - const vector &prevActiveCellsDense, + const SDR &prevActiveCells, Permanence predictedSegmentDecrement) { if (predictedSegmentDecrement > 0.0) { for (auto matchingSegment = columnMatchingSegmentsBegin; matchingSegment != columnMatchingSegmentsEnd; matchingSegment++) { - adaptSegment(connections, *matchingSegment, prevActiveCellsDense, - -predictedSegmentDecrement, 0.0); + connections.adaptSegment(*matchingSegment, prevActiveCells, + -predictedSegmentDecrement, 0.0, true); } } } @@ -416,28 +368,41 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { } auto &sparse = activeColumns.getSparse(); - vector prevActiveCellsDense(numberOfCells() + externalPredictiveInputs_, false); - for (CellIdx cell : activeCells_) { - prevActiveCellsDense[cell] = true; - } + SDR prevActiveCells({static_cast(numberOfCells() + externalPredictiveInputs_)}); + prevActiveCells.setSparse(activeCells_); activeCells_.clear(); const vector prevWinnerCells = std::move(winnerCells_); - const auto columnForSegment = [&](Segment segment) { + //maps segment S to a new segment that is at start of a column where + //S belongs. + //for 3 cells per columns: + //s1_1, s1_2, s1_3, s2_1, s2_2, s2_3, ... + //columnForSegment (for short here CFS) + //CFS(s1_1) = s1_1 = "start of column 1" + //CFS(s1_2) = s1_1 + //CFS(s1_3) = s1_1 + //CFS(s2_1) = s2_1 = "column 2" + //CFS(s2_2) = s2_1 + //... + const auto toColumns = [&](const Segment segment) { return connections.cellForSegment(segment) / cellsPerColumn_; }; 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)) { - CellIdx column; + activeSegments_, toColumns, + matchingSegments_, toColumns)) { + + Segment column; //we say "column", but it's the first segment of n-segments/cells that belong to the column vector::const_iterator activeColumnsBegin, activeColumnsEnd, columnActiveSegmentsBegin, columnActiveSegmentsEnd, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd; + // for column in activeColumns (the 'sparse' above): + // get its active segments ( >= connectedThr) + // get its matching segs ( >= mmm std::tie(column, activeColumnsBegin, activeColumnsEnd, columnActiveSegmentsBegin, columnActiveSegmentsEnd, @@ -445,32 +410,35 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { ) = columnData; const bool isActiveColumn = activeColumnsBegin != activeColumnsEnd; - if (isActiveColumn) { + if (isActiveColumn) { //current active column... if (columnActiveSegmentsBegin != columnActiveSegmentsEnd) { + //...was also predicted -> learn :o) activatePredictedColumn( activeCells_, winnerCells_, connections, rng_, columnActiveSegmentsBegin, columnActiveSegmentsEnd, - prevActiveCellsDense, prevWinnerCells, + prevActiveCells, prevWinnerCells, numActivePotentialSynapsesForSegment_, maxNewSynapseCount_, initialPermanence_, permanenceIncrement_, permanenceDecrement_, maxSynapsesPerSegment_, learn); } else { + //...has not been predicted -> burstColumn(activeCells_, winnerCells_, connections, rng_, - lastUsedIterationForSegment_, column, + column, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, - prevActiveCellsDense, prevWinnerCells, - numActivePotentialSynapsesForSegment_, iteration_, + prevActiveCells, prevWinnerCells, + numActivePotentialSynapsesForSegment_, cellsPerColumn_, maxNewSynapseCount_, initialPermanence_, permanenceIncrement_, permanenceDecrement_, maxSegmentsPerCell_, maxSynapsesPerSegment_, learn); } - } else { + + } else { // predicted but not active column -> unlearn if (learn) { punishPredictedColumn(connections, columnMatchingSegmentsBegin, - columnMatchingSegmentsEnd, prevActiveCellsDense, + columnMatchingSegmentsEnd, prevActiveCells, predictedSegmentDecrement_); } - } + } //else: not predicted & not active -> no activity -> does not show up at all } segmentsValid_ = false; } @@ -516,23 +484,23 @@ void TemporalMemory::activateDendrites(const bool learn, numActivePotentialSynapsesForSegment_.assign(length, 0); connections.computeActivity(numActiveConnectedSynapsesForSegment_, numActivePotentialSynapsesForSegment_, - activeCells_); + activeCells_, + learn); // Active segments, connected synapses. activeSegments_.clear(); for (Segment segment = 0; segment < numActiveConnectedSynapsesForSegment_.size(); segment++) { - if (numActiveConnectedSynapsesForSegment_[segment] >= activationThreshold_) { + if (numActiveConnectedSynapsesForSegment_[segment] >= activationThreshold_) { //TODO move to SegmentData.numConnected? activeSegments_.push_back(segment); } } const auto compareSegments = [&](const Segment a, const Segment b) { return connections.compareSegments(a, b); }; - std::sort( activeSegments_.begin(), activeSegments_.end(), compareSegments); + std::sort( activeSegments_.begin(), activeSegments_.end(), compareSegments); //SDR requires sorted when constructed from activeSegments_ // Update segment bookkeeping. if (learn) { - for (const auto &segment : activeSegments_) { - lastUsedIterationForSegment_[segment] = iteration_; + for (const auto segment : activeSegments_) { + connections.dataForSegment(segment).lastUsed = connections.iteration(); //TODO the destroySegments based on LRU is expensive. Better random? or "energy" based on sum permanences? } - iteration_++; } // Matching segments, potential synapses. @@ -583,14 +551,7 @@ void TemporalMemory::reset(void) { // ============================== // Helper functions // ============================== - -Segment TemporalMemory::createSegment(const CellIdx& cell) { - return ::createSegment(connections, lastUsedIterationForSegment_, cell, - iteration_, maxSegmentsPerCell_); -} - UInt TemporalMemory::columnForCell(const CellIdx cell) const { - NTA_ASSERT(cell < numberOfCells()); return cell / cellsPerColumn_; } @@ -793,7 +754,6 @@ bool TemporalMemory::operator==(const TemporalMemory &other) const { winnerCells_ != other.winnerCells_ || maxSegmentsPerCell_ != other.maxSegmentsPerCell_ || maxSynapsesPerSegment_ != other.maxSynapsesPerSegment_ || - iteration_ != other.iteration_ || anomaly_ != other.anomaly_ ) { return false; } diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index bf37c4de62..8533b5330c 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -270,7 +270,8 @@ class TemporalMemory : public Serializable * @return Segment * The created segment. */ - Segment createSegment(const CellIdx& cell); + Segment createSegment(const CellIdx& cell) { + return connections.createSegment(cell, maxSegmentsPerCell_); } /** * Returns the indices of cells that belong to a mini-column. @@ -461,7 +462,6 @@ class TemporalMemory : public Serializable CEREAL_NVP(externalPredictiveInputs_), CEREAL_NVP(maxSegmentsPerCell_), CEREAL_NVP(maxSynapsesPerSegment_), - CEREAL_NVP(iteration_), CEREAL_NVP(rng_), CEREAL_NVP(columnDimensions_), CEREAL_NVP(activeCells_), @@ -517,7 +517,6 @@ class TemporalMemory : public Serializable CEREAL_NVP(externalPredictiveInputs_), CEREAL_NVP(maxSegmentsPerCell_), CEREAL_NVP(maxSynapsesPerSegment_), - CEREAL_NVP(iteration_), CEREAL_NVP(rng_), CEREAL_NVP(columnDimensions_), CEREAL_NVP(activeCells_), @@ -549,9 +548,6 @@ class TemporalMemory : public Serializable matchingSegments_[i] = segment; numActivePotentialSynapsesForSegment_[segment] = c.syn; } - - lastUsedIterationForSegment_.resize(connections.segmentFlatListLength()); - } @@ -629,9 +625,6 @@ class TemporalMemory : public Serializable vector numActiveConnectedSynapsesForSegment_; vector numActivePotentialSynapsesForSegment_; - UInt64 iteration_; - vector lastUsedIterationForSegment_; - Real anomaly_; Random rng_; diff --git a/src/test/unit/algorithms/ConnectionsTest.cpp b/src/test/unit/algorithms/ConnectionsTest.cpp index 6df95dedce..1deed504e4 100644 --- a/src/test/unit/algorithms/ConnectionsTest.cpp +++ b/src/test/unit/algorithms/ConnectionsTest.cpp @@ -34,14 +34,14 @@ void setupSampleConnections(Connections &connections) { // - 1 connected synapse: active // - 2 matching synapses const Segment segment1_1 = connections.createSegment(10); - connections.createSynapse(segment1_1, 150, 0.85f); + connections.createSynapse(segment1_1, 150, 0.85f); //connected connections.createSynapse(segment1_1, 151, 0.15f); // Cell with 2 segments. // Segment with: // - 2 connected synapses: 2 active // - 3 matching synapses: 3 active - const Segment segment2_1 = connections.createSegment(20); + const Segment segment2_1 = connections.createSegment(20, 2/* max number of segments per cell*/); connections.createSynapse(segment2_1, 80, 0.85f); connections.createSynapse(segment2_1, 81, 0.85f); Synapse synapse = connections.createSynapse(segment2_1, 82, 0.85f); @@ -51,7 +51,7 @@ void setupSampleConnections(Connections &connections) { // - 2 connected synapses: 1 active, 1 inactive // - 3 matching synapses: 2 active, 1 inactive // - 1 non-matching synapse: 1 active - const Segment segment2_2 = connections.createSegment(20); + const Segment segment2_2 = connections.createSegment(20, 2); connections.createSynapse(segment2_2, 50, 0.85f); connections.createSynapse(segment2_2, 51, 0.85f); connections.createSynapse(segment2_2, 52, 0.15f); diff --git a/src/test/unit/algorithms/TemporalMemoryTest.cpp b/src/test/unit/algorithms/TemporalMemoryTest.cpp index cc3b29f7b7..31da6cc0f8 100644 --- a/src/test/unit/algorithms/TemporalMemoryTest.cpp +++ b/src/test/unit/algorithms/TemporalMemoryTest.cpp @@ -1186,7 +1186,7 @@ TEST(TemporalMemoryTest, ConnectionsNeverChangeWhenLearningDisabled) { 0.5f); tm.connections.createSynapse(wrongMatchingSegment, previousInactiveCell, 0.5f); - Connections before = tm.connections; + const Connections before = tm.connections; tm.compute(previousActiveColumns, false); tm.compute(activeColumns, false); @@ -1228,7 +1228,7 @@ TEST(TemporalMemoryTest, DestroySegmentsThenReachLimit) { tm.createSegment(11); EXPECT_EQ(2ul, tm.connections.numSegments()); tm.createSegment(11); - EXPECT_EQ(2ul, tm.connections.numSegments()); + EXPECT_EQ(2ul, tm.connections.numSegments()) << "Created 3 segments, but limit is 2, so this should be 2!"; EXPECT_EQ(2ul, tm.connections.numSegments(11)); } } @@ -1304,7 +1304,7 @@ TEST(TemporalMemoryTest, CreateSegmentDestroyOld) { * Hit the maxSegmentsPerCell threshold multiple times. Make sure it works * more than once. */ -TEST(ConnectionsTest, ReachSegmentLimitMultipleTimes) { +TEST(TemporalMemoryTest, ReachSegmentLimitMultipleTimes) { TemporalMemory tm( /*columnDimensions*/ {32}, /*cellsPerColumn*/ 1,