Небольшой OCR-проект на C++ для распознавания рукописных цифр.
Идея проекта была не только в том, чтобы получить рабочий классификатор на MNIST, но и в том, чтобы пройти несколько уровней реализации: от простого baseline'а до своей нейросети и более быстрой, уже более практичной версии MLP.
Сейчас проект состоит из нескольких baseline'ов, каждый из которых решает свою задачу:
- KNN — как простой и сильный baseline;
- scalar neural network + autodiff — как educational-реализация, чтобы понять backprop изнутри;
- fast MLP — как более практичная и быстрая нейросеть.
- загрузка MNIST;
- чтение BMP-изображений;
- preprocessing для реальных картинок;
- handcrafted features для классической модели;
- несколько baseline'ов;
- логирование и сравнение результатов экспериментов.
Первый и самый простой baseline.
Для KNN используются признаки, собранные из:
- raw pixels;
- zoning features;
- projection features.
Зачем это полезно:
- чистая точка отсчёта;
- быстрый способ проверить pipeline;
- baseline, с которым можно сравнивать нейросети.
По текущим экспериментам KNN показывает очень хорошую accuracy и остаётся сильным ориентиром для остальных моделей.
Это более учебная часть проекта.
Здесь цель была не в скорости, а в том, чтобы руками пройти весь путь:
- вычислительный граф;
- forward pass;
- backward pass;
- градиенты;
- обновление параметров.
Эта реализация не самая быстрая и не самая практичная, но хорошо показывает, что происходят внутри NN и backpropagation.
Её главный минус — производительность. На реальном обучении быстро становится видно, что scalar-граф и большое количество мелких операций создают слишком большой overhead по времени и памяти.
Более практичный neural baseline.
Здесь используется более прямой и быстрый подход:
- плотные массивы (2D to 1D);
- mini-batch training;
- ручной forward/backward для dense-слоёв;
- без scalar tape на каждую операцию.
На последних прогонах KNN показывал около 96.8% accuracy на evaluation по 500 test samples.
Для baseline без нейросети это очень сильный результат.
Первые рабочие запуски fast MLP выглядели так:
trainLimit=2000,testLimit=500,lr=0.05,batch=64,epochs=5
→ 71.8%
После этого начался более полноценный подбор гиперпараметров.
Лучшие результаты из текущих экспериментов:
| trainLimit | testLimit | lr | batch | epochs | accuracy |
|---|---|---|---|---|---|
| 5000 | 500 | 0.02 | 64 | 10 | 86.6% |
| 5000 | 500 | 0.01 | 64 | 10 | 86.8% |
| 5000 | 500 | 0.03 | 64 | 10 | 93.0% |
| 5000 | 500 | 0.02 | 128 | 20 | 91.2% |
| 5000 | 500 | 0.04 | 128 | 10 | 91.6% |
| 5000 | 500 | 0.02 | 32 | 10 | 92.4% |
Пока это ещё не финальные результаты на полном test set, а промежуточные результаты для подбора hyperparameters.
- fast MLP действительно работает и обучается корректно;
- batch size сильно влияет на поведение модели;
- слишком большой batch при фиксированном числе эпох делает меньше gradient descent update steps и может обучаться хуже;
- диапазон
batch = 32..64иlr ≈ 0.02..0.03пока выглядит наиболее promising.
Сейчас лучший найденный setup для fast MLP — lr=0.03, batch=64, epochs=10.
app/— CLI и общий orchestration-код;core/— базовые структуры, напримерImageMatrix;data/— загрузка MNIST;io/— чтение и запись BMP;preprocess/— preprocessing и выделение цифр;baselines/knn/— KNN и handcrafted features;baselines/neural_network/— scalar/autodiff NN;baselines/nn_mlp_fast/— fast MLP;test/— тесты и проверки.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/ocr_engine
or
make clean-run