Добрый день, коллеги. Сегодня хочется трезво посмотреть глазами инженера на так популярные сейчас искусственный интеллект и Deep learning, упорядочить, выстроить факты и выработать выигрышную стратегию – как с этим … взлететь, пролететь и не упасть кому-нибудь на голову? Потому-что, когда дело от лабораторных моделей на python/matplotlib/numpy или lua доходит до высоконагруженного production в клиентском сервисе, когда ошибка в исходных данных сводит на нет все усилия – становится не то, что весело, а даже начинается нумерологический средневековый экстаз и инженеры начинают сутки напролет танцевать, в надежде излечиться от новомодной чумы )
Танцующие инженеры, тщетно надеющиеся исцелиться
Современная разработка
Полезно для начала вспомнить, как в принципе устроена современная разработка программного обеспечения. За основу, как правило, берутся классические, хорошо изученные в «прошлом веке» алгоритмы: поиск, сортировка и т.д и т.п. Любимые всеми СУБД – хороший пример бородатых, тщательно проверенных и изученных классических алгоритмов (да простят нас Кодд и Дейт за такую попсу).
К алгоритмам добавляются согласованные сообществом инженеров (хотя нередко ни с кем не согласованные, продвинутые злой силой) – стандарты: сеть (DNS, TCP/IP), форматы файлов, сервисы операционной системы (POSIX) и т.п.
Ну и, конечно, языки. В последние годы, к сожалению, в этой области началась какая-то унылая ерундистика: добавляют-убирают автоматические геттеры и сеттеры и автоматически вводят-выводят типы (как будто это прямо так важно важно), размазывают идеи функционального программирования из «Алисы в Стране чудес» и Haskel на Scala, пытаются частично скрыть дыры С++ в Rust, создают C для гуманитариев в виде Go, в очередной раз придумывают javascript «с нуля» (ECMAScript 6) и всем сердцем продолжают верить в красоту модели акторов и забивают совершенно разные гвозди с помощью Erlang. Вся эта движуха, не без основания, подогревается идеей будущего многопоточного программирования на многоядерных процессорах и GPU, возможно в кластерах, с помощью акторов, immutable-данных, функционального ЗОЖ и неявных извращений в виде currying и рекурсивного построения бинарного дерева. Но, очевидно, близкого прорыва пока ну совсем не видно – логика столкнулась с физикой и все стало упираться в ограничение человеческого
В общем, чтобы с языками не запутаться и код не закровоточил багами, есть известное средство – писать … «правильно». Но чтобы понять значение «правильно», нужно прочитать много книг, перелопатить сотни тысяч строк кода в режиме «завтра в 10:00 должно работать и радовать клиентов» и выпить немало чашек крепкого кофе и разбить немало клавиатур о головы коллег. Но это правило – работает. Всегда.
Мир глазами инженера
Что касается библиотек, то никто, разумеется, не пишет сейчас все с «нуля». Это глупо и дорого. Но, используя «чужие» библиотеки всегда остается риск, что их писали немного «неправильно», а повлиять на это в данный момент почти никак нельзя, а сверху еще и маслица подливают: «бери готовое, вот они взяли и у них работает». Так все, конечно, и делают и используют Linux (операционка, но, по сути, такая же «библиотека» доступа к «железу»), nginx, apache, mysql, php, стандартные библиотеки коллекций (java, c++ STL). Библиотеки, к сожалению, сильно отличаются друг от друга, как по скорости, так и по степени документированности и числу «неисправленных ошибок» — поэтому успеха достигают команды, обладающие определенным чутьем и отличающие надежные «крысиные шкурки» от хорошо пропиаренных, но мало полезных, непрозрачных и/или неадекватных при чуть нестандартной нагрузке решений.
Таким образом, теоретически и, приложив определенные усилия, практически можно создать адекватное программное обеспечение в сжатые сроки с сильно ограниченными ресурсами, используя математически проверенные алгоритмы и библиотеки достаточной, но не критичной степени испорченности, на доказавшем свою «нормальность» языке/ах программирования. Примеров успеха – не то, чтобы много много, но они есть ;-)
Примеров успехов – не то, чтобы много много, но они есть
Машинное обучение
В этой области, в основном, речь идет об обучаемых алгоритмах. Когда бизнес, скажем, накопил некую «бигдату» и хочет ее монетизировать и вытащить полезный, помогающий клиентам и/или повышающий производительность труда алгоритм. Аналитически в лоб задачу эту бывает крайне сложно или невозможно решить, требуются эксперто-годы, учет множества факторов, вызов Ктулху – и, кажется, что можно поступить проще и «наглее»: протащить глубокую нейронную сеть «мордой» по данным и тыкать ее в них то тех пор, пока ошибка станет ниже определенного уровня (помним, что обычно проверяют два типа ошибок – ошибка обучения и ошибка генерализации на тестовом наборе данных). Ходит поверье, что при наличии миллиона и более примеров – глубокая нейронка способна начать работать на уровне не хуже человека, а если примеров больше – есть надежда научить ее превосходить человека. А если примеров меньше – нейронка тоже может приносить свою посильную пользу, помогая, но не заменяя человека.
Полезная нейронка внутри R2D2
Звучит красиво и практично – вот есть данные, «большие данные»: фас, нейронка, учись и помогай человечеству. Но, как известно, дьявол скрывается в мелочах.
Порог входа в разработку и машинное обучение/Deep learning
Ни для кого не секрет, что технологии разработки делятся на категории по уровню вхождения. В самых простых и доступных технологиях людей всегда значительно больше, часто с совсем непрофильным образованием и в такой обстановке нередко создается много неправильных, недолго живущих и воюющих друг с другом библиотек – в эту нишу хорошо ложиться JavaScript и Node.js. Понятно, что если копать вглубь, то появляется множество неочевидных деталей, появляются настоящие Гуру с большой буквы – но войти в эту область в шортах и сачком для бабочек за выходные вполне реально.
Юные фронт-энд разработчики на JavaScript. Но чтобы стать мегагуру, придется долго учиться, лета точно не хватит.
В «среднюю» по уровню вхождения категорию можно отнести динамические языки программирования типа: PHP, Python, Ruby, Lua. Тут все уже намного сложнее – более развитые концепции ООП, частично реализованные возможности функционального программирования, иногда доступна примитивная многопоточность, более выражена модульность и средства создания больших программ, доступны системные функции, частично реализованы стандартные типы данных и алгоритмы. За недельку, без напряжения, разобраться и начать создавать полезный код – вполне реально, даже без профильного образования.
Динамические слаботипизированные скриптовые языки имеют небольшой порог входа. За лето можно научиться и костер разжигать, и рыбку ловить
В «высшую» по уровню вхождения категорию обычно относят хорошо зарекомендовавшие себя промышленные языки типа C++, Java, C# и уже вроде бы начавшие наступать им на пятки Scala, bash и VisualBasic (последние два — шутка). Тут уже мы встретим очень развитые промышленные инструменты управления сложностью, качественные библиотеки типовых структур данных и алгоритмов, мощные возможности по созданию доменных диалектов, огромное количество качественной документации, дополнительные развитые библиотеки для отладки, профилирования и великолепные визуальные среды разработки. Войти и работать в этой категории проще всего, имея профильное образование или большую любовь к программированию и несколько лет месяцев интенсивного опыта и хорошее знание алгоритмов и структур данных – т.к. работа ведется нередко на довольно низком уровне и знания тонкостей операционной системы, сетевых протоколов – часто тут важны.
Промышленные языки программирования требуют изнурительной подготовки и нередко не прощают ошибок
Таким образом, в принципе, начинающего разработчика от полезного инженера отделяет несколько месяцев тяжелого изнурительного труда или годы эмоционального тусения в программных проектах и вытирания доски после мозговых штурмов.
А вот с машинным обучением немного… иначе. Аналитиками часто просто рождаются. Процесс обучения напоминает изучение игры на музыкальном инструменте – 2-3 года сольфеджио, 2-3 года гаммы гонять до посинения, 3 года в оркестре, 5 лет резать трупы в морге и тонны пота. Научить человека азам математики и статистики, математическому анализу, линейной алгебре, дифференциальному исчислению, теории вероятностей – за месяцы просто невозможно, нужны годы и … и не все потянут и пройдут на следующий курс. Многие сойдут с дистанции на другие кафедры. Быть ученым – не для всех, как бы не хотелось.
Стажировка аналитиков
А может, пронесет? Все в это верят по началу: за выходные разберусь! Но к сожалению, для того, чтобы понять, как работает самая элементарная в машинном обучении «логистическая регрессия», являющаяся своего рода «hello world» в программировании, требуется хорошо понимать как минимум пару разделов высшей математики: теорию вероятности и линейную алгебру. А для понимания логики «стохастического градиентного спуска» нужно также знать хотя бы в базовом виде дифференциальное исчисление.
Сырые полу-лабораторные фреймворки
Драйва добавляет высокая влажность. Имеющиеся на рынке популярные фреймворки для Deep learning – сырые до такой степени, что утром на клавиатуре появляется самая настоящая плесень.
Популярные фреймворки для машинного обучения. Они не пропали, они просто еще очень сырые
Понятно почему. Парад «универсальных» фреймворков начался лишь в прошлом, 2015 году. Deep learning уверенно пошел вверх в третий раз только в 2006 году, после многих десятилетий неуверенности и застоя. GPU совсем недавно внезапно оказались в нужное время в нужном месте.
К сожалению, TensorFlow совсем еще медленный и странноватый в production, Torch7 страдает отсутствием нормальной документации и языком Lua, deeplearning4j пытается понравится GPU, а кандидаты типа Theano на python непонятно как эффективно эксплуатировать в production без тяжелых наркотиков. Да, ходят легенды, что обучение нейронной сети это одно, а ее эксплуатация это сооовсем другое и этим должны заниматься совсем другие люди и технологии – но реальность считает деньги и это, согласитесь, страшно неудобно, дорого и не очень разумно. Наиболее универсальным и нацеленным на решение конкретных бизнес задач в сжатые сроки на «нормальном» промышленном языке является похоже пока только deeplearning4j – но от тоже находится в фазе активного роста и созревания со всеми вытекающими.
Как выбрать архитектуру нейронной сети для решения бизнес-задачи?
Читать научные публикации на птичьем диалекте с откровенным матаном без мата могут единицы, поэтому для большинства инженеров скорее всего наиболее прикладной и полезный способ изучения возможностей архитектуры – это копание в исходниках фреймворков, в большинстве случаем на “проклятом” студенческом python и изучение многочисленных примерчиков, которых в разных фреймворках становится все больше и больше и это не может не радовать.
Поэтому общий рецепт – выберите наиболее походящую решаемой задаче архитектуру в виде примера в коде, реализуйте ее 1 в 1 в удобном для эксплуатации фреймворке, закажите молебен и может вам повезет! Что значит «может повезет»? А все очень просто. Вы столкнетесь со следующим спектром инженерных рисков:
Аналитик, ведущий разработчик и руководитель проекта готовятся к молебну «О сведении нейронки». Говорят, скоро докажут теорему о влиянии молебнов на поведение градиентного спуска в условиях многочисленных плато, седловин и локальных минимумов
1) Архитектура нейронки хорошо работает с данными исследователя, но с вашими данными может работать совсем «иначе» или работать совсем наоборот.
2) В вашем фреймворке может не оказаться всего спектра элементарных кубиков: автоматического дифференцирования, алгоритма обновления с тонкой подстройкой (updater), расширенных средств регуляризации (dropout и других), нужной функции ошибки (loss), определенной операции над данными (векторное произведение) и т.п. Вы можете заменить их аналогами, но это привнесет риски.
3) Иногда, правда редко, хочется потянуть в production Matlab или R ;-) Тут один совет – сразу к врачу.
4) Скорее всего, вам потребуется подстроить нейронную сеть под дополнительные бизнес-требования, которые появились позже и совершенно не укладываются в идеальный мир математики. Например – значительно снизить уровень ложно-позитивных срабатываний, увеличить Recall, понизить время обучения, адаптировать модель к гораздо большему набору данных, добавить и учитывать новую информацию. И тут, как правило, понадобиться очень глубоко вникать в архитектуру нейронки и крутить скрытые винтики – а крутить на удачу, это путь к срыву сроков релиза и ночным кошмарам. И без профессора можно просидеть с отверткой с растерянным выражением лица и месяц и два и полгода.
Что же делать, что крутить? Разработчик на C++ пытается понять отличие softmax от softsign
Здравствуйте тензоры!
Для инженера тензор – это просто многомерный массив, позволяющий совершать над собой различные операции и жертвоприношения. Но к работе с тензорами – нужно привыкать ;-) Первые недели даже от трехмерных тензоров голова побаливает, не говоря уже о гораздо более «глубоких» тензорах для рекуррентных и сверточных сетей. Можно убить кучу времени на эти низкоуровневые манипуляции, отладку циферок в тензорах и поиск ошибок в одном значении из 40 000. Обязательно учитывайте этот риск – тензоры только кажутся простыми.
Будьте осторожны! Попытки представить структуру 4 и более мерных тензоров приводит к агрессивному косоглазию
Особенности работы с GPU
Может быть в начале неочевидно, но обычно быстрее обучать нейронку и получать ее ответы тогда, когда все необходимые данные (тензоры) загружены в память GPU. А память этих драгоценных и так обожаемых геймерами устройств – ограничена и обычно гораздо меньше памяти сервера. Приходится городить костыли – вводить промежуточное кэширование тензоров в ОЗУ, частичную генерацию тензоров во время прохождения по набору данных (ибо все могут и не поместиться) и так далее. Поэтому – учитываем и этот немаловажный инженерный риск, влияющий на трудоемкость. Сроки можно смело умножать на 3.
Видеокарта. На ней, оказывается, можно не только играть!
Нейронная сеть в production
Допустим, вам сильно «повезло», вы хорошенько потрудились и довели лабораторный прототип до production качества, подняли веб-сервер, загружаете обученную нейронку в память сервера или сразу в память GPU и адекватно быстро отдаете ответ. Но … данные меняются и за ними нужно менять/дообучать модель. Вам необходимо постоянно следить за качеством работы нейронки, измерять ее точность и ряд других параметров, зависящих от конкретной бизнес-задачи и тщательно продумать процедуру ее обновления и до/переучивания. Поверьте, мороки тут значительно больше, чем с классической СУБД, которую нужно лишь раз в 5 лет оптимизировать и раз в 10 лет убирать паутину с материнской платы :-)
А еще ходят легенды, что нейронную сеть можно просто … дообучить и не нужно будет ее заново переучивать на всем объеме данных. На самом деле — нельзя, но всем очень очень нужно и иногда… везет. Если данных достаточно мало и нужно постараться запомнить их как можно больше (не снижая ошибку на тестовом наборе данных, разумеется) – то просто так «дообучить» не «переучивая» без риска что-то важное забыть уже не получится. Нет никакой гарантии, что стохастический градиентный спуск (SGD) запомнив новое, не забудет важное старое ;-) А если данных много (миллионы фотографий например) и требований запомнить именно этот конкретный пример нет — сработает (но молебен не помешает).
Разработчик, тестировщик и суицид
Не все осознают, что если при классическом программировании ошибки могли встретиться только либо в вашем коде, либо в сторонней библиотеке, либо по причине гормонального всплеска – то при обучении и эксплуатации нейронки все усложняется на порядки и вот почему:
1) У вас все стало плохо и неточно, потому что просто взяла и поменялась к черту информация, скрытая в исходном наборе данных
2) У вас ошибка в архитектуре нейронной сети и она проявилась только что из-за изменения исходных данных. Будьте добры, надевайте каску и изучайте градиенты и веса каждого слоя: нет ли затухания градиента, нет ли «взрыва» градиента, насколько равномерно распределяются веса и не нужно ли подкрутить регуляризацию, нет ли проблем в функции ошибки на выходе (loss), нет ли затухания потока информации/градиента в пограничных режимах сигмоидов и других специфических функциях активации и т.д. и т.п. – головная боль обеспечена надолго и всерьез и каска только для красоты.
3) Вам повезло и вы нашли ошибку в фреймворке нейронной сети … за неделю до релиза.
Все вышеперечисленное учит нас тому, что программировать клиентские сервисы, использующие глубокое машинное обучение нужно не просто супер-железобетонно, а термоядерно, покрыв весь существующий и еще даже не существующих код сеткой asserts, тестов, комментариев и хорошенько залить выдержанной на перфекционизме настройкой из паранойи.
Настойка из паранойи, выдержка – 5 лет
Выводы
Мы открыто и честно обозначили ключевые факты и риски, связанные с внедрением и использованием глубоких нейронных сетей в высоконагруженных сервисах – с точки зрения инженера. Выводы – пока не делаем. Видно, что работы много, работа эта не простая, но страшно интересная и успех приходит только к профессионалам, умеющим объединять знания и людей из разных областей и владеющих вкусом к синергии. Очевидно, что нужно не просто прекрасно программировать и чувствовать систему кончиками пальцев, но и либо понимать, либо активно привлекать к подобным проектам экспертов в области математики и создавать коллегам позитивные, творческие условия – чтобы приходило побольше интересных и эффективных идей и способов их лаконичной и быстрой реализации. А иначе … придется месяцами сидеть с отверткой перед рухнувшим «Гравицапом», крутить наугад винтики, жечь спички и с завистью смотреть на блистающие в небе своей мощью и красотой искусственного интеллекта решения конкурентов. Желаю всем инженерной удачи, сходящийся нейронных сетей, уверенности, энергии и как можно меньше трудноуловимых ошибок! Ку! ;-)
Автор: 1С-Битрикс