One-time fresh-test evaluation of object pipelines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
int 2026-10-15 00:43:38 +03:00
parent 8f126bc29a
commit c2ac33767d
4 changed files with 135 additions and 2 deletions

View file

@ -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) использованы, для новой независимой оценки нужен другой источник.

View file

@ -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)

View file

@ -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)

View file

@ -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)