KNP_COL_PUNISH: dopamine punishment of wrong columns; learned k-means features for objects; extra data fetcher
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b8ea9dc6ce
commit
795f75a65b
4 changed files with 142 additions and 0 deletions
12
augment/punish_runs.sh
Normal file
12
augment/punish_runs.sh
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
# 2026-10-14 night: dopamine punishment of wrong columns (KNP_COL_PUNISH) on coarse stroke features, 30k train, validation.
|
||||
cd ~/run/exp
|
||||
B="KNP_COL_JITTER=20 KNP_MNIST_THR=60 KNP_MNIST_ADD_THR_SCALE=0 KNP_MNIST_INFER_INHIB=255"
|
||||
done_run() { grep -q "^CLASS" "$1/result.csv" 2>/dev/null || pgrep -f "run.sh $1 " >/dev/null; }
|
||||
for seed in 42 1; do
|
||||
for p in 0 0.005 0.012; do
|
||||
n=pun_fc_p${p}_s$seed
|
||||
done_run $n || MODEL=altai DATA=fcoarse_val ./run.sh $n 30000 $B KNP_COL_SEED=$seed KNP_COL_PUNISH=$p > $n.err 2>&1 &
|
||||
done
|
||||
done
|
||||
wait
|
||||
|
|
@ -39,6 +39,8 @@
|
|||
// KNP_COL_JITTER=<sigma> random initial raster->input weights, normal(0, sigma), seed KNP_COL_SEED (default 42).
|
||||
// KNP_COL_LATERAL=<w> input->input inhibition inside a column: weight -w, delay 1.
|
||||
// KNP_COL_GATE=<g1,g2,..> gate->input pulses: weight g_k with delay k (default: single 10000 pulse with delay 1).
|
||||
// KNP_COL_PUNISH=<d> target->input dopamine -d*1000 to every column except the label's one (reward is 0.179376):
|
||||
// neurons of wrong columns that fired recently lose the synapses that made them fire.
|
||||
namespace
|
||||
{
|
||||
float env_float(const char *name, float default_value)
|
||||
|
|
@ -186,6 +188,28 @@ static void create_projections(
|
|||
// Record dopamine projection for label processing.
|
||||
network.data_.projections_from_classes_.push_back(target_to_input_proj_dopamine);
|
||||
|
||||
// Dopamine punishment for the columns of the other classes.
|
||||
if (const float punish = env_float("KNP_COL_PUNISH", 0); punish > 0)
|
||||
{
|
||||
DeltaSynapseParams punish_synapse = target_to_input_synapse_dopamine;
|
||||
auto punish_creator = [punish](
|
||||
const knp::core::UID &pre_uid, const knp::core::UID &post_uid, size_t pre_size,
|
||||
size_t post_size, auto syn_gen)
|
||||
{
|
||||
return knp::framework::projection::creators::all_to_all<DeltaSynapse>(
|
||||
pre_uid, post_uid, pre_size, post_size,
|
||||
[syn_gen, punish](size_t pre, size_t post)
|
||||
{
|
||||
auto params = syn_gen(pre, post);
|
||||
params.weight_ = post / neurons_per_column == pre ? 0.F : -punish * 1000;
|
||||
return params;
|
||||
});
|
||||
};
|
||||
auto target_to_input_proj_punish = constructor.add_projection(
|
||||
punish_synapse, punish_creator, pops.target_pop_, pops.input_pop_, false, false);
|
||||
network.data_.projections_from_classes_.push_back(target_to_input_proj_punish);
|
||||
}
|
||||
|
||||
// Configure excitatory synapse for target to input projection.
|
||||
DeltaSynapseParams target_to_input_synapse_excitatory;
|
||||
target_to_input_synapse_excitatory.output_type_ = knp::synapse_traits::OutputType::EXCITATORY;
|
||||
|
|
|
|||
40
objects/fetch_more.py
Normal file
40
objects/fetch_more.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"""Download CIFAR-10 JPEG mirror train images 2000–4499 for cat and dog (unused by v1/v2) into the same cache.
|
||||
|
||||
Same source and commit as cats-dogs/prepare_jpeg.py. Existing files are skipped.
|
||||
usage: python3 fetch_more.py
|
||||
"""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
COMMIT = 'd7d05618061fe37d830fb0419063d113c575ccbc'
|
||||
BASE = f'https://raw.githubusercontent.com/YoongiKim/CIFAR-10-images/{COMMIT}'
|
||||
cache = Path('/home/int/run/cats-dogs/data/github-jpeg-cache')
|
||||
|
||||
|
||||
def load(item):
|
||||
label, index = item
|
||||
path = cache / f'train-{label}-{index:04d}.jpg'
|
||||
if path.exists():
|
||||
return 0
|
||||
for attempt in range(6):
|
||||
try:
|
||||
with urllib.request.urlopen(f'{BASE}/train/{label}/{index:04d}.jpg', timeout=30) as r:
|
||||
data = r.read()
|
||||
tmp = path.with_suffix('.part')
|
||||
tmp.write_bytes(data)
|
||||
tmp.rename(path)
|
||||
return 1
|
||||
except Exception:
|
||||
if attempt == 5:
|
||||
raise
|
||||
time.sleep(3 * (attempt + 1))
|
||||
|
||||
|
||||
items = [(label, i) for label in ('cat', 'dog') for i in range(2000, 4500)]
|
||||
with ThreadPoolExecutor(max_workers=8) as pool:
|
||||
for n, _ in enumerate(pool.map(load, items)):
|
||||
if n % 500 == 0:
|
||||
print('done', n, flush=True)
|
||||
print('all', len(items), flush=True)
|
||||
66
objects/kmeans_features.py
Normal file
66
objects/kmeans_features.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""Learned local features for small colour images (Coates & Ng 2011 style), prototype for a spiking layer.
|
||||
|
||||
Layer 1 is learned without labels: random 6×6×3 patches are contrast-normalised and ZCA-whitened, then
|
||||
spherical k-means finds K prototypes. This is the same rule as winner-take-all competitive Hebbian learning:
|
||||
the closest prototype moves towards the patch. Activation of prototype k at a position is
|
||||
max(0, <patch, d_k> - alpha) (a neuron with threshold alpha). Activations are sum-pooled over a grid of
|
||||
regions, which gives tolerance to position.
|
||||
|
||||
API: learn(images) -> model dict; encode(images, model) -> float32 features [n, K * grid * grid].
|
||||
images: uint8 [n, 3, 32, 32].
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
PATCH = 6
|
||||
|
||||
|
||||
def patches_at(img, ys, xs):
|
||||
"""img [n,3,32,32] float32 → patches [n, len(ys)*len(xs), 108]."""
|
||||
out = np.stack([img[:, :, y:y + PATCH, x:x + PATCH].reshape(len(img), -1) for y in ys for x in xs], axis=1)
|
||||
return out
|
||||
|
||||
|
||||
def normalise(p):
|
||||
p = p - p.mean(-1, keepdims=True)
|
||||
return p / np.sqrt(p.var(-1, keepdims=True) + 10 / 255)
|
||||
|
||||
|
||||
def learn(images, k=400, n_patches=200000, iters=15, seed=0):
|
||||
rng = np.random.default_rng(seed)
|
||||
x = images.astype(np.float32) / 255
|
||||
idx = rng.integers(0, len(x), n_patches)
|
||||
ys, xs = rng.integers(0, 32 - PATCH + 1, n_patches), rng.integers(0, 32 - PATCH + 1, n_patches)
|
||||
p = np.stack([x[i, :, y:y + PATCH, xx:xx + PATCH].reshape(-1) for i, y, xx in zip(idx, ys, xs)])
|
||||
p = normalise(p)
|
||||
mean = p.mean(0)
|
||||
cov = np.cov(p - mean, rowvar=False)
|
||||
e, v = np.linalg.eigh(cov)
|
||||
zca = (v * (1 / np.sqrt(e + 0.1))) @ v.T
|
||||
pw = (p - mean) @ zca
|
||||
d = rng.normal(size=(k, pw.shape[1])).astype(np.float32)
|
||||
d /= np.linalg.norm(d, axis=1, keepdims=True)
|
||||
for _ in range(iters):
|
||||
s = pw @ d.T
|
||||
win = s.argmax(1)
|
||||
new = np.zeros_like(d)
|
||||
np.add.at(new, win, pw * s[np.arange(len(pw)), win, None])
|
||||
empty = np.linalg.norm(new, axis=1) < 1e-6
|
||||
new[empty] = rng.normal(size=(empty.sum(), d.shape[1]))
|
||||
d = new / np.linalg.norm(new, axis=1, keepdims=True)
|
||||
return {"mean": mean.astype(np.float32), "zca": zca.astype(np.float32), "d": d.astype(np.float32)}
|
||||
|
||||
|
||||
def encode(images, model, alpha=0.25, grid=2, batch=250):
|
||||
x = images.astype(np.float32) / 255
|
||||
pos = list(range(0, 32 - PATCH + 1))
|
||||
side = len(pos)
|
||||
edges = np.linspace(0, side, grid + 1).astype(int)
|
||||
out = []
|
||||
for i in range(0, len(x), batch):
|
||||
p = normalise(patches_at(x[i:i + batch], pos, pos))
|
||||
a = np.maximum(((p - model["mean"]) @ model["zca"]) @ model["d"].T - alpha, 0)
|
||||
a = a.reshape(len(p), side, side, -1)
|
||||
pooled = [a[:, edges[gy]:edges[gy + 1], edges[gx]:edges[gx + 1]].sum((1, 2))
|
||||
for gy in range(grid) for gx in range(grid)]
|
||||
out.append(np.concatenate(pooled, axis=1))
|
||||
return np.concatenate(out)
|
||||
Loading…
Add table
Reference in a new issue