Delta-rule KNP: augmented MNIST robustness, object runs; report section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
int 2026-10-15 06:50:34 +03:00
parent 917b94035d
commit 6bfdbf69d3
7 changed files with 51 additions and 11 deletions

View file

@ -42,7 +42,7 @@ Dataset process_dataset(ModelDescription const& model_desc)
// Process images and labels into spike representations.
dataset.process_labels_and_images(
images_stream, labels_stream, model_desc.train_images_amount_ + model_desc.inference_images_amount_,
classes_amount, input_size, steps_per_image,
classes_amount, get_input_size(), steps_per_image,
dataset.make_incrementing_image_to_spikes_converter(active_steps, state_increment_factor));
// Split dataset into training and inference portions.

View file

@ -22,6 +22,8 @@
#pragma once
#include <cstddef>
#include <cstdlib>
#include <string>
/**
@ -36,7 +38,20 @@ constexpr size_t classes_amount = 2;
*
* @details MNIST images are 28×28 pixels, resulting in 784 total pixels per image.
*/
constexpr size_t input_size = 32 * 32 * 2;
constexpr size_t default_input_size = 32 * 32 * 2;
/**
* @brief Actual input size: `KNP_MNIST_INPUT_SIZE` environment variable or 2048.
*/
inline size_t get_input_size()
{
static const size_t size = []
{
const char *value = std::getenv("KNP_MNIST_INPUT_SIZE");
return value ? static_cast<size_t>(std::stoul(value)) : default_input_size;
}();
return size;
}
/**
* @brief Total number of simulation steps per input image.

View file

@ -135,7 +135,7 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
// Create raster population for channelized image processing (28x28 = 784 neurons).
const auto &raster_pop = constructor.add_channeled_population(input_size, true);
const auto &raster_pop = constructor.add_channeled_population(get_input_size(), true);
// Create target population for label processing (10 neurons for 10 digit classes).
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);

View file

@ -91,7 +91,7 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
// Create raster population for channelized image processing (28x28 = 784 neurons).
const auto &raster_pop = constructor.add_channeled_population(input_size, true);
const auto &raster_pop = constructor.add_channeled_population(get_input_size(), true);
// Create target population for label processing (10 neurons for 10 digit classes).
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);

View file

@ -45,3 +45,9 @@ MNIST, признаки coarse, 30 000 обучение, 10 000 проверка
| 3 | 4 | **69,1%** |
Колонки STDP на этих же плотных признаках молчали (≈0%), на разреженных top-256 — 64–65%; ridge на этих uint8-признаках 74,2%, спайковый нейрон-разность 72,4%. Симулятор `dl_cd_r3_b4`: порог 30–250, торможение 0/255 — 67,6–68,8%.
- MNIST, штраф b: 8 — 94,22%; 12 — 92,42% (оптимум b = 4–6). Устойчивость `dl_r3_a1_b6_j20` (симулятор, 2000): как есть 94,5%, сдвиг 2 px 87,5%, толще 90,8%, тоньше 84,0%.
- MNIST с аугментацией A (`fcoarseA_val90000`, 90 000, r3 b6): KNP **94,63%**; устойчивость — 94,6 / **91,3** / 91,2 / 80,8%.
- Кошки/собаки, ещё b: r3 b6 63,5%, r3 b8 62,2%, r3 b12 60,0%, r1 b4 67,7%, r1 b8 68,4%.
- `cats-dogs/source`: размер входа — `get_input_size()` из `KNP_MNIST_INPUT_SIZE` (по умолчанию 2048), пересобран. `objects/make_knp_features.py --model --name`. Признаки онлайн WTA: K=800 (3200 входов) — r3 b4 65,3%, r1 b8 67,2%; K=1600 (6400) — 65,9% / 67,2%.
- Три прохода по обучению (обучающая часть ×3 с новым перемешиванием, `*_x3_validation`, 54 000): K=512 — r1 b4 67,3%, r1 b8 65,0%, r3 b4 66,4%; K=1600 r1 b8 63,2%. Не помогает.
- Вывод по объектам: в KNP дофамин по ошибке даёт 65–69% (колонки STDP — 64–65% на разреженных, ≈0% на плотных); ridge на тех же признаках — 74%. Вероятная причина разрыва — ограниченный и несимметричный диапазон весов KNP (−253…+92) и насыщение ресурса.

View file

@ -23,11 +23,18 @@ def main():
ap.add_argument("--k", type=int, default=512)
ap.add_argument("--alpha", type=float, default=0.25)
ap.add_argument("--seed", type=int, default=20261014)
ap.add_argument("--model", help="use a learned model (npz with mean, zca, d) instead of learning K prototypes")
ap.add_argument("--name", default="kmeans", help="output file prefix")
a = ap.parse_args()
out = Path(a.out)
out.mkdir(exist_ok=True)
kf.CONTRAST_DIVIDE = False
tr, va = np.load(f"{a.data}/train.npz"), np.load(f"{a.data}/val.npz")
if a.model:
m = np.load(a.model)
model = {key: m[key] for key in ("mean", "zca", "d")}
a.k = len(model["d"])
else:
model = kf.learn(tr["rgb"], k=a.k)
ft = np.vstack([kf.encode(tr["rgb"], model, alpha=a.alpha), kf.encode(tr["rgb"][:, :, :, ::-1], model, alpha=a.alpha)])
yt = np.r_[tr["labels"], tr["labels"]]
@ -37,13 +44,13 @@ def main():
order = np.random.default_rng(a.seed).permutation(len(ft))
x = np.vstack([q(ft)[order], q(fv)])
y = np.r_[yt[order], va["labels"]]
assert x.shape[1] == 2048
x.tofile(out / "kmeans_validation.bin")
(out / "kmeans_validation.target").write_text("\n".join(map(str, y)) + "\n")
np.savez(out / "kmeans_model.npz", gain=gain, **model)
assert x.shape[1] == 4 * a.k
x.tofile(out / f"{a.name}_validation.bin")
(out / f"{a.name}_validation.target").write_text("\n".join(map(str, y)) + "\n")
np.savez(out / f"{a.name}_model.npz", gain=gain, **model)
info = {"k": a.k, "alpha": a.alpha, "train": len(ft), "validation": len(fv), "contrast_divide": False,
"mean_value": float(x.mean()), "spikes_per_image": float(x.mean() / 255 * 10 * 2048)}
(out / "manifest.json").write_text(json.dumps(info, indent=2) + "\n")
"mean_value": float(x.mean()), "spikes_per_image": float(x.mean() / 255 * 10 * x.shape[1])}
(out / f"{a.name}_manifest.json").write_text(json.dumps(info, indent=2) + "\n")
print(info)

View file

@ -63,6 +63,18 @@
- **Нейрон-решатель без подбора порога.** Смещение нейрона сдвинуто так, что «потенциал дошёл до порога» означает «сумма больше нуля». Результат не зависит от порога.
- **Больше шагов на картинку** (20 вместо 10) — +1,3 п.п.: 72,9% вместо 71,6%.
## Обучение по ошибке прямо в KNP (утро 15 октября)
В KNP добавлен режим `KNP_DELTA`. Метка даёт нейрону своего класса положительный дофамин на каждом шаге, а собственный спайк нейрона даёт ему отрицательный. Дофамин получается равным «цель минус ответ», то есть это правило перцептрона на нейронах. Для него понадобилась отдельная копия бэкенда, `readout/build_delta_cpu.py`: в ней синапс меняется на величину дофамина, если по нему только что пришёл спайк, даже когда сам нейрон молчал. Оригинальные библиотеки не менялись.
| Задача (проверочная выборка) | Колонки STDP | **Дофамин по ошибке в KNP** | Выход, обученный вне чипа |
| --- | --- | --- | --- |
| Цифры, признаки штрихов, 30 000 | 83,5% (200 нейронов) | **94,8%** (10 нейронов) | 94,9% |
| Цифры, то же + аугментация, 90 000 | — | 94,6%; сдвиг 2 px **91,3%** | 95,1%; сдвиг 89,9% |
| Кошки/собаки, выученные признаки | 64–65% (на плотных молчат) | **65–69%** | 74% (без спайков) |
На цифрах обучение внутри платформы догнало обучение вне чипа и дало самую устойчивую сеть за всё время. На объектах выигрыш у колонок небольшой. Разрыв с 74%, скорее всего, из-за диапазона весов KNP: от −253 до +92, и около краёв вес почти перестаёт меняться.
## Цифры на странице
На странице https://claude.ai/artifact/Vc1q1WCZuPY2QAHBSGtrKF появилась панель «4 · Сеть штрихов»: 16 карт штрихов, спайки 10 выходных нейронов и ответ. На тесте MNIST эта сеть даёт 94,7%, колонки — 92,2%. Сеть входит в строку «Итог всех сетей», так что её можно сравнивать на своих рисунках и сдвигах. Расчёт в браузере сверен с Python: на 105 картинках ответы совпали.