diff --git a/changes-2026-10-14.md b/changes-2026-10-14.md index 456e435..82b02e8 100644 --- a/changes-2026-10-14.md +++ b/changes-2026-10-14.md @@ -303,3 +303,17 @@ KNP, validation: `fc_v_plain` 81,0%, `fc_v_augB` 79,5%. Симулятор (2000 - Ошибка ожидания: условие `pgrep -f grid_v3_sparse.sh` находило саму команду ожидания, компьютер простаивал ~22:40–00:10. - `objects/fetch_fresh_test.py`: докачаны test 0700–0999 cat/dog (600, нигде не использовались) — для одной итоговой проверки. - `objects/spiking_pipeline.py`: точная пошаговая симуляция пиксели → слой 1 (K × 27×27 нейронов, целые веса ±127, смещение каждый активный шаг, порог, сброс 0, не ниже 0) → нейрон «собака − кошка» (веса общие для узора и четверти, задержка 1). Проба на 300 обучающих: линейный на точных счётчиках 63,3%, спайковый выход 63,0% (порог 4 × медиана входа за шаг). + +### 00:20 – 01:00: точная спайковая цепочка и однократная проверка на свежем тесте +- Точная симуляция `spiking_pipeline.py`, K=800 (`v3_k800_lin.npz`), 18 000 обучение + 1000 validation, 582 с: порог слоя 1 108,08 (99-й перцентиль) — линейный на точных счётчиках 71,0%, спайковый выход **71,6%** (lam 1e6, порог 4 × медиана); ~668 тыс. спайков слоя 1 на картинку. Порог 50 — 70,0%, порог 75 — 70,6% (не лучше). +- Свежий тест: CIFAR-10 JPEG test 0700–0999 на класс, 600 картинок, ранее не использовались (`objects/fetch_fresh_test.py`, `prepare_fresh_test.py`). Все настройки зафиксированы по validation до проверки; признаки D/E проверены на побайтное совпадение с сохранёнными. Скрипты `objects/prepare_fresh_knp.py`, `objects/final_fresh_test.py`; результаты `~/run/cats-dogs/final_fresh_test.json`, прогоны `fresh_D_v3`, `fresh_E_v2`. + +| Вариант | validation | свежий тест (600) | +| --- | --- | --- | +| E: v2, sparse HOG + колонки STDP (t4000, c20) | 62,6% | 62,5% | +| D: выученные K=512 top-256 + колонки STDP, наказание 0,012 (t1600), KNP | 64,3% | 67,0% | +| C: полностью спайковая, точная симуляция (слой 1 + нейрон «собака − кошка») | 71,6% | 68,3% | +| B: K=800 без деления на контраст, ridge (без спайков) | 77,1% | 76,5% | +| A: K=800 с делением на контраст, ridge (без спайков) | 78,4% | 77,2% | + +95% интервал при n=600 около ±3,7 п.п. **Свежий тест теперь просмотрен**; все test-картинки кошек и собак CIFAR (0–999) использованы, для новой независимой оценки нужен другой источник. diff --git a/objects/final_fresh_test.py b/objects/final_fresh_test.py new file mode 100644 index 0000000..6bae075 --- /dev/null +++ b/objects/final_fresh_test.py @@ -0,0 +1,72 @@ +"""One-time evaluation on the fresh cats/dogs test (600 images). Settings were fixed on validation before. + +A: learned features K 800 with contrast division, standardised, ridge lam 300 (no spikes; upper reference). +B: K 800 without contrast division (folds into neuron weights), alpha 0.25, ridge lam 300. +C: fully spiking, exact step-by-step simulation (spiking_pipeline.py): layer-1 threshold 108.08, readout ridge + lam 1e6 on exact layer-1 spike counts, readout neuron threshold 4 × median per-step input (unit from validation). + +usage: ~/anvenv/bin/python final_fresh_test.py +""" +import json +import sys + +import numpy as np + +sys.path.insert(0, "/mnt/c/knp/objects") +import kmeans_features as kf # noqa: E402 +import spiking_pipeline as sp # noqa: E402 + +root = "/home/int/run/cats-dogs" +fresh = np.load(f"{root}/data-v3/fresh_test.npz") +xf, yf = fresh["rgb"], fresh["labels"].astype(int) +tr, va = np.load(f"{root}/data-v3/train.npz"), np.load(f"{root}/data-v3/val.npz") +yt = np.r_[tr["labels"], tr["labels"]].astype(int) +yv = va["labels"].astype(int) +res = {} + + +def ridge(f, y, lam): + f = np.hstack([f, np.ones((len(f), 1), np.float32)]) + return np.linalg.solve(f.T @ f + lam * np.eye(f.shape[1]), f.T @ (np.eye(2)[y] * 2 - 1)) + + +def acc(w, f, y): + f = np.hstack([f, np.ones((len(f), 1), np.float32)]) + return 100 * ((f @ w).argmax(1) == y).mean() + + +# A +d = np.load(f"{root}/v3_k800.npz") +model = {k: d[k] for k in ("mean", "zca", "d")} +kf.CONTRAST_DIVIDE = True +mu, sd = d["Ft"].mean(0), d["Ft"].std(0) + 1e-3 +w = ridge((d["Ft"] - mu) / sd, yt, 300) +res["A"] = {"val": acc(w, (d["Fv"] - mu) / sd, yv), "fresh": acc(w, (kf.encode(xf, model) - mu) / sd, yf)} +print("A", res["A"], flush=True) + +# B +d = np.load(f"{root}/v3_k800_lin.npz") +model = {k: d[k] for k in ("mean", "zca", "d")} +kf.CONTRAST_DIVIDE = False +ft = np.vstack([kf.encode(tr["rgb"], model, alpha=0.25), kf.encode(tr["rgb"][:, :, :, ::-1], model, alpha=0.25)]) +mu, sd = ft.mean(0), ft.std(0) + 1e-3 +w = ridge((ft - mu) / sd, yt, 300) +res["B"] = {"val": acc(w, (kf.encode(va["rgb"], model, alpha=0.25) - mu) / sd, yv), + "fresh": acc(w, (kf.encode(xf, model, alpha=0.25) - mu) / sd, yf)} +print("B", res["B"], flush=True) + +# C +s = np.load(f"{root}/spiking_v3_k800.npz") +ct, sv = s["ct"], s["sv"] +f = lambda c: np.hstack([c.astype(np.float32), np.ones((len(c), 1), np.float32)]) +sol = np.linalg.solve(f(ct).T @ f(ct) + 1e6 * np.eye(ct.shape[1] + 1), f(ct).T @ (s["yt"] * 2 - 1.0)) +rq = np.rint(sol / np.abs(sol).max() * 255).astype(np.int64) +bias = int(np.rint(rq[-1] / sp.ACTIVE)) +unit = float(np.median(np.abs(sv[:, :sp.ACTIVE].reshape(-1, sv.shape[-1]) @ rq[:-1] + bias))) + 1 +thr = 4 * unit +sf, tf = sp.layer1_counts(xf, s["wq"], s["bq"], float(s["thr1"]), per_step=True) +res["C"] = {"val": 100 * (sp.readout_neuron(sv, rq[:-1], bias, thr) == yv).mean(), + "fresh": 100 * (sp.readout_neuron(sf, rq[:-1], bias, thr) == yf).mean(), + "layer1_spikes_per_image": int(tf.mean()), "readout_threshold": thr} +print("C", res["C"], flush=True) +json.dump(res, open(f"{root}/final_fresh_test.json", "w"), indent=1) diff --git a/objects/prepare_fresh_knp.py b/objects/prepare_fresh_knp.py new file mode 100644 index 0000000..eb2fc13 --- /dev/null +++ b/objects/prepare_fresh_knp.py @@ -0,0 +1,47 @@ +"""KNP input files for the one-time fresh-test evaluation (600 images, test 0700–0999 per class). + +D: learned features (K 512 model from make_knp_features.py, same gain), top-256 per image; training part = + the first 18000 rows of kmeans_top256_validation.bin (9000 images + mirror copies). +E: v2 sparse HOG (cats-dogs/features.py, descriptor(sparse=True)); training part = the first 4000 rows of + features-v2/sparse_hog_validation.bin (v2 training set). + +usage: ~/anvenv/bin/python prepare_fresh_knp.py +""" +import shutil +import sys + +import numpy as np + +sys.path.insert(0, "/mnt/c/knp/objects") +sys.path.insert(0, "/mnt/c/knp/cats-dogs") +import kmeans_features as kf # noqa: E402 +from features import descriptor # noqa: E402 + +root = "/home/int/run/cats-dogs" +fresh = np.load(f"{root}/data-v3/fresh_test.npz") +labels = "\n".join(map(str, fresh["labels"])) + "\n" + + +def write(path, train_bin, train_target, n_train, width, test): + x = np.fromfile(train_bin, np.uint8).reshape(-1, width)[:n_train] + y = open(train_target).read().split()[:n_train] + np.concatenate([x, test]).tofile(path + ".bin") + open(path + ".target", "w").write("\n".join(y) + "\n" + labels) + print(path, n_train, "+", len(test)) + + +# D +kf.CONTRAST_DIVIDE = False +m = dict(np.load(f"{root}/features-v3/kmeans_model.npz")) +f = kf.encode(fresh["rgb"], m, alpha=0.25) +q = np.clip(np.rint(f / m["gain"] * 255), 0, 255).astype(np.uint8) +keep = np.argpartition(q, -256, axis=1)[:, -256:] +s = np.zeros_like(q) +np.put_along_axis(s, keep, np.take_along_axis(q, keep, 1), 1) +write(f"{root}/features-v3/kmeans_top256_fresh", f"{root}/features-v3/kmeans_top256_validation.bin", + f"{root}/features-v3/kmeans_top256_validation.target", 18000, 2048, s) + +# E +h = descriptor(fresh["rgb"].reshape(-1, 3, 1024), sparse=True) +write(f"{root}/features-v2/sparse_hog_fresh", f"{root}/features-v2/sparse_hog_validation.bin", + f"{root}/features-v2/sparse_hog_validation.target", 4000, 2048, h) diff --git a/objects/spiking_pipeline.py b/objects/spiking_pipeline.py index f518aee..f0bab05 100644 --- a/objects/spiking_pipeline.py +++ b/objects/spiking_pipeline.py @@ -131,7 +131,7 @@ def main(): np.savez(a.out, ct=ct, sv=sv, yt=yy, yv=va["labels"], thr1=a.thr1, wq=wq, bq=bq) f = lambda c: np.hstack([c.astype(np.float32), np.ones((len(c), 1), np.float32)]) res = {} - for lam in [1e3, 1e4, 1e5, 1e6]: + for lam in [1e5, 1e6, 1e7]: sol = np.linalg.solve(f(ct).T @ f(ct) + lam * np.eye(ct.shape[1] + 1), f(ct).T @ (yy * 2 - 1.0)) acc = 100 * (((f(cv) @ sol) > 0) == va["labels"]).mean() rq = np.rint(sol / np.abs(sol).max() * 255).astype(np.int64) @@ -139,7 +139,7 @@ def main(): bias = int(np.rint(rq[-1] / ACTIVE)) # Readout threshold relative to the typical size of its per-step input. unit = float(np.median(np.abs(sv[:, :ACTIVE].reshape(-1, sv.shape[-1]) @ rq[:-1] + bias))) + 1 - spk = {q: 100 * (readout_neuron(sv, rq[:-1], bias, q * unit) == va["labels"]).mean() for q in (0.5, 1, 2, 4, 8)} + spk = {q: 100 * (readout_neuron(sv, rq[:-1], bias, q * unit) == va["labels"]).mean() for q in (2, 3, 4, 5, 6)} res[lam] = {"linear": acc, "spiking": spk, "unit": unit} print(f"lam {lam:g}: linear on exact layer-1 counts {acc:.1f}%; spiking readout by threshold " + ", ".join(f"{q}×: {v:.1f}%" for q, v in spk.items()) + f" (unit {unit:.0f})", flush=True)