feat(SNNBot): 1° bin resolution (360 bins) + neighbor reinforcement
5° bins caused visible jumping. 360 bins at 1° costs 5.7KB. Reinforce ±2 neighbors per learn call for faster coverage. Punish only bins >2° away from correct target.
This commit is contained in:
@@ -4,15 +4,15 @@ import std/[bitops, math]
|
|||||||
|
|
||||||
const
|
const
|
||||||
INPUT_BITS* = 80
|
INPUT_BITS* = 80
|
||||||
N_BINS* = 72
|
N_BINS* = 360
|
||||||
BIN_WIDTH* = 5.0
|
BIN_WIDTH* = 1.0
|
||||||
WORDS_IN* = 2 # 80 bits → 2 × uint64 (128 bits, only 80 used)
|
WORDS_IN* = 2 # 80 bits → 2 × uint64 (128 bits, only 80 used)
|
||||||
|
|
||||||
type
|
type
|
||||||
BitVec80* = array[WORDS_IN, uint64]
|
BitVec80* = array[WORDS_IN, uint64]
|
||||||
|
|
||||||
BinaryAimer* = object
|
BinaryAimer* = object
|
||||||
readout*: array[N_BINS, BitVec80] # 72 bins × 80-bit weight vectors
|
readout*: array[N_BINS, BitVec80] # 360 bins × 80-bit weight vectors
|
||||||
scores*: array[N_BINS, int]
|
scores*: array[N_BINS, int]
|
||||||
|
|
||||||
proc initAimer*(): BinaryAimer =
|
proc initAimer*(): BinaryAimer =
|
||||||
@@ -59,22 +59,24 @@ proc interpolatedAngle*(a: BinaryAimer, winnerBin: int): float =
|
|||||||
result = radToDeg(arctan2(sinAvg, cosAvg))
|
result = radToDeg(arctan2(sinAvg, cosAvg))
|
||||||
|
|
||||||
proc learn*(a: var BinaryAimer, input: BitVec80, correctAngle: float) =
|
proc learn*(a: var BinaryAimer, input: BitVec80, correctAngle: float) =
|
||||||
## WTA Hebbian: reinforce correct bin, punish worst wrong bin
|
## WTA Hebbian: reinforce correct bin + ±2 neighbors, punish worst wrong bin (>2° away)
|
||||||
let correctBin = angleToBin(correctAngle)
|
let correctBin = angleToBin(correctAngle)
|
||||||
|
|
||||||
# Reinforce: OR input into correct bin
|
# Reinforce correct bin + 2 neighbors on each side
|
||||||
for w in 0 ..< WORDS_IN:
|
for offset in -2 .. 2:
|
||||||
a.readout[correctBin][w] = a.readout[correctBin][w] or input[w]
|
let bin = (correctBin + offset + N_BINS) mod N_BINS
|
||||||
|
for w in 0 ..< WORDS_IN:
|
||||||
|
a.readout[bin][w] = a.readout[bin][w] or input[w]
|
||||||
|
|
||||||
# Find highest-scoring WRONG bin
|
# Punish highest-scoring wrong bin (must be >2 bins away from correct)
|
||||||
var worstBin = -1
|
var worstBin = -1
|
||||||
var worstScore = -1
|
var worstScore = -1
|
||||||
for k in 0 ..< N_BINS:
|
for k in 0 ..< N_BINS:
|
||||||
if k != correctBin and a.scores[k] > worstScore:
|
let dist = min(abs(k - correctBin), N_BINS - abs(k - correctBin)) # circular distance
|
||||||
|
if dist > 2 and a.scores[k] > worstScore:
|
||||||
worstScore = a.scores[k]
|
worstScore = a.scores[k]
|
||||||
worstBin = k
|
worstBin = k
|
||||||
|
|
||||||
# Punish: AND NOT input from worst wrong bin
|
|
||||||
if worstBin >= 0:
|
if worstBin >= 0:
|
||||||
for w in 0 ..< WORDS_IN:
|
for w in 0 ..< WORDS_IN:
|
||||||
a.readout[worstBin][w] = a.readout[worstBin][w] and (not input[w])
|
a.readout[worstBin][w] = a.readout[worstBin][w] and (not input[w])
|
||||||
|
|||||||
Reference in New Issue
Block a user