our_knp/reports/2026-10-12/Отчёт_MNIST_точность.md
2026-10-15 10:48:29 +03:00

9.1 KiB
Raw Permalink Blame History

Отчёт: почему KNP на MNIST отвечает только на 31% картинок

Oct 12, 2026

Сеть училась нормально, но на проверке у неё слишком высокий порог срабатывания. Если снизить порог только на этапе проверки, полный MNIST даёт 75,5% верных ответов вместо 30,9%; обучение при этом не меняется.

Что было

В прошлом отчёте модель blifat после обучения на 60 000 картинках дала 3086 верных ответов из 10 000 (30,9%). Ошибалась сеть редко (77 раз), а на 6837 картинок не отвечала вовсе. Причину тогда не выясняли.

Почему сеть молчала

  • Подсчёт метрик корректный. По исходникам (processor.cpp:77-82): «верно» — сеть назвала правильную цифру, «ложное срабатывание» — назвала другую, «молчание» — на выходе не было ни одного спайка. Я разобрал лог спайков по всем 10 000 картинкам: сдвига ответов на соседнюю картинку нет, сеть действительно молчит на 68% картинок.
  • Порог застывает после обучения. Входной нейрон срабатывает, когда сумма весов активных пикселей не меньше 8.571 + additional_threshold. Добавка равна 0.218 × сумма всех положительных весов нейрона (blifat_stdp.h). Обучение пересчитывает её при каждом спайке, а на проверке она застывает в последнем значении и переносится в обычный BLIFAT-нейрон.
  • Чем дольше обучение, тем выше порог. Сумма положительных весов на нейрон растёт с 12,8 (3 000 картинок) до 18 (60 000), вместе с ней растёт порог, и молчаний становится больше.
  • Информации в весах хватает. Если всегда брать колонку, ближе всех подошедшую к порогу, точность около 78% при любой длине обучения. Сеть «знает» ответ, но не дотягивает до порога.
  • Прошлое сравнение «3 000 лучше 60 000» было некорректным. Пример берёт проверочные картинки сразу после обучающих, поэтому малый прогон проверялся на картинках 3000–3999 из обучающей части MNIST, а полный — на настоящем тестовом наборе. Для этого отчёта я сделал файлы, где после N обучающих картинок всегда идут одни и те же 10 000 тестовых.

Результаты

Все прогоны на одних и тех же 10 000 тестовых картинках, однопоточный CPU-бэкенд.

Обучение Как было: верно / ошибки / молчание Порог на проверке Стало: верно / ошибки / молчание
3 000 49,5% / 477 / 4570 act=6, без добавки 76,2% / 1884 / 498
10 000 44,8% / 326 / 5198 не проверялось —
30 000 42,0% / 260 / 5543 act=6, без добавки 73,3% / 1091 / 1576
60 000 30,9% / 77 / 6837 act=6, без добавки 66,3% / 758 / 2614
60 000 act=2, без добавки 75,5% / 1369 / 1085
  • Цена улучшения: ошибок становится больше (с 77 до 1369 на 60 000), потому что сеть теперь чаще отвечает и иногда ошибается.
  • Лучший порог зависит от длины обучения. Для 60 000 я подобрал его на тех же тестовых картинках, поэтому 75,5% — немного оптимистичная оценка.
  • Потолок для этих весов — около 77–78% (оценка симулятором, правило «всегда отвечать»).

Полный MNIST с порогом act=2: результаты по цифрам

Цифра Верно Не отвечено Ложные срабатывания Precision Recall
0 807 112 61 0,930 0,878
1 1032 4 99 0,912 0,996
2 706 207 119 0,856 0,773
3 722 124 164 0,815 0,853
4 740 40 202 0,786 0,949
5 600 165 127 0,825 0,784
6 758 110 90 0,894 0,873
7 719 90 219 0,767 0,889
8 693 179 102 0,872 0,795
9 769 54 186 0,805 0,934
Итого 7546 (75,5%) 1085 1369

Что ещё всплыло

  • Колонка из 20 нейронов работает как один нейрон. У всех 20 нейронов колонки идентичные веса (разброс 0,0), и на проверке они срабатывают одновременно. Во время обучения сигнал метки возбуждает всю колонку целиком.
  • Параметр stochastic_stimulation не используется. Он задан в примере (2,212), но CPU-бэкенд его не читает.
  • Один прогон на 60 000 из пяти сломался. Все веса схлопнулись к нулю, сеть молчала на всех картинках. Остальные четыре прогона совпали до картинки (3086 / 77 / 6837, как 7 октября). Причину не нашёл. Предположение (не проверено): случайный выбор победителя в WTA при ничьей, seed берётся из std::random_device (wta.cpp:38).

Как проверял

  1. Добавил в пример диагностическую выгрузку весов и порогов входного слоя после обучения.
  2. Написал на numpy симулятор входного слоя по формулам бэкенда (затухание 2/3, задержка 1, порог, сброс). Он воспроизводит результаты KNP с точностью до нескольких картинок: например, 4954 / 476 / 4570 против 4953 / 477 / 4570 в KNP. На нём перебирал пороги за секунды.
  3. Лучшие варианты подтвердил полными прогонами в KNP; все цифры в таблице результатов — из KNP.

Что изменено

В examples/mnist-learn/models/blifat/prepare_network_for_inference.cpp добавлены переменные окружения. Без них пример работает как раньше; изменение видно в git diff, его можно откатить.

Переменная Что делает
KNP_MNIST_THR заменяет порог activation_threshold входных нейронов на проверке
KNP_MNIST_ADD_THR_SCALE умножает additional_threshold входных нейронов на проверке (0 — убрать)
KNP_MNIST_DUMP=<папка> сохраняет neurons.csv (пороги) и weights.csv (веса) после обучения

Как запускать

cd ~/run/exp
# лучший вариант для полного MNIST
./run.sh myrun 60000 KNP_MNIST_THR=2 KNP_MNIST_ADD_THR_SCALE=0
cat myrun/result.csv
Путь Что внутри
~/run/exp/tr<N>.bin, tr<N>.target N обучающих картинок + одни и те же 10 000 тестовых
~/run/exp/run.sh запуск: run.sh <имя> <N> [ПЕРЕМЕННАЯ=значение]
~/run/exp/<имя>/ результат прогона и выгрузка весов
~/anvenv Python с numpy для анализа

Следующие шаги

  • Выбирать порог на отдельной части данных, а не на тесте, чтобы оценка была честной
  • Исправить само обучение: выключить в нём добавку к порогу или не синхронизировать нейроны колонки
  • Разобраться, почему один прогон на 60 000 схлопнулся
  • Проверить модель altai: есть ли у неё та же проблема