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:
parent
917b94035d
commit
6bfdbf69d3
7 changed files with 51 additions and 11 deletions
|
|
@ -42,7 +42,7 @@ Dataset process_dataset(ModelDescription const& model_desc)
|
||||||
// Process images and labels into spike representations.
|
// Process images and labels into spike representations.
|
||||||
dataset.process_labels_and_images(
|
dataset.process_labels_and_images(
|
||||||
images_stream, labels_stream, model_desc.train_images_amount_ + model_desc.inference_images_amount_,
|
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));
|
dataset.make_incrementing_image_to_spikes_converter(active_steps, state_increment_factor));
|
||||||
|
|
||||||
// Split dataset into training and inference portions.
|
// Split dataset into training and inference portions.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#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.
|
* @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.
|
* @brief Total number of simulation steps per input image.
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
|
||||||
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
|
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
|
||||||
|
|
||||||
// Create raster population for channelized image processing (28x28 = 784 neurons).
|
// 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).
|
// Create target population for label processing (10 neurons for 10 digit classes).
|
||||||
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);
|
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ static NetworkPopulations create_populations(NetworkConstructor &constructor)
|
||||||
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
|
constructor.add_population(default_neuron, classes_amount, PopulationRole::NORMAL, false, "GATE");
|
||||||
|
|
||||||
// Create raster population for channelized image processing (28x28 = 784 neurons).
|
// 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).
|
// Create target population for label processing (10 neurons for 10 digit classes).
|
||||||
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);
|
const auto &target_pop = constructor.add_channeled_population(classes_amount, false);
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,9 @@ MNIST, признаки coarse, 30 000 обучение, 10 000 проверка
|
||||||
| 3 | 4 | **69,1%** |
|
| 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%.
|
Колонки 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) и насыщение ресурса.
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,18 @@ def main():
|
||||||
ap.add_argument("--k", type=int, default=512)
|
ap.add_argument("--k", type=int, default=512)
|
||||||
ap.add_argument("--alpha", type=float, default=0.25)
|
ap.add_argument("--alpha", type=float, default=0.25)
|
||||||
ap.add_argument("--seed", type=int, default=20261014)
|
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()
|
a = ap.parse_args()
|
||||||
out = Path(a.out)
|
out = Path(a.out)
|
||||||
out.mkdir(exist_ok=True)
|
out.mkdir(exist_ok=True)
|
||||||
kf.CONTRAST_DIVIDE = False
|
kf.CONTRAST_DIVIDE = False
|
||||||
tr, va = np.load(f"{a.data}/train.npz"), np.load(f"{a.data}/val.npz")
|
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)
|
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)])
|
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"]]
|
yt = np.r_[tr["labels"], tr["labels"]]
|
||||||
|
|
@ -37,13 +44,13 @@ def main():
|
||||||
order = np.random.default_rng(a.seed).permutation(len(ft))
|
order = np.random.default_rng(a.seed).permutation(len(ft))
|
||||||
x = np.vstack([q(ft)[order], q(fv)])
|
x = np.vstack([q(ft)[order], q(fv)])
|
||||||
y = np.r_[yt[order], va["labels"]]
|
y = np.r_[yt[order], va["labels"]]
|
||||||
assert x.shape[1] == 2048
|
assert x.shape[1] == 4 * a.k
|
||||||
x.tofile(out / "kmeans_validation.bin")
|
x.tofile(out / f"{a.name}_validation.bin")
|
||||||
(out / "kmeans_validation.target").write_text("\n".join(map(str, y)) + "\n")
|
(out / f"{a.name}_validation.target").write_text("\n".join(map(str, y)) + "\n")
|
||||||
np.savez(out / "kmeans_model.npz", gain=gain, **model)
|
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,
|
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)}
|
"mean_value": float(x.mean()), "spikes_per_image": float(x.mean() / 255 * 10 * x.shape[1])}
|
||||||
(out / "manifest.json").write_text(json.dumps(info, indent=2) + "\n")
|
(out / f"{a.name}_manifest.json").write_text(json.dumps(info, indent=2) + "\n")
|
||||||
print(info)
|
print(info)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,18 @@
|
||||||
- **Нейрон-решатель без подбора порога.** Смещение нейрона сдвинуто так, что «потенциал дошёл до порога» означает «сумма больше нуля». Результат не зависит от порога.
|
- **Нейрон-решатель без подбора порога.** Смещение нейрона сдвинуто так, что «потенциал дошёл до порога» означает «сумма больше нуля». Результат не зависит от порога.
|
||||||
- **Больше шагов на картинку** (20 вместо 10) — +1,3 п.п.: 72,9% вместо 71,6%.
|
- **Больше шагов на картинку** (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 картинках ответы совпали.
|
На странице https://claude.ai/artifact/Vc1q1WCZuPY2QAHBSGtrKF появилась панель «4 · Сеть штрихов»: 16 карт штрихов, спайки 10 выходных нейронов и ответ. На тесте MNIST эта сеть даёт 94,7%, колонки — 92,2%. Сеть входит в строку «Итог всех сетей», так что её можно сравнивать на своих рисунках и сдвигах. Расчёт в браузере сверен с Python: на 105 картинках ответы совпали.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue