def testAdaptShouldRemoveSegments(self): """ Test that connections are generated on predefined segments. """ random = Random(1981) active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") active_cells.sort() presynaptic_input = list(range(0, 10)) inputSDR = SDR(1024) inputSDR.sparse = presynaptic_input connections = Connections(NUM_CELLS, 0.51) for i in range(NUM_CELLS): seg = connections.createSegment(i, 2) seg = connections.createSegment(i, 2) #create 2 segments on each cell for cell in active_cells: segments = connections.segmentsForCell(cell) self.assertEqual(len(segments), 2, "Segments were prematurely destroyed.") segment = segments[0] numSynapsesOnSegment = len(segments) connections.adaptSegment(segment, inputSDR, 0.1, 0.001, pruneZeroSynapses=True, segmentThreshold=1) #set to =1 so that segments get always deleted in this test segments = connections.segmentsForCell(cell) self.assertEqual(len(segments), 1, "Segments were not destroyed.")
def testAdaptShouldRemoveSegments(self): """ Test that connections are generated on predefined segments. """ random = Random(1981) active_cells = np.array(random.sample( np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") active_cells.sort() presynaptic_input = list(range(0, 10)) inputSDR = SDR(1024) inputSDR.sparse = presynaptic_input connections = Connections(NUM_CELLS, 0.51) for i in range(NUM_CELLS): seg = connections.createSegment(i, 1) for cell in active_cells: segments = connections.segmentsForCell(cell) self.assertEqual(len(segments), 1, "Segments were prematurely destroyed.") segment = segments[0] connections.adaptSegment(segment, inputSDR, 0.1, 0.001, True) segments = connections.segmentsForCell(cell) self.assertEqual(len(segments), 0, "Segments were not destroyed.")
def testAdaptShouldIncrementSynapses(self): """ Test that connections are generated on predefined segments. """ random = Random(1981) active_cells = np.array(random.sample( np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") active_cells.sort() presynaptic_input = list(range(0, 10)) presynaptic_input_set = set(presynaptic_input) inputSDR = SDR(1024) inputSDR.sparse = presynaptic_input connections = Connections(NUM_CELLS, 0.51) for i in range(NUM_CELLS): seg = connections.createSegment(i, 1) for cell in active_cells: segments = connections.segmentsForCell(cell) segment = segments[0] for c in presynaptic_input: connections.createSynapse(segment, c, 0.1) connections.adaptSegment(segment, inputSDR, 0.1, 0.001, True) presynamptic_cells = self._getPresynapticCells( connections, segment, 0.2) self.assertEqual(presynamptic_cells, presynaptic_input_set, "Missing synapses") presynamptic_cells = self._getPresynapticCells( connections, segment, 0.3) self.assertEqual(presynamptic_cells, set(), "Too many synapses")
def testNumSynapses(self): """ Test that connections are generated on predefined segments. """ random = Random(1981) active_cells = np.array(random.sample( np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") active_cells.sort() presynaptic_input = list(range(0, 10)) presynaptic_input_set = set(presynaptic_input) inputSDR = SDR(1024) inputSDR.sparse = presynaptic_input connections = Connections(NUM_CELLS, 0.3) for i in range(NUM_CELLS): seg = connections.createSegment(i, 1) for cell in active_cells: segments = connections.segmentsForCell(cell) segment = segments[0] for c in presynaptic_input: connections.createSynapse(segment, c, 0.1) connections.adaptSegment(segment, inputSDR, 0.1, 0.0, False) num_synapses = connections.numSynapses(segment) self.assertEqual(num_synapses, len(presynaptic_input), "Missing synapses") self.assertEqual(connections.numSynapses(), len(presynaptic_input) * 40, "Missing synapses")
def testShuffleEmpty(self): r = Random(42) arr = numpy.zeros([0], dtype="uint32") r.shuffle(arr) self.assertEqual(arr.size, 0)
def testSampleNone(self): r = Random(42) population = numpy.array([1, 2, 3, 4], dtype="uint32") # Just make sure there is no exception thrown. choices = r.sample(population, 0) self.assertEqual(len(choices), 0)
def testSample(self): r = Random(42) population = numpy.array([1, 2, 3, 4], dtype="uint32") choices = r.sample(population, 2) self.assertEqual(choices[0], 2) self.assertEqual(choices[1], 1)
def testShuffle(self): r = Random(42) arr = numpy.array([1, 2, 3, 4], dtype="uint32") r.shuffle(arr) self.assertEqual(arr[0], 2) self.assertEqual(arr[1], 1) self.assertEqual(arr[2], 4) self.assertEqual(arr[3], 3)
def _orderForCoordinate(cls, coordinate): """ Returns the order for a coordinate. @param coordinate (np.array) Coordinate @return (float) A value in the interval [0, 1), representing the order of the coordinate """ seed = cls._hashCoordinate(coordinate) rng = Random(seed) return rng.getReal64()
def __init__(self, dataWidth, randomSeed): if dataWidth <= 0: raise ValueError("Parameter dataWidth must be > 0") # Arbitrary value that's compatible with UInt32 in the proto schema # for testing serialization of python-native property self._dataWidth = dataWidth # For testing serialization of object implemented in the extension self._rand = Random(randomSeed)
def _bitForCoordinate(cls, coordinate, n): """ Maps the coordinate to a bit in the SDR. @param coordinate (np.array) Coordinate @param n (int) The number of available bits in the SDR @return (int) The index to a bit in the SDR """ seed = cls._hashCoordinate(coordinate) rng = Random(seed) return rng.getUInt32(n)
def testComputeActivity(self): """ Test that connections are generated on predefined segments. """ random = Random(1981) active_cells = np.array(random.sample( np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") active_cells.sort() presynaptic_input = list(range(0, 10)) presynaptic_input_set = set(presynaptic_input) inputSDR = SDR(1024) inputSDR.sparse = presynaptic_input l = len(presynaptic_input) connections = Connections(NUM_CELLS, 0.51, False) for i in range(NUM_CELLS): seg = connections.createSegment(i, 1) numActiveConnectedSynapsesForSegment = connections.computeActivity( inputSDR, False) for count in numActiveConnectedSynapsesForSegment: self.assertEqual(count, 0, "Segment should not be active") for cell in active_cells: segments = connections.segmentsForCell(cell) segment = segments[0] for c in presynaptic_input: connections.createSynapse(segment, c, 0.1) numActiveConnectedSynapsesForSegment = connections.computeActivity( inputSDR, False) for count in numActiveConnectedSynapsesForSegment: self.assertEqual(count, 0, "Segment should not be active") for cell in active_cells: segments = connections.segmentsForCell(cell) segment = segments[0] connections.adaptSegment(segment, inputSDR, 0.5, 0.0, False) active_cells_set = set(active_cells) numActiveConnectedSynapsesForSegment = connections.computeActivity( inputSDR, False) for cell, count in enumerate(numActiveConnectedSynapsesForSegment): if cell in active_cells_set: self.assertEqual(count, l, "Segment should be active") else: self.assertEqual(count, 0, "Segment should not be active")
def testSamplePopulationTooSmall(self): r = Random(42) population = numpy.array([1, 2, 3, 4], dtype="uint32") #RuntimeError and not ValueError because it goes to Cpp code and there is #just NTA_CHECK that raises runtime_error self.assertRaises(RuntimeError, r.sample, population, 999)
def testSampleBadDtype(self): r = Random(42) population = numpy.array([1, 2, 3, 4], dtype="int64") # throws std::invalid_argument("Invalid numpy array precision used."); # in py_utils.hpp # so thats why it is ValueError and not TypeError self.assertRaises(ValueError, r.sample, population, 2)
def __init__(self, n, w, num=100, seed=42): """ @param n (int) Number of available bits in pattern @param w (int/list) Number of on bits in pattern If list, each pattern will have a `w` randomly selected from the list. @param num (int) Number of available patterns """ # Save member variables self._n = n self._w = w self._num = num # Initialize member variables self._random = Random(seed) self._patterns = dict() self._generate()
def testSampleSequenceRaisesTypeError(self): """Check that passing lists throws a TypeError. This behavior may change if sample is extended to understand sequences. """ r = Random(42) population = [1, 2, 3, 4] self.assertRaises(TypeError, r.sample, population, 2)
def testEquals(self): r1 = Random(42) v1 = r1.getReal64() i1 = r1.getUInt32() r2 = Random(42) v2 = r2.getReal64() i2 = r2.getUInt32() r3 = Random(66) self.assertEqual(v1, v2) self.assertEqual(i1, i2) self.assertEqual(r1, r2) self.assertNotEqual(r1, r3)
def testComputeActivityUnion(self): """ Test that connections are generated on predefined segments. """ random = Random(1981) active_cells = np.array(random.sample( np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") active_cells.sort() presynaptic_input1 = list(range(0, 10)) presynaptic_input1_set = set(presynaptic_input1) presynaptic_input2 = list(range(10, 20)) presynaptic_input2_set = set(presynaptic_input1) connections = Connections(NUM_CELLS, 0.51, False) for i in range(NUM_CELLS): seg = connections.createSegment(i, 1) self._learn(connections, active_cells, presynaptic_input1) self._learn(connections, active_cells, presynaptic_input2) numSynapses = connections.numSynapses() self.assertNotEqual( numSynapses, 40, "There should be a synapse for each presynaptic cell") active_cells_set = set(active_cells) inputSDR = SDR(1024) inputSDR.sparse = presynaptic_input1 numActiveConnectedSynapsesForSegment = connections.computeActivity( inputSDR, False) for cell, count in enumerate(numActiveConnectedSynapsesForSegment): if cell in active_cells_set: self.assertNotEqual(count, 0, "Segment should be active") inputSDR.sparse = presynaptic_input2 numActiveConnectedSynapsesForSegment = connections.computeActivity( inputSDR, False) for cell, count in enumerate(numActiveConnectedSynapsesForSegment): if cell in active_cells_set: self.assertNotEqual(count, 0, "Segment should be active")
def testNoTopology(self): rng = Random(42) presyn = SDR([5, 6, 7, 8]) postsyn = SDR([6, 5, 4, 3, 2, 1]) postsyn.randomize(1. / postsyn.size) for sparsity in (0., 1., 1. / presyn.size): function = NoTopology(sparsity) pp = function(postsyn, presyn.dimensions, rng) assert (pp.dimensions == presyn.dimensions) assert (abs(pp.getSparsity() - sparsity) < (.25 / presyn.size))
def main(): """Measure serialization performance of Random """ r = Random(42) # Measure serialization startSerializationTime = time.time() for i in range(_SERIALIZATION_LOOPS): r.saveToFile("RandonSerialization.stream") elapsedSerializationTime = time.time() - startSerializationTime # Measure deserialization startDeserializationTime = time.time() for _ in range(_DESERIALIZATION_LOOPS): r.loadFromFile("RandonSerialization.stream") elapsedDeserializationTime = time.time() - startDeserializationTime # Print report print(_SERIALIZATION_LOOPS, "Serialization loops in", \ elapsedSerializationTime, "seconds.") print("\t", elapsedSerializationTime/_SERIALIZATION_LOOPS, "seconds per loop.") print(deserializationCount, "Deserialization loops in", \ elapsedDeserializationTime, "seconds.") print("\t", elapsedDeserializationTime/deserializationCount, "seconds per loop.")
def testNupicRandomPickling(self): """Test pickling / unpickling of NuPIC randomness.""" # Simple test: make sure that dumping / loading works... r = Random(42) pickledR = pickle.dumps(r) test1 = [r.getUInt32() for _ in range(10)] r = pickle.loads(pickledR) test2 = [r.getUInt32() for _ in range(10)] self.assertEqual(test1, test2, "Simple NuPIC random pickle/unpickle failed.") # A little tricker: dump / load _after_ some numbers have been generated # (in the first test). Things should still work... # ...the idea of this test is to make sure that the pickle code isn't just # saving the initial seed... pickledR = pickle.dumps(r) test3 = [r.getUInt32() for _ in range(10)] r = pickle.loads(pickledR) test4 = [r.getUInt32() for _ in range(10)] self.assertEqual( test3, test4, "NuPIC random pickle/unpickle didn't work for saving later state.") self.assertNotEqual(test1, test3, "NuPIC random gave the same result twice.") self.assertNotEqual(test2, test4, "NuPIC random gave the same result twice.")
def __init__( self, size, sparsity, periods, seed=0, ): """ Argument size is the total number of bits in the encoded output SDR. Argument sparsity is fraction of bits which this encoder activates in the output SDR. Argument periods is a list of distances. The period of a module is the distance between the centers of a grid cells receptive fields. The length of this list defines the number of distinct modules. Argument seed controls the pseudo-random-number-generator which this encoder uses. This encoder produces deterministic output. The seed zero is special, seed zero is replaced with a truly random seed. """ self.size = size self.dimensions = (size, ) self.sparsity = sparsity self.periods = tuple(sorted(float(p) for p in periods)) assert (len(self.periods) > 0) assert (min(self.periods) >= 4) assert (self.sparsity >= 0) assert (self.sparsity <= 1) # Assign each module a range of cells in the output SDR. partitions = np.linspace(0, self.size, num=len(self.periods) + 1) partitions = list(zip(partitions, partitions[1:])) self.partitions_ = [(int(round(start)), int(round(stop))) for start, stop in partitions] # Assign each module a random offset and orientation. rng = np.random.RandomState(seed=Random(seed).getUInt32()) self.offsets_ = rng.uniform(0, max(self.periods) * 9, size=(self.size, 2)) self.angles_ = [] self.rot_mats_ = [] for period in self.periods: angle = rng.uniform() * 2 * math.pi self.angles_.append(angle) c, s = math.cos(angle), math.sin(angle) R = np.array(((c, -s), (s, c))) self.rot_mats_.append(R)
def testDefaultTopology(self): rng = Random(42) presyn = SDR([10, 10]) postsyn = SDR([10, 10]) postsyn.dense[0, 4] = 1 postsyn.dense = postsyn.dense noWrap = DefaultTopology(1.0, 1.1, False) noWrapPP = noWrap(postsyn, presyn.dimensions, rng) assert (noWrapPP.dimensions == presyn.dimensions) assert (set(noWrapPP.sparse) == set([3, 4, 5, 13, 14, 15])) wrap = DefaultTopology(1.0, 1.1, True) wrapPP = wrap(postsyn, presyn.dimensions, rng) assert (set(wrapPP.sparse) == set([3, 4, 5, 13, 14, 15, 93, 94, 95])) potentialPct = DefaultTopology(0.5, 1.1, False) assert (potentialPct(postsyn, presyn.dimensions, rng).getSum() == 3)
def __init__(self, cellsPerAxis, scale, orientation, anchorInputSize, activationThreshold=10, initialPermanence=0.21, connectedPermanence=0.50, learningThreshold=10, sampleSize=20, permanenceIncrement=0.1, permanenceDecrement=0.0, maxSynapsesPerSegment=-1, maxSegmentsPerCell=255, seed=42): self.cellsPerAxis = cellsPerAxis self.anchorInputSize = anchorInputSize self.scale = scale self.orientation = orientation self.activeCells = np.empty(0, dtype="int") # The cells that were activated by sensory input in an inference timestep, # or cells that were associated with sensory input in a learning timestep. self.sensoryAssociatedCells = np.empty(0, dtype="int") self.activeSegments = np.empty(0, dtype="uint32") self.connections = None self.initialPermanence = initialPermanence self.connectedPermanence = connectedPermanence self.learningThreshold = learningThreshold self.sampleSize = sampleSize self.permanenceIncrement = permanenceIncrement self.permanenceDecrement = permanenceDecrement self.activationThreshold = activationThreshold self.maxSynapsesPerSegment = maxSynapsesPerSegment self.maxSegmentsPerCell = maxSegmentsPerCell self.rng = Random(seed)
class PatternMachine(object): """ Base pattern machine class. """ def __init__(self, n, w, num=100, seed=42): """ @param n (int) Number of available bits in pattern @param w (int/list) Number of on bits in pattern If list, each pattern will have a `w` randomly selected from the list. @param num (int) Number of available patterns """ # Save member variables self._n = n self._w = w self._num = num # Initialize member variables self._random = Random(seed) self._patterns = dict() self._generate() def get(self, number): """ Return a pattern for a number. @param number (int) Number of pattern @return (set) Indices of on bits """ if not number in self._patterns: raise IndexError("Invalid number") return self._patterns[number] def addNoise(self, bits, amount): """ Add noise to pattern. @param bits (set) Indices of on bits @param amount (float) Probability of switching an on bit with a random bit @return (set) Indices of on bits in noisy pattern """ newBits = set() for bit in bits: if self._random.getReal64() < amount: newBits.add(self._random.getUInt32(self._n)) else: newBits.add(bit) return newBits def numbersForBit(self, bit): """ Return the set of pattern numbers that match a bit. @param bit (int) Index of bit @return (set) Indices of numbers """ if bit >= self._n: raise IndexError("Invalid bit") numbers = set() for index, pattern in self._patterns.items(): if bit in pattern: numbers.add(index) return numbers def numberMapForBits(self, bits): """ Return a map from number to matching on bits, for all numbers that match a set of bits. @param bits (set) Indices of bits @return (dict) Mapping from number => on bits. """ numberMap = dict() for bit in bits: numbers = self.numbersForBit(bit) for number in numbers: if not number in numberMap: numberMap[number] = set() numberMap[number].add(bit) return numberMap def prettyPrintPattern(self, bits, verbosity=1): """ Pretty print a pattern. @param bits (set) Indices of on bits @param verbosity (int) Verbosity level @return (string) Pretty-printed text """ numberMap = self.numberMapForBits(bits) text = "" numberList = [] numberItems = sorted(iter(numberMap.items()), key=lambda number_bits: len(number_bits[1]), reverse=True) for number, bits in numberItems: if verbosity > 2: strBits = [str(n) for n in bits] numberText = "{0} (bits: {1})".format(number, ",".join(strBits)) elif verbosity > 1: numberText = "{0} ({1} bits)".format(number, len(bits)) else: numberText = str(number) numberList.append(numberText) text += "[{0}]".format(", ".join(numberList)) return text def _generate(self): """ Generates set of random patterns. """ candidates = np.array(list(range(self._n)), np.uint32) for i in range(self._num): self._random.shuffle(candidates) pattern = candidates[0:self._getW()] self._patterns[i] = set(pattern) def _getW(self): """ Gets a value of `w` for use in generating a pattern. """ w = self._w if type(w) is list: return w[self._random.getUInt32(len(w))] else: return w
class ColumnPooler(object): """ This class constitutes a temporary implementation for a cross-column pooler. The implementation goal of this class is to prove basic properties before creating a cleaner implementation. """ def __init__( self, inputWidth, lateralInputWidths=(), cellCount=4096, sdrSize=40, onlineLearning=False, maxSdrSize=None, minSdrSize=None, # Proximal synPermProximalInc=0.1, synPermProximalDec=0.001, initialProximalPermanence=0.6, sampleSizeProximal=20, minThresholdProximal=10, connectedPermanenceProximal=0.50, predictedInhibitionThreshold=20, # Distal synPermDistalInc=0.1, synPermDistalDec=0.001, initialDistalPermanence=0.6, sampleSizeDistal=20, activationThresholdDistal=13, connectedPermanenceDistal=0.50, inertiaFactor=1., seed=42): """ Parameters: ---------------------------- @param inputWidth (int) The number of bits in the feedforward input @param lateralInputWidths (list of ints) The number of bits in each lateral input @param sdrSize (int) The number of active cells in an object SDR @param onlineLearning (Bool) Whether or not the column pooler should learn in online mode. @param maxSdrSize (int) The maximum SDR size for learning. If the column pooler has more than this many cells active, it will refuse to learn. This serves to stop the pooler from learning when it is uncertain of what object it is sensing. @param minSdrSize (int) The minimum SDR size for learning. If the column pooler has fewer than this many active cells, it will create a new representation and learn that instead. This serves to create separate representations for different objects and sequences. If online learning is enabled, this parameter should be at least inertiaFactor*sdrSize. Otherwise, two different objects may be incorrectly inferred to be the same, as SDRs may still be active enough to learn even after inertial decay. @param synPermProximalInc (float) Permanence increment for proximal synapses @param synPermProximalDec (float) Permanence decrement for proximal synapses @param initialProximalPermanence (float) Initial permanence value for proximal synapses @param sampleSizeProximal (int) Number of proximal synapses a cell should grow to each feedforward pattern, or -1 to connect to every active bit @param minThresholdProximal (int) Number of active synapses required for a cell to have feedforward support @param connectedPermanenceProximal (float) Permanence required for a proximal synapse to be connected @param predictedInhibitionThreshold (int) How much predicted input must be present for inhibitory behavior to be triggered. Only has effects if onlineLearning is true. @param synPermDistalInc (float) Permanence increment for distal synapses @param synPermDistalDec (float) Permanence decrement for distal synapses @param sampleSizeDistal (int) Number of distal synapses a cell should grow to each lateral pattern, or -1 to connect to every active bit @param initialDistalPermanence (float) Initial permanence value for distal synapses @param activationThresholdDistal (int) Number of active synapses required to activate a distal segment @param connectedPermanenceDistal (float) Permanence required for a distal synapse to be connected @param inertiaFactor (float) The proportion of previously active cells that remain active in the next timestep due to inertia (in the absence of inhibition). If onlineLearning is enabled, should be at most 1 - learningTolerance, or representations may incorrectly become mixed. @param seed (int) Random number generator seed """ assert maxSdrSize is None or maxSdrSize >= sdrSize assert minSdrSize is None or minSdrSize <= sdrSize self.inputWidth = inputWidth self.cellCount = cellCount self.sdrSize = sdrSize self.onlineLearning = onlineLearning if maxSdrSize is None: self.maxSdrSize = sdrSize else: self.maxSdrSize = maxSdrSize if minSdrSize is None: self.minSdrSize = sdrSize else: self.minSdrSize = minSdrSize self.synPermProximalInc = synPermProximalInc self.synPermProximalDec = synPermProximalDec self.initialProximalPermanence = initialProximalPermanence self.connectedPermanenceProximal = connectedPermanenceProximal self.sampleSizeProximal = sampleSizeProximal self.minThresholdProximal = minThresholdProximal self.predictedInhibitionThreshold = predictedInhibitionThreshold self.synPermDistalInc = synPermDistalInc self.synPermDistalDec = synPermDistalDec self.initialDistalPermanence = initialDistalPermanence self.connectedPermanenceDistal = connectedPermanenceDistal self.sampleSizeDistal = sampleSizeDistal self.activationThresholdDistal = activationThresholdDistal self.inertiaFactor = inertiaFactor self.lateralInputWidths = lateralInputWidths self.activeCells = np.empty(0, dtype="uint32") self._random = Random(seed) # Each cell potentially has # 1 proximal segment and 1+len(lateralInputWidths) distal segments. self.proximalPermanences = Connections(cellCount, connectedPermanenceProximal, False) #inputWidth max synapses self.internalDistalPermanences = Connections( cellCount, connectedPermanenceDistal, False) #cellCount max synapses self.distalPermanences = [ Connections(cellCount, connectedPermanenceDistal, False) for _ in lateralInputWidths ] #lateralInputWidths max synapses self.useInertia = True def compute(self, feedforwardInput=(), lateralInputs=(), feedforwardGrowthCandidates=None, learn=True, predictedInput=None): """ Runs one time step of the column pooler algorithm. @param feedforwardInput (sequence) Sorted indices of active feedforward input bits @param lateralInputs (list of sequences) For each lateral layer, a list of sorted indices of active lateral input bits @param feedforwardGrowthCandidates (sequence or None) Sorted indices of feedforward input bits that active cells may grow new synapses to. If None, the entire feedforwardInput is used. @param learn (bool) If True, we are learning a new object @param predictedInput (sequence) Sorted indices of predicted cells in the TM layer. """ if feedforwardGrowthCandidates is None: feedforwardGrowthCandidates = feedforwardInput # inference step if not learn: self._computeInferenceMode(feedforwardInput, lateralInputs) # learning step elif not self.onlineLearning: self._computeLearningMode(feedforwardInput, lateralInputs, feedforwardGrowthCandidates) # online learning step else: if (predictedInput is not None and len(predictedInput) > self.predictedInhibitionThreshold): predictedActiveInput = np.intersect1d(feedforwardInput, predictedInput) # predictedGrowthCandidates = np.intersect1d(feedforwardGrowthCandidates, predictedInput) self._computeInferenceMode(predictedActiveInput, lateralInputs) self._computeLearningMode(predictedActiveInput, lateralInputs, feedforwardGrowthCandidates) elif not self.minSdrSize <= len( self.activeCells) <= self.maxSdrSize: # If the pooler doesn't have a single representation, try to infer one, # before actually attempting to learn. self._computeInferenceMode(feedforwardInput, lateralInputs) self._computeLearningMode(feedforwardInput, lateralInputs, feedforwardGrowthCandidates) else: # If there isn't predicted input and we have a single SDR, # we are extending that representation and should just learn. self._computeLearningMode(feedforwardInput, lateralInputs, feedforwardGrowthCandidates) def _computeLearningMode(self, feedforwardInput, lateralInputs, feedforwardGrowthCandidates): """ Learning mode: we are learning a new object in an online fashion. If there is no prior activity, we randomly activate 'sdrSize' cells and create connections to incoming input. If there was prior activity, we maintain it. If we have a union, we simply do not learn at all. These cells will represent the object and learn distal connections to each other and to lateral cortical columns. Parameters: ---------------------------- @param feedforwardInput (sequence) Sorted indices of active feedforward input bits @param lateralInputs (list of sequences) For each lateral layer, a list of sorted indices of active lateral input bits @param feedforwardGrowthCandidates (sequence or None) Sorted indices of feedforward input bits that the active cells may grow new synapses to. This is assumed to be the predicted active cells of the input layer. """ prevActiveCells = self.activeCells # If there are not enough previously active cells, then we are no longer on # a familiar object. Either our representation decayed due to the passage # of time (i.e. we moved somewhere else) or we were mistaken. Either way, # create a new SDR and learn on it. # This case is the only way different object representations are created. # enforce the active cells in the output layer if len(self.activeCells) < self.minSdrSize: self.activeCells = self._sampleRange(0, self.numberOfCells(), step=1, k=self.sdrSize) self.activeCells.sort() # If we have a union of cells active, don't learn. This primarily affects # online learning. if len(self.activeCells) > self.maxSdrSize: return # Finally, now that we have decided which cells we should be learning on, do # the actual learning. if len(feedforwardInput) > 0: feedforwardInputSDR = SDR(self.inputWidth) feedforwardInputSDR.sparse = feedforwardInput self._learn(self.proximalPermanences, feedforwardInputSDR, feedforwardGrowthCandidates, self.sampleSizeProximal, self.initialProximalPermanence, self.synPermProximalInc, self.synPermProximalDec, self.connectedPermanenceProximal) # External distal learning for i, lateralInput in enumerate(lateralInputs): if len(lateralInput) > 0: lateralInputSDR = SDR(self.lateralInputWidths[i]) lateralInputSDR.sparse = lateralInput self._learn(self.distalPermanences[i], lateralInputSDR, lateralInput, self.sampleSizeDistal, self.initialDistalPermanence, self.synPermDistalInc, self.synPermDistalDec, self.connectedPermanenceDistal) # Internal distal learning if len(prevActiveCells): prevActiveCellsSDR = SDR(self.cellCount) prevActiveCellsSDR.sparse = prevActiveCells self._learn(self.internalDistalPermanences, prevActiveCellsSDR, prevActiveCells, self.sampleSizeDistal, self.initialDistalPermanence, self.synPermDistalInc, self.synPermDistalDec, self.connectedPermanenceDistal) def _computeInferenceMode(self, feedforwardInput, lateralInputs): """ Inference mode: if there is some feedforward activity, perform spatial pooling on it to recognize previously known objects, then use lateral activity to activate a subset of the cells with feedforward support. If there is no feedforward activity, use lateral activity to activate a subset of the previous active cells. Parameters: ---------------------------- @param feedforwardInput (sequence) Sorted indices of active feedforward input bits @param lateralInputs (list of sequences) For each lateral layer, a list of sorted indices of active lateral input bits """ prevActiveCells = self.activeCells # Calculate the feedforward supported cells feedforwardInputSDR = SDR(self.inputWidth) feedforwardInputSDR.sparse = feedforwardInput activeSegments = self.proximalPermanences.computeActiveSegments( feedforwardInputSDR, self.minThresholdProximal) feedforwardSupportedCells = self.proximalPermanences.mapSegmentsToCells( activeSegments) # Calculate the number of active segments on each cell numActiveSegmentsByCell = np.zeros(self.cellCount, dtype="int") prevActiveCellsSDR = SDR(self.cellCount) prevActiveCellsSDR.sparse = prevActiveCells activeSegments = self.internalDistalPermanences.computeActiveSegments( prevActiveCellsSDR, self.activationThresholdDistal) for cell in self.proximalPermanences.mapSegmentsToCells( activeSegments): numActiveSegmentsByCell[cell] += 1 for i, lateralInput in enumerate(lateralInputs): lateralInputSDR = SDR(self.lateralInputWidths[i]) lateralInputSDR.sparse = lateralInput activeSegments = self.distalPermanences[i].computeActiveSegments( lateralInputSDR, self.activationThresholdDistal) for cell in self.proximalPermanences.mapSegmentsToCells( activeSegments): numActiveSegmentsByCell[cell] += 1 chosenCells = [] # First, activate the FF-supported cells that have the highest number of # lateral active segments (as long as it's not 0) if len(feedforwardSupportedCells) == 0: pass else: numActiveSegsForFFSuppCells = numActiveSegmentsByCell[ feedforwardSupportedCells] # This loop will select the FF-supported AND laterally-active cells, in # order of descending lateral activation, until we exceed the sdrSize # quorum - but will exclude cells with 0 lateral active segments. ttop = np.max(numActiveSegsForFFSuppCells) while ttop > 0 and len(chosenCells) < self.sdrSize: supportedCells = [ feedforwardSupportedCells[i] for i in range(len(feedforwardSupportedCells)) if numActiveSegsForFFSuppCells[i] >= ttop ] chosenCells = np.union1d(chosenCells, supportedCells) ttop -= 1 # If we haven't filled the sdrSize quorum, add in inertial cells. if len(chosenCells) < self.sdrSize: if self.useInertia: prevCells = np.setdiff1d(prevActiveCells, chosenCells) inertialCap = int(len(prevCells) * self.inertiaFactor) if inertialCap > 0: numActiveSegsForPrevCells = numActiveSegmentsByCell[ prevCells] # We sort the previously-active cells by number of active lateral # segments (this really helps). We then activate them in order of # descending lateral activation. sortIndices = np.argsort(numActiveSegsForPrevCells)[::-1] prevCells = prevCells[sortIndices] numActiveSegsForPrevCells = numActiveSegsForPrevCells[ sortIndices] # We use inertiaFactor to limit the number of previously-active cells # which can become active, forcing decay even if we are below quota. prevCells = prevCells[:inertialCap] numActiveSegsForPrevCells = numActiveSegsForPrevCells[: inertialCap] # Activate groups of previously active cells by order of their lateral # support until we either meet quota or run out of cells. ttop = np.max(numActiveSegsForPrevCells) while ttop >= 0 and len(chosenCells) < self.sdrSize: chosenCells = np.union1d( chosenCells, prevCells[numActiveSegsForPrevCells >= ttop]) ttop -= 1 # If we haven't filled the sdrSize quorum, add cells that have feedforward # support and no lateral support. discrepancy = self.sdrSize - len(chosenCells) if discrepancy > 0: remainingFFcells = np.setdiff1d(feedforwardSupportedCells, chosenCells) # Inhibit cells proportionally to the number of cells that have already # been chosen. If ~0 have been chosen activate ~all of the feedforward # supported cells. If ~sdrSize have been chosen, activate very few of # the feedforward supported cells. # Use the discrepancy:sdrSize ratio to determine the number of cells to # activate. n = (len(remainingFFcells) * discrepancy) // self.sdrSize # Activate at least 'discrepancy' cells. n = max(n, discrepancy) # If there aren't 'n' available, activate all of the available cells. n = min(n, len(remainingFFcells)) if len(remainingFFcells) > n: selected = self._random.sample(remainingFFcells, n) chosenCells = np.append(chosenCells, selected) else: chosenCells = np.append(chosenCells, remainingFFcells) chosenCells.sort() self.activeCells = np.asarray(chosenCells, dtype="uint32") def numberOfInputs(self): """ Returns the number of inputs into this layer """ return self.inputWidth def numberOfCells(self): """ Returns the number of cells in this layer. @return (int) Number of cells """ return self.cellCount def getActiveCells(self): """ Returns the indices of the active cells. @return (list) Indices of active cells. """ return self.activeCells def numberOfConnectedProximalSynapses(self, cells=None): """ Returns the number of proximal connected synapses on these cells. Parameters: ---------------------------- @param cells (iterable) Indices of the cells. If None return count for all cells. """ if cells is None: cells = list(range(self.numberOfCells())) return self.proximalPermanences.numConnectedSynapsesForCells(cells) def numberOfProximalSynapses(self, cells=None): """ Returns the number of proximal synapses with permanence>0 on these cells. Parameters: ---------------------------- @param cells (iterable) Indices of the cells. If None return count for all cells. """ if cells is None: return self.proximalPermanences.numSynapses() return self.proximalPermanences.numSynapsesForCells(cells) def numberOfDistalSegments(self, cells=None): """ Returns the total number of distal segments for these cells. A segment "exists" if its row in the matrix has any permanence values > 0. Parameters: ---------------------------- @param cells (iterable) Indices of the cells """ if cells is None: cells = list(range(self.numberOfCells())) n = self.internalDistalPermanences.numSegmentsWithSynapses(cells) for permanences in self.distalPermanences: n += permanences.numSegmentsWithSynapses(cells) return n def numberOfConnectedDistalSynapses(self, cells=None): """ Returns the number of connected distal synapses on these cells. Parameters: ---------------------------- @param cells (iterable) Indices of the cells. If None return count for all cells. """ if cells is None: cells = list(range(self.numberOfCells())) n = self.internalDistalPermanences.numConnectedSynapsesForCells(cells) for permanences in self.distalPermanences: n += permanences.numConnectedSynapsesForCells(cells) return n def numberOfDistalSynapses(self, cells=None): """ Returns the total number of distal synapses for these cells. Parameters: ---------------------------- @param cells (iterable) Indices of the cells """ if cells is None: cells = list(range(self.numberOfCells())) n = self.internalDistalPermanences.numSynapsesForCells(cells) for permanences in self.distalPermanences: n += permanences.numSynapsesForCells(cells) return n def reset(self): """ Reset internal states. When learning this signifies we are to learn a unique new object. """ self.activeCells = np.empty(0, dtype="uint32") def getUseInertia(self): """ Get whether we actually use inertia (i.e. a fraction of the previously active cells remain active at the next time step unless inhibited by cells with both feedforward and lateral support). @return (Bool) Whether inertia is used. """ return self.useInertia def setUseInertia(self, useInertia): """ Sets whether we actually use inertia (i.e. a fraction of the previously active cells remain active at the next time step unless inhibited by cells with both feedforward and lateral support). @param useInertia (Bool) Whether inertia is used. """ self.useInertia = useInertia def _learn( self, permanences, # activity activeInput, growthCandidateInput, # configuration sampleSize, initialPermanence, permanenceIncrement, permanenceDecrement, connectedPermanence): """ For each active cell, reinforce active synapses, punish inactive synapses, and grow new synapses to a subset of the active input bits that the cell isn't already connected to. Parameters: ---------------------------- @param permanences (Connections) Matrix of permanences, with cells as rows and inputs as columns @param activeInput (SDR) Active bits in the input @param growthCandidateInput (sorted sequence) Sorted list of active bits in the input that the activeCells may grow new synapses to For remaining parameters, see the __init__ docstring. """ active_input_array = activeInput.sparse growthCandidateInput = np.uint32(growthCandidateInput) for cell in self.activeCells: segments = permanences.segmentsForCell(cell) if not segments: segment = permanences.createSegment(cell, 1) else: segment = segments[0] # Should only have one segment per cell permanences.adaptSegment(segment, activeInput, permanenceIncrement, permanenceDecrement, False) presynamptic_cells = np.array([ permanences.presynapticCellForSynapse(synapse) for synapse in permanences.synapsesForSegment(segment) ]) if sampleSize == -1: active_cells_without_synapses = np.setdiff1d( growthCandidateInput, presynamptic_cells, assume_unique=True) else: active_cells_without_synapses = [] existingSynapseCounts = len( np.intersect1d(presynamptic_cells, active_input_array, assume_unique=True)) effective_sample_size = sampleSize - existingSynapseCounts if effective_sample_size > 0: active_cells_without_synapses = np.setdiff1d( growthCandidateInput, presynamptic_cells, assume_unique=True) if effective_sample_size < len( active_cells_without_synapses): active_cells_without_synapses = self._random.sample( active_cells_without_synapses, effective_sample_size) for c in active_cells_without_synapses: permanences.createSynapse(segment, c, initialPermanence) # # Functionality that could be added to the C code or bindings # def _sampleRange(self, start, end, step, k): """ Equivalent to: random.sample(xrange(start, end, step), k) except it uses our random number generator. """ return np.array(self._random.sample( np.arange(start, end, step, dtype="uint32"), k), dtype="uint32")
class SerializationTestPyRegion(PyRegion): """Custom python region for testing serialization/deserialization of network containing a python region that contains a nupic.bindings-based Random instance. """ def __init__(self, dataWidth, randomSeed): if dataWidth <= 0: raise ValueError("Parameter dataWidth must be > 0") # Arbitrary value that's compatible with UInt32 in the proto schema # for testing serialization of python-native property self._dataWidth = dataWidth # For testing serialization of object implemented in the extension self._rand = Random(randomSeed) @property def dataWidth(self): return self._dataWidth @property def randomSeed(self): return self._rand.getSeed() def initialize(self, dims=None, splitterMaps=None): pass def compute(self, inputs, outputs): """ Run one iteration of SerializationTestPyRegion's compute """ outputs["out"][:] = inputs["in"] @classmethod def getSpec(cls): """Return the Spec for SerializationTestPyRegion. """ spec = { "description": SerializationTestPyRegion.__doc__, "singleNodeOnly": True, "inputs": { "in": { "description": "The input vector.", "dataType": "Real32", "count": 0, "required": True, "regionLevel": False, "isDefaultInput": True, "requireSplitterMap": False }, }, "outputs": { "out": { "description": "A copy of the input vector.", "dataType": "Real32", "count": 0, "regionLevel": True, "isDefaultOutput": True }, }, "parameters": { "dataWidth": { "description": "Size of inputs", "accessMode": "Create", "dataType": "UInt32", "count": 1, "constraints": "" }, "randomSeed": { "description": "Seed for constructing the Random instance", "accessMode": "Create", "dataType": "UInt32", "count": 1, "constraints": "" }, }, } return spec def getOutputElementCount(self, name): if name == "out": return self._dataWidth else: raise Exception("Unrecognized output: " + name)
def __init__( self, inputWidth, lateralInputWidths=(), cellCount=4096, sdrSize=40, onlineLearning=False, maxSdrSize=None, minSdrSize=None, # Proximal synPermProximalInc=0.1, synPermProximalDec=0.001, initialProximalPermanence=0.6, sampleSizeProximal=20, minThresholdProximal=10, connectedPermanenceProximal=0.50, predictedInhibitionThreshold=20, # Distal synPermDistalInc=0.1, synPermDistalDec=0.001, initialDistalPermanence=0.6, sampleSizeDistal=20, activationThresholdDistal=13, connectedPermanenceDistal=0.50, inertiaFactor=1., seed=42): """ Parameters: ---------------------------- @param inputWidth (int) The number of bits in the feedforward input @param lateralInputWidths (list of ints) The number of bits in each lateral input @param sdrSize (int) The number of active cells in an object SDR @param onlineLearning (Bool) Whether or not the column pooler should learn in online mode. @param maxSdrSize (int) The maximum SDR size for learning. If the column pooler has more than this many cells active, it will refuse to learn. This serves to stop the pooler from learning when it is uncertain of what object it is sensing. @param minSdrSize (int) The minimum SDR size for learning. If the column pooler has fewer than this many active cells, it will create a new representation and learn that instead. This serves to create separate representations for different objects and sequences. If online learning is enabled, this parameter should be at least inertiaFactor*sdrSize. Otherwise, two different objects may be incorrectly inferred to be the same, as SDRs may still be active enough to learn even after inertial decay. @param synPermProximalInc (float) Permanence increment for proximal synapses @param synPermProximalDec (float) Permanence decrement for proximal synapses @param initialProximalPermanence (float) Initial permanence value for proximal synapses @param sampleSizeProximal (int) Number of proximal synapses a cell should grow to each feedforward pattern, or -1 to connect to every active bit @param minThresholdProximal (int) Number of active synapses required for a cell to have feedforward support @param connectedPermanenceProximal (float) Permanence required for a proximal synapse to be connected @param predictedInhibitionThreshold (int) How much predicted input must be present for inhibitory behavior to be triggered. Only has effects if onlineLearning is true. @param synPermDistalInc (float) Permanence increment for distal synapses @param synPermDistalDec (float) Permanence decrement for distal synapses @param sampleSizeDistal (int) Number of distal synapses a cell should grow to each lateral pattern, or -1 to connect to every active bit @param initialDistalPermanence (float) Initial permanence value for distal synapses @param activationThresholdDistal (int) Number of active synapses required to activate a distal segment @param connectedPermanenceDistal (float) Permanence required for a distal synapse to be connected @param inertiaFactor (float) The proportion of previously active cells that remain active in the next timestep due to inertia (in the absence of inhibition). If onlineLearning is enabled, should be at most 1 - learningTolerance, or representations may incorrectly become mixed. @param seed (int) Random number generator seed """ assert maxSdrSize is None or maxSdrSize >= sdrSize assert minSdrSize is None or minSdrSize <= sdrSize self.inputWidth = inputWidth self.cellCount = cellCount self.sdrSize = sdrSize self.onlineLearning = onlineLearning if maxSdrSize is None: self.maxSdrSize = sdrSize else: self.maxSdrSize = maxSdrSize if minSdrSize is None: self.minSdrSize = sdrSize else: self.minSdrSize = minSdrSize self.synPermProximalInc = synPermProximalInc self.synPermProximalDec = synPermProximalDec self.initialProximalPermanence = initialProximalPermanence self.connectedPermanenceProximal = connectedPermanenceProximal self.sampleSizeProximal = sampleSizeProximal self.minThresholdProximal = minThresholdProximal self.predictedInhibitionThreshold = predictedInhibitionThreshold self.synPermDistalInc = synPermDistalInc self.synPermDistalDec = synPermDistalDec self.initialDistalPermanence = initialDistalPermanence self.connectedPermanenceDistal = connectedPermanenceDistal self.sampleSizeDistal = sampleSizeDistal self.activationThresholdDistal = activationThresholdDistal self.inertiaFactor = inertiaFactor self.lateralInputWidths = lateralInputWidths self.activeCells = np.empty(0, dtype="uint32") self._random = Random(seed) # Each cell potentially has # 1 proximal segment and 1+len(lateralInputWidths) distal segments. self.proximalPermanences = Connections(cellCount, connectedPermanenceProximal, False) #inputWidth max synapses self.internalDistalPermanences = Connections( cellCount, connectedPermanenceDistal, False) #cellCount max synapses self.distalPermanences = [ Connections(cellCount, connectedPermanenceDistal, False) for _ in lateralInputWidths ] #lateralInputWidths max synapses self.useInertia = True
def testRandomRNG(self): x = SDR(1000).randomize(.5, Random(77)) y = SDR(1000).randomize(.5, Random(99)) assert (x != y) z = SDR(1000).randomize(.5, Random(77)) assert (x == z)
def __init__(self, columnCount=2048, basalInputSize=0, apicalInputSize=0, cellsPerColumn=32, activationThreshold=13, reducedBasalThreshold=13, initialPermanence=0.21, connectedPermanence=0.50, minThreshold=10, sampleSize=20, permanenceIncrement=0.1, permanenceDecrement=0.1, basalPredictedSegmentDecrement=0.0, apicalPredictedSegmentDecrement=0.0, maxSynapsesPerSegment=-1, maxSegmentsPerCell=255, seed=42): """ @param columnCount (int) The number of minicolumns @param basalInputSize (sequence) The number of bits in the basal input @param apicalInputSize (int) The number of bits in the apical input @param cellsPerColumn (int) Number of cells per column @param activationThreshold (int) If the number of active connected synapses on a segment is at least this threshold, the segment is said to be active. @param reducedBasalThreshold (int) The activation threshold of basal (lateral) segments for cells that have active apical segments. If equal to activationThreshold (default), this parameter has no effect. @param initialPermanence (float) Initial permanence of a new synapse @param connectedPermanence (float) If the permanence value for a synapse is greater than this value, it is said to be connected. @param minThreshold (int) If the number of potential synapses active on a segment is at least this threshold, it is said to be "matching" and is eligible for learning. @param sampleSize (int) How much of the active SDR to sample with synapses. @param permanenceIncrement (float) Amount by which permanences of synapses are incremented during learning. @param permanenceDecrement (float) Amount by which permanences of synapses are decremented during learning. @param basalPredictedSegmentDecrement (float) Amount by which segments are punished for incorrect predictions. @param apicalPredictedSegmentDecrement (float) Amount by which segments are punished for incorrect predictions. @param maxSynapsesPerSegment The maximum number of synapses per segment. @param maxSegmentsPerCell The maximum number of segments per cell. @param seed (int) Seed for the random number generator. """ self.columnCount = columnCount self.cellsPerColumn = cellsPerColumn self.initialPermanence = initialPermanence self.connectedPermanence = connectedPermanence self.reducedBasalThreshold = reducedBasalThreshold self.minThreshold = minThreshold self.sampleSize = sampleSize self.permanenceIncrement = permanenceIncrement self.permanenceDecrement = permanenceDecrement self.basalPredictedSegmentDecrement = basalPredictedSegmentDecrement self.apicalPredictedSegmentDecrement = apicalPredictedSegmentDecrement self.activationThreshold = activationThreshold self.maxSynapsesPerSegment = maxSynapsesPerSegment self.maxSegmentsPerCell = maxSegmentsPerCell self.basalConnections = Connections(columnCount*cellsPerColumn, connectedPermanence, False) self.apicalConnections = Connections(columnCount*cellsPerColumn, connectedPermanence, False) self.rng = Random(seed) self.activeCells = np.empty(0, dtype="uint32") self.winnerCells = np.empty(0, dtype="uint32") self.predictedCells = np.empty(0, dtype="uint32") self.predictedActiveCells = np.empty(0, dtype="uint32") self.activeBasalSegments = np.empty(0, dtype="uint32") self.activeApicalSegments = np.empty(0, dtype="uint32") self.matchingBasalSegments = np.empty(0, dtype="uint32") self.matchingApicalSegments = np.empty(0, dtype="uint32") self.basalPotentialOverlaps = np.empty(0, dtype="int32") self.apicalPotentialOverlaps = np.empty(0, dtype="int32") self.basalInputSize = basalInputSize self.apicalInputSize = apicalInputSize self.useApicalTiebreak=True self.useApicalModulationBasalThreshold=True