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