Online WTA feature learning; configurable active steps in spiking pipeline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
int 2026-10-15 00:46:14 +03:00
parent 899e0e3601
commit ec913f51c1
3 changed files with 28 additions and 0 deletions

View file

@ -317,3 +317,7 @@ KNP, validation: `fc_v_plain` 81,0%, `fc_v_augB` 79,5%. Симулятор (2000
| A: K=800 с делением на контраст, ridge (без спайков) | 78,4% | 77,2% | | A: K=800 с делением на контраст, ridge (без спайков) | 78,4% | 77,2% |
95% интервал при n=600 около ±3,7 п.п. **Свежий тест теперь просмотрен**; все test-картинки кошек и собак CIFAR (0–999) использованы, для новой независимой оценки нужен другой источник. 95% интервал при n=600 около ±3,7 п.п. **Свежий тест теперь просмотрен**; все test-картинки кошек и собак CIFAR (0–999) использованы, для новой независимой оценки нужен другой источник.
### 01:00 – : слой признаков нейронным правилом; число шагов
- `kmeans_features.learn_online`: обучение «победитель забирает всё» по одному кусочку за проход (как WTA-слой с Хеббом/STDP): победивший нейрон сдвигает нормированные веса к кусочку, скорость lr0/(1 + t/20000), 200 000 кусочков, то же выбеливание. Validation, K=400, без деления на контраст, +зеркало, ridge 300: пакетные k-средние 74,9%, **онлайн WTA 75,9%** — правило на нейронах даёт признаки не хуже.
- `spiking_pipeline.py --active N`: число активных шагов входа (по умолчанию 10). Запущены 20 и 30 шагов (порог слоя 1 тот же 108,08).

View file

@ -69,3 +69,24 @@ def encode(images, model, alpha=0.25, grid=2, batch=250):
for gy in range(grid) for gx in range(grid)] for gy in range(grid) for gx in range(grid)]
out.append(np.concatenate(pooled, axis=1)) out.append(np.concatenate(pooled, axis=1))
return np.concatenate(out) return np.concatenate(out)
def learn_online(images, k=400, n_patches=200000, lr0=0.05, seed=0, base=None):
"""Online winner-take-all learning, one patch at a time (what a spiking WTA layer with Hebbian/STDP does):
the most responsive neuron moves its normalised weights towards the patch, the others stay. Uses the same
whitening as `learn` (from `base` if given). Single pass, learning rate decays as lr0 / (1 + t / 20000)."""
rng = np.random.default_rng(seed)
if base is None:
base = learn(images, k=k, n_patches=min(n_patches, 50000), iters=0, seed=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)])
pw = (normalise(p) - base["mean"]) @ base["zca"]
d = pw[rng.choice(len(pw), k, replace=False)].copy()
d /= np.linalg.norm(d, axis=1, keepdims=True)
for t, v in enumerate(pw):
win = int(np.argmax(d @ v))
d[win] += lr0 / (1 + t / 20000) * v
d[win] /= np.linalg.norm(d[win])
return {"mean": base["mean"], "zca": base["zca"], "d": d.astype(np.float32)}

View file

@ -108,7 +108,10 @@ def main():
ap.add_argument("--thr1", type=float, default=0, help="layer-1 threshold in quantised units (0: auto)") ap.add_argument("--thr1", type=float, default=0, help="layer-1 threshold in quantised units (0: auto)")
ap.add_argument("--train-limit", type=int, default=0) ap.add_argument("--train-limit", type=int, default=0)
ap.add_argument("--out", required=True) ap.add_argument("--out", required=True)
ap.add_argument("--active", type=int, default=ACTIVE, help="active (spiking) input steps; 5 silent steps follow")
a = ap.parse_args() a = ap.parse_args()
global ACTIVE, STEPS
ACTIVE, STEPS = a.active, a.active + 5
model = dict(np.load(a.model)) model = dict(np.load(a.model))
w, b = fold(model, a.alpha) w, b = fold(model, a.alpha)
wq, bq, s = quant_layer1(w, b) wq, bq, s = quant_layer1(w, b)