# Отчёт: почему 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` (веса) после обучения | ## Как запускать ```bash 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.bin`, `tr.target` | N обучающих картинок + одни и те же 10 000 тестовых | | `~/run/exp/run.sh` | запуск: `run.sh <имя> [ПЕРЕМЕННАЯ=значение]` | | `~/run/exp/<имя>/` | результат прогона и выгрузка весов | | `~/anvenv` | Python с numpy для анализа | ## Следующие шаги - [ ] Выбирать порог на отдельной части данных, а не на тесте, чтобы оценка была честной - [ ] Исправить само обучение: выключить в нём добавку к порогу или не синхронизировать нейроны колонки - [ ] Разобраться, почему один прогон на 60 000 схлопнулся - [ ] Проверить модель `altai`: есть ли у неё та же проблема