
org.numenta.nupic.algorithms.SpatialPooler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htm.java Show documentation
Show all versions of htm.java Show documentation
The Java version of Numenta's HTM technology
The newest version!
/* ---------------------------------------------------------------------
* Numenta Platform for Intelligent Computing (NuPIC)
* Copyright (C) 2016, Numenta, Inc. Unless you have an agreement
* with Numenta, Inc., for a separate license for this software code, the
* following terms and conditions apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero Public License for more details.
*
* You should have received a copy of the GNU Affero Public License
* along with this program. If not, see http://www.gnu.org/licenses.
*
* http://numenta.org/licenses/
* ---------------------------------------------------------------------
*/
package org.numenta.nupic.algorithms;
import java.util.Arrays;
import java.util.stream.IntStream;
import org.numenta.nupic.model.Column;
import org.numenta.nupic.model.Connections;
import org.numenta.nupic.model.Persistable;
import org.numenta.nupic.model.Pool;
import org.numenta.nupic.util.ArrayUtils;
import org.numenta.nupic.util.Condition;
import org.numenta.nupic.util.SparseBinaryMatrix;
import org.numenta.nupic.util.SparseMatrix;
import org.numenta.nupic.util.SparseObjectMatrix;
import org.numenta.nupic.util.Topology;
import chaschev.lang.Pair;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TDoubleArrayList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.hash.TIntHashSet;
/**
* Handles the relationships between the columns of a region
* and the inputs bits. The primary public interface to this function is the
* "compute" method, which takes in an input vector and returns a list of
* activeColumns columns.
* Example Usage:
* >
* > SpatialPooler sp = SpatialPooler();
* > Connections c = new Connections();
* > sp.init(c);
* > for line in file:
* > inputVector = prepared int[] (containing 1's and 0's)
* > sp.compute(inputVector)
*
* @author David Ray
*
*/
public class SpatialPooler implements Persistable {
/** Default Serial Version */
private static final long serialVersionUID = 1L;
/**
* Constructs a new {@code SpatialPooler}
*/
public SpatialPooler() {}
/**
* Initializes the specified {@link Connections} object which contains
* the memory and structural anatomy this spatial pooler uses to implement
* its algorithms.
*
* @param c a {@link Connections} object
*/
public void init(Connections c) {
if(c.getNumActiveColumnsPerInhArea() == 0 && (c.getLocalAreaDensity() == 0 ||
c.getLocalAreaDensity() > 0.5)) {
throw new InvalidSPParamValueException("Inhibition parameters are invalid");
}
c.doSpatialPoolerPostInit();
initMatrices(c);
connectAndConfigureInputs(c);
}
/**
* Called to initialize the structural anatomy with configured values and prepare
* the anatomical entities for activation.
*
* @param c
*/
public void initMatrices(final Connections c) {
SparseObjectMatrix mem = c.getMemory();
c.setMemory(mem == null ?
mem = new SparseObjectMatrix<>(c.getColumnDimensions()) : mem);
c.setInputMatrix(new SparseBinaryMatrix(c.getInputDimensions()));
// Initiate the topologies
c.setColumnTopology(new Topology(c.getColumnDimensions()));
c.setInputTopology(new Topology(c.getInputDimensions()));
//Calculate numInputs and numColumns
int numInputs = c.getInputMatrix().getMaxIndex() + 1;
int numColumns = c.getMemory().getMaxIndex() + 1;
if(numColumns <= 0) {
throw new InvalidSPParamValueException("Invalid number of columns: " + numColumns);
}
if(numInputs <= 0) {
throw new InvalidSPParamValueException("Invalid number of inputs: " + numInputs);
}
c.setNumInputs(numInputs);
c.setNumColumns(numColumns);
//Fill the sparse matrix with column objects
for(int i = 0;i < numColumns;i++) { mem.set(i, new Column(c.getCellsPerColumn(), i)); }
c.setPotentialPools(new SparseObjectMatrix(c.getMemory().getDimensions()));
c.setConnectedMatrix(new SparseBinaryMatrix(new int[] { numColumns, numInputs }));
//Initialize state meta-management statistics
c.setOverlapDutyCycles(new double[numColumns]);
c.setActiveDutyCycles(new double[numColumns]);
c.setMinOverlapDutyCycles(new double[numColumns]);
c.setMinActiveDutyCycles(new double[numColumns]);
c.setBoostFactors(new double[numColumns]);
Arrays.fill(c.getBoostFactors(), 1);
}
/**
* Step two of pooler initialization kept separate from initialization
* of static members so that they may be set at a different point in
* the initialization (as sometimes needed by tests).
*
* This step prepares the proximal dendritic synapse pools with their
* initial permanence values and connected inputs.
*
* @param c the {@link Connections} memory
*/
public void connectAndConfigureInputs(Connections c) {
// Initialize the set of permanence values for each column. Ensure that
// each column is connected to enough input bits to allow it to be
// activated.
int numColumns = c.getNumColumns();
for(int i = 0;i < numColumns;i++) {
int[] potential = mapPotential(c, i, c.isWrapAround());
Column column = c.getColumn(i);
c.getPotentialPools().set(i, column.createPotentialPool(c, potential));
double[] perm = initPermanence(c, potential, i, c.getInitConnectedPct());
updatePermanencesForColumn(c, perm, column, potential, true);
}
// The inhibition radius determines the size of a column's local
// neighborhood. A cortical column must overcome the overlap score of
// columns in its neighborhood in order to become active. This radius is
// updated every learning round. It grows and shrinks with the average
// number of connected synapses per column.
updateInhibitionRadius(c);
}
/**
* This is the primary public method of the SpatialPooler class. This
* function takes a input vector and outputs the indices of the active columns.
* If 'learn' is set to True, this method also updates the permanences of the
* columns.
* @param inputVector An array of 0's and 1's that comprises the input to
* the spatial pooler. The array will be treated as a one
* dimensional array, therefore the dimensions of the array
* do not have to match the exact dimensions specified in the
* class constructor. In fact, even a list would suffice.
* The number of input bits in the vector must, however,
* match the number of bits specified by the call to the
* constructor. Therefore there must be a '0' or '1' in the
* array for every input bit.
* @param activeArray An array whose size is equal to the number of columns.
* Before the function returns this array will be populated
* with 1's at the indices of the active columns, and 0's
* everywhere else.
* @param learn A boolean value indicating whether learning should be
* performed. Learning entails updating the permanence
* values of the synapses, and hence modifying the 'state'
* of the model. Setting learning to 'off' freezes the SP
* and has many uses. For example, you might want to feed in
* various inputs and examine the resulting SDR's.
*/
public void compute(Connections c, int[] inputVector, int[] activeArray, boolean learn) {
if(inputVector.length != c.getNumInputs()) {
throw new InvalidSPParamValueException(
"Input array must be same size as the defined number of inputs: From Params: " + c.getNumInputs() +
", From Input Vector: " + inputVector.length);
}
updateBookeepingVars(c, learn);
int[] overlaps = c.setOverlaps(calculateOverlap(c, inputVector));
double[] boostedOverlaps;
if(learn) {
boostedOverlaps = ArrayUtils.multiply(c.getBoostFactors(), overlaps);
}else{
boostedOverlaps = ArrayUtils.toDoubleArray(overlaps);
}
int[] activeColumns = inhibitColumns(c, c.setBoostedOverlaps(boostedOverlaps));
if(learn) {
adaptSynapses(c, inputVector, activeColumns);
updateDutyCycles(c, overlaps, activeColumns);
bumpUpWeakColumns(c);
updateBoostFactors(c);
if(isUpdateRound(c)) {
updateInhibitionRadius(c);
updateMinDutyCycles(c);
}
}
Arrays.fill(activeArray, 0);
if(activeColumns.length > 0) {
ArrayUtils.setIndexesTo(activeArray, activeColumns, 1);
}
}
/**
* Removes the set of columns who have never been active from the set of
* active columns selected in the inhibition round. Such columns cannot
* represent learned pattern and are therefore meaningless if only inference
* is required. This should not be done when using a random, unlearned SP
* since you would end up with no active columns.
*
* @param activeColumns An array containing the indices of the active columns
* @return a list of columns with a chance of activation
*/
public int[] stripUnlearnedColumns(Connections c, int[] activeColumns) {
TIntHashSet active = new TIntHashSet(activeColumns);
TIntHashSet aboveZero = new TIntHashSet();
int numCols = c.getNumColumns();
double[] colDutyCycles = c.getActiveDutyCycles();
for(int i = 0;i < numCols;i++) {
if(colDutyCycles[i] <= 0) {
aboveZero.add(i);
}
}
active.removeAll(aboveZero);
TIntArrayList l = new TIntArrayList(active);
l.sort();
return Arrays.stream(activeColumns).filter(i -> c.getActiveDutyCycles()[i] > 0).toArray();
}
/**
* Updates the minimum duty cycles defining normal activity for a column. A
* column with activity duty cycle below this minimum threshold is boosted.
*
* @param c
*/
public void updateMinDutyCycles(Connections c) {
if(c.getGlobalInhibition() || c.getInhibitionRadius() > c.getNumInputs()) {
updateMinDutyCyclesGlobal(c);
}else{
updateMinDutyCyclesLocal(c);
}
}
/**
* Updates the minimum duty cycles in a global fashion. Sets the minimum duty
* cycles for the overlap and activation of all columns to be a percent of the
* maximum in the region, specified by {@link Connections#getMinOverlapDutyCycles()} and
* minPctActiveDutyCycle respectively. Functionality it is equivalent to
* {@link #updateMinDutyCyclesLocal(Connections)}, but this function exploits the globalness of the
* computation to perform it in a straightforward, and more efficient manner.
*
* @param c
*/
public void updateMinDutyCyclesGlobal(Connections c) {
Arrays.fill(c.getMinOverlapDutyCycles(),
c.getMinPctOverlapDutyCycles() * ArrayUtils.max(c.getOverlapDutyCycles()));
Arrays.fill(c.getMinActiveDutyCycles(),
c.getMinPctActiveDutyCycles() * ArrayUtils.max(c.getActiveDutyCycles()));
}
/**
* Updates the minimum duty cycles. The minimum duty cycles are determined
* locally. Each column's minimum duty cycles are set to be a percent of the
* maximum duty cycles in the column's neighborhood. Unlike
* {@link #updateMinDutyCyclesGlobal(Connections)}, here the values can be
* quite different for different columns.
*
* @param c
*/
public void updateMinDutyCyclesLocal(final Connections c) {
int len = c.getNumColumns();
int inhibitionRadius = c.getInhibitionRadius();
double[] activeDutyCycles = c.getActiveDutyCycles();
double minPctActiveDutyCycles = c.getMinPctActiveDutyCycles();
double[] overlapDutyCycles = c.getOverlapDutyCycles();
double minPctOverlapDutyCycles = c.getMinPctOverlapDutyCycles();
// Parallelize for speed up
IntStream.range(0, len).forEach(i -> {
int[] neighborhood = getColumnNeighborhood(c, i, inhibitionRadius);
double maxActiveDuty = ArrayUtils.max(
ArrayUtils.sub(activeDutyCycles, neighborhood));
double maxOverlapDuty = ArrayUtils.max(
ArrayUtils.sub(overlapDutyCycles, neighborhood));
c.getMinActiveDutyCycles()[i] = maxActiveDuty * minPctActiveDutyCycles;
c.getMinOverlapDutyCycles()[i] = maxOverlapDuty * minPctOverlapDutyCycles;
});
}
/**
* Updates the duty cycles for each column. The OVERLAP duty cycle is a moving
* average of the number of inputs which overlapped with each column. The
* ACTIVITY duty cycles is a moving average of the frequency of activation for
* each column.
*
* @param c the {@link Connections} (spatial pooler memory)
* @param overlaps an array containing the overlap score for each column.
* The overlap score for a column is defined as the number
* of synapses in a "connected state" (connected synapses)
* that are connected to input bits which are turned on.
* @param activeColumns An array containing the indices of the active columns,
* the sparse set of columns which survived inhibition
*/
public void updateDutyCycles(Connections c, int[] overlaps, int[] activeColumns) {
double[] overlapArray = new double[c.getNumColumns()];
double[] activeArray = new double[c.getNumColumns()];
ArrayUtils.greaterThanXThanSetToYInB(overlaps, overlapArray, 0, 1);
if(activeColumns.length > 0) {
ArrayUtils.setIndexesTo(activeArray, activeColumns, 1);
}
int period = c.getDutyCyclePeriod();
if(period > c.getIterationNum()) {
period = c.getIterationNum();
}
c.setOverlapDutyCycles(
updateDutyCyclesHelper(c, c.getOverlapDutyCycles(), overlapArray, period));
c.setActiveDutyCycles(
updateDutyCyclesHelper(c, c.getActiveDutyCycles(), activeArray, period));
}
/**
* Updates a duty cycle estimate with a new value. This is a helper
* function that is used to update several duty cycle variables in
* the Column class, such as: overlapDutyCucle, activeDutyCycle,
* minPctDutyCycleBeforeInh, minPctDutyCycleAfterInh, etc. returns
* the updated duty cycle. Duty cycles are updated according to the following
* formula:
*
*
* (period - 1)*dutyCycle + newValue
* dutyCycle := ----------------------------------
* period
*
* @param c the {@link Connections} (spatial pooler memory)
* @param dutyCycles An array containing one or more duty cycle values that need
* to be updated
* @param newInput A new numerical value used to update the duty cycle
* @param period The period of the duty cycle
* @return
*/
public double[] updateDutyCyclesHelper(Connections c, double[] dutyCycles, double[] newInput, double period) {
return ArrayUtils.divide(ArrayUtils.d_add(ArrayUtils.multiply(dutyCycles, period - 1), newInput), period);
}
/**
* Update the inhibition radius. The inhibition radius is a measure of the
* square (or hypersquare) of columns that each a column is "connected to"
* on average. Since columns are are not connected to each other directly, we
* determine this quantity by first figuring out how many *inputs* a column is
* connected to, and then multiplying it by the total number of columns that
* exist for each input. For multiple dimension the aforementioned
* calculations are averaged over all dimensions of inputs and columns. This
* value is meaningless if global inhibition is enabled.
*
* @param c the {@link Connections} (spatial pooler memory)
*/
public void updateInhibitionRadius(Connections c) {
if(c.getGlobalInhibition()) {
c.setInhibitionRadius(ArrayUtils.max(c.getColumnDimensions()));
return;
}
TDoubleArrayList avgCollected = new TDoubleArrayList();
int len = c.getNumColumns();
for(int i = 0;i < len;i++) {
avgCollected.add(avgConnectedSpanForColumnND(c, i));
}
double avgConnectedSpan = ArrayUtils.average(avgCollected.toArray());
double diameter = avgConnectedSpan * avgColumnsPerInput(c);
double radius = (diameter - 1) / 2.0d;
radius = Math.max(1, radius);
c.setInhibitionRadius((int)(radius + 0.5));
}
/**
* The average number of columns per input, taking into account the topology
* of the inputs and columns. This value is used to calculate the inhibition
* radius. This function supports an arbitrary number of dimensions. If the
* number of column dimensions does not match the number of input dimensions,
* we treat the missing, or phantom dimensions as 'ones'.
*
* @param c the {@link Connections} (spatial pooler memory)
* @return
*/
public double avgColumnsPerInput(Connections c) {
int[] colDim = Arrays.copyOf(c.getColumnDimensions(), c.getColumnDimensions().length);
int[] inputDim = Arrays.copyOf(c.getInputDimensions(), c.getInputDimensions().length);
double[] columnsPerInput = ArrayUtils.divide(
ArrayUtils.toDoubleArray(colDim), ArrayUtils.toDoubleArray(inputDim), 0, 0);
return ArrayUtils.average(columnsPerInput);
}
/**
* The range of connectedSynapses per column, averaged for each dimension.
* This value is used to calculate the inhibition radius. This variation of
* the function supports arbitrary column dimensions.
*
* @param c the {@link Connections} (spatial pooler memory)
* @param columnIndex the current column for which to avg.
* @return
*/
public double avgConnectedSpanForColumnND(Connections c, int columnIndex) {
int[] dimensions = c.getInputDimensions();
int[] connected = c.getColumn(columnIndex).getProximalDendrite().getConnectedSynapsesSparse(c);
if(connected == null || connected.length == 0) return 0;
int[] maxCoord = new int[c.getInputDimensions().length];
int[] minCoord = new int[c.getInputDimensions().length];
Arrays.fill(maxCoord, -1);
Arrays.fill(minCoord, ArrayUtils.max(dimensions));
SparseMatrix> inputMatrix = c.getInputMatrix();
for(int i = 0;i < connected.length;i++) {
maxCoord = ArrayUtils.maxBetween(maxCoord, inputMatrix.computeCoordinates(connected[i]));
minCoord = ArrayUtils.minBetween(minCoord, inputMatrix.computeCoordinates(connected[i]));
}
return ArrayUtils.average(ArrayUtils.add(ArrayUtils.subtract(maxCoord, minCoord), 1));
}
/**
* The primary method in charge of learning. Adapts the permanence values of
* the synapses based on the input vector, and the chosen columns after
* inhibition round. Permanence values are increased for synapses connected to
* input bits that are turned on, and decreased for synapses connected to
* inputs bits that are turned off.
*
* @param c the {@link Connections} (spatial pooler memory)
* @param inputVector a integer array that comprises the input to
* the spatial pooler. There exists an entry in the array
* for every input bit.
* @param activeColumns an array containing the indices of the columns that
* survived inhibition.
*/
public void adaptSynapses(Connections c, int[] inputVector, int[] activeColumns) {
int[] inputIndices = ArrayUtils.where(inputVector, ArrayUtils.INT_GREATER_THAN_0);
double[] permChanges = new double[c.getNumInputs()];
Arrays.fill(permChanges, -1 * c.getSynPermInactiveDec());
ArrayUtils.setIndexesTo(permChanges, inputIndices, c.getSynPermActiveInc());
for(int i = 0;i < activeColumns.length;i++) {
Pool pool = c.getPotentialPools().get(activeColumns[i]);
double[] perm = pool.getDensePermanences(c);
int[] indexes = pool.getSparsePotential();
ArrayUtils.raiseValuesBy(permChanges, perm);
Column col = c.getColumn(activeColumns[i]);
updatePermanencesForColumn(c, perm, col, indexes, true);
}
}
/**
* This method increases the permanence values of synapses of columns whose
* activity level has been too low. Such columns are identified by having an
* overlap duty cycle that drops too much below those of their peers. The
* permanence values for such columns are increased.
*
* @param c
*/
public void bumpUpWeakColumns(final Connections c) {
int[] weakColumns = ArrayUtils.where(c.getMemory().get1DIndexes(), new Condition.Adapter() {
@Override public boolean eval(int i) {
return c.getOverlapDutyCycles()[i] < c.getMinOverlapDutyCycles()[i];
}
});
for(int i = 0;i < weakColumns.length;i++) {
Pool pool = c.getPotentialPools().get(weakColumns[i]);
double[] perm = pool.getSparsePermanences();
ArrayUtils.raiseValuesBy(c.getSynPermBelowStimulusInc(), perm);
int[] indexes = pool.getSparsePotential();
Column col = c.getColumn(weakColumns[i]);
updatePermanencesForColumnSparse(c, perm, col, indexes, true);
}
}
/**
* This method ensures that each column has enough connections to input bits
* to allow it to become active. Since a column must have at least
* 'stimulusThreshold' overlaps in order to be considered during the
* inhibition phase, columns without such minimal number of connections, even
* if all the input bits they are connected to turn on, have no chance of
* obtaining the minimum threshold. For such columns, the permanence values
* are increased until the minimum number of connections are formed.
*
* @param c the {@link Connections} memory
* @param perm the permanence values
* @param maskPotential
*/
public void raisePermanenceToThreshold(Connections c, double[] perm, int[] maskPotential) {
if(maskPotential.length < c.getStimulusThreshold()) {
throw new IllegalStateException("This is likely due to a " +
"value of stimulusThreshold that is too large relative " +
"to the input size. [len(mask) < self._stimulusThreshold]");
}
ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
while(true) {
int numConnected = ArrayUtils.valueGreaterCountAtIndex(c.getSynPermConnected(), perm, maskPotential);
if(numConnected >= c.getStimulusThreshold()) return;
ArrayUtils.raiseValuesBy(c.getSynPermBelowStimulusInc(), perm, maskPotential);
}
}
/**
* This method ensures that each column has enough connections to input bits
* to allow it to become active. Since a column must have at least
* 'stimulusThreshold' overlaps in order to be considered during the
* inhibition phase, columns without such minimal number of connections, even
* if all the input bits they are connected to turn on, have no chance of
* obtaining the minimum threshold. For such columns, the permanence values
* are increased until the minimum number of connections are formed.
*
* Note: This method services the "sparse" versions of corresponding methods
*
* @param c The {@link Connections} memory
* @param perm permanence values
*/
public void raisePermanenceToThresholdSparse(Connections c, double[] perm) {
ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
while(true) {
int numConnected = ArrayUtils.valueGreaterCount(c.getSynPermConnected(), perm);
if(numConnected >= c.getStimulusThreshold()) return;
ArrayUtils.raiseValuesBy(c.getSynPermBelowStimulusInc(), perm);
}
}
/**
* This method updates the permanence matrix with a column's new permanence
* values. The column is identified by its index, which reflects the row in
* the matrix, and the permanence is given in 'sparse' form, i.e. an array
* whose members are associated with specific indexes. It is in
* charge of implementing 'clipping' - ensuring that the permanence values are
* always between 0 and 1 - and 'trimming' - enforcing sparseness by zeroing out
* all permanence values below 'synPermTrimThreshold'. It also maintains
* the consistency between 'permanences' (the matrix storing the
* permanence values), 'connectedSynapses', (the matrix storing the bits
* each column is connected to), and 'connectedCounts' (an array storing
* the number of input bits each column is connected to). Every method wishing
* to modify the permanence matrix should do so through this method.
*
* @param c the {@link Connections} which is the memory model.
* @param perm An array of permanence values for a column. The array is
* "dense", i.e. it contains an entry for each input bit, even
* if the permanence value is 0.
* @param column The column in the permanence, potential and connectivity matrices
* @param maskPotential The indexes of inputs in the specified {@link Column}'s pool.
* @param raisePerm a boolean value indicating whether the permanence values
*/
public void updatePermanencesForColumn(Connections c, double[] perm, Column column, int[] maskPotential, boolean raisePerm) {
if(raisePerm) {
raisePermanenceToThreshold(c, perm, maskPotential);
}
ArrayUtils.lessThanOrEqualXThanSetToY(perm, c.getSynPermTrimThreshold(), 0);
ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
column.setProximalPermanences(c, perm);
}
/**
* This method updates the permanence matrix with a column's new permanence
* values. The column is identified by its index, which reflects the row in
* the matrix, and the permanence is given in 'sparse' form, (i.e. an array
* whose members are associated with specific indexes). It is in
* charge of implementing 'clipping' - ensuring that the permanence values are
* always between 0 and 1 - and 'trimming' - enforcing sparseness by zeroing out
* all permanence values below 'synPermTrimThreshold'. Every method wishing
* to modify the permanence matrix should do so through this method.
*
* @param c the {@link Connections} which is the memory model.
* @param perm An array of permanence values for a column. The array is
* "sparse", i.e. it contains an entry for each input bit, even
* if the permanence value is 0.
* @param column The column in the permanence, potential and connectivity matrices
* @param raisePerm a boolean value indicating whether the permanence values
*/
public void updatePermanencesForColumnSparse(Connections c, double[] perm, Column column, int[] maskPotential, boolean raisePerm) {
if(raisePerm) {
raisePermanenceToThresholdSparse(c, perm);
}
ArrayUtils.lessThanOrEqualXThanSetToY(perm, c.getSynPermTrimThreshold(), 0);
ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
column.setProximalPermanencesSparse(c, perm, maskPotential);
}
/**
* Returns a randomly generated permanence value for a synapse that is
* initialized in a connected state. The basic idea here is to initialize
* permanence values very close to synPermConnected so that a small number of
* learning steps could make it disconnected or connected.
*
* Note: experimentation was done a long time ago on the best way to initialize
* permanence values, but the history for this particular scheme has been lost.
*
* @return a randomly generated permanence value
*/
public static double initPermConnected(Connections c) {
double p = c.getSynPermConnected() + (c.getSynPermMax() - c.getSynPermConnected()) * c.random.nextDouble();
// Note from Python implementation on conditioning below:
// Ensure we don't have too much unnecessary precision. A full 64 bits of
// precision causes numerical stability issues across platforms and across
// implementations
p = ((int)(p * 100000)) / 100000.0d;
return p;
}
/**
* Returns a randomly generated permanence value for a synapses that is to be
* initialized in a non-connected state.
*
* @return a randomly generated permanence value
*/
public static double initPermNonConnected(Connections c) {
double p = c.getSynPermConnected() * c.getRandom().nextDouble();
// Note from Python implementation on conditioning below:
// Ensure we don't have too much unnecessary precision. A full 64 bits of
// precision causes numerical stability issues across platforms and across
// implementations
p = ((int)(p * 100000)) / 100000.0d;
return p;
}
/**
* Initializes the permanences of a column. The method
* returns a 1-D array the size of the input, where each entry in the
* array represents the initial permanence value between the input bit
* at the particular index in the array, and the column represented by
* the 'index' parameter.
*
* @param c the {@link Connections} which is the memory model
* @param potentialPool An array specifying the potential pool of the column.
* Permanence values will only be generated for input bits
* corresponding to indices for which the mask value is 1.
* WARNING: potentialPool is sparse, not an array of "1's"
* @param index the index of the column being initialized
* @param connectedPct A value between 0 or 1 specifying the percent of the input
* bits that will start off in a connected state.
* @return
*/
public double[] initPermanence(Connections c, int[] potentialPool, int index, double connectedPct) {
double[] perm = new double[c.getNumInputs()];
for(int idx : potentialPool) {
if(c.random.nextDouble() <= connectedPct) {
perm[idx] = initPermConnected(c);
}else{
perm[idx] = initPermNonConnected(c);
}
perm[idx] = perm[idx] < c.getSynPermTrimThreshold() ? 0 : perm[idx];
}
c.getColumn(index).setProximalPermanences(c, perm);
return perm;
}
/**
* Maps a column to its respective input index, keeping to the topology of
* the region. It takes the index of the column as an argument and determines
* what is the index of the flattened input vector that is to be the center of
* the column's potential pool. It distributes the columns over the inputs
* uniformly. The return value is an integer representing the index of the
* input bit. Examples of the expected output of this method:
* * If the topology is one dimensional, and the column index is 0, this
* method will return the input index 0. If the column index is 1, and there
* are 3 columns over 7 inputs, this method will return the input index 3.
* * If the topology is two dimensional, with column dimensions [3, 5] and
* input dimensions [7, 11], and the column index is 3, the method
* returns input index 8.
*
* @param columnIndex The index identifying a column in the permanence, potential
* and connectivity matrices.
* @return A boolean value indicating that boundaries should be
* ignored.
*/
public int mapColumn(Connections c, int columnIndex) {
int[] columnCoords = c.getMemory().computeCoordinates(columnIndex);
double[] colCoords = ArrayUtils.toDoubleArray(columnCoords);
double[] ratios = ArrayUtils.divide(
colCoords, ArrayUtils.toDoubleArray(c.getColumnDimensions()), 0, 0);
double[] inputCoords = ArrayUtils.multiply(
ArrayUtils.toDoubleArray(c.getInputDimensions()), ratios, 0, 0);
inputCoords = ArrayUtils.d_add(
inputCoords,
ArrayUtils.multiply(
ArrayUtils.divide(
ArrayUtils.toDoubleArray(c.getInputDimensions()),
ArrayUtils.toDoubleArray(c.getColumnDimensions()), 0, 0),
0.5));
int[] inputCoordInts = ArrayUtils.clip(ArrayUtils.toIntArray(inputCoords), c.getInputDimensions(), -1);
return c.getInputMatrix().computeIndex(inputCoordInts);
}
/**
* Maps a column to its input bits. This method encapsulates the topology of
* the region. It takes the index of the column as an argument and determines
* what are the indices of the input vector that are located within the
* column's potential pool. The return value is a list containing the indices
* of the input bits. The current implementation of the base class only
* supports a 1 dimensional topology of columns with a 1 dimensional topology
* of inputs. To extend this class to support 2-D topology you will need to
* override this method. Examples of the expected output of this method:
* * If the potentialRadius is greater than or equal to the entire input
* space, (global visibility), then this method returns an array filled with
* all the indices
* * If the topology is one dimensional, and the potentialRadius is 5, this
* method will return an array containing 5 consecutive values centered on
* the index of the column (wrapping around if necessary).
* * If the topology is two dimensional (not implemented), and the
* potentialRadius is 5, the method should return an array containing 25
* '1's, where the exact indices are to be determined by the mapping from
* 1-D index to 2-D position.
*
* @param c {@link Connections} the main memory model
* @param columnIndex The index identifying a column in the permanence, potential
* and connectivity matrices.
* @param wrapAround A boolean value indicating that boundaries should be
* ignored.
* @return
*/
public int[] mapPotential(Connections c, int columnIndex, boolean wrapAround) {
int centerInput = mapColumn(c, columnIndex);
int[] columnInputs = getInputNeighborhood(c, centerInput, c.getPotentialRadius());
// Select a subset of the receptive field to serve as the
// the potential pool
int numPotential = (int)(columnInputs.length * c.getPotentialPct() + 0.5);
int[] retVal = new int[numPotential];
return ArrayUtils.sample(columnInputs, retVal, c.getRandom());
}
/**
* Performs inhibition. This method calculates the necessary values needed to
* actually perform inhibition and then delegates the task of picking the
* active columns to helper functions.
*
* @param c the {@link Connections} matrix
* @param overlaps an array containing the overlap score for each column.
* The overlap score for a column is defined as the number
* of synapses in a "connected state" (connected synapses)
* that are connected to input bits which are turned on.
* @return
*/
public int[] inhibitColumns(Connections c, double[] overlaps) {
overlaps = Arrays.copyOf(overlaps, overlaps.length);
double density;
double inhibitionArea;
if((density = c.getLocalAreaDensity()) <= 0) {
inhibitionArea = Math.pow(2 * c.getInhibitionRadius() + 1, c.getColumnDimensions().length);
inhibitionArea = Math.min(c.getNumColumns(), inhibitionArea);
density = c.getNumActiveColumnsPerInhArea() / inhibitionArea;
density = Math.min(density, 0.5);
}
//Add our fixed little bit of random noise to the scores to help break ties.
//ArrayUtils.d_add(overlaps, c.getTieBreaker());
if(c.getGlobalInhibition() || c.getInhibitionRadius() > ArrayUtils.max(c.getColumnDimensions())) {
return inhibitColumnsGlobal(c, overlaps, density);
}
return inhibitColumnsLocal(c, overlaps, density);
}
/**
* Perform global inhibition. Performing global inhibition entails picking the
* top 'numActive' columns with the highest overlap score in the entire
* region. At most half of the columns in a local neighborhood are allowed to
* be active.
*
* @param c the {@link Connections} matrix
* @param overlaps an array containing the overlap score for each column.
* The overlap score for a column is defined as the number
* of synapses in a "connected state" (connected synapses)
* that are connected to input bits which are turned on.
* @param density The fraction of columns to survive inhibition.
*
* @return
*/
public int[] inhibitColumnsGlobal(Connections c, double[] overlaps, double density) {
int numCols = c.getNumColumns();
int numActive = (int)(density * numCols);
int[] sortedWinnerIndices = IntStream.range(0,overlaps.length)
.mapToObj(i-> new Pair<>(i,overlaps[i]))
.sorted(c.inhibitionComparator)
.mapToInt(Pair::getFirst)
.toArray();
// Enforce the stimulus threshold
double stimulusThreshold = c.getStimulusThreshold();
int start = sortedWinnerIndices.length - numActive;
while(start < sortedWinnerIndices.length) {
int i = sortedWinnerIndices[start];
if(overlaps[i] >= stimulusThreshold) break;
++start;
}
return IntStream.of(sortedWinnerIndices).skip(start).toArray();
}
/**
* Performs inhibition. This method calculates the necessary values needed to
* actually perform inhibition and then delegates the task of picking the
* active columns to helper functions.
*
* @param c the {@link Connections} matrix
* @param overlaps an array containing the overlap score for each column.
* The overlap score for a column is defined as the number
* of synapses in a "connected state" (connected synapses)
* that are connected to input bits which are turned on.
* @param density The fraction of columns to survive inhibition. This
* value is only an intended target. Since the surviving
* columns are picked in a local fashion, the exact fraction
* of surviving columns is likely to vary.
* @return indices of the winning columns
*/
public int[] inhibitColumnsLocal(Connections c, double[] overlaps, double density) {
double addToWinners = ArrayUtils.max(overlaps) / 1000.0d;
if(addToWinners == 0) {
addToWinners = 0.001;
}
double[] tieBrokenOverlaps = Arrays.copyOf(overlaps, overlaps.length);
TIntList winners = new TIntArrayList();
double stimulusThreshold = c.getStimulusThreshold();
int inhibitionRadius = c.getInhibitionRadius();
for(int i = 0;i < overlaps.length;i++) {
int column = i;
if(overlaps[column] >= stimulusThreshold) {
int[] neighborhood = getColumnNeighborhood(c, column, inhibitionRadius);
double[] neighborhoodOverlaps = ArrayUtils.sub(tieBrokenOverlaps, neighborhood);
long numBigger = Arrays.stream(neighborhoodOverlaps)
.parallel()
.filter(d -> d > overlaps[column])
.count();
int numActive = (int)(0.5 + density * neighborhood.length);
if(numBigger < numActive) {
winners.add(column);
tieBrokenOverlaps[column] += addToWinners;
}
}
}
return winners.toArray();
}
/**
* Update the boost factors for all columns. The boost factors are used to
* increase the overlap of inactive columns to improve their chances of
* becoming active. and hence encourage participation of more columns in the
* learning process. This is a line defined as: y = mx + b boost =
* (1-maxBoost)/minDuty * dutyCycle + maxFiringBoost. Intuitively this means
* that columns that have been active enough have a boost factor of 1, meaning
* their overlap is not boosted. Columns whose active duty cycle drops too much
* below that of their neighbors are boosted depending on how infrequently they
* have been active. The more infrequent, the more they are boosted. The exact
* boost factor is linearly interpolated between the points (dutyCycle:0,
* boost:maxFiringBoost) and (dutyCycle:minDuty, boost:1.0).
*
* boostFactor
* ^
* maxBoost _ |
* |\
* | \
* 1 _ | \ _ _ _ _ _ _ _
* |
* +--------------------> activeDutyCycle
* |
* minActiveDutyCycle
*/
public void updateBoostFactors(Connections c) {
double[] activeDutyCycles = c.getActiveDutyCycles();
double[] minActiveDutyCycles = c.getMinActiveDutyCycles();
//Indexes of values > 0
int[] mask = ArrayUtils.where(minActiveDutyCycles, ArrayUtils.GREATER_THAN_0);
double[] boostInterim;
if(mask.length < 1) {
boostInterim = c.getBoostFactors();
}else{
double[] numerator = new double[c.getNumColumns()];
Arrays.fill(numerator, 1 - c.getMaxBoost());
boostInterim = ArrayUtils.divide(numerator, minActiveDutyCycles, 0, 0);
boostInterim = ArrayUtils.multiply(boostInterim, activeDutyCycles, 0, 0);
boostInterim = ArrayUtils.d_add(boostInterim, c.getMaxBoost());
}
ArrayUtils.setIndexesTo(boostInterim, ArrayUtils.where(activeDutyCycles, new Condition.Adapter
© 2015 - 2025 Weber Informatics LLC | Privacy Policy