From 103e8fce6ded7e517cda8a0058617178edd24a54 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 1 Jul 2019 22:17:29 +0200 Subject: [PATCH 01/35] destroySynapse: const arg --- src/htm/algorithms/Connections.cpp | 2 +- src/htm/algorithms/Connections.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index c3deb5d2cc..d74a0b9292 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -210,7 +210,7 @@ void Connections::destroySegment(Segment segment) { 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); diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 26243bb83b..069a1b02ce 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -251,7 +251,7 @@ class Connections : public Serializable * * @param synapse Synapse to destroy. */ - void destroySynapse(Synapse synapse); + void destroySynapse(const Synapse synapse); /** * Updates a synapse's permanence. From b6a103adbb6886c13de6fadd08324f67c75f1677 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 1 Jul 2019 23:39:03 +0200 Subject: [PATCH 02/35] TM: fix when custom minPermanence use minPermanence as "zero", also avoid hard-coded crop for permanence, is done in connections. --- src/htm/algorithms/TemporalMemory.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 6f88bd7f06..ae6563a199 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -186,10 +186,7 @@ static void adaptSegment(Connections &connections, Segment segment, permanence -= permanenceDecrement; } - permanence = min(permanence, (Permanence)1.0); - permanence = max(permanence, (Permanence)0.0); - - if (permanence < htm::Epsilon) { + if (permanence < htm::minPermanence + htm::Epsilon) { connections.destroySynapse(synapses[i]); // Synapses vector is modified in-place, so don't update `i`. } else { From cc476a936401554f9a2fc62ae26a3bdd2f6c8c22 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 08:32:40 +0200 Subject: [PATCH 03/35] COnnections: adaptSegment: deduplicate code for timeseries merge the common code in if-else branches --- src/htm/algorithms/Connections.cpp | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index d74a0b9292..2fc1cb84be 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -450,8 +450,9 @@ void Connections::adaptSegment(const Segment segment, if( timeseries_ ) { previousUpdates_.resize( synapses_.size(), 0.0f ); currentUpdates_.resize( synapses_.size(), 0.0f ); + } - for( const auto synapse : synapsesForSegment(segment) ) { + for( const auto synapse : synapsesForSegment(segment) ) { const SynapseData &synapseData = dataForSynapse(synapse); Permanence update; @@ -460,25 +461,14 @@ void Connections::adaptSegment(const Segment segment, } else { update = -decrement; } - + //update synapse, but for TS only if changed + if(timeseries_) { if( update != previousUpdates_[synapse] ) { updateSynapsePermanence(synapse, synapseData.permanence + update); } currentUpdates_[ synapse ] = 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); + } else { + updateSynapsePermanence(synapse, synapseData.permanence + update); } } } From 21c315ced5278fdcd785d05be5f92adaec02551f Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 08:35:04 +0200 Subject: [PATCH 04/35] Connections: adaptSegment fix hardcoded minPermanence was 0.0f, now minPermanence --- src/htm/algorithms/Connections.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 2fc1cb84be..e2289f3132 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -448,8 +448,8 @@ void Connections::adaptSegment(const Segment segment, 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) ) { From cdcf5df342cf73f85fce3e2fee77051ea6da96b2 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 08:54:04 +0200 Subject: [PATCH 05/35] TM:adaptSegment make similar to connections' make the code same as in connections --- src/htm/algorithms/TemporalMemory.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index ae6563a199..5e8d21a546 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -170,7 +170,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, +static void adaptSegment(Connections &connections, Segment segment, //TODO replace with Connections::adaptSegment const vector &prevActiveCellsDense, Permanence permanenceIncrement, Permanence permanenceDecrement) { @@ -179,18 +179,18 @@ static void adaptSegment(Connections &connections, Segment segment, for (SynapseIdx i = 0; i < synapses.size();) { const SynapseData &synapseData = connections.dataForSynapse(synapses[i]); - Permanence permanence = synapseData.permanence; + Permanence update; if (prevActiveCellsDense[synapseData.presynapticCell]) { - permanence += permanenceIncrement; + update = permanenceIncrement; } else { - permanence -= permanenceDecrement; + update = -permanenceDecrement; } - if (permanence < htm::minPermanence + htm::Epsilon) { + if (synapseData.permanence + update < htm::minPermanence + htm::Epsilon) { connections.destroySynapse(synapses[i]); // Synapses vector is modified in-place, so don't update `i`. } else { - connections.updateSynapsePermanence(synapses[i], permanence); + connections.updateSynapsePermanence(synapses[i], synapseData.permanence + update); i++; } } From dd455670916bd22b3ea77ea470a7f7e9f10d4886 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 09:47:06 +0200 Subject: [PATCH 06/35] TM:adaptSegment uses connections', WIP call connections.adaptSegment instead of reimplementing the logic --- src/htm/algorithms/TemporalMemory.cpp | 31 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 5e8d21a546..a2eb67a4d2 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -170,31 +170,36 @@ 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, //TODO replace with Connections::adaptSegment - const vector &prevActiveCellsDense, +static void adaptSegment(Connections &connections, Segment segment, + const vector &prevActiveCellsDense, //TODO use SDR here to match connections::adaptSegment Permanence permanenceIncrement, Permanence permanenceDecrement) { - const vector &synapses = connections.synapsesForSegment(segment); + //1. update synapses + const auto sz = prevActiveCellsDense.size(); + SDR_sparse_t v(sz); + for(ElemSparse i = 0; i < (ElemSparse)sz; i++) { + if(prevActiveCellsDense[i]) { + v.push_back(i); + } + } + SDR tmp({(CellIdx)sz}); + tmp.setSparse(v); //TODO get setDense working to avoid the loop with `v` above! + connections.adaptSegment(segment, tmp, permanenceIncrement, permanenceDecrement); + //2. remove disconnected synapses & empty segments + const vector &synapses = connections.synapsesForSegment(segment); for (SynapseIdx i = 0; i < synapses.size();) { const SynapseData &synapseData = connections.dataForSynapse(synapses[i]); - - Permanence update; - if (prevActiveCellsDense[synapseData.presynapticCell]) { - update = permanenceIncrement; - } else { - update = -permanenceDecrement; - } - - if (synapseData.permanence + update < htm::minPermanence + htm::Epsilon) { + //2.1 remove synapse with permanence == 0.0 + if (synapseData.permanence < htm::minPermanence + htm::Epsilon) { //TODO make the pruning part of connections::adaptSegment connections.destroySynapse(synapses[i]); // Synapses vector is modified in-place, so don't update `i`. } else { - connections.updateSynapsePermanence(synapses[i], synapseData.permanence + update); i++; } } + //2.2 remove empty segments if (synapses.size() == 0) { connections.destroySegment(segment); } From 34401b4d57483939c8ee265a47760be4522e66d9 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 10:38:00 +0200 Subject: [PATCH 07/35] Connections:adaptSegment allow to prune zero synapses this is functionality used in TM --- src/htm/algorithms/Connections.cpp | 18 +++++++++++++++--- src/htm/algorithms/Connections.hpp | 8 ++++++-- src/htm/algorithms/TemporalMemory.cpp | 13 +------------ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index e2289f3132..0c71d4d6ab 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -259,7 +259,7 @@ void Connections::destroySynapse(const 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 ); @@ -443,7 +443,8 @@ 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(); @@ -452,7 +453,9 @@ void Connections::adaptSegment(const Segment segment, 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; @@ -461,6 +464,15 @@ void Connections::adaptSegment(const Segment segment, } else { update = -decrement; } + + //prune permanences that reached zero + if (pruneZeroSynapses && synapseData.permanence + update < htm::minPermanence + htm::Epsilon) { + destroySynapse(synapse); + 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] ) { diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 069a1b02ce..f367938495 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -259,7 +259,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. @@ -417,11 +418,14 @@ 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. */ 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 diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index a2eb67a4d2..13df7b644e 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -184,21 +184,10 @@ static void adaptSegment(Connections &connections, Segment segment, } SDR tmp({(CellIdx)sz}); tmp.setSparse(v); //TODO get setDense working to avoid the loop with `v` above! - connections.adaptSegment(segment, tmp, permanenceIncrement, permanenceDecrement); + connections.adaptSegment(segment, tmp, permanenceIncrement, permanenceDecrement, true); //2. remove disconnected synapses & empty segments const vector &synapses = connections.synapsesForSegment(segment); - for (SynapseIdx i = 0; i < synapses.size();) { - const SynapseData &synapseData = connections.dataForSynapse(synapses[i]); - //2.1 remove synapse with permanence == 0.0 - if (synapseData.permanence < htm::minPermanence + htm::Epsilon) { //TODO make the pruning part of connections::adaptSegment - connections.destroySynapse(synapses[i]); - // Synapses vector is modified in-place, so don't update `i`. - } else { - i++; - } - } - //2.2 remove empty segments if (synapses.size() == 0) { connections.destroySegment(segment); From 86077101b6538d341531509043a99a1875aa3838 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 11:09:43 +0200 Subject: [PATCH 08/35] TM: adaptSegment use SDR for prevActiveCells to match Connections this matches signiture in Connections::adaptSegment and avoids need for conversions --- src/htm/algorithms/TemporalMemory.cpp | 40 ++++++++++----------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 13df7b644e..c193f4ae27 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -171,24 +171,14 @@ static CellIdx getLeastUsedCell(Random &rng, UInt column, //TODO remove static m } static void adaptSegment(Connections &connections, Segment segment, - const vector &prevActiveCellsDense, //TODO use SDR here to match connections::adaptSegment + const SDR &prevActiveCells, Permanence permanenceIncrement, Permanence permanenceDecrement) { //1. update synapses - const auto sz = prevActiveCellsDense.size(); - SDR_sparse_t v(sz); - for(ElemSparse i = 0; i < (ElemSparse)sz; i++) { - if(prevActiveCellsDense[i]) { - v.push_back(i); - } - } - SDR tmp({(CellIdx)sz}); - tmp.setSparse(v); //TODO get setDense working to avoid the loop with `v` above! - connections.adaptSegment(segment, tmp, permanenceIncrement, permanenceDecrement, true); + connections.adaptSegment(segment, prevActiveCells, permanenceIncrement, permanenceDecrement, true /* prune empty synapses */); - //2. remove disconnected synapses & empty segments + //2. remove empty segments //TODO move to Connections? const vector &synapses = connections.synapsesForSegment(segment); - //2.2 remove empty segments if (synapses.size() == 0) { connections.destroySegment(segment); } @@ -244,7 +234,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, @@ -262,7 +252,7 @@ static void activatePredictedColumn( // This cell might have multiple active segments. do { if (learn) { - adaptSegment(connections, *activeSegment, prevActiveCellsDense, + adaptSegment(connections, *activeSegment, prevActiveCells, permanenceIncrement, permanenceDecrement); const Int32 nGrowDesired = @@ -313,7 +303,7 @@ burstColumn(vector &activeCells, UInt column, vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, - const vector &prevActiveCellsDense, + const SDR &prevActiveCells, const vector &prevWinnerCells, const vector &numActivePotentialSynapsesForSegment, UInt64 iteration, @@ -350,7 +340,7 @@ burstColumn(vector &activeCells, if (learn) { if (bestMatchingSegment != columnMatchingSegmentsEnd) { // Learn on the best matching segment. - adaptSegment(connections, *bestMatchingSegment, prevActiveCellsDense, + adaptSegment(connections, *bestMatchingSegment, prevActiveCells, permanenceIncrement, permanenceDecrement); const Int32 nGrowDesired = @@ -384,12 +374,12 @@ 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, + adaptSegment(connections, *matchingSegment, prevActiveCells, -predictedSegmentDecrement, 0.0); } } @@ -407,10 +397,8 @@ 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_); @@ -439,7 +427,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { activatePredictedColumn( activeCells_, winnerCells_, connections, rng_, columnActiveSegmentsBegin, columnActiveSegmentsEnd, - prevActiveCellsDense, prevWinnerCells, + prevActiveCells, prevWinnerCells, numActivePotentialSynapsesForSegment_, maxNewSynapseCount_, initialPermanence_, permanenceIncrement_, permanenceDecrement_, maxSynapsesPerSegment_, learn); @@ -447,7 +435,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { burstColumn(activeCells_, winnerCells_, connections, rng_, lastUsedIterationForSegment_, column, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, - prevActiveCellsDense, prevWinnerCells, + prevActiveCells, prevWinnerCells, numActivePotentialSynapsesForSegment_, iteration_, cellsPerColumn_, maxNewSynapseCount_, initialPermanence_, permanenceIncrement_, permanenceDecrement_, @@ -456,7 +444,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { } else { if (learn) { punishPredictedColumn(connections, columnMatchingSegmentsBegin, - columnMatchingSegmentsEnd, prevActiveCellsDense, + columnMatchingSegmentsEnd, prevActiveCells, predictedSegmentDecrement_); } } From f6f029ec306ef0a712e175a18c2bfe517d045768 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 11:34:14 +0200 Subject: [PATCH 09/35] Connections:adaptSegment remove empty segments segments with no synapses (can be removed when pruneZeroSynapses is set ON) are pruned too by calling `destroySegment`. --- src/htm/algorithms/Connections.cpp | 5 +++++ src/htm/algorithms/Connections.hpp | 3 ++- src/htm/algorithms/TemporalMemory.cpp | 25 ++++++------------------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 0c71d4d6ab..5506b7bc71 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -483,6 +483,11 @@ void Connections::adaptSegment(const Segment segment, updateSynapsePermanence(synapse, synapseData.permanence + update); } } + + //destroy segment if it is empty + if(synapses.empty()) { + destroySegment(segment); + } } /** diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index f367938495..473e2db025 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -419,7 +419,8 @@ class Connections : public Serializable * @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. + * 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, diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index c193f4ae27..d101124f2f 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -170,19 +170,6 @@ 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 SDR &prevActiveCells, - Permanence permanenceIncrement, - Permanence permanenceDecrement) { - //1. update synapses - connections.adaptSegment(segment, prevActiveCells, permanenceIncrement, permanenceDecrement, true /* prune empty synapses */); - - //2. remove empty segments //TODO move to Connections? - const vector &synapses = connections.synapsesForSegment(segment); - if (synapses.size() == 0) { - connections.destroySegment(segment); - } -} static void growSynapses(Connections &connections, Random &rng, @@ -252,8 +239,8 @@ static void activatePredictedColumn( // This cell might have multiple active segments. do { if (learn) { - adaptSegment(connections, *activeSegment, prevActiveCells, - permanenceIncrement, permanenceDecrement); + connections.adaptSegment(*activeSegment, prevActiveCells, + permanenceIncrement, permanenceDecrement, true); const Int32 nGrowDesired = maxNewSynapseCount - @@ -340,8 +327,8 @@ burstColumn(vector &activeCells, if (learn) { if (bestMatchingSegment != columnMatchingSegmentsEnd) { // Learn on the best matching segment. - adaptSegment(connections, *bestMatchingSegment, prevActiveCells, - permanenceIncrement, permanenceDecrement); + connections.adaptSegment(*bestMatchingSegment, prevActiveCells, + permanenceIncrement, permanenceDecrement, true); const Int32 nGrowDesired = maxNewSynapseCount - @@ -379,8 +366,8 @@ static void punishPredictedColumn( if (predictedSegmentDecrement > 0.0) { for (auto matchingSegment = columnMatchingSegmentsBegin; matchingSegment != columnMatchingSegmentsEnd; matchingSegment++) { - adaptSegment(connections, *matchingSegment, prevActiveCells, - -predictedSegmentDecrement, 0.0); + connections.adaptSegment(*matchingSegment, prevActiveCells, + -predictedSegmentDecrement, 0.0, true); } } } From 14871c9ebe3f3bac8897b147c56ca3bcd8fa2556 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 12:23:57 +0200 Subject: [PATCH 10/35] SegmentData has simple default constructor --- src/htm/algorithms/Connections.cpp | 11 ++++------- src/htm/algorithms/Connections.hpp | 4 +++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 5506b7bc71..fe9526aa92 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -75,7 +75,7 @@ void Connections::unsubscribe(UInt32 token) { eventHandlers_.erase(token); } -Segment Connections::createSegment(CellIdx cell) { +Segment Connections::createSegment(const CellIdx cell) { Segment segment; if (!destroyedSegments_.empty() ) { //reuse old, destroyed segs segment = destroyedSegments_.back(); @@ -84,17 +84,14 @@ 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()); + const SegmentData& segmentData = SegmentData(cell); + segments_.push_back(segmentData); segmentOrdinals_.push_back(0); } - 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); diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 473e2db025..56eb5a240f 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -90,6 +90,8 @@ struct SynapseData: public Serializable { * The cell that this segment is on. */ struct SegmentData { + SegmentData(const CellIdx cell) : cell(cell), numConnected(0) {} //default constructor + std::vector synapses; CellIdx cell; SynapseIdx numConnected; @@ -224,7 +226,7 @@ class Connections : public Serializable * * @retval Created segment. */ - Segment createSegment(CellIdx cell); + Segment createSegment(const CellIdx cell); /** * Creates a synapse on the specified segment. From a25040c7aa2da3e145c00778ed11418d767d7a3a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 13:14:03 +0200 Subject: [PATCH 11/35] Connections:createSegment can check for maxSegmentsPerCell can set limit on max segments on cell, if reached, least recently used segments will be pruned to make space. Used by TM. --- .../bindings/algorithms/py_Connections.cpp | 6 ++++- src/htm/algorithms/Connections.cpp | 24 ++++++++++++++++++- src/htm/algorithms/Connections.hpp | 10 +++++--- src/htm/algorithms/TemporalMemory.cpp | 22 +++-------------- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp index 16ccca5602..01c3031f5f 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp @@ -49,7 +49,11 @@ 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::arg("usage") = nullptr, + py::arg("iteration") = 0 + ); py_Connections.def("destroySegment", &Connections::destroySegment); diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index fe9526aa92..df23c2a84a 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -75,7 +75,23 @@ void Connections::unsubscribe(UInt32 token) { eventHandlers_.erase(token); } -Segment Connections::createSegment(const CellIdx cell) { +Segment Connections::createSegment(const CellIdx cell, + const SegmentIdx maxSegmentsPerCell, + vector* usage, //TODO move usage to SegmentData.lastUsed ? + const UInt64 iteration) { //TODO move iteration to Connections.iteration_ ? + + //limit number of segmets per cell. If exceeded, remove the least recently used ones. + while (usage != nullptr && numSegments(cell) >= maxSegmentsPerCell) { + const auto& destroyCandidates = segmentsForCell(cell); + const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { + return (usage->operator[](a) < usage->operator[](b)); }; + 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(); @@ -89,6 +105,11 @@ Segment Connections::createSegment(const CellIdx cell) { segmentOrdinals_.push_back(0); } + if(maxSegmentsPerCell > 0) { + usage->resize(segmentFlatListLength()); + usage->operator[](segment) = iteration; + } + CellData &cellData = cells_[cell]; segmentOrdinals_[segment] = nextSegmentOrdinal_++; cellData.segments.push_back(segment); //assign the new segment to its mother-cell @@ -100,6 +121,7 @@ Segment Connections::createSegment(const CellIdx cell) { return segment; } + Synapse Connections::createSynapse(Segment segment, CellIdx presynapticCell, Permanence permanence) { diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 56eb5a240f..17abc1cb48 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -93,8 +93,9 @@ struct SegmentData { SegmentData(const CellIdx cell) : cell(cell), numConnected(0) {} //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` }; /** @@ -226,7 +227,10 @@ class Connections : public Serializable * * @retval Created segment. */ - Segment createSegment(const CellIdx cell); + Segment createSegment(const CellIdx cell, + const SegmentIdx maxSegmentsPerCell = 0, + std::vector* usage = nullptr, + const UInt64 iteration = 0); //TODO document /** * Creates a synapse on the specified segment. diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index d101124f2f..24496b46c7 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -238,12 +238,12 @@ static void activatePredictedColumn( // This cell might have multiple active segments. do { - if (learn) { + 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, @@ -260,24 +260,8 @@ static Segment createSegment(Connections &connections, //TODO remove, use TM::c 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; + const Segment segment = connections.createSegment(cell, maxSegmentsPerCell, &lastUsedIterationForSegment, iteration); return segment; } From 9648ae08af25fa7cd452c73e2b37c7eddcef2026 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 15:18:52 +0200 Subject: [PATCH 12/35] TM: move lastUsedIterationForSegment to SegmentData.lastUsed this field is used for createSegment which optionally (maxSegmentsPerCell > 0) removes excessive segments (least used first). Only used by TM. --- .../bindings/algorithms/py_Connections.cpp | 1 - src/htm/algorithms/Connections.cpp | 26 +++----------- src/htm/algorithms/Connections.hpp | 34 ++++++++++++++----- src/htm/algorithms/SpatialPooler.cpp | 2 +- src/htm/algorithms/TemporalMemory.cpp | 8 ++--- 5 files changed, 35 insertions(+), 36 deletions(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp index 01c3031f5f..6c03358761 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp @@ -51,7 +51,6 @@ R"(Compatibility Warning: This classes API is unstable and may change without wa py_Connections.def("createSegment", &Connections::createSegment, py::arg("cell"), py::arg("maxSegmentsPerCell") = 0, - py::arg("usage") = nullptr, py::arg("iteration") = 0 ); diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index df23c2a84a..4110ed23d7 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -77,14 +77,14 @@ void Connections::unsubscribe(UInt32 token) { Segment Connections::createSegment(const CellIdx cell, const SegmentIdx maxSegmentsPerCell, - vector* usage, //TODO move usage to SegmentData.lastUsed ? - const UInt64 iteration) { //TODO move iteration to Connections.iteration_ ? + const UInt32 iteration) { //TODO move iteration to Connections.iteration_ ? //limit number of segmets per cell. If exceeded, remove the least recently used ones. - while (usage != nullptr && numSegments(cell) >= maxSegmentsPerCell) { + NTA_ASSERT(maxSegmentsPerCell > 0); + while (maxSegmentsPerCell > 1 && numSegments(cell) >= maxSegmentsPerCell) { const auto& destroyCandidates = segmentsForCell(cell); const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { - return (usage->operator[](a) < usage->operator[](b)); }; + return (dataForSegment(a).lastUsed < dataForSegment(b).lastUsed); }; const auto leastRecentlyUsedSegment = std::min_element(destroyCandidates.cbegin(), destroyCandidates.cend(), compareSegmentsByLRU); @@ -100,16 +100,11 @@ Segment Connections::createSegment(const 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()); - const SegmentData& segmentData = SegmentData(cell); + const SegmentData& segmentData = SegmentData(cell, iteration); segments_.push_back(segmentData); segmentOrdinals_.push_back(0); } - if(maxSegmentsPerCell > 0) { - usage->resize(segmentFlatListLength()); - usage->operator[](segment) = iteration; - } - CellData &cellData = cells_[cell]; segmentOrdinals_[segment] = nextSegmentOrdinal_++; cellData.segments.push_back(segment); //assign the new segment to its mother-cell @@ -366,17 +361,6 @@ 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]; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 17abc1cb48..0bb5ff0857 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -90,12 +90,12 @@ struct SynapseData: public Serializable { * The cell that this segment is on. */ struct SegmentData { - SegmentData(const CellIdx cell) : cell(cell), numConnected(0) {} //default constructor + SegmentData(const CellIdx cell, UInt32 lastUsed = 0) : cell(cell), numConnected(0), lastUsed(lastUsed) {} //default constructor std::vector synapses; 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` + UInt32 lastUsed = 0; //last used time (iteration). Used for segment pruning by "least recently used" (LRU) in `createSegment` }; /** @@ -225,12 +225,19 @@ class Connections : public Serializable * * @param cell Cell to create segment on. * - * @retval Created segment. + * @param maxSegmetsPerCell Optional, default 1. Enforce limit on maximum number of segments that can be + * created on a Cell. `createSegment` checks this limit only if `maxSegmentsPerCell > 1`. If the limit + * is exceeded, call `destroySegment` to remove least used segments (ordered by LRU `SegmentData.lastUsed`) + * + * @param iteration Optional. Used only if `maxSegmentsPerCell` > 1. In that case, `iteration` is assigned as + * "timestamp" to SegmentData.lastUsed. + * + * @retval Created segment `seg`. Use `dataForSegment(seg)` to obtain the segment's data. Use `idxOfSegmentOnCell()` + * to get SegmentIdx of `seg` on this `cell`. */ Segment createSegment(const CellIdx cell, - const SegmentIdx maxSegmentsPerCell = 0, - std::vector* usage = nullptr, - const UInt64 iteration = 0); //TODO document + const SegmentIdx maxSegmentsPerCell = 1, + const UInt32 iteration = 0); /** * Creates a synapse on the specified segment. @@ -324,7 +331,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. @@ -333,7 +342,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. @@ -342,7 +356,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. diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 8b83b79e08..aa29fb375a 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -361,7 +361,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; } diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 24496b46c7..dcee85f030 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -261,7 +261,7 @@ static Segment createSegment(Connections &connections, //TODO remove, use TM::c CellIdx cell, UInt64 iteration, UInt maxSegmentsPerCell) { - const Segment segment = connections.createSegment(cell, maxSegmentsPerCell, &lastUsedIterationForSegment, iteration); + const Segment segment = connections.createSegment(cell, maxSegmentsPerCell, static_cast(iteration)); return segment; } @@ -474,11 +474,11 @@ void TemporalMemory::activateDendrites(const bool learn, } } 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); //TODO why sorted? // Update segment bookkeeping. if (learn) { - for (const auto &segment : activeSegments_) { - lastUsedIterationForSegment_[segment] = iteration_; + for (auto &segment : activeSegments_) { + connections.dataForSegment(segment).lastUsed = iteration_; //TODO the destroySegments based on LRU is expensive. Better random? or "energy" based on sum permanences? } iteration_++; } From e20c2676be8d09090d0b00d4ebb13d3c919e284d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 15:49:42 +0200 Subject: [PATCH 13/35] TM uses Connections' createSegment --- src/htm/algorithms/TemporalMemory.cpp | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index dcee85f030..d86274f5ac 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -256,14 +256,6 @@ static void activatePredictedColumn( } while (activeSegment != columnActiveSegmentsEnd); } -static Segment createSegment(Connections &connections, //TODO remove, use TM::createSegment - vector &lastUsedIterationForSegment, - CellIdx cell, UInt64 iteration, - UInt maxSegmentsPerCell) { - - const Segment segment = connections.createSegment(cell, maxSegmentsPerCell, static_cast(iteration)); - return segment; -} static void burstColumn(vector &activeCells, @@ -330,8 +322,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, iteration); growSynapses(connections, rng, segment, nGrowExact, prevWinnerCells, initialPermanence, maxSynapsesPerSegment); @@ -531,14 +522,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_; } From 24b2c36bb77da2b47680c4e070cf5c47835e5cfa Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 17:46:40 +0200 Subject: [PATCH 14/35] Connections: add const + fix tests --- src/htm/algorithms/Connections.cpp | 34 ++++++-------- src/htm/algorithms/Connections.hpp | 45 ++++++++++++------- src/htm/algorithms/TemporalMemory.cpp | 3 +- src/htm/algorithms/TemporalMemory.hpp | 9 ++-- .../unit/algorithms/TemporalMemoryTest.cpp | 4 +- 5 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 4110ed23d7..e34e8863a1 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -31,7 +31,9 @@ using std::string; using std::vector; using namespace htm; -Connections::Connections(CellIdx numCells, Permanence connectedThreshold, bool timeseries) { +Connections::Connections(const CellIdx numCells, + const Permanence connectedThreshold, + const bool timeseries) { initialize(numCells, connectedThreshold, timeseries); } @@ -158,14 +160,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()); } -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; @@ -195,7 +197,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); @@ -224,6 +227,7 @@ void Connections::destroySegment(Segment segment) { destroyedSegments_.push_back(segment); } + void Connections::destroySynapse(const Synapse synapse) { NTA_ASSERT(synapseExists_(synapse)); for (auto h : eventHandlers_) { @@ -273,6 +277,7 @@ void Connections::destroySynapse(const Synapse synapse) { destroyedSynapses_.push_back(synapse); } + void Connections::updateSynapsePermanence(const Synapse synapse, Permanence permanence) { permanence = std::min(permanence, maxPermanence ); @@ -325,30 +330,15 @@ void Connections::updateSynapsePermanence(const 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 { @@ -394,6 +384,7 @@ void Connections::reset() currentUpdates_.clear(); } + void Connections::computeActivity( vector &numActiveConnectedSynapsesForSegment, const vector &activePresynapticCells) @@ -493,6 +484,7 @@ void Connections::adaptSegment(const Segment segment, } } + /** * Called for under-performing Segments (can have synapses pruned, etc.). After * the call, Segment will have at least segmentThreshold synapses connected, so diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 0bb5ff0857..1f1def05aa 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -204,8 +204,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() {} @@ -217,8 +218,9 @@ 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. @@ -282,7 +284,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. @@ -291,7 +295,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. @@ -300,7 +307,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. @@ -368,7 +377,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. @@ -387,7 +398,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. @@ -397,7 +408,7 @@ class Connections : public Serializable * @return Synapse indices */ std::vector - synapsesForPresynapticCell(CellIdx presynapticCell) const; + synapsesForPresynapticCell(const CellIdx presynapticCell) const; /** * For use with time-series datasets. @@ -546,7 +557,7 @@ class Connections : public Serializable */ size_t numCells() const { return cells_.size(); } - Permanence getConnectedThreshold() const { return connectedThreshold_; } + constexpr Permanence getConnectedThreshold() const { return connectedThreshold_; } /** * Gets the number of segments. @@ -562,7 +573,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. @@ -579,7 +592,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. @@ -619,7 +634,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. @@ -628,7 +643,7 @@ 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. diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index d86274f5ac..663133cff5 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -262,7 +262,6 @@ burstColumn(vector &activeCells, vector &winnerCells, Connections &connections, Random &rng, - vector &lastUsedIterationForSegment, UInt column, vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, @@ -395,7 +394,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { maxSynapsesPerSegment_, learn); } else { burstColumn(activeCells_, winnerCells_, connections, rng_, - lastUsedIterationForSegment_, column, + column, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, prevActiveCells, prevWinnerCells, numActivePotentialSynapsesForSegment_, iteration_, diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index 4c77045d2a..062826cecd 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -266,7 +266,8 @@ class TemporalMemory : public Serializable * @return Segment * The created segment. */ - Segment createSegment(const CellIdx& cell); + Segment createSegment(const CellIdx& cell, const UInt32 iteration = 0) { + return connections.createSegment(cell, maxSegmentsPerCell_, iteration); } /** * Returns the indices of cells that belong to a mini-column. @@ -545,9 +546,6 @@ class TemporalMemory : public Serializable matchingSegments_[i] = segment; numActivePotentialSynapsesForSegment_[segment] = c.syn; } - - lastUsedIterationForSegment_.resize(connections.segmentFlatListLength()); - } @@ -625,8 +623,7 @@ class TemporalMemory : public Serializable vector numActiveConnectedSynapsesForSegment_; vector numActivePotentialSynapsesForSegment_; - UInt64 iteration_; - vector lastUsedIterationForSegment_; + UInt32 iteration_; //each call to `compute` increases this counter Real anomaly_; diff --git a/src/test/unit/algorithms/TemporalMemoryTest.cpp b/src/test/unit/algorithms/TemporalMemoryTest.cpp index cc3b29f7b7..a8703757df 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)); } } From abb8f967dbf4ed29c41db71a3c6c7d72210ecbb8 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 2 Jul 2019 18:04:21 +0200 Subject: [PATCH 15/35] comment --- src/htm/algorithms/TemporalMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 663133cff5..9d6b81023c 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -186,7 +186,7 @@ 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 //TODO is this biological? Randomly creating a synapse would be faster! 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); From cbf0d38c8af7a887bc67050566886d0ac434166c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jul 2019 12:52:08 +0200 Subject: [PATCH 16/35] Hotgym: print connections stats for SP, TM --- src/examples/hotgym/HelloSPTP.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index 50883c70ff..aaaac269d3 100644 --- a/src/examples/hotgym/HelloSPTP.cpp +++ b/src/examples/hotgym/HelloSPTP.cpp @@ -147,6 +147,12 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool if (e == EPOCHS - 1) { tAll.stop(); + //print connections stats + cout << "\nSP(local) " << spLocal.connections + << "\nSP(global) " << spGlobal.connections + << "\nTM " << tm.connections << "\n"; + + // output values cout << "Epoch = " << e << endl; cout << "Anomaly = " << an << endl; cout << "Anomaly (avg) = " << avgAnom10.getCurrentAvg() << endl; @@ -154,6 +160,8 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool cout << "SP (g)= " << outSP << endl; cout << "SP (l)= " << outSPlocal < Date: Wed, 3 Jul 2019 12:53:17 +0200 Subject: [PATCH 17/35] Connections: print stats on usage of pruned Segs, Syns and their reuse in destroyed buffers Maybe the buffers are not useful? --- src/htm/algorithms/Connections.cpp | 17 +++++++++++++---- src/htm/algorithms/Connections.hpp | 5 +++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index e34e8863a1..8e42e28c4e 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -124,7 +124,7 @@ Synapse Connections::createSynapse(Segment segment, Permanence permanence) { // Get an index into the synapses_ list, for the new synapse to reside at. Synapse synapse; - if (!destroyedSynapses_.empty() ) { + if (!destroyedSynapses_.empty() ) { //TODO implement some capping for mem footprint for destroyed synapses & segments synapse = destroyedSynapses_.back(); destroyedSynapses_.pop_back(); } else { @@ -462,6 +462,7 @@ void Connections::adaptSegment(const Segment segment, //prune permanences that reached zero if (pruneZeroSynapses && synapseData.permanence + update < htm::minPermanence + htm::Epsilon) { 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; @@ -481,6 +482,7 @@ void Connections::adaptSegment(const Segment segment, //destroy segment if it is empty if(synapses.empty()) { destroySegment(segment); + prunedSegs_++; //statistics } } @@ -587,6 +589,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; @@ -627,9 +632,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++; } } } @@ -638,7 +643,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; @@ -647,6 +652,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; } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 1f1def05aa..5736adbf51 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -689,6 +689,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 From 47eb41851fb66a4b04cf7bdcb45611be35a04d1d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jul 2019 13:05:38 +0200 Subject: [PATCH 18/35] Hotgym: add Metrics for SDRs from SP, TM, Input --- src/examples/hotgym/HelloSPTP.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index aaaac269d3..9a1584bb33 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 */ @@ -148,9 +156,14 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool tAll.stop(); //print connections stats - cout << "\nSP(local) " << spLocal.connections + cout << "\nInput :\n" << statsInput + << "\nSP(local) " << spLocal.connections + << "\nSP(local) " << statsSPlocal << "\nSP(global) " << spGlobal.connections - << "\nTM " << tm.connections << "\n"; + << "\nSP(global) " << statsSPglobal + << "\nTM " << tm.connections + << "\nTM " << statsTM + << "\n"; // output values cout << "Epoch = " << e << endl; From cccc3e5d733fa4e176ba9b8fec249362204b7e5a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 5 Jul 2019 08:13:41 +0200 Subject: [PATCH 19/35] Connection:createSegment fix check for maxSegmentsPerCell check even if maxSegmentPerCell == 1, otherwise we could create > 1 segments on such cell. --- src/htm/algorithms/Connections.cpp | 2 +- src/htm/algorithms/Connections.hpp | 11 ++++++----- src/htm/algorithms/SpatialPooler.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 8e42e28c4e..468771ef01 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -83,7 +83,7 @@ Segment Connections::createSegment(const CellIdx cell, //limit number of segmets per cell. If exceeded, remove the least recently used ones. NTA_ASSERT(maxSegmentsPerCell > 0); - while (maxSegmentsPerCell > 1 && numSegments(cell) >= maxSegmentsPerCell) { + while (numSegments(cell) >= maxSegmentsPerCell) { const auto& destroyCandidates = segmentsForCell(cell); const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { return (dataForSegment(a).lastUsed < dataForSegment(b).lastUsed); }; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 5736adbf51..0781d1c5f3 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -228,18 +228,19 @@ class Connections : public Serializable * @param cell Cell to create segment on. * * @param maxSegmetsPerCell Optional, default 1. Enforce limit on maximum number of segments that can be - * created on a Cell. `createSegment` checks this limit only if `maxSegmentsPerCell > 1`. If the limit - * is exceeded, call `destroySegment` to remove least used segments (ordered by LRU `SegmentData.lastUsed`) + * created on a Cell. If the limit is exceeded, call `destroySegment` to remove least used segments + * (ordered by LRU `SegmentData.lastUsed`) * * @param iteration Optional. Used only if `maxSegmentsPerCell` > 1. In that case, `iteration` is assigned as * "timestamp" to SegmentData.lastUsed. * - * @retval Created segment `seg`. Use `dataForSegment(seg)` to obtain the segment's data. Use `idxOfSegmentOnCell()` - * to get SegmentIdx of `seg` on this `cell`. + * @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(const CellIdx cell, const SegmentIdx maxSegmentsPerCell = 1, - const UInt32 iteration = 0); + const UInt32 iteration = 0); //TODO make max_int & check that it's provided when needed /** * Creates a synapse on the specified segment. diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index aa29fb375a..d70b378891 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -446,7 +446,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_); From 8a1e871ef8da2aee2f14a6ec2ff09015b8b8921d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 5 Jul 2019 08:26:04 +0200 Subject: [PATCH 20/35] COnnections:createSegment make sort deterministic --- src/htm/algorithms/Connections.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 468771ef01..073b2aaebb 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -86,7 +86,11 @@ Segment Connections::createSegment(const CellIdx cell, while (numSegments(cell) >= maxSegmentsPerCell) { const auto& destroyCandidates = segmentsForCell(cell); const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { - return (dataForSegment(a).lastUsed < dataForSegment(b).lastUsed); }; + 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); From e8fb5201ec5f5443468f1762739710ddacd341b3 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 5 Jul 2019 09:42:24 +0200 Subject: [PATCH 21/35] Connections: make sort statements deterministic and hopefully more readable. Deterministic by adding rule for case xx[a] == xx[b]: then return a < b --- src/htm/algorithms/Connections.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 073b2aaebb..bdce206e65 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -218,9 +218,11 @@ void Connections::destroySegment(const 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]; + std::lower_bound(cellData.segments.cbegin(), cellData.segments.cend(), + segment, + [&](const Segment a, const Segment b) { + if(segmentOrdinals_[a] == segmentOrdinals_[b]) return a < b; + else return segmentOrdinals_[a] < segmentOrdinals_[b]; }); NTA_ASSERT(segmentOnCell != cellData.segments.end()); @@ -268,9 +270,11 @@ void Connections::destroySynapse(const 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) { + if(synapseOrdinals_[a] == synapseOrdinals_[b]) return a < b; + else return synapseOrdinals_[a] < synapseOrdinals_[b]; }); NTA_ASSERT(synapseOnSegment != segmentData.synapses.end()); @@ -359,13 +363,12 @@ void Connections::mapSegmentsToCells(const Segment *segments_begin, 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: + if(segmentOrdinals_[a] == segmentOrdinals_[b]) return a < b; + else return segmentOrdinals_[a] < segmentOrdinals_[b]; + else return aData.cell < bData.cell; } vector From 37e7a1398ccbc9d739e01e97aedccf0a42b60c4e Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 5 Jul 2019 10:09:14 +0200 Subject: [PATCH 22/35] COnnections: make createSegment maxSegments default to disabled disabled = using max value of the data type --- src/htm/algorithms/Connections.hpp | 9 +++++---- src/test/unit/algorithms/ConnectionsTest.cpp | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 0781d1c5f3..388fdd701f 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -22,7 +22,7 @@ #ifndef NTA_CONNECTIONS_HPP #define NTA_CONNECTIONS_HPP -#include +#include #include #include #include @@ -227,9 +227,10 @@ class Connections : public Serializable * * @param cell Cell to create segment on. * - * @param maxSegmetsPerCell Optional, default 1. Enforce limit on maximum number of segments that can be + * @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`) + * (ordered by LRU `SegmentData.lastUsed`). Default value is numeric_limits::max() of the data-type, + * so effectively disabled. * * @param iteration Optional. Used only if `maxSegmentsPerCell` > 1. In that case, `iteration` is assigned as * "timestamp" to SegmentData.lastUsed. @@ -239,7 +240,7 @@ class Connections : public Serializable * */ Segment createSegment(const CellIdx cell, - const SegmentIdx maxSegmentsPerCell = 1, + const SegmentIdx maxSegmentsPerCell = std::numeric_limits::max(), const UInt32 iteration = 0); //TODO make max_int & check that it's provided when needed /** diff --git a/src/test/unit/algorithms/ConnectionsTest.cpp b/src/test/unit/algorithms/ConnectionsTest.cpp index 3099a608c9..bce1293b89 100644 --- a/src/test/unit/algorithms/ConnectionsTest.cpp +++ b/src/test/unit/algorithms/ConnectionsTest.cpp @@ -38,14 +38,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); @@ -55,7 +55,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); From 60a384ea66474caf2078ced2565cd18e0d0d55f5 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 9 Jul 2019 16:20:07 +0200 Subject: [PATCH 23/35] TM: optimize growSynapses --- src/htm/algorithms/TemporalMemory.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 97beaaf732..88154f4c83 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -207,10 +207,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? } } From c95c177e8365673a76f5ad732c1fb18ad0b218e6 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 10 Jul 2019 18:02:44 +0200 Subject: [PATCH 24/35] Connections: guard adaptSegment remove segment with pruneZeroSynapses --- src/htm/algorithms/Connections.cpp | 2 +- src/htm/algorithms/TemporalMemory.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index bdce206e65..6ccbb50816 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -487,7 +487,7 @@ void Connections::adaptSegment(const Segment segment, } //destroy segment if it is empty - if(synapses.empty()) { + if(pruneZeroSynapses and synapses.empty()) { destroySegment(segment); prunedSegs_++; //statistics } diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 9a77bd8901..03f062e320 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -464,7 +464,7 @@ void TemporalMemory::activateDendrites(const bool learn, } } const auto compareSegments = [&](const Segment a, const Segment b) { return connections.compareSegments(a, b); }; - std::sort( activeSegments_.begin(), activeSegments_.end(), compareSegments); //TODO why sorted? + // std::sort( activeSegments_.begin(), activeSegments_.end(), compareSegments); //not needed (?) // Update segment bookkeeping. if (learn) { for (auto &segment : activeSegments_) { From 4672daffc34b8448f750e26cc93434c4a9354aaa Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 10 Jul 2019 19:12:50 +0200 Subject: [PATCH 25/35] Connections: move iteration from TM to Connections. New method Connections::iteration() +provide python wrapper. Also, Connections computeActivity() takes param learn. --- .../bindings/algorithms/py_Connections.cpp | 27 +++++++++++------- src/htm/algorithms/Connections.cpp | 19 +++++++++---- src/htm/algorithms/Connections.hpp | 28 ++++++++++++++----- src/htm/algorithms/SpatialPooler.cpp | 7 +++-- src/htm/algorithms/SpatialPooler.hpp | 2 +- src/htm/algorithms/TemporalMemory.cpp | 15 ++++------ src/htm/algorithms/TemporalMemory.hpp | 8 ++---- 7 files changed, 64 insertions(+), 42 deletions(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp index 6c03358761..5cd7f5cad3 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp @@ -50,12 +50,13 @@ R"(Compatibility Warning: This classes API is unstable and may change without wa py_Connections.def("createSegment", &Connections::createSegment, py::arg("cell"), - py::arg("maxSegmentsPerCell") = 0, - py::arg("iteration") = 0 + 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"), @@ -96,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); @@ -113,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 ); @@ -126,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/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 6ccbb50816..ee31197189 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -53,6 +53,7 @@ void Connections::initialize(CellIdx numCells, Permanence connectedThreshold, bo NTA_CHECK(connectedThreshold >= minPermanence); NTA_CHECK(connectedThreshold <= maxPermanence); connectedThreshold_ = connectedThreshold - htm::Epsilon; + iteration_ = 0; // 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 @@ -78,8 +79,7 @@ void Connections::unsubscribe(UInt32 token) { } Segment Connections::createSegment(const CellIdx cell, - const SegmentIdx maxSegmentsPerCell, - const UInt32 iteration) { //TODO move iteration to Connections.iteration_ ? + const SegmentIdx maxSegmentsPerCell) { //limit number of segmets per cell. If exceeded, remove the least recently used ones. NTA_ASSERT(maxSegmentsPerCell > 0); @@ -106,7 +106,7 @@ Segment Connections::createSegment(const 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()); - const SegmentData& segmentData = SegmentData(cell, iteration); + const SegmentData& segmentData = SegmentData(cell, iteration_); segments_.push_back(segmentData); segmentOrdinals_.push_back(0); } @@ -394,9 +394,11 @@ void Connections::reset() 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 @@ -410,6 +412,7 @@ void Connections::computeActivity( if (connectedSegmentsForPresynapticCell_.count(cell)) { for(const auto& segment : connectedSegmentsForPresynapticCell_.at(cell)) { ++numActiveConnectedSynapsesForSegment[segment]; + //TODO move LRU update here from TM } } } @@ -418,14 +421,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(), @@ -674,6 +679,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 fc3a3592e0..b216d5102b 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -233,16 +233,12 @@ class Connections : public Serializable * (ordered by LRU `SegmentData.lastUsed`). Default value is numeric_limits::max() of the data-type, * so effectively disabled. * - * @param iteration Optional. Used only if `maxSegmentsPerCell` > 1. In that case, `iteration` is assigned as - * "timestamp" to SegmentData.lastUsed. - * * @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(const CellIdx cell, - const SegmentIdx maxSegmentsPerCell = std::numeric_limits::max(), - const UInt32 iteration = 0); //TODO make max_int & check that it's provided when needed + const SegmentIdx maxSegmentsPerCell = std::numeric_limits::max()); /** * Creates a synapse on the specified segment. @@ -434,13 +430,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 @@ -475,6 +476,16 @@ 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_; } + /** * Modify all permanence on the given segment, uniformly. * @@ -527,6 +538,7 @@ class Connections : public Serializable ar(CEREAL_NVP(connectedThreshold_)); ar(CEREAL_NVP(sizes)); ar(CEREAL_NVP(syndata)); + ar(CEREAL_NVP(iteration_)); } template @@ -551,6 +563,7 @@ class Connections : public Serializable } } } + ar(CEREAL_NVP(iteration_)); } /** @@ -674,6 +687,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. std::unordered_map> potentialSynapsesForPresynapticCell_; diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 3b91785df4..23ae04e249 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -466,7 +466,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_); @@ -813,9 +813,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 3f2d1cbe33..ae8de0d51d 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -900,7 +900,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 03f062e320..5fb5742524 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -131,7 +131,6 @@ void TemporalMemory::initialize( maxSegmentsPerCell_ = maxSegmentsPerCell; maxSynapsesPerSegment_ = maxSynapsesPerSegment; - iteration_ = 0; reset(); } @@ -266,7 +265,6 @@ burstColumn(vector &activeCells, const SDR &prevActiveCells, const vector &prevWinnerCells, const vector &numActivePotentialSynapsesForSegment, - UInt64 iteration, CellIdx cellsPerColumn, UInt maxNewSynapseCount, const Permanence initialPermanence, @@ -319,7 +317,7 @@ burstColumn(vector &activeCells, std::min(maxNewSynapseCount, (UInt32)prevWinnerCells.size()); if (nGrowExact > 0) { const Segment segment = - connections.createSegment(winnerCell, maxSegmentsPerCell, iteration); + connections.createSegment(winnerCell, maxSegmentsPerCell); growSynapses(connections, rng, segment, nGrowExact, prevWinnerCells, initialPermanence, maxSynapsesPerSegment); @@ -397,7 +395,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { column, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, prevActiveCells, prevWinnerCells, - numActivePotentialSynapsesForSegment_, iteration_, + numActivePotentialSynapsesForSegment_, cellsPerColumn_, maxNewSynapseCount_, initialPermanence_, permanenceIncrement_, permanenceDecrement_, maxSegmentsPerCell_, maxSynapsesPerSegment_, learn); @@ -454,7 +452,8 @@ void TemporalMemory::activateDendrites(const bool learn, numActivePotentialSynapsesForSegment_.assign(length, 0); connections.computeActivity(numActiveConnectedSynapsesForSegment_, numActivePotentialSynapsesForSegment_, - activeCells_); + activeCells_, + learn); // Active segments, connected synapses. activeSegments_.clear(); @@ -467,10 +466,9 @@ void TemporalMemory::activateDendrites(const bool learn, // std::sort( activeSegments_.begin(), activeSegments_.end(), compareSegments); //not needed (?) // Update segment bookkeeping. if (learn) { - for (auto &segment : activeSegments_) { - connections.dataForSegment(segment).lastUsed = iteration_; //TODO the destroySegments based on LRU is expensive. Better random? or "energy" based on sum permanences? + 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. @@ -724,7 +722,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 ff16639a6f..8533b5330c 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -270,8 +270,8 @@ class TemporalMemory : public Serializable * @return Segment * The created segment. */ - Segment createSegment(const CellIdx& cell, const UInt32 iteration = 0) { - return connections.createSegment(cell, maxSegmentsPerCell_, iteration); } + Segment createSegment(const CellIdx& cell) { + return connections.createSegment(cell, maxSegmentsPerCell_); } /** * Returns the indices of cells that belong to a mini-column. @@ -462,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_), @@ -518,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_), @@ -627,8 +625,6 @@ class TemporalMemory : public Serializable vector numActiveConnectedSynapsesForSegment_; vector numActivePotentialSynapsesForSegment_; - UInt32 iteration_; //each call to `compute` increases this counter - Real anomaly_; Random rng_; From 95b8b6d5e5d48a4f5557763a2075d958b7990ef5 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 10 Jul 2019 19:17:42 +0200 Subject: [PATCH 26/35] Conn: segment ordinals are never same for a != b so simplified compare lambdas --- src/htm/algorithms/Connections.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index ee31197189..f475b18332 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -128,7 +128,7 @@ Synapse Connections::createSynapse(Segment segment, Permanence permanence) { // Get an index into the synapses_ list, for the new synapse to reside at. Synapse synapse; - if (!destroyedSynapses_.empty() ) { //TODO implement some capping for mem footprint for destroyed synapses & segments + if (!destroyedSynapses_.empty() ) { synapse = destroyedSynapses_.back(); destroyedSynapses_.pop_back(); } else { @@ -221,8 +221,7 @@ void Connections::destroySegment(const Segment segment) { std::lower_bound(cellData.segments.cbegin(), cellData.segments.cend(), segment, [&](const Segment a, const Segment b) { - if(segmentOrdinals_[a] == segmentOrdinals_[b]) return a < b; - else return segmentOrdinals_[a] < segmentOrdinals_[b]; + return segmentOrdinals_[a] < segmentOrdinals_[b]; }); NTA_ASSERT(segmentOnCell != cellData.segments.end()); @@ -366,8 +365,7 @@ bool Connections::compareSegments(const Segment a, const Segment b) const { // default sort by cell if (aData.cell == bData.cell) //fallback to ordinals: - if(segmentOrdinals_[a] == segmentOrdinals_[b]) return a < b; - else return segmentOrdinals_[a] < segmentOrdinals_[b]; + return segmentOrdinals_[a] < segmentOrdinals_[b]; else return aData.cell < bData.cell; } From b5498f39cbcf792c36becf6ad9e976d384c5a2b2 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 10 Jul 2019 19:38:52 +0200 Subject: [PATCH 27/35] COnnections: prune segment if it never can connect again that is if num synapses left < connectedThreshold_ --- src/examples/mnist/MNIST_SP.cpp | 2 +- src/htm/algorithms/Connections.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/examples/mnist/MNIST_SP.cpp b/src/examples/mnist/MNIST_SP.cpp index 35c46fae1b..57593ddaf1 100644 --- a/src/examples/mnist/MNIST_SP.cpp +++ b/src/examples/mnist/MNIST_SP.cpp @@ -67,7 +67,7 @@ class MNIST { public: UInt verbosity = 1; - const UInt train_dataset_iterations = 2u; //epochs somewhat help, at linear time + const UInt train_dataset_iterations = 1u; //epochs somewhat help, at linear time void setup() { diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index f475b18332..b531083419 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -470,7 +470,8 @@ void Connections::adaptSegment(const Segment segment, } //prune permanences that reached zero - if (pruneZeroSynapses && synapseData.permanence + update < htm::minPermanence + htm::Epsilon) { + 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]` @@ -489,8 +490,8 @@ void Connections::adaptSegment(const Segment segment, } } - //destroy segment if it is empty - if(pruneZeroSynapses and synapses.empty()) { + //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 } From 6e07f068361924f4d34be4d1e9d29c7308879bc2 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 10 Jul 2019 21:27:28 +0200 Subject: [PATCH 28/35] TM code comments --- src/htm/algorithms/Connections.cpp | 1 - src/htm/algorithms/TemporalMemory.cpp | 41 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index b531083419..6d923f1c93 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -410,7 +410,6 @@ void Connections::computeActivity( if (connectedSegmentsForPresynapticCell_.count(cell)) { for(const auto& segment : connectedSegmentsForPresynapticCell_.at(cell)) { ++numActiveConnectedSynapsesForSegment[segment]; - //TODO move LRU update here from TM } } } diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 5fb5742524..3cae2610ec 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -135,6 +135,7 @@ void TemporalMemory::initialize( reset(); } +///* static CellIdx getLeastUsedCell(Random &rng, UInt column, //TODO remove static methods, use private instead const Connections &connections, UInt cellsPerColumn) { @@ -168,7 +169,7 @@ static CellIdx getLeastUsedCell(Random &rng, UInt column, //TODO remove static m NTA_THROW << "getLeastUsedCell failed to find a cell"; } - +//*/ static void growSynapses(Connections &connections, Random &rng, @@ -290,7 +291,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); @@ -360,20 +361,35 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { 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, @@ -381,8 +397,9 @@ 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, @@ -391,6 +408,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { initialPermanence_, permanenceIncrement_, permanenceDecrement_, maxSynapsesPerSegment_, learn); } else { + //...has not been predicted -> burstColumn(activeCells_, winnerCells_, connections, rng_, column, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, @@ -400,13 +418,14 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { permanenceIncrement_, permanenceDecrement_, maxSegmentsPerCell_, maxSynapsesPerSegment_, learn); } - } else { + + } else { // predicted but not active column -> unlearn if (learn) { punishPredictedColumn(connections, columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, prevActiveCells, predictedSegmentDecrement_); } - } + } //else: not predicted & not active -> no activity -> does not show up at all } segmentsValid_ = false; } @@ -458,12 +477,12 @@ void TemporalMemory::activateDendrites(const bool 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); //not needed (?) + std::sort( activeSegments_.begin(), activeSegments_.end(), compareSegments); //SDR requires sorted when constructed from activeSegments_ // Update segment bookkeeping. if (learn) { for (const auto segment : activeSegments_) { From 184ad2c711bafaa1278f6a50a7b32ae8a388190e Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 11 Jul 2019 08:21:09 +0200 Subject: [PATCH 29/35] TM:getLeastUsedCells improve doc --- src/htm/algorithms/TemporalMemory.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 3cae2610ec..34b3a42f54 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -136,26 +136,30 @@ void TemporalMemory::initialize( } ///* -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) { From 36ed0ca4b0e99941b1cbcbd87dbe1823050a144b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 12 Jul 2019 02:11:55 +0200 Subject: [PATCH 30/35] TM: add explanation for synapses only on new, unsynapsed segments --- src/htm/algorithms/TemporalMemory.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 34b3a42f54..97395c7f65 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -190,7 +190,16 @@ static void growSynapses(Connections &connections, vector candidates(prevWinnerCells.begin(), prevWinnerCells.end()); NTA_ASSERT(std::is_sorted(candidates.begin(), candidates.end())); - // Skip cells that are already synapsed on by this segment //TODO is this biological? Randomly creating a synapse would be faster! + // 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); From 5fe36973d1cc9995f49289d4fde02518e1442d04 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 13 Jul 2019 23:38:21 +0200 Subject: [PATCH 31/35] Connections: some more const --- src/htm/algorithms/Connections.cpp | 4 ++-- src/htm/algorithms/Connections.hpp | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 392fbd596e..d233f4bd04 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -167,8 +167,8 @@ Synapse Connections::createSynapse(Segment segment, 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_(const Synapse synapse) const { diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index b435415fbe..821910ad32 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -249,8 +249,8 @@ class Connections : public Serializable * * @reval Created synapse. */ - Synapse createSynapse(Segment segment, - CellIdx presynapticCell, + Synapse createSynapse(const Segment segment, + const CellIdx presynapticCell, Permanence permanence); /** @@ -258,7 +258,7 @@ class Connections : public Serializable * * @param segment Segment to destroy. */ - void destroySegment(Segment segment); + void destroySegment(const Segment segment); /** * Destroys synapse. @@ -317,7 +317,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. @@ -406,8 +406,7 @@ class Connections : public Serializable * * @return Synapse indices */ - std::vector - synapsesForPresynapticCell(const CellIdx presynapticCell) const; + std::vector synapsesForPresynapticCell(const CellIdx presynapticCell) const; /** * For use with time-series datasets. @@ -594,9 +593,9 @@ class Connections : public Serializable * * @retval Number of cells. */ - size_t numCells() const { return cells_.size(); } + size_t numCells() const noexcept { return cells_.size(); } - constexpr Permanence getConnectedThreshold() const { return connectedThreshold_; } + constexpr Permanence getConnectedThreshold() const noexcept { return connectedThreshold_; } /** * Gets the number of segments. @@ -689,7 +688,7 @@ class Connections : public Serializable * * @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. From d3186edde8f3c0f1edd5546fbd3ece1c135b1426 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sun, 14 Jul 2019 03:05:30 +0200 Subject: [PATCH 32/35] update determ output results --- src/examples/hotgym/HelloSPTP.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index 82772ebe04..757f691ed5 100644 --- a/src/examples/hotgym/HelloSPTP.cpp +++ b/src/examples/hotgym/HelloSPTP.cpp @@ -205,12 +205,12 @@ Real64 BenchmarkHotgym::run(UInt EPOCHS, bool useSPlocal, bool useSPglobal, bool SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 51, 62, 72, 77, 102, 155, 287, 306, 337, 340, 370, 493, 542, 952, 1089, 1110, 1115, 1193, 1463, 1488, 1507, 1518, 1547, 1626, 1668, 1694, 1781, 1803, 1805, 1827, 1841, 1858,1859, 1860, 1861, 1862, 1878, 1881, 1915, 1918, 1923, 1929, 1933, 1939, 1941, 1953, 1955, 1956, 1958, 1961, 1965, 1968, 1975, 1976, 1980, 1981, 1985, 1986, 1987, 1991, 1992, 1994, 1997, 2002, 2006, 2008, 2012, 2013, 2040, 2042 + 62, 77, 85, 322, 340, 432, 952, 1120, 1488, 1502, 1512, 1518, 1547, 1627, 1633, 1668, 1727, 1729, 1797, 1803, 1805, 1812, 1858, 1859, 1896, 1918, 1923, 1925, 1929, 1931, 1939, 1941, 1942, 1944, 1950, 1953, 1955, 1956, 1965, 1966, 1967, 1968, 1974, 1980, 1987, 1996, 2006, 2008, 2011, 2027, 2030, 2042, 2046 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.745098f; - const float goldAnAvg = 0.408286f; + const float goldAn = 0.627451f; + const float goldAnAvg = 0.407265f; if(EPOCHS == 5000) { //these hand-written values are only valid for EPOCHS = 5000 (default), but not for debug and custom runs. NTA_CHECK(input == goldEnc) << "Deterministic output of Encoder failed!\n" << input << "should be:\n" << goldEnc; From fc38d899d10d46e9937ceb643b81062fb808bba7 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sun, 14 Jul 2019 02:29:20 +0200 Subject: [PATCH 33/35] Conn: synapseOrdinals_ moved to SynapseData --- src/htm/algorithms/Connections.cpp | 15 +++------------ src/htm/algorithms/Connections.hpp | 10 +++++++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index d233f4bd04..fd52e96309 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -48,19 +48,12 @@ void Connections::initialize(CellIdx numCells, Permanence connectedThreshold, bo potentialSegmentsForPresynapticCell_.clear(); connectedSegmentsForPresynapticCell_.clear(); segmentOrdinals_.clear(); - synapseOrdinals_.clear(); eventHandlers_.clear(); NTA_CHECK(connectedThreshold >= minPermanence); NTA_CHECK(connectedThreshold <= maxPermanence); connectedThreshold_ = connectedThreshold - htm::Epsilon; iteration_ = 0; - // 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; - nextEventToken_ = 0; timeseries_ = timeseries; @@ -136,14 +129,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_ = @@ -271,9 +263,8 @@ void Connections::destroySynapse(const Synapse synapse) { const auto synapseOnSegment = std::lower_bound(segmentData.synapses.cbegin(), segmentData.synapses.cend(), synapse, - [&](const Synapse a, const Synapse b) { - if(synapseOrdinals_[a] == synapseOrdinals_[b]) return a < b; - else return synapseOrdinals_[a] < synapseOrdinals_[b]; + [&](const Synapse a, const Synapse b) -> bool { + return dataForSynapse(a).id < dataForSynapse(b).id; }); NTA_ASSERT(synapseOnSegment != segmentData.synapses.end()); diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 821910ad32..5c2b951c32 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -47,6 +47,7 @@ const Permanence minPermanence = 0.0f; const Permanence maxPermanence = 1.0f; + /** * SynapseData class used in Connections. * @@ -64,6 +65,9 @@ struct SynapseData: public Serializable { Permanence permanence; Segment segment; Synapse presynapticMapIndex_; + Synapse id; + + SynapseData() {} CerealAdapter; template @@ -96,6 +100,7 @@ struct SegmentData { std::vector synapses; CellIdx cell; //mother cell that this segment originates from SynapseIdx numConnected; //number of permanences from `synapses` that are >= synPermConnected, ie connected synapses + Segment id; UInt32 lastUsed = 0; //last used time (iteration). Used for segment pruning by "least recently used" (LRU) in `createSegment` }; @@ -718,9 +723,8 @@ class Connections : public Serializable std::map> 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. From 5b368d83862e9f443f5d10c60735c5ff4921b2c4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sun, 14 Jul 2019 01:57:54 +0200 Subject: [PATCH 34/35] Conn: move segment ordinals (id) to SegmentData --- src/htm/algorithms/Connections.cpp | 9 +++------ src/htm/algorithms/Connections.hpp | 5 ++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index fd52e96309..f11362ce3b 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -47,7 +47,6 @@ void Connections::initialize(CellIdx numCells, Permanence connectedThreshold, bo connectedSynapsesForPresynapticCell_.clear(); potentialSegmentsForPresynapticCell_.clear(); connectedSegmentsForPresynapticCell_.clear(); - segmentOrdinals_.clear(); eventHandlers_.clear(); NTA_CHECK(connectedThreshold >= minPermanence); NTA_CHECK(connectedThreshold <= maxPermanence); @@ -99,13 +98,11 @@ Segment Connections::createSegment(const 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()); - const SegmentData& segmentData = SegmentData(cell, iteration_); + const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); segments_.push_back(segmentData); - segmentOrdinals_.push_back(0); } CellData &cellData = cells_[cell]; - segmentOrdinals_[segment] = nextSegmentOrdinal_++; cellData.segments.push_back(segment); //assign the new segment to its mother-cell for (auto h : eventHandlers_) { @@ -213,7 +210,7 @@ void Connections::destroySegment(const Segment segment) { std::lower_bound(cellData.segments.cbegin(), cellData.segments.cend(), segment, [&](const Segment a, const Segment b) { - return segmentOrdinals_[a] < segmentOrdinals_[b]; + return dataForSegment(a).id < dataForSegment(b).id; }); NTA_ASSERT(segmentOnCell != cellData.segments.end()); @@ -356,7 +353,7 @@ bool Connections::compareSegments(const Segment a, const Segment b) const { // default sort by cell if (aData.cell == bData.cell) //fallback to ordinals: - return segmentOrdinals_[a] < segmentOrdinals_[b]; + return aData.id < bData.id; else return aData.cell < bData.cell; } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 5c2b951c32..1bc4b74492 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -95,13 +95,13 @@ struct SynapseData: public Serializable { * The cell that this segment is on. */ struct SegmentData { - SegmentData(const CellIdx cell, UInt32 lastUsed = 0) : cell(cell), numConnected(0), lastUsed(lastUsed) {} //default constructor + SegmentData(const CellIdx cell, Segment id, UInt32 lastUsed = 0) : cell(cell), numConnected(0), lastUsed(lastUsed), id(id) {} //default constructor std::vector synapses; CellIdx cell; //mother cell that this segment originates from SynapseIdx numConnected; //number of permanences from `synapses` that are >= synPermConnected, ie connected synapses - Segment id; UInt32 lastUsed = 0; //last used time (iteration). Used for segment pruning by "least recently used" (LRU) in `createSegment` + Segment id; }; /** @@ -722,7 +722,6 @@ class Connections : public Serializable std::map> potentialSegmentsForPresynapticCell_; std::map> connectedSegmentsForPresynapticCell_; - std::vector segmentOrdinals_; Segment nextSegmentOrdinal_ = 0; Synapse nextSynapseOrdinal_ = 0; From 24983cf1505940d0aacc0d06b6e66d28d3f4c791 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 18 Jul 2019 19:35:45 +0200 Subject: [PATCH 35/35] fixup Connections:destroySegment use find use find to check the segment exists on the cell. Issue was in comparing const iteratior vs segments.end() --- src/htm/algorithms/Connections.cpp | 11 ++--------- src/test/unit/algorithms/TemporalMemoryTest.cpp | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 7fb36ec9ea..68a87a5371 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -206,18 +206,11 @@ void Connections::destroySegment(const Segment segment) { CellData &cellData = cells_[segmentData.cell]; - const auto segmentOnCell = - std::lower_bound(cellData.segments.cbegin(), cellData.segments.cend(), - segment, - [&](const Segment a, const Segment b) { - return dataForSegment(a).id < dataForSegment(b).id; - }); - - 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); } diff --git a/src/test/unit/algorithms/TemporalMemoryTest.cpp b/src/test/unit/algorithms/TemporalMemoryTest.cpp index a8703757df..31da6cc0f8 100644 --- a/src/test/unit/algorithms/TemporalMemoryTest.cpp +++ b/src/test/unit/algorithms/TemporalMemoryTest.cpp @@ -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,