Хочу поделиться опытом переписывания нейронной сети с Python на Java

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



Я Антон, Java-разработчик в Сбере, работаю над продуктом GigaIDE. В этой статье я хочу поделиться опытом переписывания нейронной сети с Python на Java. Изначально это была классическая сеть для распознавания рукописных цифр из датасета MNIST. Мне было интересно не просто скопировать код, а по-настоящему разобраться, как всё устроено, поэкспериментировать и даже заглянуть в её «мозги».

Сначала немного теории: нейросеть — это математическая модель, которая преобразует входной сигнал в выходной. В нашем случае сеть состоит из трёх слоёв: входного (784 нейрона), скрытого (200 нейронов) и выходного (10 нейронов). Каждый нейрон одного слоя связан с каждым нейроном следующего, и у каждой связи есть вес. Сигнал, проходя через связи, умножается на вес, суммируется и пропускается через функцию активации. Входной слой получает значения пикселей картинки 28?28, нормализованные к диапазону 0–1. Выходной слой показывает «уверенность» сети в том, что на картинке та или иная цифра.

Датасет MNIST — это классика: 60 000 картинок для обучения и 10 000 для тестирования. Каждая картинка представлена в CSV как строка: сначала эталонная цифра, затем 784 значения яркости от 0 до 255. Я написал небольшое приложение, чтобы посмотреть на эти цифры вживую. Было интересно, как можно 6000 раз по-разному написать один и тот же ноль или единицу. Оказалось, что многие цифры написаны в американской манере: единица — это просто наклонная чёрточка, девятка часто без завитка внизу. Это важное наблюдение, ведь сеть обучается именно на таких образах.

Самой интересной частью было переписать нейросеть на Java. В Python она занимает несколько строк благодаря библиотеке numpy, которая быстро перемножает матрицы. Я не хотел сразу подключать тяжёлые зависимости, поэтому написал собственный прикладной класс для матричных вычислений — в лоб, по определению. Это немного монструозно, но зато позволяет понять, что реально происходит под капотом. В конструкторе сети задаётся количество узлов в слоях, коэффициент обучения, а веса инициализируются специальным способом: из нормального распределения с центром в нуле и стандартным отклонением, обратно пропорциональным корню из количества узлов. Для детерминированности эксперимента я добавил также стратегии с нулевыми и единичными весами — заведомо провальные, но полезные для сверки результатов с Python-версией.

Обучение методом обратного распространения — это самая сложная часть. Я переписал код строчка в строчку, чтобы минимизировать ошибки. На Java формула корректировки весов выглядит громоздко, но работает. Обучение проходило на 60 000 картинках в течение 5 эпох, каждая эпоха занимала около минуты. Это небыстро, но терпимо. После проверки на тестовом множестве точность составила 97,5%, то есть ошибка всего 2,5%. Отличный результат для такой простой сети!

Затем я решил пойти дальше: сохранять обученную сеть в файл и загружать её, а также нарисовать свои цифры мышкой и посмотреть, как сеть их распознает. Я написал приложение-визуализатор, где можно открыть сеть, посмотреть её структуру, порисовать на холсте и увидеть вероятности для каждой цифры. И вот тут началось самое интересное. Свои рисунки сеть распознавала очень плохо — точность была примерно 50–60%, никаких 97,5%. Я пробовал менять размер кисти, добавлял размытие, чтобы линии были похожи на рукописные — не помогало. Обиднее всего, когда рисуешь почти одинаковые цифры, а сеть выдаёт разные ответы: то 0,95 для одной, то для другой. Кружок в середине картинки — очевидный ноль для человека, а сеть может принять его за что-то другое.

Чтобы понять, что происходит у неё в голове, я реализовал обратный запрос: подаю на выход желаемую цифру, а с входа снимаю «образ», который сеть считает характерным для этой цифры. Ожидал увидеть размытые, но узнаваемые силуэты. На деле получилось, что только ноль и семёрка хоть как-то похожи на цифры, остальное — мазня. Сравнил с картинкой из книги Тарика Рашида, там тоже не идеально, но ноль чёткий. Впрочем, глядя на эти образы, я стал лучше понимать, как рисовать, чтобы сеть угадывала: семёрки, девятки и единицы стали получаться точнее, и точность распознавания немного возросла.

В итоге я сделал несколько выводов. Во-первых, нейросеть, обученная на стандартном датасете, не обязана хорошо работать на ваших «каракулях», если они отличаются от того, что было в обучающей выборке. Во-вторых, рукописные цифры мы пишем иначе, чем американские школьники из MNIST. В-третьих, чтобы получить хороший результат, надо либо больше и разнообразнее обучать сеть, либо генерировать синтетические данные с похожим искажением. Эксперимент получился очень познавательным: я не только переписал код, но и на практике увидел сильные и слабые стороны простой нейросети.

А вы сталкивались с ситуацией, когда модель отлично работает на тестовой выборке, но плохо справляется с реальными данными? Что бы вы посоветовали для улучшения распознавания таких «живых» примеров?


Телеграм: t.me/ainewsline

Источник: vk.com

Комментарии: