simplehtm
Version:
Simple HTM implementation.
297 lines (244 loc) • 9.37 kB
JavaScript
const assert = require('chai').assert
const d3 = require('d3')
const SpatialPooler = require('../../../src/algorithms/spatialPooler')
const defaultInputCount = 500
const defaultSpSize = 200
describe('when calculating overlaps', () => {
const inputSaturations = [0.1, .3, .6, .1]
const inputCount = defaultInputCount * 2
const spSize = defaultSpSize
const connectionThreshold = 0.5
const connectedPercent = 0.85
const sp = new SpatialPooler({
// Assume 1D input, global inhibition, no topology
inputCount: inputCount,
// Assume 1D input, global inhibition, no topology
size: spSize,
connectedPercent: connectedPercent,
connectionThreshold: connectionThreshold,
learn: false,
})
inputSaturations.forEach(inputSaturation => {
describe(`when input is ${inputSaturation * 100}% saturated`, () => {
const input = []
for (let x = 0; x < inputCount; x++) {
input.push(Math.random() < inputSaturation ? 1 : 0)
}
it('overlap is within acceptable limits', () => {
for (let mcIndex = 0; mcIndex < spSize; mcIndex++) {
const overlapIndices = sp.calculateOverlap(mcIndex, input)
assert.isAtMost(Math.max(...overlapIndices), inputCount - 1,
'overlap index is above input range')
assert.isAtLeast(Math.min(...overlapIndices), 0,
'overlap index is below input range')
assert.equal(new Set(overlapIndices).size, overlapIndices.length,
'overlap contains non-unique indices')
const overlap = overlapIndices.length
const perms = sp.getPermanences()[mcIndex]
const totalConnections = perms.reduce((cons, perm) => {
let out = cons
if (perm >= connectionThreshold) {
out += 1
}
return out
}, 0)
assert.isAtMost(overlap, totalConnections,
'overlap should not be more than current connection count')
const observedOverlapPerc = overlap / totalConnections
assert.closeTo(observedOverlapPerc, inputSaturation, 0.1,
`observed overlap percent of total connections should be similar to input saturation`)
}
})
})
})
})
describe('during minicolumn competition', () => {
[1, 10, 40, 100].forEach(winnerCount => {
const inputCount = defaultInputCount
const spSize = defaultSpSize
const connectionThreshold = 0.5
const connectedPercent = 0.85
const permanenceInc = 0.1
const permanenceDec = 0.05
const sp = new SpatialPooler({
// Assume 1D input, global inhibition, no topology
inputCount: inputCount,
// Assume 1D input, global inhibition, no topology
size: spSize,
connectedPercent: connectedPercent,
connectionThreshold: connectionThreshold,
winnerCount: winnerCount,
permanenceInc: permanenceInc,
permanenceDec: permanenceDec,
learn: false,
})
describe(`with ${winnerCount} winning minicolumns`, () => {
[0.1, .3, .6, 1].forEach(inputSaturation => {
describe(`input saturation ${inputSaturation * 100}%`, () => {
it('minicolumns with most overlap win', () => {
const input = []
for (let x = 0; x < inputCount; x++) {
input.push(Math.random() < inputSaturation ? 1 : 0)
}
const winners = sp.compete(input)
assert.isArray(winners, 'SP competition result must be an array')
assert.lengthOf(winners, winnerCount, 'wrong minicolumn competition winner count')
winners.forEach(winner => {
for (let x = 0; x < sp.opts.size; x++) {
// only test against losers
if (!winners.map(v => v.index).indexOf(x)) {
assert.isAtLeast(winner.overlap.length, sp.calculateOverlap(x, input).length,
'all winner minicolumns must have higher overlap scores than losers')
}
}
})
// Ensure getOverlap returns simple data structure that matches.
sp.getOverlaps().forEach((overlap, mcIndex) => {
assert.deepEqual(overlap, sp.calculateOverlap(mcIndex, input),
'overlap array from getOverlap was not expected')
})
})
const input = []
for (let x = 0; x < inputCount; x++) {
input.push(Math.random() < inputSaturation ? 1 : 0)
}
describe('on new input when learning enabled', () => {
sp.enableLearning()
describe('for winner minicolumns', () => {
const allOriginalPerms = sp.getPermanences()
const winners = sp.compete(input)
const allNewPerms = sp.getPermanences()
const allPools = sp.getPotentialPools()
it('permanences are properly updated', () => {
winners.forEach(winner => {
const mcIndex = winner.index
const overlap = winner.overlap
const originalPerms = allOriginalPerms[mcIndex]
const newPerms = allNewPerms[mcIndex]
const pools = allPools[mcIndex]
assert.notDeepEqual(newPerms, originalPerms, 'permanences should be updated')
// Only loop through the pools
pools.forEach((inputIndex, poolIndex) => {
// Use the pool indices to find the right perm values
const originalPerm = originalPerms[poolIndex]
const newPerm = newPerms[poolIndex]
if (overlap.includes(inputIndex)) {
const expected = Math.min(1.0, originalPerm + permanenceInc)
assert.equal(newPerm, expected,
`winner minicolumn at ${mcIndex} was not incremented for overlapping input index ${inputIndex}`)
} else {
const expected = Math.max(0.0, originalPerm - permanenceDec)
assert.equal(newPerm, expected,
`winner minicolumn at ${mcIndex} was not decremented for NON-overlapping input index ${inputIndex}`)
}
})
})
})
})
describe('for loser minicolumns', () => {
const allOriginalPerms = sp.getPermanences()
const winnerIndices = sp.compete(input).map(w => w.index)
const allNewPerms = sp.getPermanences()
const allPools = sp.getPotentialPools()
it('permanences are never changed', () => {
for (let mcIndex = 0; mcIndex < sp.size; mcIndex++) {
if (!winnerIndices.includes(mcIndex)) {
const originalPerms = allOriginalPerms[mcIndex]
const newPerms = allNewPerms[mcIndex]
const pools = allPools[mcIndex]
// Only loop through the pools
pools.forEach((inputIndex, poolIndex) => {
// Use the pool indices to find the right perm values
const originalPerm = originalPerms[poolIndex]
const newPerm = newPerms[poolIndex]
assert.equal(newPerm, originalPerm,
`loser minicolumn permanence at ${mcIndex} at input index ${inputIndex} was changed`)
})
}
}
})
})
})
describe('on new input when learning disabled', () => {
sp.disableLearning()
describe('for winner minicolumns', () => {
const allOriginalPerms = sp.getPermanences()
const winners = sp.compete(input)
const allNewPerms = sp.getPermanences()
it('permanences are NOT updated', () => {
winners.forEach(winner => {
const mcIndex = winner.index
const overlaps = winner.overlay
const originalPerms = allOriginalPerms[mcIndex]
const newPerms = allNewPerms[mcIndex]
input.forEach((inputBit, inputIndex) => {
const originalPerm = originalPerms[inputIndex]
const newPerm = newPerms[inputIndex]
assert.equal(newPerm, originalPerm,
`winner minicolumn at ${mcIndex} at input index ${inputIndex} was updated when learning was disabled`)
})
})
})
})
})
})
})
})
})
describe('during learning permanence values are bounded', () => {
it('when perms are near 1.0 initially, learning caps them at 1.0', () => {
const inputCount = defaultInputCount
const spSize = defaultSpSize
const sp = new SpatialPooler({
// Assume 1D input, global inhibition, no topology
inputCount: inputCount,
// Assume 1D input, global inhibition, no topology
size: spSize,
connectedPercent: .85,
connectionThreshold: 0.5,
distributionCenter: 0.99,
winnerCount: Math.floor(spSize * 0.02),
permanenceInc: 0.5,
permanenceDec: 0.0,
learn: true,
})
const input = []
for (let x = 0; x < inputCount; x++) {
input.push(Math.random() < 0.5 ? 1 : 0)
}
sp.compete(input)
sp.getPermanences().forEach(perms => {
perms.forEach(perm => {
assert.isAtMost(perm, 1.0, 'permenance value is too high')
})
})
})
it('when perms are near 0.0 initially, learning bottoms them at 0.0', () => {
const inputCount = defaultInputCount
const spSize = defaultSpSize
const sp = new SpatialPooler({
// Assume 1D input, global inhibition, no topology
inputCount: inputCount,
// Assume 1D input, global inhibition, no topology
size: spSize,
connectedPercent: .85,
connectionThreshold: 0.5,
distributionCenter: 0.01,
winnerCount: Math.floor(spSize * 0.02),
permanenceInc: 0.0,
permanenceDec: 0.5,
learn: true,
})
const input = []
for (let x = 0; x < inputCount; x++) {
input.push(Math.random() < 0.5 ? 1 : 0)
}
sp.compete(input)
sp.getPermanences().forEach(perms => {
perms.forEach(perm => {
assert.isAtLeast(perm, 0.0, 'permenance value is too low')
})
})
})
})
})