examples/ is based on KasperskyLab/knp at 55470ed6 with local changes to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.1 KiB
9.1 KiB
Отчёт: почему 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).
Как проверял
- Добавил в пример диагностическую выгрузку весов и порогов входного слоя после обучения.
- Написал на numpy симулятор входного слоя по формулам бэкенда (затухание 2/3, задержка 1, порог, сброс). Он воспроизводит результаты KNP с точностью до нескольких картинок: например, 4954 / 476 / 4570 против 4953 / 477 / 4570 в KNP. На нём перебирал пороги за секунды.
- Лучшие варианты подтвердил полными прогонами в 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: есть ли у неё та же проблема