Skip to content

Commit 09d5391

Browse files
authored
Merge pull request #466 from htm-community/syn-comp
New method Connections::synapseCompetiton
2 parents 44b2c4f + ac7a1d6 commit 09d5391

File tree

5 files changed

+219
-17
lines changed

5 files changed

+219
-17
lines changed

bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ R"(Returns pair of:
143143

144144
py_Connections.def("raisePermanencesToThreshold", &Connections::raisePermanencesToThreshold);
145145

146+
py_Connections.def("synapseCompetition", &Connections::synapseCompetition);
147+
146148
py_Connections.def("bumpSegment", &Connections::bumpSegment);
147149

148150
py_Connections.def("destroyMinPermanenceSynapses", &Connections::destroyMinPermanenceSynapses);

docs/synapse_competition.docx

181 KB
Binary file not shown.

src/htm/algorithms/Connections.cpp

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,15 +534,68 @@ void Connections::raisePermanencesToThreshold(
534534
if( increment <= 0 ) // If minPermSynPtr is already connected then ...
535535
return; // Enough synapses are already connected.
536536

537-
// Raise the permance of all synapses in the potential pool uniformly.
538-
for( const auto &syn : synapses ) //TODO vectorize: vector + const to all members
539-
updateSynapsePermanence(syn, synapses_[syn].permanence + increment); //this is performance HOTSPOT
537+
// Raise the permanence of all synapses in the potential pool uniformly.
538+
bumpSegment(segment, increment);
539+
}
540+
541+
542+
void Connections::synapseCompetition(
543+
const Segment segment,
544+
const SynapseIdx minimumSynapses,
545+
const SynapseIdx maximumSynapses)
546+
{
547+
NTA_ASSERT( minimumSynapses <= maximumSynapses);
548+
NTA_ASSERT( maximumSynapses > 0 );
549+
550+
const auto &segData = dataForSegment( segment );
551+
552+
if( segData.synapses.empty())
553+
return; // No synapses to work with, no work to do.
554+
555+
// Sort the potential pool by permanence values, and look for the synapse with
556+
// the N'th greatest permanence, where N is the desired number of connected
557+
// synapses. Then calculate how much to change the N'th synapses permance by
558+
// such that it becomes a connected synapse. After that there will be exactly
559+
// N synapses connected.
560+
SynapseIdx desiredConnected;
561+
if( segData.numConnected < minimumSynapses ) {
562+
desiredConnected = minimumSynapses;
563+
}
564+
else if( segData.numConnected > maximumSynapses ) {
565+
desiredConnected = maximumSynapses;
566+
}
567+
else {
568+
return; // The segment already satisfies the requirements, done.
569+
}
570+
// Can't connect more synapses than there are in the potential pool.
571+
desiredConnected = std::min( (SynapseIdx) segData.synapses.size(), desiredConnected);
572+
// The N'th synapse is at index N-1
573+
if( desiredConnected != 0 ) {
574+
desiredConnected--;
575+
}
576+
// else {
577+
// Corner case: there are no synapses on this segment.
578+
// }
579+
580+
vector<Permanence> permanences; permanences.reserve( segData.synapses.size() );
581+
for( Synapse syn : segData.synapses )
582+
permanences.push_back( synapses_[syn].permanence );
583+
584+
// Do a partial sort, it's faster than a full sort.
585+
auto minPermPtr = permanences.begin() + (segData.synapses.size() - 1 - desiredConnected);
586+
std::nth_element(permanences.begin(), minPermPtr, permanences.end());
587+
588+
Permanence delta = (connectedThreshold_ + htm::Epsilon) - *minPermPtr;
589+
590+
// Change the permance of all synapses in the potential pool uniformly.
591+
bumpSegment( segment, delta ) ;
540592
}
541593

542594

543595
void Connections::bumpSegment(const Segment segment, const Permanence delta) {
544596
const vector<Synapse> &synapses = synapsesForSegment(segment);
545-
for( const auto &syn : synapses ) {
597+
// TODO: vectorize?
598+
for( const auto syn : synapses ) {
546599
updateSynapsePermanence(syn, synapses_[syn].permanence + delta);
547600
}
548601
}

src/htm/algorithms/Connections.hpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,12 +428,32 @@ class Connections : public Serializable
428428
* values until the desired number of synapses have permanences above the
429429
* connectedThreshold. This is applied to a single segment.
430430
*
431-
* @param segment Index of segment on cell. Is returned by method getSegment.
431+
* @param segment Index of segment in connections. Is returned by method getSegment.
432432
* @param segmentThreshold Desired number of connected synapses.
433433
*/
434434
void raisePermanencesToThreshold(const Segment segment,
435435
const UInt segmentThreshold);
436436

437+
/**
438+
* Ensures that the number of connected synapses is sane. This method
439+
* controls the sparsity of the synaptic connections, which is important for
440+
* the segment to detect things. If there are too few connections then the
441+
* segment will not detect anything, and if there are too many connections
442+
* then the segment will detect everything.
443+
*
444+
* See file: docs/synapse_competition.docx
445+
*
446+
* This method connects and disconnects synapses by uniformly changing the
447+
* permanences of all synapses on the segment.
448+
*
449+
* @param segment Index of segment in connections. Is returned by method getSegment.
450+
* @param minimumSynapses Minimum number of connected synapses allowed on this segment (inclusive).
451+
* @param maximumSynapses Maximum number of connected synapses allowed on this segment (inclusive).
452+
*/
453+
void synapseCompetition( const Segment segment,
454+
const SynapseIdx minimumSynapses,
455+
const SynapseIdx maximumSynapses);
456+
437457
/**
438458
* Modify all permanence on the given segment, uniformly.
439459
*

src/test/unit/algorithms/ConnectionsTest.cpp

Lines changed: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,9 @@
2424
#include <iostream>
2525
#include <htm/algorithms/Connections.hpp>
2626

27-
namespace testing {
28-
2927
using namespace std;
3028
using namespace htm;
3129

32-
#define EPSILON 0.0000001
33-
3430

3531
void setupSampleConnections(Connections &connections) {
3632
// Cell with 1 segment.
@@ -122,11 +118,11 @@ TEST(ConnectionsTest, testCreateSynapse) {
122118

123119
SynapseData synapseData1 = connections.dataForSynapse(synapses[0]);
124120
ASSERT_EQ(50ul, synapseData1.presynapticCell);
125-
ASSERT_NEAR((Permanence)0.34, synapseData1.permanence, EPSILON);
121+
ASSERT_NEAR((Permanence)0.34, synapseData1.permanence, htm::Epsilon);
126122

127123
SynapseData synapseData2 = connections.dataForSynapse(synapses[1]);
128124
ASSERT_EQ(synapseData2.presynapticCell, 150ul);
129-
ASSERT_NEAR((Permanence)0.48, synapseData2.permanence, EPSILON);
125+
ASSERT_NEAR((Permanence)0.48, synapseData2.permanence, htm::Epsilon);
130126
}
131127

132128
/**
@@ -293,14 +289,14 @@ TEST(ConnectionsTest, testUpdateSynapsePermanence) {
293289
connections.updateSynapsePermanence(synapse, 0.21f);
294290

295291
SynapseData synapseData = connections.dataForSynapse(synapse);
296-
ASSERT_NEAR(synapseData.permanence, (Real)0.21, EPSILON);
292+
ASSERT_NEAR(synapseData.permanence, (Real)0.21, htm::Epsilon);
297293

298294
// Test permanence floor
299295
connections.updateSynapsePermanence(synapse, -0.02f);
300296
synapseData = connections.dataForSynapse(synapse);
301297
ASSERT_EQ(synapseData.permanence, (Real)0.0f );
302298

303-
connections.updateSynapsePermanence(synapse, (Real)(-EPSILON / 10.0));
299+
connections.updateSynapsePermanence(synapse, (Real)(-htm::Epsilon / 10.0));
304300
synapseData = connections.dataForSynapse(synapse);
305301
ASSERT_EQ(synapseData.permanence, (Real)0.0f );
306302

@@ -309,7 +305,7 @@ TEST(ConnectionsTest, testUpdateSynapsePermanence) {
309305
synapseData = connections.dataForSynapse(synapse);
310306
ASSERT_EQ(synapseData.permanence, (Real)1.0f );
311307

312-
connections.updateSynapsePermanence(synapse, 1.0f + (Real)(EPSILON / 10.0));
308+
connections.updateSynapsePermanence(synapse, 1.0f + (Real)(htm::Epsilon / 10.0));
313309
synapseData = connections.dataForSynapse(synapse);
314310
ASSERT_EQ(synapseData.permanence, (Real)1.0f );
315311
}
@@ -407,7 +403,7 @@ TEST(ConnectionsTest, testAdaptSynapses) {
407403
perms[ synData.presynapticCell ] = synData.permanence;
408404
}
409405
for(UInt i = 0; i < numInputs; i++)
410-
ASSERT_NEAR( truePerms[cell][i], perms[i], EPSILON );
406+
ASSERT_NEAR( truePerms[cell][i], perms[i], htm::Epsilon );
411407
}
412408
}
413409

@@ -480,6 +476,139 @@ TEST(ConnectionsTest, testRaisePermanencesToThresholdOutOfBounds) {
480476
<< "raisePermanence fails when lower number of available synapses than requested by threshold";
481477
}
482478

479+
TEST(ConnectionsTest, testSynapseCompetition) {
480+
481+
struct testCase {
482+
UInt nsyn; // Total number of potential synapses on segment
483+
UInt ncon; // Number of connected synapses, before calling synapseCompetition
484+
UInt min; // Bounds of synapseCompetition
485+
UInt max; // Bounds of synapseCompetition
486+
// The target number of synapses can't be met, just make sure it does not crash.
487+
bool expect_fail = false;
488+
};
489+
490+
testCase emptySegment;
491+
emptySegment.nsyn = 0;
492+
emptySegment.ncon = 0;
493+
emptySegment.min = 3;
494+
emptySegment.max = 100;
495+
emptySegment.expect_fail = true;
496+
497+
testCase fullSegment;
498+
fullSegment.nsyn = 100;
499+
fullSegment.ncon = 100;
500+
fullSegment.min = 3;
501+
fullSegment.max = 100;
502+
503+
testCase disconnect1;
504+
disconnect1.nsyn = 100;
505+
disconnect1.ncon = 100;
506+
disconnect1.min = 3;
507+
disconnect1.max = 99;
508+
509+
testCase minimum;
510+
minimum.nsyn = 100;
511+
minimum.ncon = 5;
512+
minimum.min = 10;
513+
minimum.max = 30;
514+
515+
testCase maximum;
516+
maximum.nsyn = 100;
517+
maximum.ncon = 77;
518+
maximum.min = 10;
519+
maximum.max = 30;
520+
521+
testCase no_change1;
522+
no_change1.nsyn = 100;
523+
no_change1.ncon = 10;
524+
no_change1.min = 10;
525+
no_change1.max = 30;
526+
527+
testCase no_change2;
528+
no_change2.nsyn = 100;
529+
no_change2.ncon = 20;
530+
no_change2.min = 10;
531+
no_change2.max = 30;
532+
533+
testCase no_change3;
534+
no_change3.nsyn = 100;
535+
no_change3.ncon = 30;
536+
no_change3.min = 10;
537+
no_change3.max = 30;
538+
539+
testCase exact1;
540+
exact1.nsyn = 100;
541+
exact1.ncon = 33;
542+
exact1.min = 33;
543+
exact1.max = 33;
544+
545+
testCase exact2;
546+
exact2.nsyn = 100;
547+
exact2.ncon = 0;
548+
exact2.min = 33;
549+
exact2.max = 33;
550+
551+
testCase exact3;
552+
exact3.nsyn = 100;
553+
exact3.ncon = 88;
554+
exact3.min = 33;
555+
exact3.max = 33;
556+
557+
testCase corner1;
558+
corner1.nsyn = 100;
559+
corner1.ncon = 30;
560+
corner1.min = 200;
561+
corner1.max = 300;
562+
corner1.expect_fail = true;
563+
564+
const Permanence thresh = 0.5f;
565+
Connections con(1u, thresh);
566+
Random rnd( 42u );
567+
CellIdx presyn = 0u;
568+
for(const testCase &test : {
569+
emptySegment, fullSegment, disconnect1, minimum, maximum, no_change1,
570+
no_change2, no_change3, exact1, exact2, exact3, corner1, })
571+
{
572+
const auto segment = con.createSegment( 0 );
573+
UInt ncon_done = 0;
574+
for(UInt i = test.nsyn; i > 0 ; --i) {
575+
// Randomly sample which synapses will connected.
576+
if( rnd.getReal64() <= Real64(test.ncon - ncon_done) / i ) {
577+
ncon_done++;
578+
con.createSynapse( segment, presyn++, rnd.realRange(thresh, 1.0f) );
579+
}
580+
else {
581+
con.createSynapse( segment, presyn++, rnd.realRange(0.0f, thresh) );
582+
}
583+
}
584+
// Check test setup is good.
585+
const auto &segData = con.dataForSegment( segment );
586+
ASSERT_EQ( test.nsyn, segData.synapses.size() );
587+
ASSERT_EQ( test.ncon, segData.numConnected );
588+
589+
con.synapseCompetition( segment, test.min, test.max );
590+
591+
// Check synapse data "numConnected" is accurate.
592+
int real_ncon = 0;
593+
for( const auto syn : segData.synapses ) {
594+
const auto &synData = con.dataForSynapse( syn );
595+
if( synData.permanence >= thresh - htm::Epsilon ) {
596+
real_ncon++;
597+
}
598+
}
599+
EXPECT_EQ( segData.numConnected, real_ncon );
600+
601+
// Check results of synapse competition.
602+
if( not test.expect_fail ) {
603+
EXPECT_GE( segData.numConnected, test.min );
604+
EXPECT_LE( segData.numConnected, test.max );
605+
if( test.ncon >= test.min and test.ncon <= test.max ) {
606+
EXPECT_EQ( segData.numConnected, test.ncon );
607+
}
608+
}
609+
}
610+
}
611+
483612
TEST(ConnectionsTest, testBumpSegment) {
484613
UInt numInputs = 8;
485614
UInt numSegments = 5;
@@ -737,5 +866,3 @@ TEST(ConnectionsTest, testTimeseries) {
737866
ASSERT_TRUE( (synData.permanence == 0.0f) or (synData.permanence == 1.0f) );
738867
}
739868
}
740-
741-
} // namespace

0 commit comments

Comments
 (0)