После многолетнего перерыва я наконец решился взять ещё один отпуск, который провёл за программированием. Целую неделю я смог спокойно работать в режиме отшельника вдали от привычного давления работы. Моя жена великодушно предлагала мне взять такой отпуск вот уже несколько лет, но отпуск и я — это в принципе слабо совместимые вещи.
В качестве смены обстановки после моей текущей работы на Oculus, я хотел написать с нуля на C++ несколько реализаций нейронных сетей, и планировал сделать это, используя строго систему OpenBSD. Кто-то из моих знакомых заметил, что это достаточно случайный набор технологий, но в итоге всё сработало хорошо.
Несмотря на то, что мне не приходилось использовать OpenBSD в своей работе, мне всегда нравилась её идея – это относительно минималистичная и самостоятельная система с целостными видением, а также акцентом на качество и мастерство. Linux способен на многое, но целостность – это не про Linux.
Я не являюсь ярым фанатом Unix. Я неплохо справляюсь с этой операционной системой, но комфортнее всего мне работается в Visual Studio под Windows. Я подумал, что неделя погружения в работу в стиле старой школы Unix по полной будет мне интересна — пусть даже если это и будет означать, что я буду работать медленнее. Это было своего рода приключение в духе ретрокомпьютинга — моими лучшими друзьями на время стали fvwm и vi. Заметьте — не vim, а самый настоящий настоящий vi из BSD.
В конце концов, мне не удалось исследовать систему настолько глубоко, насколько мне хотелось — 95% времени я провёл, занимаясь лишь простыми действиями в vi / make / gdb. Я оценил высокое качество страниц man, поскольку я попробовал делать всё в самой системе, не прибегая к поиску в Интернете. Было забавно увидеть ссылки на вещи, отошедший в прошлое свыше 30 лет назад, вроде терминалов Tektronix.
Я был немного удивлен тем, что поддержка C++ оказалась не на высоте. G++ не поддерживал C++11, LLVM C++ плохо работал с gdb. Gdb неоднократно падал – как мне кажется, из-за проблем с C++. Я в курсе того, что можно через порты (ports) обновиться до свежих версий, но я решил использовать только базовую систему.
Оглядываясь назад, я считаю, что мне стоило просто пойти путём «полного ретро» и написать всё на ANSI C. В моей жизни часто бывают дни, когда я, как и многие программисты постарше, размышляю в следующем духе: «Возможно, в конечном итоге C++ не настолько хорош, как это принято думать...». Мне многое в нём нравится, но мне не в тягость писать небольшие проекты на чистом C.
Возможно, в свой следующий отпуск я попытаюсь пользоваться только одним emacs — это ещё один важный пласт программистской культуры, с котором я так и не успел как следует познакомиться.
У меня есть отличное общее понимание того, как работает большинство алгоритмов машинного обучения, мне приходилось писать линейный классификатор и дерево принятия решений, но по какой-то причине я всегда избегал нейронные сети. В глубине души, я подозреваю что модный «хайп» вокруг машинного обучения пробудил во мне осторожность скептика, и у меня по-прежнему сохраняется некоторая рефлексивная предвзятость насчёт подхода «давайте закинем всё в нейронную сеть, а она пусть там разбирается».
Продолжая придерживаться ретро-тематики, я распечатал несколько старых публикаций Яна Лекуна и собирался производить всю свою работу в оффлайне, словно я действительно нахожусь в горной хижине — но кончилось всё тем, что я пересмотрел многие лекции курса Stanford CS231N на YouTube, и они реально оказались полезными. Я крайне редко смотрю видео лекции в силу того, что обычно мне трудно оправдать для себя столько серьезную трату времени — но в отпуске-то можно себе это позволить.
Не думаю, что у меня есть какие-либо стоящие мысли насчёт нейросетей, которыми стоило бы поделиться — но это была крайне продуктивная для меня неделя, которая помогла превратить теоретически «книжные» знания в реальный опыт.
В работе я воспользовался своим традиционным подходом: сначала быстро получить результат написав наспех грубоватый «хакнутый» код, после чего написать с нуля новую реализацию, основываясь на извлечённых уроках — таким образом, обе реализации будут рабочими, и при необходимости я смогу сравнить их между собой (cross check).
Вначале я пару раз неправильно понял метод обратного распространения ошибки (backprop) — переломным моментом стало сравнение с численным дифференцированием! Мне показалось интересным, что тренировка нейросети идёт даже тогда, когда различные её части могут быть не совсем правильными — пока полученный знак остается правильным большую часть времени, дело зачастую продвигается дальше.
Я остался доволен получившимся кодом моей многослойной нейронной сети; он в той форме, которую я могу просто использовать в своих дальнейших экспериментах. Да, для чего-нибудь серьезного я должен буду использовать существующую библиотеку, но в жизни бывает много случаев, когда удобно, что у вас под рукой есть всего пара .cpp и .h файлов, в которых вы самолично написали каждую строку кода. Мой код для свёрточной нейронной сети (conv net) получился успел дойти лишь до фазы «работает, но с кучей хаков» — я мог бы потратить на него еще день или два для того, чтобы написать ясную и гибкую реализацию.
Мне показалось интересным, что когда я тестировал свою первоначальную нейронную сеть на MNIST (базе данных образцов рукописного написания цифр) прежде, чем добавлять любые свертки, я получил гораздо лучшие результаты, чем указанные значения для не-свёрточных нейросетей (non-convolutional NN), указанных в сравнении ЛеКуна 1998 года — примерно 2% ошибок на тестовом множестве с одним слоем из 100 узлов, против 3% для более широких и глубоких сетей того времени. Думаю, здесь дело в современных лучших практиках — ReLU, Softmax и улучшенной инициализации.
Это и есть одно из самых впечатляющих свойств работы нейронных сетей — все они настолько просты, что прорывные достижения зачастую могут быть выражены всего несколькими строчками кода. Судя по всему, здесь есть некоторое сходство с трассировкой лучей из мира компьютерной графики, где вы можете достаточно быстро реализовать физически корректный трассировщик лучей (physically based light transport ray tracer) и создавать современные изображения, если у вас есть необходимые данные и достаточно терпения, чтобы дождаться результатов выполнения.
Я гораздо лучше разобрался с пониманием принципов перетренировки / генерелизации / регуляризации при помощи изучения нескольких параметров тренировки. В последнюю ночь своего отпуска, я уже не трогал архитектуру и просто игрался с гиперпараметрами. Как оказалось, сохранять концентрацию в то время, пока "она тренируется" оказалось значительно тяжелее, чем в классическом случае, когда "оно компилируется".
Теперь я буду смотреть в оба, чтобы найти подходящую возможность применить новые навыки в в своей работе в Oculus!
Мне страшно даже подумать о том, во что успели превратиться мой почтовый ящик и рабочее место за время моего отсутствия — завтра увидим.
Комментариев нет:
Отправить комментарий