From e8217a9d2491b48a0d8bd7541b00abc2bd23cd4d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 22 Apr 2020 17:33:44 +0200 Subject: [PATCH 01/17] TM, SP: fix possible underflow in seed. Default random (0) seed. - fixed type for possible integer underflow in SP - default seed is "random" (0). Also in Python. - use directly fixed seed where needed in tests for deterministic results. (for this added c++ helper methods `setSeed(UInt)` ) - fix docs - update tests To be used by NAB detector. --- .../bindings/algorithms/py_SpatialPooler.cpp | 6 ++--- .../bindings/algorithms/py_TemporalMemory.cpp | 5 ++-- src/examples/hello/HelloSPTP.cpp | 11 +++++--- src/htm/algorithms/SpatialPooler.cpp | 4 +-- src/htm/algorithms/SpatialPooler.hpp | 26 ++++++++++++++----- src/htm/algorithms/TemporalMemory.cpp | 4 +-- src/htm/algorithms/TemporalMemory.hpp | 14 +++++++--- src/htm/regions/RDSEEncoderRegion.cpp | 6 ++--- src/htm/regions/SPRegion.cpp | 6 ++--- src/htm/regions/SPRegion.hpp | 2 +- src/htm/regions/TMRegion.cpp | 6 ++--- src/htm/regions/TMRegion.hpp | 2 +- .../unit/algorithms/SpatialPoolerTest.cpp | 15 +++++------ src/test/unit/engine/CppRegionTest.cpp | 2 +- .../unit/regions/ClassifierRegionTest.cpp | 6 ++--- src/test/unit/regions/TMRegionTest.cpp | 4 +-- 16 files changed, 71 insertions(+), 48 deletions(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp b/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp index 9561a7981e..4d25e10513 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_SpatialPooler.cpp @@ -157,9 +157,9 @@ Argument boostStrength A number greater or equal than 0, used to too much boosting may also lead to instability of SP outputs. -Argument seed Seed for our random number generator. If seed is < 0 +Argument seed Seed for our random number generator. If seed is 0 a randomly generated seed is used. The behavior of the spatial - pooler is deterministic once the seed is set. + pooler is deterministic once the seed is set > 0. Argument spVerbosity spVerbosity level: 0, 1, 2, or 3 @@ -180,7 +180,7 @@ Argument wrapAround boolean value that determines whether or not inputs , py::arg("minPctOverlapDutyCycle") = 0.001 , py::arg("dutyCyclePeriod") = 1000 , py::arg("boostStrength") = 0.0 - , py::arg("seed") = 1 + , py::arg("seed") = 0 , py::arg("spVerbosity") = 0 , py::arg("wrapAround") = true ); diff --git a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp index f7aa07150e..4ec4ccec73 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp @@ -112,7 +112,8 @@ Argument predictedSegmentDecrement 0.01 = 0.0004 Argument seed - Seed for the random number generator. + Seed for the random number generator. Default (0) means truly random, + > 0 is a fixed pseudorandom-sequence. See Random.hpp. Argument maxSegmentsPerCell The maximum number of segments per cell. @@ -146,7 +147,7 @@ Argument anomalyMode (optional, default ANMode::RAW) selects mode for `TM.anomal , py::arg("permanenceIncrement") = 0.1 , py::arg("permanenceDecrement") = 0.1 , py::arg("predictedSegmentDecrement") = 0.0 - , py::arg("seed") = 42 + , py::arg("seed") = 0 , py::arg("maxSegmentsPerCell") = 255 , py::arg("maxSynapsesPerSegment") = 255 , py::arg("checkInputs") = true diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 018b60479a..89c217ac08 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -72,8 +72,7 @@ EPOCHS = 2; // make test faster in Debug SpatialPooler spLocal(enc.dimensions, vector{COLS}); // Spatial pooler with local inh spGlobal.setGlobalInhibition(true); spLocal.setGlobalInhibition(false); - Random rnd(42); //uses fixed seed for deterministic output checks - + TemporalMemory tm(vector{COLS}, CELLS); AnomalyLikelihood anLikelihood; @@ -94,6 +93,12 @@ EPOCHS = 2; // make test faster in Debug Metrics statsSPglobal(outSPglobal, 1000); Metrics statsTM(outTM, 1000); + //uses fixed seed for deterministic output checks: + Random rnd(42); + spGlobal.setSeed(1); + spLocal.setSeed(1); + tm.setSeed(42); + /* * For example: fn = sin(x) -> periodic >= 2Pi ~ 6.3 && x+=0.01 -> 630 steps to 1st period -> window >= 630 */ @@ -201,7 +206,7 @@ EPOCHS = 2; // make test faster in Debug SDR goldSP({COLS}); const SDR_sparse_t deterministicSP{ - 62, 72, 73, 82, 85, 102, 263, 277, 287, 303, 306, 308, 309, 322, 337, 339, 340, 352, 370, 493, 1094, 1095, 1114, 1115, 1120, 1463, 1512, 1518, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1770, 1774, 1775, 1781, 1797, 1798, 1803, 1804, 1805, 1812, 1827, 1828, 1831, 1832, 1858, 1859, 1860, 1861, 1862, 1875, 1878, 1880, 1881, 1898, 1918, 1923, 1929, 1931,1936, 1950, 1953, 1956, 1958, 1961, 1964, 1965, 1967, 1971, 1973, 1975, 1976, 1979, 1980, 1981, 1982, 1984, 1985, 1986, 1988, 1991, 1994, 1996, 1997, 1998, 1999, 2002, 2006, 2008, 2011, 2012, 2013, 2017, 2019, 2022, 2027, 2030 + 66, 70, 72, 75, 76, 82, 83, 85, 99, 297, 300, 301, 303, 305, 306, 308, 309, 311, 316, 320, 321, 323, 324, 325, 327, 329, 330, 343, 345, 347, 363, 508, 1084, 1110, 1112, 1115, 1120, 1131, 1522, 1532, 1535, 1634, 1684, 1697, 1732, 1769, 1777, 1778, 1800, 1801, 1803, 1817, 1818, 1823, 1830, 1834, 1836, 1844, 1847, 1851, 1855, 1859, 1860, 1862, 1866, 1882, 1886, 1897,1906, 1918, 1921, 1922, 1931, 1936, 1954, 1961, 1964, 1965, 1966, 1967, 1968, 1970, 1971, 1972, 1973, 1975, 1977, 1978, 1980, 1981, 1987, 1989, 1990, 2001, 2006, 2007, 2009, 2015, 2016, 2018, 2020, 2042 }; goldSP.setSparse(deterministicSP); diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 594d84b6ad..634b53f562 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -76,7 +76,7 @@ SpatialPooler::SpatialPooler( Real localAreaDensity, UInt stimulusThreshold, Real synPermInactiveDec, Real synPermActiveInc, Real synPermConnected, Real minPctOverlapDutyCycles, UInt dutyCyclePeriod, - Real boostStrength, Int seed, UInt spVerbosity, bool wrapAround) + Real boostStrength, UInt seed, UInt spVerbosity, bool wrapAround) : SpatialPooler::SpatialPooler() { // The current version number for serialzation. @@ -370,7 +370,7 @@ void SpatialPooler::initialize( Real minPctOverlapDutyCycles, UInt dutyCyclePeriod, Real boostStrength, - Int seed, + UInt seed, UInt spVerbosity, bool wrapAround) { diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index fffd29ff87..bd26b296df 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -77,7 +77,7 @@ class SpatialPooler : public Serializable Real minPctOverlapDutyCycles = 0.001f, UInt dutyCyclePeriod = 1000u, Real boostStrength = 0.0f, - Int seed = 1, + UInt seed = 0u, //random UInt spVerbosity = 0u, bool wrapAround = true); @@ -186,9 +186,9 @@ class SpatialPooler : public Serializable too much boosting may also lead to instability of SP outputs. - @param seed Seed for our random number generator. If seed is < 0 + @param seed Seed for our random number generator. If seed is 0, a randomly generated seed is used. The behavior of the spatial - pooler is deterministic once the seed is set. + pooler is deterministic once the seed is fixed to > 0. @param spVerbosity spVerbosity level: 0, 1, 2, or 3 @@ -205,10 +205,15 @@ class SpatialPooler : public Serializable bool globalInhibition = true, Real localAreaDensity = 0.05f, UInt stimulusThreshold = 0u, - Real synPermInactiveDec = 0.01f, Real synPermActiveInc = 0.1f, - Real synPermConnected = 0.1f, Real minPctOverlapDutyCycles = 0.001f, - UInt dutyCyclePeriod = 1000u, Real boostStrength = 0.0f, - Int seed = 1, UInt spVerbosity = 0u, bool wrapAround = true); + Real synPermInactiveDec = 0.01f, + Real synPermActiveInc = 0.1f, + Real synPermConnected = 0.1f, + Real minPctOverlapDutyCycles = 0.001f, + UInt dutyCyclePeriod = 1000u, + Real boostStrength = 0.0f, + UInt seed = 0u, + UInt spVerbosity = 0u, + bool wrapAround = true); /** @@ -735,6 +740,13 @@ class SpatialPooler : public Serializable */ void getConnectedCounts(UInt connectedCounts[]) const; + /** set seed for internal random number generator. + * See @ref `seed` arg in the constructor. + */ + void setSeed(UInt seed) { + rng_ = Random(seed); + } + /** Returns the boosted overlap score for each column. diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index f493759e61..79e6945e82 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -63,7 +63,7 @@ TemporalMemory::TemporalMemory( Permanence permanenceIncrement, Permanence permanenceDecrement, Permanence predictedSegmentDecrement, - Int seed, + UInt seed, SegmentIdx maxSegmentsPerCell, SynapseIdx maxSynapsesPerSegment, bool checkInputs, @@ -90,7 +90,7 @@ void TemporalMemory::initialize( Permanence permanenceIncrement, Permanence permanenceDecrement, Permanence predictedSegmentDecrement, - Int seed, + UInt seed, SegmentIdx maxSegmentsPerCell, SynapseIdx maxSynapsesPerSegment, bool checkInputs, diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index 56c8825114..18fdbb7262 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -104,7 +104,8 @@ class TemporalMemory : public Serializable * something like 4% * 0.01 = 0.0004). * * @param seed - * Seed for the random number generator. + * Seed for the random number generator. 0 (default) means truly random, + * use > 0 for fixed pseudo-random. * * @param maxSegmentsPerCell * The maximum number of segments per cell. @@ -144,7 +145,7 @@ class TemporalMemory : public Serializable Permanence permanenceIncrement = 0.10, Permanence permanenceDecrement = 0.10, Permanence predictedSegmentDecrement = 0.0, - Int seed = 42, + UInt seed = 0, //random SegmentIdx maxSegmentsPerCell = 255, SynapseIdx maxSynapsesPerSegment = 255, bool checkInputs = true, @@ -164,7 +165,7 @@ class TemporalMemory : public Serializable Permanence permanenceIncrement = 0.10, Permanence permanenceDecrement = 0.10, Permanence predictedSegmentDecrement = 0.0, - Int seed = 42, + UInt seed = 0, SegmentIdx maxSegmentsPerCell = 255, SynapseIdx maxSynapsesPerSegment = 255, bool checkInputs = true, @@ -595,6 +596,10 @@ class TemporalMemory : public Serializable virtual bool operator==(const TemporalMemory &other) const; inline bool operator!=(const TemporalMemory &other) const { return not this->operator==(other); } + void setSeed(UInt seed) { + rng_ = Random(seed); + } + //---------------------------------------------------------------------- // Debugging helpers //---------------------------------------------------------------------- @@ -638,6 +643,9 @@ class TemporalMemory : public Serializable * */ SDR cellsToColumns(const SDR& cells) const; + + + private: void punishPredictedColumn_(vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, diff --git a/src/htm/regions/RDSEEncoderRegion.cpp b/src/htm/regions/RDSEEncoderRegion.cpp index dd93c58d47..cd9bdc7006 100644 --- a/src/htm/regions/RDSEEncoderRegion.cpp +++ b/src/htm/regions/RDSEEncoderRegion.cpp @@ -68,7 +68,7 @@ namespace htm { RDSEEncoderRegion::RDSEEncoderRegion(const ValueMap &par, Region *region) : RegionImpl(region) { - rnd_ = Random(42); + rnd_ = Random(42); //TODO use seed value here? spec_.reset(createSpec()); ValueMap params = ValidateParameters(par, spec_.get()); @@ -78,8 +78,8 @@ RDSEEncoderRegion::RDSEEncoderRegion(const ValueMap &par, Region *region) : Regi args.sparsity = params.getScalarT("sparsity"); args.radius = params.getScalarT("radius"); args.resolution = params.getScalarT("resolution"); - args.category = params.getScalarT("category"); - args.seed = params.getScalarT("seed"); + args.category = params.getScalarT("category", false); + args.seed = params.getScalarT("seed", 0); encoder_ = std::make_shared(args); sensedValue_ = params.getScalarT("sensedValue"); diff --git a/src/htm/regions/SPRegion.cpp b/src/htm/regions/SPRegion.cpp index 7efaa434b2..b98f02c0aa 100644 --- a/src/htm/regions/SPRegion.cpp +++ b/src/htm/regions/SPRegion.cpp @@ -54,7 +54,7 @@ SPRegion::SPRegion(const ValueMap &values, Region *region) args_.minPctOverlapDutyCycles = values.getScalarT("minPctOverlapDutyCycles", 0.001f); args_.dutyCyclePeriod = values.getScalarT("dutyCyclePeriod", 1000); args_.boostStrength = values.getScalarT("boostStrength", 0.0f); - args_.seed = values.getScalarT("seed", 1); + args_.seed = values.getScalarT("seed", 0u); args_.spVerbosity = values.getScalarT("spVerbosity", 0); args_.wrapAround = values.getScalarT("wrapAround", true); spatialImp_ = values.getString("spatialImp", ""); @@ -421,11 +421,11 @@ Spec *SPRegion::createSpec() { "seed", ParameterSpec( "(int)\n" - "Seed for our own pseudo - random number generator. Default ``-1``.", + "Seed for our own pseudo - random number generator. Default ``0``(=random), >0 means fixed.", NTA_BasicType_Int32, // type 1, // elementCount "", // constraints - "1", // defaultValue + "0", // defaultValue ParameterSpec::CreateAccess)); // access ns->parameters.add( diff --git a/src/htm/regions/SPRegion.hpp b/src/htm/regions/SPRegion.hpp index 021e24d08f..50d3b60777 100644 --- a/src/htm/regions/SPRegion.hpp +++ b/src/htm/regions/SPRegion.hpp @@ -174,7 +174,7 @@ class SPRegion : public RegionImpl, Serializable Real minPctOverlapDutyCycles; UInt dutyCyclePeriod; Real boostStrength; - Int seed; + UInt seed; UInt spVerbosity; bool wrapAround; bool learningMode; diff --git a/src/htm/regions/TMRegion.cpp b/src/htm/regions/TMRegion.cpp index 381a95e1d0..c3ed58e8ed 100644 --- a/src/htm/regions/TMRegion.cpp +++ b/src/htm/regions/TMRegion.cpp @@ -52,7 +52,7 @@ TMRegion::TMRegion(const ValueMap ¶ms, Region *region) args_.permanenceIncrement = params.getScalarT("permanenceIncrement", 0.10f); args_.permanenceDecrement = params.getScalarT("permanenceDecrement", 0.10f); args_.predictedSegmentDecrement = params.getScalarT("predictedSegmentDecrement", 0.0f); - args_.seed = params.getScalarT("seed", 42); + args_.seed = params.getScalarT("seed", 0); args_.maxSegmentsPerCell = params.getScalarT("maxSegmentsPerCell", 255u); args_.maxSynapsesPerSegment = params.getScalarT("maxSynapsesPerSegment", 255u); args_.checkInputs = params.getScalarT("checkInputs", true); @@ -416,11 +416,11 @@ Spec *TMRegion::createSpec() { "seed", ParameterSpec("(int) Random number generator seed. The seed affects the random " "aspects of initialization like the initial permanence values. A " - "fixed value ensures a reproducible result.", + "fixed value > 0 ensures a reproducible result.", NTA_BasicType_Int32, // type 1, // elementCount "", // constraints - "42", // defaultValue + "0", // defaultValue ParameterSpec::CreateAccess)); // access ///////// Parameters not part of the calling arguments ////////// diff --git a/src/htm/regions/TMRegion.hpp b/src/htm/regions/TMRegion.hpp index 905a04cfe1..495afea24a 100644 --- a/src/htm/regions/TMRegion.hpp +++ b/src/htm/regions/TMRegion.hpp @@ -165,7 +165,7 @@ class TMRegion : public RegionImpl, Serializable { Real32 permanenceIncrement; Real32 permanenceDecrement; Real32 predictedSegmentDecrement; - Int32 seed; + UInt32 seed; Int32 maxSegmentsPerCell; Int32 maxSynapsesPerSegment; UInt32 externalPredictiveInputs; diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 0b02ffdf0f..b4de9c9fef 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -1936,18 +1936,17 @@ TEST(SpatialPoolerTest, testSerialization_ar) { SpatialPooler sp1; sp1.initialize(inputDims, colDims); + sp1.setSeed(1); SDR input(inputDims); SDR output(colDims); + //burn-in the SP for (UInt i = 0; i < 100; ++i) { input.randomize(0.05f, random); //5% random ON sp1.compute(input, true, output); } - // Now we reuse the last input to test after serialization - - auto activeColumnsBefore = output.getSparse(); // Save initial trained model stringstream ss; @@ -1955,8 +1954,6 @@ TEST(SpatialPoolerTest, testSerialization_ar) { ss.precision(std::numeric_limits::digits10 + 1); sp1.save(ss); - SpatialPooler sp2; - htm::Timer testTimer; for (UInt i = 0; i < 6; ++i) { @@ -1967,14 +1964,14 @@ TEST(SpatialPoolerTest, testSerialization_ar) { SDR outputBaseline(output); sp1.compute(input, true, outputBaseline); - // C - Next do old version + // C - Next, verify the same results come from the de/serialized version { SpatialPooler spTemp; testTimer.start(); // Deserialize ss.seekg(0); - spTemp.load(ss); + EXPECT_NO_THROW(spTemp.load(ss)); // Feed new record through SDR outputC({numColumns}); @@ -1982,11 +1979,11 @@ TEST(SpatialPoolerTest, testSerialization_ar) { // Serialize ss.clear(); - spTemp.save(ss); + EXPECT_NO_THROW(spTemp.save(ss)); testTimer.stop(); - EXPECT_EQ(outputBaseline, outputC); + EXPECT_EQ(outputBaseline, outputC); //FIXME this test randomly fails. (De/serialization of rng_ is correct?) } } ss.clear(); diff --git a/src/test/unit/engine/CppRegionTest.cpp b/src/test/unit/engine/CppRegionTest.cpp index 78a44099ae..f930b606cd 100644 --- a/src/test/unit/engine/CppRegionTest.cpp +++ b/src/test/unit/engine/CppRegionTest.cpp @@ -111,7 +111,7 @@ TEST(CppRegionTest, testCppLinkingSDR) { Network net; std::shared_ptr region1 = net.addRegion("region1", "ScalarEncoderRegion", "{dim: [6,1], n: 6, w: 2}"); - std::shared_ptr region2 = net.addRegion("region2", "SPRegion", "{dim: [20,3]}"); + std::shared_ptr region2 = net.addRegion("region2", "SPRegion", "{dim: [20,3], seed: 1}"); //seed is fixed for deterministic results in tests net.link("region1", "region2"); diff --git a/src/test/unit/regions/ClassifierRegionTest.cpp b/src/test/unit/regions/ClassifierRegionTest.cpp index 1810c3084a..86513aa59d 100644 --- a/src/test/unit/regions/ClassifierRegionTest.cpp +++ b/src/test/unit/regions/ClassifierRegionTest.cpp @@ -101,7 +101,7 @@ TEST(ClassifierRegionTest, asCategoryDecoder) { Network net; std::shared_ptr encoder = net.addRegion("encoder", "RDSEEncoderRegion", "{size: 400, seed: 42, category: true, activeBits: 40}"); - std::shared_ptr sp = net.addRegion("sp", "SPRegion", "{columnCount: 1000, globalInhibition: true}"); + std::shared_ptr sp = net.addRegion("sp", "SPRegion", "{columnCount: 1000, globalInhibition: true, seed: 1}"); std::shared_ptr classifier = net.addRegion("classifier", "ClassifierRegion", "{learn: true}"); net.link("encoder", "sp", "", "", "encoded", "bottomUpIn"); @@ -144,7 +144,7 @@ TEST(ClassifierRegionTest, asRealDecoder) { Network net; std::shared_ptr encoder = net.addRegion("encoder", "RDSEEncoderRegion", "{size: 400, radius: 0.1, seed: 42, activeBits: 40}"); - std::shared_ptr sp = net.addRegion("sp", "SPRegion", "{columnCount: 1000, globalInhibition: true}"); + std::shared_ptr sp = net.addRegion("sp", "SPRegion", "{columnCount: 1000, globalInhibition: true, seed: 1}"); std::shared_ptr classifier = net.addRegion("classifier", "ClassifierRegion", "{learn: true}"); net.link("encoder", "sp", "", "", "encoded", "bottomUpIn"); @@ -244,4 +244,4 @@ TEST(ClassifierRegionTest, testSerialization) { Directory::removeTree("TestOutputDir", true); } -} // namespace testing \ No newline at end of file +} // namespace testing diff --git a/src/test/unit/regions/TMRegionTest.cpp b/src/test/unit/regions/TMRegionTest.cpp index b1d3d1193b..7f3c1ea9b1 100644 --- a/src/test/unit/regions/TMRegionTest.cpp +++ b/src/test/unit/regions/TMRegionTest.cpp @@ -211,9 +211,9 @@ TEST(TMRegionTest, testLinking) { // you can use JSON format as well) std::string parameters = "{activeOutputCount: " + std::to_string(dataWidth) + "}"; std::shared_ptr region1 = net.addRegion("region1", "FileInputRegion",parameters); - std::shared_ptr region2 = net.addRegion("region2", "SPRegion", "{dim: [2,10]}"); + std::shared_ptr region2 = net.addRegion("region2", "SPRegion", "{dim: [2,10], seed: 1}"); std::shared_ptr region3 = net.addRegion("region3", "TMRegion", - "{activationThreshold: 11, cellsPerColumn: 5}"); + "{activationThreshold: 11, cellsPerColumn: 5, seed: 42}"); std::shared_ptr region4 = net.addRegion("region4", "FileOutputRegion", "{outputFile: '" + test_output_file + "'}"); From e159de9e3950d5ff5902e58d960dd83cf4490261 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 15:39:10 +0200 Subject: [PATCH 02/17] Fix random seed for RDSERegion random seed was not properly applied. --- src/htm/regions/RDSEEncoderRegion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/htm/regions/RDSEEncoderRegion.cpp b/src/htm/regions/RDSEEncoderRegion.cpp index cd9bdc7006..c6537a7a26 100644 --- a/src/htm/regions/RDSEEncoderRegion.cpp +++ b/src/htm/regions/RDSEEncoderRegion.cpp @@ -68,7 +68,6 @@ namespace htm { RDSEEncoderRegion::RDSEEncoderRegion(const ValueMap &par, Region *region) : RegionImpl(region) { - rnd_ = Random(42); //TODO use seed value here? spec_.reset(createSpec()); ValueMap params = ValidateParameters(par, spec_.get()); @@ -82,6 +81,7 @@ RDSEEncoderRegion::RDSEEncoderRegion(const ValueMap &par, Region *region) : Regi args.seed = params.getScalarT("seed", 0); encoder_ = std::make_shared(args); + rnd_ = Random(encoder_->parameters.seed); sensedValue_ = params.getScalarT("sensedValue"); noise_ = params.getScalarT("noise"); } From a4a428348eda6b221101dd45601943724d6938b4 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 16:14:14 +0200 Subject: [PATCH 03/17] Random: improve rng serialization tests --- src/htm/algorithms/SpatialPooler.hpp | 3 +++ src/test/unit/algorithms/SpatialPoolerTest.cpp | 6 ++++-- src/test/unit/utils/RandomTest.cpp | 16 ++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index 9d8069f065..c5e38993fc 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -780,6 +780,9 @@ class SpatialPooler : public Serializable void setSeed(UInt seed) { rng_ = Random(seed); } + UInt getSeed() const { + return rng_.getSeed(); + } /** diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 001caa65b6..e34e4522f6 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -1958,8 +1958,8 @@ TEST(SpatialPoolerTest, testSerialization_ar) { // Save initial trained model stringstream ss; - ss.precision(std::numeric_limits::digits10 + 1); - ss.precision(std::numeric_limits::digits10 + 1); +// ss.precision(std::numeric_limits::digits10 + 1); +// ss.precision(std::numeric_limits::digits10 + 1); sp1.save(ss); htm::Timer testTimer; @@ -1975,6 +1975,8 @@ TEST(SpatialPoolerTest, testSerialization_ar) { // C - Next, verify the same results come from the de/serialized version { SpatialPooler spTemp; + ASSERT_EQ(spTemp.getSeed(), 1u); + testTimer.start(); // Deserialize diff --git a/src/test/unit/utils/RandomTest.cpp b/src/test/unit/utils/RandomTest.cpp index 01e7972d06..17fdb2f43e 100644 --- a/src/test/unit/utils/RandomTest.cpp +++ b/src/test/unit/utils/RandomTest.cpp @@ -180,7 +180,11 @@ TEST(RandomTest, testSerialization2) { TEST(RandomTest, testSerialization_ar) { // test serialization/deserialization - Random r1(862973); + const UInt SEED = 862973u; + Random r1(SEED); + ASSERT_EQ(r1.getSeed(), SEED) << "RNG seed not set properly"; + + //burn-in for (int i = 0; i < 100; i++) r1.getUInt32(); @@ -196,14 +200,14 @@ TEST(RandomTest, testSerialization_ar) { // r1 and r2 should be identical EXPECT_EQ(r1, r2) << "load from serialization"; EXPECT_EQ(r2.getUInt32(), 3537119063u) << "Deserialized is not deterministic"; - r1.getUInt32(); //move the same number of steps + EXPECT_EQ(r1.getUInt32(), 3537119063u); //move the same number of steps - UInt32 v1, v2; for (int i = 0; i < 100; i++) { - v1 = r1.getUInt32(); - v2 = r2.getUInt32(); - EXPECT_EQ(v1, v2) << "serialization"; + EXPECT_EQ(r1.getUInt32(), r2.getUInt32()) << "serialization"; } + + EXPECT_EQ(r2.getSeed(), SEED) << "Rng deserialized seed is not the same!"; + } From d66c1e976b2d61c3b17bb653218828441f2c3484 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 17:14:29 +0200 Subject: [PATCH 04/17] Random: simplify seeding for random seed (=0) --- src/htm/utils/Random.cpp | 18 +++++------------- src/test/unit/utils/RandomTest.cpp | 8 +++++++- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/htm/utils/Random.cpp b/src/htm/utils/Random.cpp index 83d3933c13..0b51752066 100644 --- a/src/htm/utils/Random.cpp +++ b/src/htm/utils/Random.cpp @@ -32,27 +32,19 @@ bool Random::operator==(const Random &o) const { gen == o.gen; } -bool static_gen_seeded = false; //used only for seeding seed if 0/auto is passed for seed -std::mt19937 static_gen; +std::random_device rd; //HW RNG, undeterministic, platform dependant. Use only for seeding rng if random seed wanted (seed=0) Random::Random(UInt64 seed) { if (seed == 0) { - if( !static_gen_seeded ) { - #if NDEBUG - unsigned int static_seed = (unsigned int)std::chrono::system_clock::now().time_since_epoch().count(); - #else - unsigned int static_seed = DEBUG_RANDOM_SEED; - #endif - static_gen.seed( static_seed ); - static_gen_seeded = true; + const unsigned int static_seed = rd(); + std::mt19937 static_gen(static_seed); NTA_INFO << "Random seed: " << static_seed; - } + seed_ = static_gen(); //generate random value from HW RNG } else { seed_ = seed; } - // if seed is zero at this point, there is a logic error. - NTA_CHECK(seed_ != 0); + NTA_CHECK(seed_ != 0) << "Random: if seed is zero at this point, there is a logic error"; gen.seed(static_cast(seed_)); //seed the generator steps_ = 0; } diff --git a/src/test/unit/utils/RandomTest.cpp b/src/test/unit/utils/RandomTest.cpp index 17fdb2f43e..d0d4e6b393 100644 --- a/src/test/unit/utils/RandomTest.cpp +++ b/src/test/unit/utils/RandomTest.cpp @@ -37,8 +37,10 @@ using namespace std; TEST(RandomTest, Seeding) { { Random r; + ASSERT_TRUE(r.getSeed() != 0) << "Should initialize with randomized seed"; + auto x = r.getUInt32(); - ASSERT_TRUE(x != 0); + ASSERT_NE(x, 0u); } // test getSeed @@ -64,6 +66,10 @@ TEST(RandomTest, Seeding) { ASSERT_EQ(r(), 419326371u); } + for(int i=0; i< 10; i++) { + ASSERT_NE(Random(0).getSeed(), Random(0).getSeed()) << "Randomly seeded generators should not be identical!"; + } + } From 3995e4021aacbdc90a67177c81f8bba01d089d80 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 17:50:58 +0200 Subject: [PATCH 05/17] Random: remove serialization via operator<< replaced by Cereal --- .../py/cpp_src/bindings/math/py_Random.cpp | 21 ++-- src/htm/utils/Random.cpp | 37 +------ src/htm/utils/Random.hpp | 9 +- src/test/unit/utils/RandomTest.cpp | 96 ------------------- 4 files changed, 15 insertions(+), 148 deletions(-) diff --git a/bindings/py/cpp_src/bindings/math/py_Random.cpp b/bindings/py/cpp_src/bindings/math/py_Random.cpp index 3c5fa10592..e5a01d0658 100644 --- a/bindings/py/cpp_src/bindings/math/py_Random.cpp +++ b/bindings/py/cpp_src/bindings/math/py_Random.cpp @@ -42,14 +42,12 @@ namespace htm_ext { py::class_ Random(m, "Random"); Random.def(py::init(), py::arg("seed") = 0) - .def("getUInt32", &Random_t::getUInt32, py::arg("max") = (htm::UInt32)-1l) - .def("getReal64", &Random_t::getReal64) - .def("getSeed", &Random_t::getSeed) - .def("max", &Random_t::max) - .def("min", &Random_t::min) - .def("__eq__", [](Random_t const & self, Random_t const & other) {//wrapping operator== - return self == other; - }, py::is_operator()); + .def("getUInt32", &Random_t::getUInt32, py::arg("max") = (htm::UInt32)-1l) + .def("getReal64", &Random_t::getReal64) + .def("getSeed", &Random_t::getSeed) + .def("max", &Random_t::max) + .def("min", &Random_t::min) + .def("__eq__", [](Random_t const & self, Random_t const & other) { return self == other; }, py::is_operator()); //operator== Random.def_property_readonly_static("MAX32", [](py::object) { return Random_t::MAX32; @@ -149,9 +147,11 @@ namespace htm_ext { [](const Random_t& r) { std::stringstream ss; - ss << r; + cereal::JSONOutputArchive ar( ss ); + ar(r); //save r's state to archive (stream) with cereal return ss.str(); }, + [](const std::string& str) { if (str.empty()) @@ -160,8 +160,9 @@ namespace htm_ext { } std::stringstream ss(str); + cereal::JSONInputArchive ar( ss ); Random_t r; - ss >> r; + ar(r); //load from stream to Random 'r' return r; } diff --git a/src/htm/utils/Random.cpp b/src/htm/utils/Random.cpp index 0b51752066..fca58f3657 100644 --- a/src/htm/utils/Random.cpp +++ b/src/htm/utils/Random.cpp @@ -18,9 +18,6 @@ /** @file Random Number Generator implementation */ -#include // for istream, ostream -#include // for random seeds - #include #include @@ -34,7 +31,7 @@ bool Random::operator==(const Random &o) const { std::random_device rd; //HW RNG, undeterministic, platform dependant. Use only for seeding rng if random seed wanted (seed=0) -Random::Random(UInt64 seed) { +Random::Random(const UInt64 seed) { if (seed == 0) { const unsigned int static_seed = rd(); std::mt19937 static_gen(static_seed); @@ -49,39 +46,9 @@ Random::Random(UInt64 seed) { steps_ = 0; } - namespace htm { -std::ostream &operator<<(std::ostream &outStream, const Random &r) { - outStream << "random-v2" << " "; - outStream << r.seed_ << " "; - outStream << r.steps_ << " "; - outStream << "endrandom-v2" << " "; - return outStream; -} - - -std::istream &operator>>(std::istream &inStream, Random &r) { - std::string version; - - inStream >> version; - NTA_CHECK(version == "random-v2") << "Random() deserializer -- found unexpected version string '" - << version << "'"; - inStream >> r.seed_; - r.gen.seed(static_cast(r.seed_)); //reseed - inStream >> r.steps_; - r.gen.discard(r.steps_); //advance n steps - //FIXME we could de/serialize directly RNG gen, it should be multi-platform according to standard, - //but on OSX CI it wasn't (25/11/2018). So "hacking" the above instead. - std::string endtag; - inStream >> endtag; - NTA_CHECK(endtag == "endrandom-v2") << "Random() deserializer -- found unexpected end tag '" << endtag << "'"; - inStream.ignore(1); - - return inStream; -} - // helper function for seeding RNGs across the plugin barrier UInt32 GetRandomSeed() { - return htm::Random().getUInt32(); + return htm::Random(0).getUInt32(); } } // namespace htm diff --git a/src/htm/utils/Random.hpp b/src/htm/utils/Random.hpp index a5c0bb8fa9..064b0de4f6 100644 --- a/src/htm/utils/Random.hpp +++ b/src/htm/utils/Random.hpp @@ -71,9 +71,10 @@ namespace htm { */ class Random : public Serializable { public: - Random(UInt64 seed = 0); + Random(const UInt64 seed = 0); + // Serialization CerealAdapter; template void save_ar(Archive & ar) const { @@ -160,8 +161,6 @@ class Random : public Serializable { protected: friend class RandomTest; - friend std::ostream &operator<<(std::ostream &, const Random &); - friend std::istream &operator>>(std::istream &, Random &); friend UInt32 GetRandomSeed(); private: UInt64 seed_; @@ -186,10 +185,6 @@ class Random : public Serializable { } }; -// serialization/deserialization -std::ostream &operator<<(std::ostream &, const Random &); -std::istream &operator>>(std::istream &, Random &); - // This function returns seeds from the Random singleton in our // "universe" (application, plugin, python module). If, when the // Random constructor is called, seeder_ is NULL, then seeder_ is diff --git a/src/test/unit/utils/RandomTest.cpp b/src/test/unit/utils/RandomTest.cpp index d0d4e6b393..3931ba04cb 100644 --- a/src/test/unit/utils/RandomTest.cpp +++ b/src/test/unit/utils/RandomTest.cpp @@ -110,80 +110,6 @@ TEST(RandomTest, OperatorEquals) { } -TEST(RandomTest, SerializationDeserialization) { - // test serialization/deserialization - Random r1(862973); - for (int i = 0; i < 100; i++) - r1.getUInt32(); - - EXPECT_EQ(r1.getUInt32(), 2276275187u) << "Before serialization must be same"; - // serialize - std::stringstream ostream; - ostream << r1; - - // print out serialization for debugging - std::string x(ostream.str()); -// NTA_INFO << "random serialize string: '" << x << "'"; - // Serialization should be deterministic and platform independent - const std::string expectedString = "random-v2 862973 101 endrandom-v2 "; - EXPECT_EQ(expectedString, x) << "De/serialization"; - - // deserialize into r2 - std::string s(ostream.str()); - std::stringstream ss(s); - Random r2; - ss >> r2; - - // r1 and r2 should be identical - EXPECT_EQ(r1, r2) << "load from serialization"; - EXPECT_EQ(r2.getUInt32(), 3537119063u) << "Deserialized is not deterministic"; - r1.getUInt32(); //move the same number of steps - - UInt32 v1, v2; - for (int i = 0; i < 100; i++) { - v1 = r1.getUInt32(); - v2 = r2.getUInt32(); - EXPECT_EQ(v1, v2) << "serialization"; - } -} - - -TEST(RandomTest, testSerialization2) { - const UInt n=1000; - Random r1(7); - Random r2; - - htm::Timer testTimer; - testTimer.start(); - for (UInt i = 0; i < n; ++i) { - r1.getUInt32(); - - // Serialize - ofstream os("random3.stream", ofstream::binary); - os << r1; - os.flush(); - os.close(); - - // Deserialize - ifstream is("random3.stream", ifstream::binary); - is >> r2; - is.close(); - - // Test - ASSERT_EQ(r1.getUInt32(), r2.getUInt32()); - ASSERT_EQ(r1.getUInt32(), r2.getUInt32()); - ASSERT_EQ(r1.getUInt32(), r2.getUInt32()); - ASSERT_EQ(r1.getUInt32(), r2.getUInt32()); - ASSERT_EQ(r1.getUInt32(), r2.getUInt32()); - } - testTimer.stop(); - - remove("random3.stream"); - - cout << "Random serialization: " << testTimer.getElapsed() << endl; -} - - TEST(RandomTest, testSerialization_ar) { // test serialization/deserialization const UInt SEED = 862973u; @@ -233,28 +159,6 @@ TEST(RandomTest, ReturnInCorrectRange) { } } -/* -TEST(RandomTest, getUInt64) { - // tests for getUInt64 - Random r1(1); - ASSERT_EQ(2469588189546311528u, r1.getUInt64()) - << "check getUInt64, seed 1, first call"; - ASSERT_EQ(2516265689700432462u, r1.getUInt64()) - << "check getUInt64, seed 1, second call"; - - Random r2(2); - ASSERT_EQ(16668552215174154828u, r2.getUInt64()) - << "check getUInt64, seed 2, first call"; - EXPECT_EQ(15684088468973760345u, r2.getUInt64()) - << "check getUInt64, seed 2, second call"; - - Random r3(7464235991977222558); - EXPECT_EQ(8035066300482877360u, r3.getUInt64()) - << "check getUInt64, big seed, first call"; - EXPECT_EQ(623784303608610892u, r3.getUInt64()) - << "check getUInt64, big seed, second call"; -} -*/ TEST(RandomTest, getUInt32) { // tests for getUInt32 From 26a5d0f8f011ab7a82b82d04379ee81ab77fd1b0 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 17:50:58 +0200 Subject: [PATCH 06/17] Random: remove serialization via operator<< replaced by Cereal --- .../py/cpp_src/bindings/math/py_Random.cpp | 6 ++---- .../unit/algorithms/SpatialPoolerTest.cpp | 20 +++++++------------ src/test/unit/utils/RandomTest.cpp | 12 +++++------ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/bindings/py/cpp_src/bindings/math/py_Random.cpp b/bindings/py/cpp_src/bindings/math/py_Random.cpp index e5a01d0658..ba6026d3c5 100644 --- a/bindings/py/cpp_src/bindings/math/py_Random.cpp +++ b/bindings/py/cpp_src/bindings/math/py_Random.cpp @@ -147,8 +147,7 @@ namespace htm_ext { [](const Random_t& r) { std::stringstream ss; - cereal::JSONOutputArchive ar( ss ); - ar(r); //save r's state to archive (stream) with cereal + r.save(ss); //save r's state to archive (stream) with cereal return ss.str(); }, @@ -162,8 +161,7 @@ namespace htm_ext { std::stringstream ss(str); cereal::JSONInputArchive ar( ss ); Random_t r; - ar(r); //load from stream to Random 'r' - + r.load(ss); //load from stream to Random 'r' return r; } )); diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index e34e4522f6..54fc24e210 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -1945,6 +1945,7 @@ TEST(SpatialPoolerTest, testSerialization_ar) { SpatialPooler sp1; sp1.initialize(inputDims, colDims); sp1.setSeed(1); + ASSERT_EQ(sp1.getSeed(), 1u); SDR input(inputDims); SDR output(colDims); @@ -1960,9 +1961,8 @@ TEST(SpatialPoolerTest, testSerialization_ar) { stringstream ss; // ss.precision(std::numeric_limits::digits10 + 1); // ss.precision(std::numeric_limits::digits10 + 1); - sp1.save(ss); + EXPECT_NO_THROW(sp1.save(ss)) << "serializing failed"; - htm::Timer testTimer; for (UInt i = 0; i < 6; ++i) { // Create new input @@ -1974,31 +1974,25 @@ TEST(SpatialPoolerTest, testSerialization_ar) { // C - Next, verify the same results come from the de/serialized version { + // Deserialize: SpatialPooler spTemp; - ASSERT_EQ(spTemp.getSeed(), 1u); - - testTimer.start(); - - // Deserialize - ss.seekg(0); + //ss.seekg(0); EXPECT_NO_THROW(spTemp.load(ss)); + ASSERT_EQ(spTemp.getSeed(), 1u); + check_spatial_eq(sp1, spTemp); // Feed new record through SDR outputC({numColumns}); spTemp.compute(input, true, outputC); - // Serialize + // Serialize: ss.clear(); EXPECT_NO_THROW(spTemp.save(ss)); - testTimer.stop(); EXPECT_EQ(outputBaseline, outputC); //FIXME this test randomly fails. (De/serialization of rng_ is correct?) } } - ss.clear(); - - cout << "[ ] Timing for SP serialization: " << testTimer.getElapsed() << "sec" << endl; } diff --git a/src/test/unit/utils/RandomTest.cpp b/src/test/unit/utils/RandomTest.cpp index 3931ba04cb..7c32ec6eb6 100644 --- a/src/test/unit/utils/RandomTest.cpp +++ b/src/test/unit/utils/RandomTest.cpp @@ -110,17 +110,15 @@ TEST(RandomTest, OperatorEquals) { } -TEST(RandomTest, testSerialization_ar) { - // test serialization/deserialization +TEST(RandomTest, testSerialization) { // test serialization/deserialization using Cereal const UInt SEED = 862973u; Random r1(SEED); ASSERT_EQ(r1.getSeed(), SEED) << "RNG seed not set properly"; //burn-in - for (int i = 0; i < 100; i++) - r1.getUInt32(); - + for (int i = 0; i < 100; i++) r1.getUInt32(); EXPECT_EQ(r1.getUInt32(), 2276275187u) << "Before serialization must be same"; + // serialize std::stringstream ss; r1.save(ss); @@ -133,13 +131,13 @@ TEST(RandomTest, testSerialization_ar) { EXPECT_EQ(r1, r2) << "load from serialization"; EXPECT_EQ(r2.getUInt32(), 3537119063u) << "Deserialized is not deterministic"; EXPECT_EQ(r1.getUInt32(), 3537119063u); //move the same number of steps + EXPECT_EQ(r1.getSeed(), r2.getSeed()); + EXPECT_EQ(r2.getSeed(), SEED) << "Rng deserialized seed is not the same!"; for (int i = 0; i < 100; i++) { EXPECT_EQ(r1.getUInt32(), r2.getUInt32()) << "serialization"; } - EXPECT_EQ(r2.getSeed(), SEED) << "Rng deserialized seed is not the same!"; - } From b0c2909f42db51c97dcf282a6f384899ef10986d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 18:45:09 +0200 Subject: [PATCH 07/17] SP: test is more descriptive, on Serialization --- .../unit/algorithms/SpatialPoolerTest.cpp | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 54fc24e210..ceb9b12ad2 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -121,34 +121,30 @@ bool check_vector_eq(vector vec1, vector vec2) { return true; } +//helper method to check detailed equality of 2 spatial poolers void check_spatial_eq(const SpatialPooler& sp1, const SpatialPooler& sp2) { UInt numColumns = sp1.getNumColumns(); UInt numInputs = sp2.getNumInputs(); - ASSERT_TRUE(sp1.getNumColumns() == sp2.getNumColumns()); - ASSERT_TRUE(sp1.getNumInputs() == sp2.getNumInputs()); - ASSERT_TRUE(sp1.getPotentialRadius() == sp2.getPotentialRadius()); - ASSERT_TRUE(sp1.getPotentialPct() == sp2.getPotentialPct()); - ASSERT_TRUE(sp1.getGlobalInhibition() == sp2.getGlobalInhibition()); - ASSERT_TRUE(almost_eq(sp1.getLocalAreaDensity(), sp2.getLocalAreaDensity())); - ASSERT_TRUE(sp1.getStimulusThreshold() == sp2.getStimulusThreshold()); - ASSERT_TRUE(sp1.getDutyCyclePeriod() == sp2.getDutyCyclePeriod()); - ASSERT_TRUE(almost_eq(sp1.getBoostStrength(), sp2.getBoostStrength())); - ASSERT_TRUE(sp1.getIterationNum() == sp2.getIterationNum()); - ASSERT_TRUE(sp1.getIterationLearnNum() == sp2.getIterationLearnNum()); - ASSERT_TRUE(sp1.getSpVerbosity() == sp2.getSpVerbosity()); - ASSERT_TRUE(sp1.getWrapAround() == sp2.getWrapAround()); - ASSERT_TRUE(sp1.getUpdatePeriod() == sp2.getUpdatePeriod()); - cout << "check: " << sp1.getSynPermActiveInc() << " " - << sp2.getSynPermActiveInc() << endl; - ASSERT_TRUE(almost_eq(sp1.getSynPermActiveInc(), sp2.getSynPermActiveInc())); - ASSERT_TRUE( - almost_eq(sp1.getSynPermInactiveDec(), sp2.getSynPermInactiveDec())); - ASSERT_TRUE(almost_eq(sp1.getSynPermBelowStimulusInc(), - sp2.getSynPermBelowStimulusInc())); - ASSERT_TRUE(almost_eq(sp1.getSynPermConnected(), sp2.getSynPermConnected())); - ASSERT_TRUE(almost_eq(sp1.getMinPctOverlapDutyCycles(), - sp2.getMinPctOverlapDutyCycles())); + EXPECT_EQ(sp1.getNumColumns(), sp2.getNumColumns()); + EXPECT_EQ(sp1.getNumInputs(), sp2.getNumInputs()); + EXPECT_EQ(sp1.getPotentialRadius(), sp2.getPotentialRadius()); + EXPECT_EQ(sp1.getPotentialPct(), sp2.getPotentialPct()); + EXPECT_EQ(sp1.getGlobalInhibition(), sp2.getGlobalInhibition()); + EXPECT_FLOAT_EQ(sp1.getLocalAreaDensity(), sp2.getLocalAreaDensity()); + EXPECT_EQ(sp1.getStimulusThreshold(), sp2.getStimulusThreshold()); + EXPECT_EQ(sp1.getDutyCyclePeriod(), sp2.getDutyCyclePeriod()); + EXPECT_FLOAT_EQ(sp1.getBoostStrength(), sp2.getBoostStrength()); + EXPECT_EQ(sp1.getIterationNum(), sp2.getIterationNum()); + EXPECT_EQ(sp1.getIterationLearnNum(), sp2.getIterationLearnNum()); + EXPECT_EQ(sp1.getSpVerbosity(), sp2.getSpVerbosity()); + EXPECT_EQ(sp1.getWrapAround(), sp2.getWrapAround()); + EXPECT_EQ(sp1.getUpdatePeriod(), sp2.getUpdatePeriod()); + EXPECT_FLOAT_EQ(sp1.getSynPermActiveInc(), sp2.getSynPermActiveInc()); + EXPECT_FLOAT_EQ(sp1.getSynPermInactiveDec(), sp2.getSynPermInactiveDec()); + EXPECT_FLOAT_EQ(sp1.getSynPermBelowStimulusInc(), sp2.getSynPermBelowStimulusInc()); + EXPECT_FLOAT_EQ(sp1.getSynPermConnected(), sp2.getSynPermConnected()); + EXPECT_FLOAT_EQ(sp1.getMinPctOverlapDutyCycles(), sp2.getMinPctOverlapDutyCycles()); auto boostFactors1 = new Real[numColumns]; auto boostFactors2 = new Real[numColumns]; @@ -1964,14 +1960,6 @@ TEST(SpatialPoolerTest, testSerialization_ar) { EXPECT_NO_THROW(sp1.save(ss)) << "serializing failed"; - for (UInt i = 0; i < 6; ++i) { - // Create new input - input.randomize(0.05f, random); - - // Get expected output - SDR outputBaseline(output); - sp1.compute(input, true, outputBaseline); - // C - Next, verify the same results come from the de/serialized version { // Deserialize: @@ -1979,20 +1967,27 @@ TEST(SpatialPoolerTest, testSerialization_ar) { //ss.seekg(0); EXPECT_NO_THROW(spTemp.load(ss)); ASSERT_EQ(spTemp.getSeed(), 1u); - check_spatial_eq(sp1, spTemp); + check_spatial_eq(sp1, spTemp); //detailed "equals" method here in test + ASSERT_EQ(sp1, spTemp) << "Loaded SP is not the same as original"; //equals method used in SP - // Feed new record through - SDR outputC({numColumns}); - spTemp.compute(input, true, outputC); + // 1 step: Create new input & compute SP output + for(int i=0; i < 42; i++) { + input.randomize(0.05f, random); + SDR expected(colDims); + sp1.compute(input, true, expected); + + // Feed new record through + SDR outputC(colDims); + spTemp.compute(input, true, outputC); + + + EXPECT_EQ(expected, outputC) << "Output of original and deserialized SP must be the same"; + } // Serialize: ss.clear(); EXPECT_NO_THROW(spTemp.save(ss)); - - - EXPECT_EQ(outputBaseline, outputC); //FIXME this test randomly fails. (De/serialization of rng_ is correct?) } - } } From c029f61284f138ba991d371e6248b4189ee3e874 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 23 Apr 2020 19:39:32 +0200 Subject: [PATCH 08/17] HelloSPTP: formatting --- src/examples/hello/HelloSPTP.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 89c217ac08..80e6761cbd 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -116,6 +116,7 @@ EPOCHS = 2; // make test faster in Debug for (UInt e = 0; e < EPOCHS; e++) { //FIXME EPOCHS is actually steps, there's just 1 pass through data/epoch. //Encode + { tEnc.start(); x+=0.01f; //step size for fn(x) enc.encode(sin(x), input); //model sin(x) function //TODO replace with CSV data @@ -125,32 +126,38 @@ EPOCHS = 2; // make test faster in Debug tRng.start(); input.addNoise(0.01f, rnd); //change 1% of the SDR for each iteration, this makes a random sequence, but seemingly stable tRng.stop(); + } //SP (global and local) if(useSPlocal) { - tSPloc.start(); - spLocal.compute(input, true, outSPlocal); - tSPloc.stop(); + tSPloc.start(); + spLocal.compute(input, true, outSPlocal); + tSPloc.stop(); + + outSP = outSPlocal; } if(useSPglobal) { - tSPglob.start(); - spGlobal.compute(input, true, outSPglobal); - tSPglob.stop(); + tSPglob.start(); + spGlobal.compute(input, true, outSPglobal); + tSPglob.stop(); + + outSP = outSPglobal; } - outSP = outSPglobal; //toggle if local/global SP is used further down the chain (TM, Anomaly) // TM if(useTM) { - tTM.start(); + tTM.start(); tm.compute(outSP, true /*learn*/); //to uses output of SPglobal tm.activateDendrites(); //required to enable tm.getPredictiveCells() - outTM = tm.cellsToColumns( tm.getPredictiveCells() ); tTM.stop(); + + outTM = tm.cellsToColumns( tm.getPredictiveCells() ); } //Anomaly (pure x likelihood) + { an = tm.anomaly; avgAnom10.compute(an); //moving average if(e % 1000 == 0) { @@ -161,6 +168,7 @@ EPOCHS = 2; // make test faster in Debug tAnLikelihood.start(); anLikely = anLikelihood.anomalyProbability(an); tAnLikelihood.stop(); + } // print From a7420a5d232f5368dc681d14d7c26973bff3684c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 24 Apr 2020 09:38:56 +0200 Subject: [PATCH 09/17] SP: equals with detailed checks I need more precise detail if/why two SPs differ. --- src/examples/hello/HelloSPTP.cpp | 9 + src/htm/algorithms/SpatialPooler.cpp | 159 ++++++++++++------ src/htm/algorithms/SpatialPooler.hpp | 5 +- .../unit/algorithms/SpatialPoolerTest.cpp | 122 ++------------ 4 files changed, 140 insertions(+), 155 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 80e6761cbd..7803312faf 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -235,6 +235,15 @@ EPOCHS = 2; // make test faster in Debug #ifdef _ARCH_DETERMINISTIC if(e+1 == 5000) { + // For debugging: save SP's state in 1 step, comment out, recompile, load SP and compare in another + // step 1: + //spGlobal.saveToFile("/tmp/spG.save"); + // step 2: + SpatialPooler resumedSP; + resumedSP.loadFromFile("/tmp/spG.save"); + NTA_CHECK(spGlobal == resumedSP) << "SPs differ!"; + // --end of debugging + //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; if(useSPglobal) { NTA_CHECK(outSPglobal == goldSP) << "Deterministic output of SP (g) failed!\n" << outSP << "should be:\n" << goldSP; } diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index c9a31d5342..f01d3ac037 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -71,17 +71,24 @@ SpatialPooler::SpatialPooler() { } SpatialPooler::SpatialPooler( - const vector inputDimensions, const vector columnDimensions, - UInt potentialRadius, Real potentialPct, bool globalInhibition, - Real localAreaDensity, UInt numActiveColumnsPerInhArea, - UInt stimulusThreshold, Real synPermInactiveDec, Real synPermActiveInc, - Real synPermConnected, Real minPctOverlapDutyCycles, UInt dutyCyclePeriod, - Real boostStrength, UInt seed, UInt spVerbosity, bool wrapAround) - : SpatialPooler::SpatialPooler() + const vector inputDimensions, + const vector columnDimensions, + UInt potentialRadius, + Real potentialPct, + bool globalInhibition, + Real localAreaDensity, + UInt numActiveColumnsPerInhArea, + UInt stimulusThreshold, + Real synPermInactiveDec, + Real synPermActiveInc, + Real synPermConnected, + Real minPctOverlapDutyCycles, + UInt dutyCyclePeriod, + Real boostStrength, + UInt seed, + UInt spVerbosity, + bool wrapAround) : SpatialPooler::SpatialPooler() { - // The current version number for serialzation. - version_ = 2; - initialize(inputDimensions, columnDimensions, potentialRadius, @@ -116,14 +123,14 @@ UInt SpatialPooler::getNumInputs() const { return numInputs_; } UInt SpatialPooler::getPotentialRadius() const { return potentialRadius_; } void SpatialPooler::setPotentialRadius(UInt potentialRadius) { - NTA_CHECK(potentialRadius < numInputs_); + NTA_CHECK(potentialRadius < numInputs_) << "SP setPotentialRadius: " << potentialRadius << " must be < " << numInputs_; potentialRadius_ = potentialRadius; } Real SpatialPooler::getPotentialPct() const { return potentialPct_; } void SpatialPooler::setPotentialPct(Real potentialPct) { - NTA_CHECK(potentialPct > 0.0f && potentialPct <= 1.0f); + NTA_CHECK(potentialPct > 0.0f && potentialPct <= 1.0f) << "SP setPotentialPct(): out of bounds (0, 1]"; potentialPct_ = potentialPct; } @@ -415,9 +422,9 @@ void SpatialPooler::initialize( rng_ = Random(seed); - potentialRadius_ = potentialRadius > numInputs_ ? numInputs_ : potentialRadius; - NTA_CHECK(potentialPct > 0 && potentialPct <= 1); - potentialPct_ = potentialPct; + potentialRadius_ = min(numInputs_ , potentialRadius); + //!setPotentialRadius(potentialRadius); + setPotentialPct(potentialPct);; globalInhibition_ = globalInhibition; stimulusThreshold_ = stimulusThreshold; synPermInactiveDec_ = synPermInactiveDec; @@ -1012,45 +1019,101 @@ void SpatialPooler::printState(const vector &state, std::ostream& out) con } -/** equals implementation based on text serialization */ bool SpatialPooler::operator==(const SpatialPooler& o) const{ + //Note on implementation: NTA_CHECKs are used, so we know + //what condition failed. + + try { // Store the simple variables first. - if (numInputs_ != o.numInputs_) return false; - if (numColumns_ != o.numColumns_) return false; - if (potentialRadius_ != o.potentialRadius_) return false; - if (potentialPct_ != o.potentialPct_) return false; - if (initConnectedPct_ != o.initConnectedPct_) return false; - if (globalInhibition_ != o.globalInhibition_) return false; - if (numActiveColumnsPerInhArea_ != o.numActiveColumnsPerInhArea_) return false; - if (localAreaDensity_ != o.localAreaDensity_) return false; - if (stimulusThreshold_ != o.stimulusThreshold_) return false; - if (inhibitionRadius_ != o.inhibitionRadius_) return false; - if (dutyCyclePeriod_ != o.dutyCyclePeriod_) return false; - if (boostStrength_ != o.boostStrength_) return false; - if (iterationNum_ != o.iterationNum_) return false; - if (iterationLearnNum_ != o.iterationLearnNum_) return false; - if (spVerbosity_ != o.spVerbosity_) return false; - if (updatePeriod_ != o.updatePeriod_) return false; - if (synPermInactiveDec_ != o.synPermInactiveDec_) return false; - if (synPermActiveInc_ != o.synPermActiveInc_) return false; - if (synPermBelowStimulusInc_ != o.synPermBelowStimulusInc_) return false; - if (synPermConnected_ != o.synPermConnected_) return false; - if (minPctOverlapDutyCycles_ != o.minPctOverlapDutyCycles_) return false; - if (wrapAround_ != o.wrapAround_) return false; + NTA_CHECK (numInputs_ == o.numInputs_) << "SP equals: numInputs:" << numInputs_ << " vs. " << o.numInputs_ ; + NTA_CHECK (numColumns_ == o.numColumns_) << "SP equals: numColumns: " << numColumns_ << " vs. " << o.numColumns_; + NTA_CHECK (potentialRadius_ == o.potentialRadius_) << "SP equals: potentialRadius: " << potentialRadius_ << " vs. " << o.potentialRadius_; + NTA_CHECK (potentialPct_ == o.potentialPct_) << "SP equals: potentialPct: " << potentialPct_ << " vs. " << o.potentialPct_; + NTA_CHECK (initConnectedPct_ == o.initConnectedPct_) << "SP equals: initConnectedPct: " << initConnectedPct_ << " vs. " << o.initConnectedPct_; + NTA_CHECK (globalInhibition_ == o.globalInhibition_) << "SP equals: globalInhibition: " << globalInhibition_ << " vs. " << o.globalInhibition_; + NTA_CHECK (numActiveColumnsPerInhArea_ == o.numActiveColumnsPerInhArea_) << "SP equals: numActiveColumnsPerInhArea: " + << numActiveColumnsPerInhArea_ << " vs. " << o.numActiveColumnsPerInhArea_; + NTA_CHECK (localAreaDensity_ == o.localAreaDensity_) << "SP equals: localAreaDensity: " << localAreaDensity_ << " vs. " << o.localAreaDensity_; + NTA_CHECK (stimulusThreshold_ == o.stimulusThreshold_) << "SP equals: stimulusThreshold: " << stimulusThreshold_ << " vs. " << o.stimulusThreshold_; + NTA_CHECK (inhibitionRadius_ == o.inhibitionRadius_) << "SP equals: inhibitionRadius: " << inhibitionRadius_ << " vs. " << o.inhibitionRadius_; + NTA_CHECK (dutyCyclePeriod_ == o.dutyCyclePeriod_) << "SP equals: dutyCyclePeriod: " << dutyCyclePeriod_ << " vs. " << o.dutyCyclePeriod_; + NTA_CHECK (boostStrength_ == o.boostStrength_) << "SP equals: boostStrength: " << boostStrength_ << " vs. " << o.boostStrength_; + NTA_CHECK (iterationNum_ == o.iterationNum_) << "SP equals: iterationNum: " << iterationNum_ << " vs. " << o.iterationNum_; + NTA_CHECK (iterationLearnNum_ == o.iterationLearnNum_) << "SP equals: iterationLearnNum: " << iterationLearnNum_ << " vs. " << o.iterationLearnNum_; + NTA_CHECK (spVerbosity_ == o.spVerbosity_) << "SP equals: spVerbosity: " << spVerbosity_ << " vs. " << o.spVerbosity_; + NTA_CHECK (updatePeriod_ == o.updatePeriod_) << "SP equals: updatePeriod: " << updatePeriod_ << " vs. " << o.updatePeriod_; + NTA_CHECK (synPermInactiveDec_ == o.synPermInactiveDec_) << "SP equals: synPermInactiveDec: " << synPermInactiveDec_ << " vs. " << o.synPermInactiveDec_; + NTA_CHECK (synPermActiveInc_ == o.synPermActiveInc_) << "SP equals: synPermActiveInc: " << synPermActiveInc_ << " vs. " << o.synPermActiveInc_; + NTA_CHECK (synPermBelowStimulusInc_ == o.synPermBelowStimulusInc_) << "SP equals: synPermBelowStimulusInc: " << synPermBelowStimulusInc_ << " vs. " << o.synPermBelowStimulusInc_; + NTA_CHECK (synPermConnected_ == o.synPermConnected_) << "SP equals: synPermConnected: " << synPermConnected_ << " vs. " << o.synPermConnected_; + NTA_CHECK (minPctOverlapDutyCycles_ == o.minPctOverlapDutyCycles_) + << "SP equals: minPctOverlapDutyCycles: " << minPctOverlapDutyCycles_ << " vs. " << minPctOverlapDutyCycles_; + NTA_CHECK (wrapAround_ == o.wrapAround_) << "SP equals: wrapAround: " << wrapAround_ << " vs. " << o.wrapAround_; - // compare vectors. - if (inputDimensions_ != o.inputDimensions_) return false; - if (columnDimensions_ != o.columnDimensions_) return false; - if (boostFactors_ != o.boostFactors_) return false; - if (overlapDutyCycles_ != o.overlapDutyCycles_) return false; - if (activeDutyCycles_ != o.activeDutyCycles_) return false; - if (minOverlapDutyCycles_ != o.minOverlapDutyCycles_) return false; + + //Random + NTA_CHECK (rng_ == o.rng_) << "SP equals: rng differs"; // compare connections - if (connections_ != o.connections_) return false; + NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; - //Random - if (rng_ != o.rng_) return false; + // compare vectors. + NTA_CHECK (inputDimensions_ == o.inputDimensions_) << "SP equals: inputDimensions differ"; + NTA_CHECK (columnDimensions_ == o.columnDimensions_) << "SP equals: columnDimensions differ"; + NTA_CHECK (boostFactors_ == o.boostFactors_) << "SP equals: boostFactors"; + NTA_CHECK (overlapDutyCycles_ == o.overlapDutyCycles_) << "SP equals: overlapDutyCycles"; //seems bug is here?! + NTA_CHECK (activeDutyCycles_ == o.activeDutyCycles_) << "SP equals: activeDutyCucles"; + NTA_CHECK (minOverlapDutyCycles_ == o.minOverlapDutyCycles_) << "SP equals: minOverlapDutyCycles"; + + //detailed compare potentials + for (UInt i = 0; i < numColumns_; i++) { + auto potential1 = new UInt[numInputs_]; + auto potential2 = new UInt[numInputs_]; + this->getPotential(i, potential1); + o.getPotential(i, potential2); + for(size_t j=0; j< numInputs_; j++) { + NTA_CHECK(potential1[j] == potential2[j]) << "SP potential " << j << " is " << potential1[j] << " vs " << potential2[j] << ".\n"; + } + + //!NTA_CHECK(potential1 == potential2) << "SP equals: potentials"; //NOTE vectors can be compared, but does not work for arrays! + //...therefore need to iterate. + delete[] potential1; + delete[] potential2; + } + + // check get permanences + for (UInt i = 0; i < numColumns_; i++) { + const auto& perm1 = this->getPermanence(i); + const auto& perm2 = o.getPermanence(i); + NTA_CHECK(perm1 == perm2) << "SP equals: permanences"; + } + + // check get connected synapses + for (UInt i = 0; i < numColumns_; i++) { + const auto& con1 = this->getPermanence(i, this->connections.getConnectedThreshold()); + const auto& con2 = o.getPermanence(i, o.connections.getConnectedThreshold()); + NTA_CHECK(con1 == con2) << "SP equals: connected synapses"; + } + + { + auto conCounts1 = new UInt[numColumns_]; + auto conCounts2 = new UInt[numColumns_]; + this->getConnectedCounts(conCounts1); + o.getConnectedCounts(conCounts2); + //!NTA_CHECK(conCounts1 == conCounts2) << "SP equals: connected column counts"; + for(size_t i=0; i< numColumns_; i++) { + NTA_CHECK(conCounts1[i] == conCounts2[i]) << "SP equals: connected column counts. at" << i << " is " << conCounts1[i] << " vs " << conCounts2[i] << "\n"; + } + delete[] conCounts1; + delete[] conCounts2; + } + + + } catch(const htm::Exception& ex) { + //some check failed -> not equal + std::cout << "SPP " << ex.what(); + return false; + } return true; } diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index c5e38993fc..eeeeb36e1d 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -312,6 +312,9 @@ class SpatialPooler : public Serializable ar(CEREAL_NVP(minOverlapDutyCycles_)); ar(CEREAL_NVP(connections_)); ar(CEREAL_NVP(rng_)); + + //also save empheral members + ar(CEREAL_NVP(boostedOverlaps_)); } // FOR Cereal Deserialization template @@ -348,7 +351,7 @@ class SpatialPooler : public Serializable ar(CEREAL_NVP(rng_)); // initialize ephemeral members - boostedOverlaps_.resize(numColumns_); + ar(CEREAL_NVP(boostedOverlaps_)); } /** diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index ceb9b12ad2..90154824dd 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -121,105 +121,12 @@ bool check_vector_eq(vector vec1, vector vec2) { return true; } -//helper method to check detailed equality of 2 spatial poolers -void check_spatial_eq(const SpatialPooler& sp1, const SpatialPooler& sp2) { - UInt numColumns = sp1.getNumColumns(); - UInt numInputs = sp2.getNumInputs(); - - EXPECT_EQ(sp1.getNumColumns(), sp2.getNumColumns()); - EXPECT_EQ(sp1.getNumInputs(), sp2.getNumInputs()); - EXPECT_EQ(sp1.getPotentialRadius(), sp2.getPotentialRadius()); - EXPECT_EQ(sp1.getPotentialPct(), sp2.getPotentialPct()); - EXPECT_EQ(sp1.getGlobalInhibition(), sp2.getGlobalInhibition()); - EXPECT_FLOAT_EQ(sp1.getLocalAreaDensity(), sp2.getLocalAreaDensity()); - EXPECT_EQ(sp1.getStimulusThreshold(), sp2.getStimulusThreshold()); - EXPECT_EQ(sp1.getDutyCyclePeriod(), sp2.getDutyCyclePeriod()); - EXPECT_FLOAT_EQ(sp1.getBoostStrength(), sp2.getBoostStrength()); - EXPECT_EQ(sp1.getIterationNum(), sp2.getIterationNum()); - EXPECT_EQ(sp1.getIterationLearnNum(), sp2.getIterationLearnNum()); - EXPECT_EQ(sp1.getSpVerbosity(), sp2.getSpVerbosity()); - EXPECT_EQ(sp1.getWrapAround(), sp2.getWrapAround()); - EXPECT_EQ(sp1.getUpdatePeriod(), sp2.getUpdatePeriod()); - EXPECT_FLOAT_EQ(sp1.getSynPermActiveInc(), sp2.getSynPermActiveInc()); - EXPECT_FLOAT_EQ(sp1.getSynPermInactiveDec(), sp2.getSynPermInactiveDec()); - EXPECT_FLOAT_EQ(sp1.getSynPermBelowStimulusInc(), sp2.getSynPermBelowStimulusInc()); - EXPECT_FLOAT_EQ(sp1.getSynPermConnected(), sp2.getSynPermConnected()); - EXPECT_FLOAT_EQ(sp1.getMinPctOverlapDutyCycles(), sp2.getMinPctOverlapDutyCycles()); - - auto boostFactors1 = new Real[numColumns]; - auto boostFactors2 = new Real[numColumns]; - sp1.getBoostFactors(boostFactors1); - sp2.getBoostFactors(boostFactors2); - ASSERT_TRUE(check_vector_eq(boostFactors1, boostFactors2, numColumns)); - delete[] boostFactors1; - delete[] boostFactors2; - - auto overlapDutyCycles1 = new Real[numColumns]; - auto overlapDutyCycles2 = new Real[numColumns]; - sp1.getOverlapDutyCycles(overlapDutyCycles1); - sp2.getOverlapDutyCycles(overlapDutyCycles2); - ASSERT_TRUE( - check_vector_eq(overlapDutyCycles1, overlapDutyCycles2, numColumns)); - delete[] overlapDutyCycles1; - delete[] overlapDutyCycles2; - - auto activeDutyCycles1 = new Real[numColumns]; - auto activeDutyCycles2 = new Real[numColumns]; - sp1.getActiveDutyCycles(activeDutyCycles1); - sp2.getActiveDutyCycles(activeDutyCycles2); - ASSERT_TRUE( - check_vector_eq(activeDutyCycles1, activeDutyCycles2, numColumns)); - delete[] activeDutyCycles1; - delete[] activeDutyCycles2; - - auto minOverlapDutyCycles1 = new Real[numColumns]; - auto minOverlapDutyCycles2 = new Real[numColumns]; - sp1.getMinOverlapDutyCycles(minOverlapDutyCycles1); - sp2.getMinOverlapDutyCycles(minOverlapDutyCycles2); - ASSERT_TRUE(check_vector_eq(minOverlapDutyCycles1, minOverlapDutyCycles2, - numColumns)); - delete[] minOverlapDutyCycles1; - delete[] minOverlapDutyCycles2; - - for (UInt i = 0; i < numColumns; i++) { - auto potential1 = new UInt[numInputs]; - auto potential2 = new UInt[numInputs]; - sp1.getPotential(i, potential1); - sp2.getPotential(i, potential2); - ASSERT_TRUE(check_vector_eq(potential1, potential2, numInputs)); - delete[] potential1; - delete[] potential2; - } - - // check get permanences - for (UInt i = 0; i < numColumns; i++) { - const auto& perm1 = sp1.getPermanence(i); - const auto& perm2 = sp2.getPermanence(i); - ASSERT_TRUE(check_vector_eq(perm1, perm2)); - } - - // check get connected synapses - for (UInt i = 0; i < numColumns; i++) { - const auto& con1 = sp1.getPermanence(i, sp1.connections.getConnectedThreshold()); - const auto& con2 = sp2.getPermanence(i, sp2.connections.getConnectedThreshold()); - ASSERT_TRUE(check_vector_eq(con1, con2)); - } - - auto conCounts1 = new UInt[numColumns]; - auto conCounts2 = new UInt[numColumns]; - sp1.getConnectedCounts(conCounts1); - sp2.getConnectedCounts(conCounts2); - ASSERT_TRUE(check_vector_eq(conCounts1, conCounts2, numColumns)); - delete[] conCounts1; - delete[] conCounts2; -} - void setup(SpatialPooler &sp, vector inputDim, vector columnDim, Real sparsity = 0.5f) { //we are interested in the sparsity, should make it artificially high. //As we added SP check that sparsity*numColumns > 0, which is correct requirement. //But many tests have very small (artificial) number of columns (for convenient results), //therefore the check is failing -> we must set high sparsity at initialization. - EXPECT_NO_THROW(sp.initialize(inputDim, columnDim, 16u, 0.5f, true, sparsity)); + EXPECT_NO_THROW(sp.initialize(inputDim, columnDim, 16u, 0.5f, true, sparsity)) << "SP test: failed to initialize() SP"; } void setup(SpatialPooler& sp, UInt numIn, UInt numCols, Real sparsity = 0.5f) { setup(sp, vector{numIn}, vector{numCols}, sparsity); @@ -231,11 +138,11 @@ TEST(SpatialPoolerTest, testUpdateInhibitionRadius) { colDim.push_back(57); colDim.push_back(31); colDim.push_back(2); - inputDim.push_back(1); + inputDim.push_back(100); inputDim.push_back(1); inputDim.push_back(1); - EXPECT_NO_THROW(sp.initialize(inputDim, colDim)); + EXPECT_NO_THROW(sp.initialize(inputDim, colDim, /*potentialRadius: must be <= numInputs_ */16u)); sp.setGlobalInhibition(true); ASSERT_EQ(sp.getInhibitionRadius(), 57u); @@ -1831,6 +1738,9 @@ TEST(SpatialPoolerTest, testSaveLoad) { sp1.save(outfile); outfile.close(); + EXPECT_NE(sp1, sp2); + + // now deserialize to the sp2 ifstream infile(filename, ifstream::binary); sp2.load(infile); infile.close(); @@ -1838,7 +1748,7 @@ TEST(SpatialPoolerTest, testSaveLoad) { int ret = ::remove(filename); ASSERT_TRUE(ret == 0) << "Failed to delete " << filename; - check_spatial_eq(sp1, sp2); + EXPECT_EQ(sp1, sp2) << "Deserialized SP must be the same!"; } @@ -1863,8 +1773,8 @@ TEST(SpatialPoolerTest, testSerialization2) { // Save initial trained model ofstream osC("outC.stream", ofstream::binary); - osC.precision(std::numeric_limits::digits10 + 1); - osC.precision(std::numeric_limits::digits10 + 1); +// osC.precision(std::numeric_limits::digits10 + 1); +// osC.precision(std::numeric_limits::digits10 + 1); sp1.save(osC); osC.close(); @@ -1894,8 +1804,8 @@ TEST(SpatialPoolerTest, testSerialization2) { // Serialize ofstream os("outC.stream", ofstream::binary); - os.precision(std::numeric_limits::digits10 + 1); - os.precision(std::numeric_limits::digits10 + 1); +// os.precision(std::numeric_limits::digits10 + 1); +// os.precision(std::numeric_limits::digits10 + 1); spTemp.save(os); os.close(); @@ -1924,7 +1834,7 @@ TEST(SpatialPoolerTest, testSaveLoad_ar) { int ret = ::remove(filename); ASSERT_TRUE(ret == 0) << "Failed to delete " << filename; - check_spatial_eq(sp1, sp2); + EXPECT_EQ(sp1, sp2); } @@ -1967,7 +1877,6 @@ TEST(SpatialPoolerTest, testSerialization_ar) { //ss.seekg(0); EXPECT_NO_THROW(spTemp.load(ss)); ASSERT_EQ(spTemp.getSeed(), 1u); - check_spatial_eq(sp1, spTemp); //detailed "equals" method here in test ASSERT_EQ(sp1, spTemp) << "Loaded SP is not the same as original"; //equals method used in SP // 1 step: Create new input & compute SP output @@ -2012,6 +1921,9 @@ TEST(SpatialPoolerTest, testConstructorVsInitialize) { /*spVerbosity*/ 0, /*wrapAround*/ true); + const SpatialPooler spCopy = sp1; + EXPECT_EQ(sp1, spCopy) << "Copy constructor should be equal"; + // Initialize SP using the "initialize" method SpatialPooler sp2; sp2.initialize( @@ -2034,9 +1946,7 @@ TEST(SpatialPoolerTest, testConstructorVsInitialize) { /*wrapAround*/ true); // The two SP should be the same - check_spatial_eq(sp1, sp2); - EXPECT_EQ(sp1, sp2); - EXPECT_TRUE(sp1 == sp2) << "Spatial Poolers not equal"; + EXPECT_EQ(sp1, sp2) << "Spatial Poolers not equal"; } From e0fdb4946d12edfb51babd8497e701e48e927009 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 24 Apr 2020 11:43:03 +0200 Subject: [PATCH 10/17] Connections: add Serializable interface to *Data structures these are helper data classes that hold "heavy" info on connections: SegmentData, SynapseData --- src/examples/hello/HelloSPTP.cpp | 2 +- src/htm/algorithms/Connections.hpp | 128 ++++++++++++------- src/test/unit/algorithms/ConnectionsTest.cpp | 3 + 3 files changed, 88 insertions(+), 45 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 7803312faf..41519805d6 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -237,7 +237,7 @@ EPOCHS = 2; // make test faster in Debug if(e+1 == 5000) { // For debugging: save SP's state in 1 step, comment out, recompile, load SP and compare in another // step 1: - //spGlobal.saveToFile("/tmp/spG.save"); + spGlobal.saveToFile("/tmp/spG.save"); // step 2: SpatialPooler resumedSP; resumedSP.loadFromFile("/tmp/spG.save"); diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index d8bb35e8a5..8a40f4b733 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -73,11 +73,15 @@ struct SynapseData: public Serializable { template void save_ar(Archive & ar) const { ar(cereal::make_nvp("perm", permanence), - cereal::make_nvp("presyn", presynapticCell)); + cereal::make_nvp("presyn", presynapticCell), + cereal::make_nvp("segment", segment), + cereal::make_nvp("presynapticMapIndex", presynapticMapIndex_), + cereal::make_nvp("id", id) + ); } template void load_ar(Archive & ar) { - ar( permanence, presynapticCell); + ar( permanence, presynapticCell, segment, presynapticMapIndex_, id); } }; @@ -94,7 +98,7 @@ struct SynapseData: public Serializable { * @param cell * The cell that this segment is on. */ -struct SegmentData { +struct SegmentData: public Serializable { SegmentData(const CellIdx cell, Segment id, UInt32 lastUsed = 0) : cell(cell), numConnected(0), lastUsed(lastUsed), id(id) {} //default constructor std::vector synapses; @@ -102,6 +106,23 @@ struct SegmentData { SynapseIdx numConnected; //number of permanences from `synapses` that are >= synPermConnected, ie connected synapses UInt32 lastUsed = 0; //last used time (iteration). Used for segment pruning by "least recently used" (LRU) in `createSegment` Segment id; + + //Serialize + SegmentData() {}; //empty constructor for serialization, do not use + CerealAdapter; + template + void save_ar(Archive & ar) const { + ar(cereal::make_nvp("synapses", synapses), + cereal::make_nvp("cell", cell), + cereal::make_nvp("numConnected", numConnected), + cereal::make_nvp("lastUsed", lastUsed), + cereal::make_nvp("id", id) + ); + } + template + void load_ar(Archive & ar) { + ar( synapses, cell, numConnected, lastUsed, id); + } }; /** @@ -115,8 +136,19 @@ struct SegmentData { * Segments on this cell. * */ -struct CellData { +struct CellData : public Serializable { std::vector segments; + + //Serialization + CerealAdapter; + template + void save_ar(Archive & ar) const { + ar(cereal::make_nvp("segments", segments)); + } + template + void load_ar(Archive & ar) { + ar( segments); + } }; /** @@ -547,54 +579,62 @@ class Connections : public Serializable CerealAdapter; template void save_ar(Archive & ar) const { - // make this look like a queue of items to be sent. - // and a queue of sizes so we can distribute the - // correct number for each level when deserializing. - std::deque syndata; - std::deque sizes; - sizes.push_back(cells_.size()); - for (CellData cellData : cells_) { - const std::vector &segments = cellData.segments; - sizes.push_back(segments.size()); - for (Segment segment : segments) { - const SegmentData &segmentData = segments_[segment]; - const std::vector &synapses = segmentData.synapses; - sizes.push_back(synapses.size()); - for (Synapse synapse : synapses) { - const SynapseData &synapseData = synapses_[synapse]; - syndata.push_back(synapseData); - } - } - } ar(CEREAL_NVP(connectedThreshold_)); - ar(CEREAL_NVP(sizes)); - ar(CEREAL_NVP(syndata)); ar(CEREAL_NVP(iteration_)); + ar(cereal::make_nvp("numCells", cells_.size())); //not real member, helper for constructor + ar(CEREAL_NVP(cells_)); + ar(CEREAL_NVP(segments_)); + ar(CEREAL_NVP(synapses_)); + + ar(CEREAL_NVP(destroyedSynapses_)); + ar(CEREAL_NVP(destroyedSegments_)); + + ar(CEREAL_NVP(potentialSynapsesForPresynapticCell_)); + ar(CEREAL_NVP(connectedSynapsesForPresynapticCell_)); + ar(CEREAL_NVP(potentialSegmentsForPresynapticCell_)); + ar(CEREAL_NVP(connectedSegmentsForPresynapticCell_)); + + ar(CEREAL_NVP(nextSegmentOrdinal_)); + ar(CEREAL_NVP(nextSynapseOrdinal_)); + + ar(CEREAL_NVP(timeseries_)); + ar(CEREAL_NVP(previousUpdates_)); + ar(CEREAL_NVP(currentUpdates_)); + + ar(CEREAL_NVP(prunedSyns_)); + ar(CEREAL_NVP(prunedSegs_)); } template void load_ar(Archive & ar) { - std::deque sizes; - std::deque syndata; ar(CEREAL_NVP(connectedThreshold_)); - ar(CEREAL_NVP(sizes)); - ar(CEREAL_NVP(syndata)); - - CellIdx numCells = static_cast(sizes.front()); sizes.pop_front(); - initialize(numCells, connectedThreshold_); - for (UInt cell = 0; cell < numCells; cell++) { - size_t numSegments = sizes.front(); sizes.pop_front(); - for (SegmentIdx j = 0; j < static_cast(numSegments); j++) { - Segment segment = createSegment( cell ); - - size_t numSynapses = sizes.front(); sizes.pop_front(); - for (SynapseIdx k = 0; k < static_cast(numSynapses); k++) { - SynapseData& syn = syndata.front(); syndata.pop_front(); - createSynapse( segment, syn.presynapticCell, syn.permanence ); - } - } - } ar(CEREAL_NVP(iteration_)); + { + size_t numCells; + ar(CEREAL_NVP(numCells)); //helper for constructor + initialize(numCells, connectedThreshold_); //initialize Connections + } + ar(CEREAL_NVP(cells_)); + ar(CEREAL_NVP(segments_)); + ar(CEREAL_NVP(synapses_)); + + ar(CEREAL_NVP(destroyedSynapses_)); + ar(CEREAL_NVP(destroyedSegments_)); + + ar(CEREAL_NVP(potentialSynapsesForPresynapticCell_)); + ar(CEREAL_NVP(connectedSynapsesForPresynapticCell_)); + ar(CEREAL_NVP(potentialSegmentsForPresynapticCell_)); + ar(CEREAL_NVP(connectedSegmentsForPresynapticCell_)); + + ar(CEREAL_NVP(nextSegmentOrdinal_)); + ar(CEREAL_NVP(nextSynapseOrdinal_)); + + ar(CEREAL_NVP(timeseries_)); + ar(CEREAL_NVP(previousUpdates_)); + ar(CEREAL_NVP(currentUpdates_)); + + ar(CEREAL_NVP(prunedSyns_)); + ar(CEREAL_NVP(prunedSegs_)); } /** diff --git a/src/test/unit/algorithms/ConnectionsTest.cpp b/src/test/unit/algorithms/ConnectionsTest.cpp index 453449b20b..ede888b1d6 100644 --- a/src/test/unit/algorithms/ConnectionsTest.cpp +++ b/src/test/unit/algorithms/ConnectionsTest.cpp @@ -802,6 +802,7 @@ TEST(ConnectionsTest, testSaveLoad) { ASSERT_EQ(c1, c2); } + TEST(ConnectionsTest, testCreateSegmentOverflow) { const auto LIMIT = std::numeric_limits::max(); if(LIMIT <= 256) { //connections::Segment is too large (likely uint32), so this test would run, but memory @@ -819,6 +820,7 @@ TEST(ConnectionsTest, testCreateSegmentOverflow) { } } + TEST(ConnectionsTest, testCreateSynapseOverflow) { const auto LIMIT = std::numeric_limits::max(); if(LIMIT <= 256) { //connections::Synapse is too large (likely uint32), so this test would run, but memory @@ -837,6 +839,7 @@ TEST(ConnectionsTest, testCreateSynapseOverflow) { } } + TEST(ConnectionsTest, testTimeseries) { Connections C( 1, .5, true ); auto seg = C.createSegment(0); From fa46037657ca0d2fcdb575808790fb3b6ce51d43 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 24 Apr 2020 15:04:48 +0200 Subject: [PATCH 11/17] Connections: rework equals operator== to be more descriptive when difference is found. Useful for debugging. --- src/htm/algorithms/Connections.cpp | 72 +++++++++++--------- src/htm/algorithms/Connections.hpp | 49 ++++++++++++- src/test/unit/algorithms/ConnectionsTest.cpp | 13 ++++ 3 files changed, 99 insertions(+), 35 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 0843383d1f..3ce3a27b43 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -702,49 +702,53 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) -bool Connections::operator==(const Connections &other) const { - if (cells_.size() != other.cells_.size()) - return false; +bool Connections::operator==(const Connections &o) const { + try { + NTA_CHECK (cells_ == o.cells_) << "Connections equals: cells_"; + + //also check underlying datastructures (segments, and subsequently synapses). Can be time consuming. + //1.cells: + for(const auto cellD : cells_) { + //2.segments: + const auto& segments = cellD.segments; + for(const auto seg : segments) { + NTA_CHECK( dataForSegment(seg) == o.dataForSegment(seg) ) << "CellData equals: segmentData"; + //3.synapses: + const auto& synapses = dataForSegment(seg).synapses; + for(const auto syn : synapses) { + NTA_CHECK(dataForSynapse(syn) == o.dataForSynapse(syn) ) << "SegmentData equals: synapseData"; + } + } + } - if(iteration_ != other.iteration_) return false; + NTA_CHECK (segments_ == o.segments_ ) << "Connections equals: segments_"; + NTA_CHECK (destroyedSegments_ == o.destroyedSegments_ ) << "Connections equals: destroyedSegments_"; - for (CellIdx i = 0; i < static_cast(cells_.size()); i++) { - const CellData &cellData = cells_[i]; - const CellData &otherCellData = other.cells_[i]; + NTA_CHECK (synapses_ == o.synapses_ ) << "Connections equals: synapses_"; + NTA_CHECK (destroyedSynapses_ == o.destroyedSynapses_ ) << "Connections equals: destroyedSynapses_"; - if (cellData.segments.size() != otherCellData.segments.size()) { - return false; - } + NTA_CHECK (connectedThreshold_ == o.connectedThreshold_ ) << "Connections equals: connectedThreshold_"; + NTA_CHECK (iteration_ == o.iteration_ ) << "Connections equals: iteration_"; - for (SegmentIdx j = 0; j < static_cast(cellData.segments.size()); j++) { - const Segment segment = cellData.segments[j]; - const SegmentData &segmentData = segments_[segment]; - const Segment otherSegment = otherCellData.segments[j]; - const SegmentData &otherSegmentData = other.segments_[otherSegment]; + NTA_CHECK(potentialSynapsesForPresynapticCell_ == o.potentialSynapsesForPresynapticCell_); + NTA_CHECK(connectedSynapsesForPresynapticCell_ == o.connectedSynapsesForPresynapticCell_); + NTA_CHECK(potentialSegmentsForPresynapticCell_ == o.potentialSegmentsForPresynapticCell_); + NTA_CHECK(connectedSegmentsForPresynapticCell_ == o.connectedSegmentsForPresynapticCell_); - if (segmentData.synapses.size() != otherSegmentData.synapses.size() || - segmentData.cell != otherSegmentData.cell) { - return false; - } + NTA_CHECK (nextSegmentOrdinal_ == o.nextSegmentOrdinal_ ) << "Connections equals: nextSegmentOrdinal_"; + NTA_CHECK (nextSynapseOrdinal_ == o.nextSynapseOrdinal_ ) << "Connections equals: nextSynapseOrdinal_"; - for (SynapseIdx k = 0; k < static_cast(segmentData.synapses.size()); k++) { - const Synapse synapse = segmentData.synapses[k]; - const SynapseData &synapseData = synapses_[synapse]; - const Synapse otherSynapse = otherSegmentData.synapses[k]; - const SynapseData &otherSynapseData = other.synapses_[otherSynapse]; + NTA_CHECK (timeseries_ == o.timeseries_ ) << "Connections equals: timeseries_"; + NTA_CHECK (previousUpdates_ == o.previousUpdates_ ) << "Connections equals: previousUpdates_"; + NTA_CHECK (currentUpdates_ == o.currentUpdates_ ) << "Connections equals: currentUpdates_"; - if (synapseData.presynapticCell != otherSynapseData.presynapticCell || - synapseData.permanence != otherSynapseData.permanence) { - return false; - } + NTA_CHECK (prunedSyns_ == o.prunedSyns_ ) << "Connections equals: prunedSyns_"; + NTA_CHECK (prunedSegs_ == o.prunedSegs_ ) << "Connections equals: prunedSegs_"; - // Two functionally identical instances may have different flatIdxs. - NTA_ASSERT(synapseData.segment == segment); - NTA_ASSERT(otherSynapseData.segment == otherSegment); - } - } + } catch(const htm::Exception& ex) { + NTA_WARN << "Connection equals: differ! " << ex.what(); + return false; } - return true; } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 8a40f4b733..19ad7f042e 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -69,6 +69,7 @@ struct SynapseData: public Serializable { SynapseData() {} + //Serialization CerealAdapter; template void save_ar(Archive & ar) const { @@ -84,6 +85,22 @@ struct SynapseData: public Serializable { ar( permanence, presynapticCell, segment, presynapticMapIndex_, id); } + //operator== + bool operator==(const SynapseData& o) const { + try { + NTA_CHECK(presynapticCell == o.presynapticCell ) << "SynapseData equals: presynapticCell"; + NTA_CHECK(permanence == o.permanence ) << "SynapseData equals: permanence"; + NTA_CHECK(segment == o.segment ) << "SynapseData equals: segment"; + NTA_CHECK(presynapticMapIndex_ == o.presynapticMapIndex_ ) << "SynapseData equals: presynapticMapIndex_"; + NTA_CHECK(id == o.id ) << "SynapseData equals: id"; + } catch(const htm::Exception& ex) { + NTA_WARN << "SynapseData equals: " << ex.what(); + return false; + } + return true; + } + inline bool operator!=(const SynapseData& o) const { return !operator==(o); } + }; /** @@ -123,6 +140,23 @@ struct SegmentData: public Serializable { void load_ar(Archive & ar) { ar( synapses, cell, numConnected, lastUsed, id); } + + //equals op== + bool operator==(const SegmentData& o) const { + try { + NTA_CHECK(synapses == o.synapses) << "SegmentData equals: synapses"; + NTA_CHECK(cell == o.cell) << "SegmentData equals: cell"; + NTA_CHECK(numConnected == o.numConnected) << "SegmentData equals: numConnected"; + NTA_CHECK(lastUsed == o.lastUsed) << "SegmentData equals: lastUsed"; + NTA_CHECK(id == o.id) << "SegmentData equals: id"; + + } catch(const htm::Exception& ex) { + NTA_WARN << "SegmentData equals: " << ex.what(); + return false; + } + return true; + } + inline bool operator!=(const SegmentData& o) const { return !operator==(o); } }; /** @@ -149,8 +183,21 @@ struct CellData : public Serializable { void load_ar(Archive & ar) { ar( segments); } + + //operator== + bool operator==(const CellData& o) const { + try { + NTA_CHECK( segments == o.segments ) << "CellData equals: segments"; + } catch(const htm::Exception& ex) { + NTA_WARN << "CellData equals: " << ex.what(); + return false; + } + return true; + } + inline bool operator!=(const CellData& o) const { return !operator==(o); } }; + /** * A base class for Connections event handlers. * @@ -782,7 +829,7 @@ class Connections : public Serializable Synapse prunedSyns_ = 0; //how many synapses have been removed? Segment prunedSegs_ = 0; - //for listeners + //for listeners //TODO listeners are not serialized, nor included in equals == UInt32 nextEventToken_; std::map eventHandlers_; }; // end class Connections diff --git a/src/test/unit/algorithms/ConnectionsTest.cpp b/src/test/unit/algorithms/ConnectionsTest.cpp index ede888b1d6..728c0e82bb 100644 --- a/src/test/unit/algorithms/ConnectionsTest.cpp +++ b/src/test/unit/algorithms/ConnectionsTest.cpp @@ -874,3 +874,16 @@ TEST(ConnectionsTest, testTimeseries) { ASSERT_TRUE( (synData.permanence == 0.0f) or (synData.permanence == 1.0f) ); } } + + +TEST(ConnectionsTest, testEquals) { + Connections c1(100, 0.5, false), c2(100, 0.5, false); + ASSERT_EQ(c1, c2); + + setupSampleConnections(c1); //..creates some synapses. + ASSERT_NE(c1, c2); + + setupSampleConnections(c2); + ASSERT_EQ(c1, c2); + +} From bd84c41c813bf2f1c0839fa256be63e0d8fc2eba Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 27 Apr 2020 11:43:30 +0200 Subject: [PATCH 12/17] Connection: serialization err fixed apparently improper combination of cereal::make_nvp() and CEERAL_NVP(). Only the latter should be used. Finished simplified Conn serialization, only data-members are directly serialized. No need to call initialize() and other functions manually. --- VERSION | 2 +- src/examples/hello/HelloSPTP.cpp | 2 +- src/htm/algorithms/Connections.cpp | 19 ++++++---- src/htm/algorithms/Connections.hpp | 39 ++++++++++---------- src/test/unit/algorithms/ConnectionsTest.cpp | 10 +++-- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/VERSION b/VERSION index 826e142463..76b426fd03 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.1.1 +v2.1.1 \ No newline at end of file diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 41519805d6..76bb120fa4 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -235,7 +235,7 @@ EPOCHS = 2; // make test faster in Debug #ifdef _ARCH_DETERMINISTIC if(e+1 == 5000) { - // For debugging: save SP's state in 1 step, comment out, recompile, load SP and compare in another + // For debugging serialization: save SP's state in 1 step, comment out, recompile, load SP and compare in another // step 1: spGlobal.saveToFile("/tmp/spG.save"); // step 2: diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 3ce3a27b43..23016d1cef 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -704,8 +704,16 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) bool Connections::operator==(const Connections &o) const { try { - NTA_CHECK (cells_ == o.cells_) << "Connections equals: cells_"; - + NTA_CHECK (cells_.size() == o.cells_.size()) << "Connections equals: cells_" << cells_.size() << " vs. " << o.cells_.size(); + NTA_CHECK (cells_ == o.cells_) << "Connections equals: cells_" << cells_.size() << " vs. " << o.cells_.size(); + + NTA_CHECK (segments_ == o.segments_ ) << "Connections equals: segments_"; + NTA_CHECK (destroyedSegments_ == o.destroyedSegments_ ) << "Connections equals: destroyedSegments_"; + + NTA_CHECK (synapses_ == o.synapses_ ) << "Connections equals: synapses_"; + NTA_CHECK (destroyedSynapses_ == o.destroyedSynapses_ ) << "Connections equals: destroyedSynapses_"; + + //also check underlying datastructures (segments, and subsequently synapses). Can be time consuming. //1.cells: for(const auto cellD : cells_) { @@ -721,11 +729,6 @@ bool Connections::operator==(const Connections &o) const { } } - NTA_CHECK (segments_ == o.segments_ ) << "Connections equals: segments_"; - NTA_CHECK (destroyedSegments_ == o.destroyedSegments_ ) << "Connections equals: destroyedSegments_"; - - NTA_CHECK (synapses_ == o.synapses_ ) << "Connections equals: synapses_"; - NTA_CHECK (destroyedSynapses_ == o.destroyedSynapses_ ) << "Connections equals: destroyedSynapses_"; NTA_CHECK (connectedThreshold_ == o.connectedThreshold_ ) << "Connections equals: connectedThreshold_"; NTA_CHECK (iteration_ == o.iteration_ ) << "Connections equals: iteration_"; @@ -746,7 +749,7 @@ bool Connections::operator==(const Connections &o) const { NTA_CHECK (prunedSegs_ == o.prunedSegs_ ) << "Connections equals: prunedSegs_"; } catch(const htm::Exception& ex) { - NTA_WARN << "Connection equals: differ! " << ex.what(); + //NTA_WARN << "Connection equals: differ! " << ex.what(); return false; } return true; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 19ad7f042e..7c2c8278bf 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -73,11 +73,11 @@ struct SynapseData: public Serializable { CerealAdapter; template void save_ar(Archive & ar) const { - ar(cereal::make_nvp("perm", permanence), - cereal::make_nvp("presyn", presynapticCell), - cereal::make_nvp("segment", segment), - cereal::make_nvp("presynapticMapIndex", presynapticMapIndex_), - cereal::make_nvp("id", id) + ar(CEREAL_NVP(permanence), + CEREAL_NVP(presynapticCell), + CEREAL_NVP(segment), + CEREAL_NVP(presynapticMapIndex_), + CEREAL_NVP(id) ); } template @@ -94,7 +94,9 @@ struct SynapseData: public Serializable { NTA_CHECK(presynapticMapIndex_ == o.presynapticMapIndex_ ) << "SynapseData equals: presynapticMapIndex_"; NTA_CHECK(id == o.id ) << "SynapseData equals: id"; } catch(const htm::Exception& ex) { - NTA_WARN << "SynapseData equals: " << ex.what(); + //NTA_WARN << "SynapseData equals: " << ex.what(); //Note: uncomment for debug, tells you + //where the diff is. It's perfectly OK for the "exception" to occur, as it just denotes + //that the data is NOT equal. return false; } return true; @@ -129,11 +131,11 @@ struct SegmentData: public Serializable { CerealAdapter; template void save_ar(Archive & ar) const { - ar(cereal::make_nvp("synapses", synapses), - cereal::make_nvp("cell", cell), - cereal::make_nvp("numConnected", numConnected), - cereal::make_nvp("lastUsed", lastUsed), - cereal::make_nvp("id", id) + ar(CEREAL_NVP(synapses), + CEREAL_NVP(cell), + CEREAL_NVP(numConnected), + CEREAL_NVP(lastUsed), + CEREAL_NVP(id) ); } template @@ -151,7 +153,7 @@ struct SegmentData: public Serializable { NTA_CHECK(id == o.id) << "SegmentData equals: id"; } catch(const htm::Exception& ex) { - NTA_WARN << "SegmentData equals: " << ex.what(); + //NTA_WARN << "SegmentData equals: " << ex.what(); return false; } return true; @@ -177,7 +179,8 @@ struct CellData : public Serializable { CerealAdapter; template void save_ar(Archive & ar) const { - ar(cereal::make_nvp("segments", segments)); + ar(CEREAL_NVP(segments) + ); } template void load_ar(Archive & ar) { @@ -189,7 +192,7 @@ struct CellData : public Serializable { try { NTA_CHECK( segments == o.segments ) << "CellData equals: segments"; } catch(const htm::Exception& ex) { - NTA_WARN << "CellData equals: " << ex.what(); + //NTA_WARN << "CellData equals: " << ex.what(); return false; } return true; @@ -628,7 +631,6 @@ class Connections : public Serializable void save_ar(Archive & ar) const { ar(CEREAL_NVP(connectedThreshold_)); ar(CEREAL_NVP(iteration_)); - ar(cereal::make_nvp("numCells", cells_.size())); //not real member, helper for constructor ar(CEREAL_NVP(cells_)); ar(CEREAL_NVP(segments_)); ar(CEREAL_NVP(synapses_)); @@ -656,11 +658,8 @@ class Connections : public Serializable void load_ar(Archive & ar) { ar(CEREAL_NVP(connectedThreshold_)); ar(CEREAL_NVP(iteration_)); - { - size_t numCells; - ar(CEREAL_NVP(numCells)); //helper for constructor - initialize(numCells, connectedThreshold_); //initialize Connections - } + //!initialize(numCells, connectedThreshold_); //initialize Connections //Note: we actually don't call Connections + //initialize() as all the members are de/serialized. ar(CEREAL_NVP(cells_)); ar(CEREAL_NVP(segments_)); ar(CEREAL_NVP(synapses_)); diff --git a/src/test/unit/algorithms/ConnectionsTest.cpp b/src/test/unit/algorithms/ConnectionsTest.cpp index 728c0e82bb..1d698b1ead 100644 --- a/src/test/unit/algorithms/ConnectionsTest.cpp +++ b/src/test/unit/algorithms/ConnectionsTest.cpp @@ -792,14 +792,16 @@ TEST(ConnectionsTest, testSaveLoad) { c1.destroySegment(segment); computeSampleActivity(c1); + ASSERT_NE(c1, c2) << "shouldn't be eq"; { stringstream ss; c1.save(ss); c2.load(ss); } + if(c1 == c2) NTA_WARN << "nice"; - ASSERT_EQ(c1, c2); + ASSERT_EQ(c1, c2) << "Deserialized must be equal"; } @@ -878,12 +880,12 @@ TEST(ConnectionsTest, testTimeseries) { TEST(ConnectionsTest, testEquals) { Connections c1(100, 0.5, false), c2(100, 0.5, false); - ASSERT_EQ(c1, c2); + ASSERT_EQ(c1, c2) << "Conn Eq: 1"; setupSampleConnections(c1); //..creates some synapses. - ASSERT_NE(c1, c2); + ASSERT_NE(c1, c2) << "Conn Eq: 2"; setupSampleConnections(c2); - ASSERT_EQ(c1, c2); + ASSERT_EQ(c1, c2) << "Conn Eq: 3"; } From ce72ea745067eb3d45c0cf71fde87090ae79dc50 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 27 Apr 2020 21:42:31 +0200 Subject: [PATCH 13/17] SP: debugging HelloSPTP equals, save --- src/htm/algorithms/Connections.cpp | 2 +- src/htm/algorithms/SpatialPooler.cpp | 49 ++++++++++++---------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 23016d1cef..40b1007e34 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -749,7 +749,7 @@ bool Connections::operator==(const Connections &o) const { NTA_CHECK (prunedSegs_ == o.prunedSegs_ ) << "Connections equals: prunedSegs_"; } catch(const htm::Exception& ex) { - //NTA_WARN << "Connection equals: differ! " << ex.what(); + std::cout << "Connection equals: differ! " << ex.what(); return false; } return true; diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index f01d3ac037..31b76c1692 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -1022,9 +1022,9 @@ void SpatialPooler::printState(const vector &state, std::ostream& out) con bool SpatialPooler::operator==(const SpatialPooler& o) const{ //Note on implementation: NTA_CHECKs are used, so we know //what condition failed. - try { - // Store the simple variables first. + + // Check SP's derect member variables first:. NTA_CHECK (numInputs_ == o.numInputs_) << "SP equals: numInputs:" << numInputs_ << " vs. " << o.numInputs_ ; NTA_CHECK (numColumns_ == o.numColumns_) << "SP equals: numColumns: " << numColumns_ << " vs. " << o.numColumns_; NTA_CHECK (potentialRadius_ == o.potentialRadius_) << "SP equals: potentialRadius: " << potentialRadius_ << " vs. " << o.potentialRadius_; @@ -1050,64 +1050,55 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ << "SP equals: minPctOverlapDutyCycles: " << minPctOverlapDutyCycles_ << " vs. " << minPctOverlapDutyCycles_; NTA_CHECK (wrapAround_ == o.wrapAround_) << "SP equals: wrapAround: " << wrapAround_ << " vs. " << o.wrapAround_; - //Random NTA_CHECK (rng_ == o.rng_) << "SP equals: rng differs"; // compare connections - NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; + //NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; + // compare vectors. NTA_CHECK (inputDimensions_ == o.inputDimensions_) << "SP equals: inputDimensions differ"; NTA_CHECK (columnDimensions_ == o.columnDimensions_) << "SP equals: columnDimensions differ"; NTA_CHECK (boostFactors_ == o.boostFactors_) << "SP equals: boostFactors"; - NTA_CHECK (overlapDutyCycles_ == o.overlapDutyCycles_) << "SP equals: overlapDutyCycles"; //seems bug is here?! - NTA_CHECK (activeDutyCycles_ == o.activeDutyCycles_) << "SP equals: activeDutyCucles"; + //!NTA_CHECK (overlapDutyCycles_ == o.overlapDutyCycles_) << "SP equals: overlapDutyCycles"; //FIXME seems bug is here?! + //!NTA_CHECK (activeDutyCycles_ == o.activeDutyCycles_) << "SP equals: activeDutyCucles"; //FIXME here? NTA_CHECK (minOverlapDutyCycles_ == o.minOverlapDutyCycles_) << "SP equals: minOverlapDutyCycles"; //detailed compare potentials for (UInt i = 0; i < numColumns_; i++) { - auto potential1 = new UInt[numInputs_]; - auto potential2 = new UInt[numInputs_]; - this->getPotential(i, potential1); - o.getPotential(i, potential2); - for(size_t j=0; j< numInputs_; j++) { - NTA_CHECK(potential1[j] == potential2[j]) << "SP potential " << j << " is " << potential1[j] << " vs " << potential2[j] << ".\n"; - } - - //!NTA_CHECK(potential1 == potential2) << "SP equals: potentials"; //NOTE vectors can be compared, but does not work for arrays! - //...therefore need to iterate. - delete[] potential1; - delete[] potential2; + std::vector potential1(numInputs_, 0); + std::vector potential2(numInputs_, 0); + this->getPotential(i, potential1.data()); //TODO make the method return vect? + o.getPotential(i, potential2.data()); + //!NTA_CHECK(potential1 == potential2) << "SP equals: potentials"; //FIXME } // check get permanences for (UInt i = 0; i < numColumns_; i++) { const auto& perm1 = this->getPermanence(i); const auto& perm2 = o.getPermanence(i); - NTA_CHECK(perm1 == perm2) << "SP equals: permanences"; + //!NTA_CHECK(perm1 == perm2) << "SP equals: permanences"; } // check get connected synapses for (UInt i = 0; i < numColumns_; i++) { const auto& con1 = this->getPermanence(i, this->connections.getConnectedThreshold()); const auto& con2 = o.getPermanence(i, o.connections.getConnectedThreshold()); - NTA_CHECK(con1 == con2) << "SP equals: connected synapses"; + //!NTA_CHECK(con1 == con2) << "SP equals: connected synapses"; } { - auto conCounts1 = new UInt[numColumns_]; - auto conCounts2 = new UInt[numColumns_]; - this->getConnectedCounts(conCounts1); - o.getConnectedCounts(conCounts2); + std::vector conCounts1(numColumns_, 0); + std::vector conCounts2(numColumns_, 0); + this->getConnectedCounts(conCounts1.data()); + o.getConnectedCounts(conCounts2.data()); //!NTA_CHECK(conCounts1 == conCounts2) << "SP equals: connected column counts"; - for(size_t i=0; i< numColumns_; i++) { - NTA_CHECK(conCounts1[i] == conCounts2[i]) << "SP equals: connected column counts. at" << i << " is " << conCounts1[i] << " vs " << conCounts2[i] << "\n"; - } - delete[] conCounts1; - delete[] conCounts2; } + std::cout << "here\n"; + // compare connections +NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; //FIXME connections.segments_ } catch(const htm::Exception& ex) { //some check failed -> not equal From ea7b88df2c928714b90291d3f6c3bcb36e95c94c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 28 Apr 2020 00:30:17 +0200 Subject: [PATCH 14/17] Random: small cleanup --- src/htm/utils/Random.cpp | 9 +++------ src/htm/utils/Random.hpp | 17 ++++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/htm/utils/Random.cpp b/src/htm/utils/Random.cpp index fca58f3657..e0bb45d0c2 100644 --- a/src/htm/utils/Random.cpp +++ b/src/htm/utils/Random.cpp @@ -33,10 +33,7 @@ std::random_device rd; //HW RNG, undeterministic, platform dependant. Use only f Random::Random(const UInt64 seed) { if (seed == 0) { - const unsigned int static_seed = rd(); - std::mt19937 static_gen(static_seed); - NTA_INFO << "Random seed: " << static_seed; - + std::mt19937 static_gen(rd()); seed_ = static_gen(); //generate random value from HW RNG } else { seed_ = seed; @@ -48,7 +45,7 @@ Random::Random(const UInt64 seed) { namespace htm { // helper function for seeding RNGs across the plugin barrier -UInt32 GetRandomSeed() { - return htm::Random(0).getUInt32(); +UInt32 GetRandomSeed(const UInt seed) { + return htm::Random(seed).getUInt32(); } } // namespace htm diff --git a/src/htm/utils/Random.hpp b/src/htm/utils/Random.hpp index 064b0de4f6..9cc7d57f65 100644 --- a/src/htm/utils/Random.hpp +++ b/src/htm/utils/Random.hpp @@ -78,12 +78,16 @@ class Random : public Serializable { CerealAdapter; template void save_ar(Archive & ar) const { - ar( cereal::make_nvp("seed", seed_), cereal::make_nvp("steps", steps_)); + ar( CEREAL_NVP(seed_), + CEREAL_NVP(steps_) + ); } template void load_ar(Archive & ar) { - ar( seed_, steps_); - gen.seed(static_cast(seed_)); //reseed + ar( CEREAL_NVP(seed_), + CEREAL_NVP(steps_) + ); + gen.seed(static_cast(seed_)); //reseed gen.discard(steps_); //advance n steps } @@ -161,7 +165,7 @@ class Random : public Serializable { protected: friend class RandomTest; - friend UInt32 GetRandomSeed(); + friend UInt32 GetRandomSeed(const UInt seed); private: UInt64 seed_; UInt64 steps_ = 0; //step counter, used in serialization. It is important that steps_ is in sync with number of @@ -179,8 +183,7 @@ class Random : public Serializable { typename std::iterator_traits::difference_type i, n; n = last - first; for (i = n-1; i > 0; --i) { - using std::swap; - swap(first[i], first[this->getUInt32(static_cast(i+1))]); + std::swap(first[i], first[this->getUInt32(static_cast(i+1))]); } } }; @@ -191,7 +194,7 @@ class Random : public Serializable { // set to this function. The plugin framework can override this // behavior by explicitly setting the seeder to the RandomSeeder // function provided by the application. -UInt32 GetRandomSeed(); +UInt32 GetRandomSeed(const UInt seed=0); } // namespace htm From 284dc2e31b839938a6d799b1ad1fda2eaffa670a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 4 Jun 2020 09:21:40 +0200 Subject: [PATCH 15/17] SP enable == checks now passes after merging the last PR --- src/examples/hello/HelloSPTP.cpp | 18 +++++++++--------- src/htm/algorithms/SpatialPooler.cpp | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index cdbf059581..f0f3cafa2b 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -214,34 +214,34 @@ EPOCHS = 2; // make test faster in Debug SDR goldSP({COLS}); const SDR_sparse_t deterministicSP{ - 66, 70, 72, 75, 76, 82, 83, 85, 99, 297, 300, 301, 303, 305, 306, 308, 309, 311, 316, 320, 321, 323, 324, 325, 327, 329, 330, 343, 345, 347, 363, 508, 1084, 1110, 1112, 1115, 1120, 1131, 1522, 1532, 1535, 1634, 1684, 1697, 1732, 1769, 1777, 1778, 1800, 1801, 1803, 1817, 1818, 1823, 1830, 1834, 1836, 1844, 1847, 1851, 1855, 1859, 1860, 1862, 1866, 1882, 1886, 1897,1906, 1918, 1921, 1922, 1931, 1936, 1954, 1961, 1964, 1965, 1966, 1967, 1968, 1970, 1971, 1972, 1973, 1975, 1977, 1978, 1980, 1981, 1987, 1989, 1990, 2001, 2006, 2007, 2009, 2015, 2016, 2018, 2020, 2042 + 68, 79, 86, 98, 105, 257, 263, 286, 302, 306, 307, 309, 310, 313, 315, 317, 318, 320, 323, 325, 326, 329, 334, 350, 356, 363, 539, 935, 1089, 1093, 1098, 1111, 1112, 1118, 1120, 1124, 1133, 1508, 1513, 1521, 1624, 1746, 1765, 1774, 1775, 1776, 1780, 1784, 1787, 1802, 1804, 1811, 1813, 1815, 1819, 1844, 1845, 1865, 1876, 1884, 1891, 1900, 1903, 1904, 1908, 1909, 1925, 1926, 1928, 1932, 1933, 1943, 1947, 1952, 1955, 1959, 1961, 1962, 1964, 1966, 1967, 1969, 1970, 1971, 1973, 1975, 1977, 1980, 1981, 1982, 1983, 1985, 1987, 1991, 1994, 2002, 2004, 2011, 2027, 2030, 2031, 2045 }; goldSP.setSparse(deterministicSP); SDR goldSPlocal({COLS}); const SDR_sparse_t deterministicSPlocal{ - 12, 13, 71, 72, 75, 78, 82, 85, 131, 171, 182, 186, 189, 194, 201, 263, 277, 287, 308, 319, 323, 337, 339, 365, 407, 429, 432, 434, 443, 445, 493, 494, 502, 508, 523, 542, 554, 559, 585, 586, 610, 611, 612, 644, 645, 647, 691, 698, 699, 701, 702, 707, 777, 809, 810, 811, 833, 839, 841, 920, 923, 928, 929, 935, 955, 1003, 1005, 1073, 1076, 1094, 1095, 1114, 1115, 1133, 1134, 1184, 1203, 1232, 1233, 1244, 1253, 1268, 1278, 1291, 1294, 1306, 1309, 1331, 1402, 1410, 1427, 1434, 1442, 1463, 1508, 1512, 1514, 1515, 1518, 1561, 1564, 1623, 1626, 1630, 1640, 1647, 1691, 1694, 1729, 1745, 1746, 1760, 1797, 1804, 1805, 1812, 1827, 1831, 1858, 1861, 1862, 1918, 1956, 1961, 1965, 1971, 1975, 1994, 2012 + 17, 71, 75, 79, 81, 86, 89, 164, 189, 198, 203, 262, 297, 314, 324, 326, 329, 337, 360, 379, 432, 443, 448, 452, 509, 520, 525, 526, 529, 536, 612, 619, 624, 630, 649, 652, 693, 717, 719, 720, 754, 810, 813, 815, 835, 839, 849, 884, 890, 914, 925, 931, 937, 945, 971, 1016, 1088, 1089, 1095, 1105, 1109, 1133, 1159, 1209, 1214, 1228, 1235, 1241, 1244, 1273, 1295, 1314, 1329, 1336, 1342, 1427, 1435, 1436, 1448, 1461, 1486, 1496, 1500, 1523, 1561, 1572, 1576, 1603, 1610, 1624, 1635, 1649, 1664, 1685, 1725, 1732, 1741, 1758, 1800, 1804, 1811, 1824, 1862, 1870, 1882, 1883, 1887, 1903, 1956, 1963, 1971, 1977, 1984, 2015 }; goldSPlocal.setSparse(deterministicSPlocal); SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 72, 85, 102, 114, 122, 126, 287, 308, 337, 339, 542, 920, 939, 952, 1268, 1507, 1508, 1518, 1546, 1547, 1626, 1627, 1633, 1668, 1727, 1804, 1805, 1827, 1832, 1844, 1859, 1862, 1918, 1920, 1924, 1931, 1933, 1945, 1961, 1965, 1966, 1968, 1970, 1973, 1975, 1976, 1977, 1979, 1986, 1987, 1991, 1992, 1996, 1998, 2002, 2006, 2008, 2012, 2042, 2045 + 79, 92, 98, 128, 136, 286, 307, 309, 310, 313, 315, 325, 356, 454, 539, 1093, 1111, 1112, 1120, 1237, 1278, 1467, 1497, 1508, 1513, 1521, 1614, 1624, 1635, 1668, 1669, 1673,1699, 1774, 1775, 1776, 1780, 1784, 1808, 1813, 1815, 1816, 1821, 1827, 1845, 1865, 1900, 1911, 1913, 1925, 1929, 1931, 1932, 1933, 1940, 1941, 1947, 1949, 1955, 1956, 1959, 1961, 1962, 1964, 1966, 1967, 1968, 1969, 1970, 1972, 1975, 1977, 1978, 1979, 1981, 1982, 1985, 1987, 1988, 1991, 1994, 2027, 2030, 2044, 2045 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.637255f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms - const float goldAnAvg = 0.40804f; // ...the averaged value, on the other hand, should improve/decrease. + const float goldAn = 0.470588f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms + const float goldAnAvg = 0.40961f; // ...the averaged value, on the other hand, should improve/decrease. #ifdef _ARCH_DETERMINISTIC if(e+1 == 5000) { // For debugging serialization: save SP's state in 1 step, comment out, recompile, load SP and compare in another // step 1: - spGlobal.saveToFile("/tmp/spG.save"); + //spGlobal.saveToFile("/tmp/spG.save"); // step 2: - SpatialPooler resumedSP; - resumedSP.loadFromFile("/tmp/spG.save"); - NTA_CHECK(spGlobal == resumedSP) << "SPs differ!"; + //SpatialPooler resumedSP; + //resumedSP.loadFromFile("/tmp/spG.save"); + //NTA_CHECK(spGlobal == resumedSP) << "SPs differ!"; // --end of debugging //these hand-written values are only valid for EPOCHS = 5000 (default), but not for debug and custom runs. diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index c41ac0597e..9d1c250da6 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -1052,15 +1052,15 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ NTA_CHECK (rng_ == o.rng_) << "SP equals: rng differs"; // compare connections - //NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; + NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; // compare vectors. NTA_CHECK (inputDimensions_ == o.inputDimensions_) << "SP equals: inputDimensions differ"; NTA_CHECK (columnDimensions_ == o.columnDimensions_) << "SP equals: columnDimensions differ"; NTA_CHECK (boostFactors_ == o.boostFactors_) << "SP equals: boostFactors"; - //!NTA_CHECK (overlapDutyCycles_ == o.overlapDutyCycles_) << "SP equals: overlapDutyCycles"; //FIXME seems bug is here?! - //!NTA_CHECK (activeDutyCycles_ == o.activeDutyCycles_) << "SP equals: activeDutyCucles"; //FIXME here? + NTA_CHECK (overlapDutyCycles_ == o.overlapDutyCycles_) << "SP equals: overlapDutyCycles"; //FIXME seems bug is here?! + NTA_CHECK (activeDutyCycles_ == o.activeDutyCycles_) << "SP equals: activeDutyCucles"; //FIXME here? NTA_CHECK (minOverlapDutyCycles_ == o.minOverlapDutyCycles_) << "SP equals: minOverlapDutyCycles"; //detailed compare potentials @@ -1069,21 +1069,21 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ std::vector potential2(numInputs_, 0); this->getPotential(i, potential1.data()); //TODO make the method return vect? o.getPotential(i, potential2.data()); - //!NTA_CHECK(potential1 == potential2) << "SP equals: potentials"; //FIXME + NTA_CHECK(potential1 == potential2) << "SP equals: potentials"; //FIXME } // check get permanences for (UInt i = 0; i < numColumns_; i++) { const auto& perm1 = this->getPermanence(i); const auto& perm2 = o.getPermanence(i); - //!NTA_CHECK(perm1 == perm2) << "SP equals: permanences"; + NTA_CHECK(perm1 == perm2) << "SP equals: permanences"; } // check get connected synapses for (UInt i = 0; i < numColumns_; i++) { const auto& con1 = this->getPermanence(i, this->connections.getConnectedThreshold()); const auto& con2 = o.getPermanence(i, o.connections.getConnectedThreshold()); - //!NTA_CHECK(con1 == con2) << "SP equals: connected synapses"; + NTA_CHECK(con1 == con2) << "SP equals: connected synapses"; } { @@ -1091,12 +1091,12 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ std::vector conCounts2(numColumns_, 0); this->getConnectedCounts(conCounts1.data()); o.getConnectedCounts(conCounts2.data()); - //!NTA_CHECK(conCounts1 == conCounts2) << "SP equals: connected column counts"; + NTA_CHECK(conCounts1 == conCounts2) << "SP equals: connected column counts"; } std::cout << "here\n"; // compare connections -NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; //FIXME connections.segments_ + NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; } catch(const htm::Exception& ex) { //some check failed -> not equal From bd6c27502c768f38935908596d2612debd7226b9 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Sep 2020 15:15:07 +0200 Subject: [PATCH 16/17] update seed in expected results for SP,TMRegion tests --- src/test/unit/regions/SPRegionTest.cpp | 4 ++-- src/test/unit/regions/TMRegionTest.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/unit/regions/SPRegionTest.cpp b/src/test/unit/regions/SPRegionTest.cpp index 4abd7f628b..416cc42040 100644 --- a/src/test/unit/regions/SPRegionTest.cpp +++ b/src/test/unit/regions/SPRegionTest.cpp @@ -400,7 +400,7 @@ TEST(SPRegionTest, testGetParameters) "minPctOverlapDutyCycles": 0.001000, "dutyCyclePeriod": 1000, "boostStrength": 0.000000, - "seed": 1, + "seed": 0, "spVerbosity": 0, "wrapAround": true, "learningMode": 1, @@ -429,7 +429,7 @@ TEST(SPRegionTest, testGetParameters) "minPctOverlapDutyCycles": 0.001000, "dutyCyclePeriod": 1000, "boostStrength": 0.000000, - "seed": 1, + "seed": 0, "spVerbosity": 0, "wrapAround": true, "learningMode": 1, diff --git a/src/test/unit/regions/TMRegionTest.cpp b/src/test/unit/regions/TMRegionTest.cpp index a6b6379606..e95a71ad45 100644 --- a/src/test/unit/regions/TMRegionTest.cpp +++ b/src/test/unit/regions/TMRegionTest.cpp @@ -446,7 +446,7 @@ TEST(TMRegionTest, testGetParameters) { "predictedSegmentDecrement": 0.000000, "maxSegmentsPerCell": 255, "maxSynapsesPerSegment": 255, - "seed": 42, + "seed": 0, "inputWidth": 0, "learningMode": true, "activeOutputCount": 0, @@ -472,7 +472,7 @@ TEST(TMRegionTest, testGetParameters) { "predictedSegmentDecrement": 0.000000, "maxSegmentsPerCell": 255, "maxSynapsesPerSegment": 255, - "seed": 42, + "seed": 0, "inputWidth": 100, "learningMode": true, "activeOutputCount": 0, From 3ce080eb9a25d93b5c10226409b92fa93de6717c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 3 Sep 2020 17:53:48 +0200 Subject: [PATCH 17/17] debugging SP-Connections-Segments serialization --- src/examples/hello/HelloSPTP.cpp | 8 +++---- src/htm/algorithms/Connections.cpp | 31 +++++++++++++++++++--------- src/htm/algorithms/Connections.hpp | 8 +++---- src/htm/algorithms/SpatialPooler.cpp | 3 +-- src/htm/algorithms/SpatialPooler.hpp | 3 ++- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index f0f3cafa2b..7f7e2147b5 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -237,11 +237,11 @@ EPOCHS = 2; // make test faster in Debug if(e+1 == 5000) { // For debugging serialization: save SP's state in 1 step, comment out, recompile, load SP and compare in another // step 1: - //spGlobal.saveToFile("/tmp/spG.save"); + spGlobal.saveToFile("/tmp/spG.save"); // step 2: - //SpatialPooler resumedSP; - //resumedSP.loadFromFile("/tmp/spG.save"); - //NTA_CHECK(spGlobal == resumedSP) << "SPs differ!"; + SpatialPooler resumedSP; + resumedSP.loadFromFile("/tmp/spG.save"); + NTA_CHECK(spGlobal == resumedSP) << "SPs differ!"; // --end of debugging //these hand-written values are only valid for EPOCHS = 5000 (default), but not for debug and custom runs. diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index b6361fd35f..cc5ef2597f 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -31,6 +31,8 @@ using std::string; using std::vector; using namespace htm; +const Permanence MARK_SYNAPSE_AS_REMOVED = -1.0; + Connections::Connections(const CellIdx numCells, const Permanence connectedThreshold, const bool timeseries) { @@ -210,14 +212,14 @@ bool Connections::synapseExists_(const Synapse synapse, bool fast) const { const bool found = (std::find(synapsesOnSegment.begin(), synapsesOnSegment.end(), synapse) != synapsesOnSegment.end()); //validate the fast & slow methods for same result: #ifdef NTA_ASSERTIONS_ON - const bool removed = synapses_[synapse].permanence == -1; - NTA_ASSERT( (removed and not found) or (not removed and found) ); + const bool removed = fabs(synapses_[synapse].permanence - MARK_SYNAPSE_AS_REMOVED) < 0.001f; + NTA_CHECK( (removed and not found) or (not removed and found) ); #endif return found; } else { //quick method. Relies on hack in destroySynapse() where we set synapseData.permanence == -1 - return synapses_[synapse].permanence != -1; + return fabs(synapses_[synapse].permanence - MARK_SYNAPSE_AS_REMOVED) < 0.001f; } } @@ -261,8 +263,8 @@ void Connections::destroySegment(const Segment segment) { CellData &cellData = cells_[segmentData.cell]; 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); + NTA_CHECK(segmentOnCell != cellData.segments.cend()) << "Segment to be destroyed not found on the cell!"; + NTA_CHECK(*segmentOnCell == segment); cellData.segments.erase(segmentOnCell); destroyedSegments_++; @@ -313,15 +315,18 @@ void Connections::destroySynapse(const Synapse synapse) { [&](const Synapse a, const Synapse b) -> bool { return dataForSynapse(a).id < dataForSynapse(b).id;} ); - NTA_ASSERT(synapseOnSegment != segmentData.synapses.cend()); - NTA_ASSERT(*synapseOnSegment == synapse); + NTA_CHECK(synapseOnSegment != segmentData.synapses.cend()); + NTA_CHECK(*synapseOnSegment == synapse); segmentData.synapses.erase(synapseOnSegment); //Note: dataForSynapse(synapse) are not deleted, unfortunately. And are still accessible. //To mark them as "removed", we set SynapseData.permanence = -1, this can be used for a quick check later - synapseData.permanence = -1; //marking as "removed" + synapseData.permanence = MARK_SYNAPSE_AS_REMOVED; //marking as "removed" + //actually erase from synapses_ : + //const auto it = synapses_.begin() + synapse; + //synapses_.erase(it); //slow destroyedSynapses_++; - NTA_ASSERT(not synapseExists_(synapse)); + NTA_ASSERT(not synapseExists_(synapse, false)); } @@ -768,6 +773,7 @@ bool Connections::operator==(const Connections &o) const { NTA_CHECK (cells_.size() == o.cells_.size()) << "Connections equals: cells_" << cells_.size() << " vs. " << o.cells_.size(); NTA_CHECK (cells_ == o.cells_) << "Connections equals: cells_" << cells_.size() << " vs. " << o.cells_.size(); + NTA_CHECK (segments_.size() == o.segments_.size()) << "Connections equals: segments_ size: " << segments_.size() << " vs. " << o.segments_.size(); NTA_CHECK (segments_ == o.segments_ ) << "Connections equals: segments_"; NTA_CHECK (destroyedSegments_ == o.destroyedSegments_ ) << "Connections equals: destroyedSegments_"; @@ -777,6 +783,7 @@ bool Connections::operator==(const Connections &o) const { //also check underlying datastructures (segments, and subsequently synapses). Can be time consuming. //1.cells: + /* for(const auto cellD : cells_) { //2.segments: const auto& segments = cellD.segments; @@ -789,6 +796,7 @@ bool Connections::operator==(const Connections &o) const { } } } + */ NTA_CHECK (connectedThreshold_ == o.connectedThreshold_ ) << "Connections equals: connectedThreshold_"; @@ -810,7 +818,10 @@ bool Connections::operator==(const Connections &o) const { NTA_CHECK (prunedSegs_ == o.prunedSegs_ ) << "Connections equals: prunedSegs_"; } catch(const htm::Exception& ex) { - std::cout << "Connection equals: differ! " << ex.what(); + UNUSED(ex); //avoid warning if the debug below is not used. + //uncomment below to start debugging Connections op==. It's perfectly + //..normal for unequality here. + NTA_WARN << "Connection equals: differ! " << ex.what(); return false; } return true; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 0407e1efef..9bde2813bb 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -95,7 +95,7 @@ struct SynapseData: public Serializable { NTA_CHECK(id == o.id ) << "SynapseData equals: id"; } catch(const htm::Exception& ex) { UNUSED(ex); // this avoids the warning if ex is not used. - //NTA_WARN << "SynapseData equals: " << ex.what(); //Note: uncomment for debug, tells you + NTA_WARN << "SynapseData equals: " << ex.what(); //Note: uncomment for debug, tells you //where the diff is. It's perfectly OK for the "exception" to occur, as it just denotes //that the data is NOT equal. return false; @@ -155,7 +155,7 @@ struct SegmentData: public Serializable { } catch(const htm::Exception& ex) { UNUSED(ex); // this avoids the warning if ex is not used. - //NTA_WARN << "SegmentData equals: " << ex.what(); + NTA_WARN << "SegmentData equals: " << ex.what(); return false; } return true; @@ -195,7 +195,7 @@ struct CellData : public Serializable { NTA_CHECK( segments == o.segments ) << "CellData equals: segments"; } catch(const htm::Exception& ex) { UNUSED(ex); // this avoids the warning if ex is not used. - //NTA_WARN << "CellData equals: " << ex.what(); + NTA_WARN << "CellData equals: " << ex.what(); return false; } return true; @@ -464,7 +464,7 @@ class Connections : public Serializable * @retval Synapse data. */ inline const SynapseData& dataForSynapse(const Synapse synapse) const { - NTA_CHECK(synapseExists_(synapse, true)); + NTA_CHECK(synapseExists_(synapse, false)); return synapses_[synapse]; } diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 2c98c8154b..49298011d3 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -1093,14 +1093,13 @@ bool SpatialPooler::operator==(const SpatialPooler& o) const{ o.getConnectedCounts(conCounts2.data()); NTA_CHECK(conCounts1 == conCounts2) << "SP equals: connected column counts"; } - std::cout << "here\n"; // compare connections NTA_CHECK (connections_ == o.connections_) << "SP equals: connections: " << connections_ << " vs. " << o.connections_; } catch(const htm::Exception& ex) { //some check failed -> not equal - std::cout << "SPP " << ex.what(); + NTA_WARN << "SP unequal output: " << ex.what(); return false; } return true; diff --git a/src/htm/algorithms/SpatialPooler.hpp b/src/htm/algorithms/SpatialPooler.hpp index bf48b47f4a..9e09994c16 100644 --- a/src/htm/algorithms/SpatialPooler.hpp +++ b/src/htm/algorithms/SpatialPooler.hpp @@ -784,8 +784,9 @@ class SpatialPooler : public Serializable /** set seed for internal random number generator. * See @ref `seed` arg in the constructor. */ - void setSeed(UInt seed) { + void setSeed(const UInt seed) { rng_ = Random(seed); + NTA_CHECK(seed == getSeed()) << "SP: Seed not set correctly"; } UInt getSeed() const { return rng_.getSeed();