Мало кто верит, что современный data science-стек может быть построен не на Python, но такие прецеденты есть :). Стек Одноклассников формировался долгие годы, в первую очередь программистами, перешедшими в data science, но всё ещё остались близкими к проду, поэтому в его основе лежат открытые технологии JVM-стека: Hadoop, Spark, Kafka, Cassandra и т.д. Это помогает нам сокращать время и затраты на ввод моделей в эксплуатацию, но иногда создаёт и сложности. Например, при подготовке базовых решений для участников SNA Hackathon 2019 пришлось сжать волю в кулак и погрузиться в мир динамической типизации. Подробности (и лёгкий троллинг) под катом :)
Установка
Какой-то питон найдётся почти на любой девелоперской машине. Нашёлся он и на моей, аж в двух экземплярах — 2.7 и 3.4. Порывшись в закромах памяти я вспомнил, что версию 3.4 поставил три года назад, после того, как на SNA Hackathon 2016 участники столкнулись с эпичными проблемами, пытаясь разложить в памяти полуторагигабайтный граф (по итогам получилось небольшое обучающее видео и отдельное соревнование), но сегодня это хозяйство уже морально устарело и нуждалось в обновлении.
В мире Явы каждый проект при сборке указывает всё то, что хочет в себя вобрать, и с этим дальше и живёт. В теории всё просто и красиво, на практике же, когда тебе нужна библиотека А и библиотека Б, обязательно выяснится что им обеим нужна библиотека C, при этом двух разных несовместимых версий :). В тщетных попытках разорвать этот порочный круг некоторые библиотеки пакуют все свои зависимости в себя самих и прячут от остальных, а остальные крутятся, как могут.
Что с этим в питоне? Проекта как такового нет, но есть «окружение», и в рамках каждого окружения можно сформировать независимую экосистему из пакетов определённых версий. При этом есть и инструменты для ленивых, с помощью которых локальным питоновским окружением управлять не сложнее, чем распределённым гетерогенным кластером Клаудеры или Хортона. Но взаимные конфликты версий пакетов никуда не денутся.Я сразу же столкнулся с тем, что релиз Pandas 0.24 перевёл приватный метод _maybe_box_datetimelike в публичный API, и неожиданно оказалось, что много кто его в прежнем виде юзал и теперь отвалился :) (и да, в мире Явы все то же самое). Но в итоге удалось всё починить, не считая страшных вариннгов о новых деприкейшинах, заработало.
Коллаборативный baseline
Задачи на SNA Hackathon 2019 разделены на три направления — рекомендации по логам, по текстам и по картинкам. Начнём с логов (спойлер — мегапаттерн Cmd+C/Cmd+V со стэковерфлоу работает и с питоном). Данные были собраны с живого продакшина — каждому пользователю случайным образом, без взвешивания, показывался некоторый фид из его окружения, после чего в лог записывались все признаки на момент показа и итоговая реакция. Задача piece of cake: берём признаки, пихаем в логрег, профит!
Но план зафэйлился на первом же этапе, на чтении данных. В теории, есть замечательный пакет Apache Arrow, который дожен был унифицировать работу с данными в разных экосистемах и, в том числе, позволить читать «паркетные» файлы из питона без спарка. На практике же оказалось. что даже с чтением простых вложенных структур у него проблемы, и наша красивая иерархия превратилась в плоское убожество :(.
Но были и положительные стороны. Jupyter, в целом, порадовал, он почти такой же удобный, хоть и не такой няшный, как Zeppelin. Даже скала-ядро есть! Ну и скорость логистической регрессии на небольшом куске данных в памяти порадовала — до мощности распределённого варианта не дотягивает, но на четырёх признаках и паре сотен тысяч примеров учится мгновенно.
Потом, правда, возникший было энтузиазм получил удар под дых: если необходимая трансформация данных (сгруппировать по ключу и собрать в список) отсутствует в стандартном списке и появляется apply или map, то скорость падает на порядки. В итоге, 80 % времени baselineа занимает не чтение данных, джоины, обучение модели и сортировка, а банальное составление списка.
К слову, именно из-за этой особенности я всегда удивляюсь пользователям pySpark — ведь почти вся стандартная функциональность доступна в виде Spark SQL, который одинаков и в питоне, и в скале, а после появления первой питонячей юдф-ины с чем-то личным десятитысячеядерный кластер превращается в тыкву…
Но, в итоге, все препятствия преодолены и для score 0.65 хватило девяти параграфов!
Текстовый baseline
Ну вот, теперь у нас задача посложнее — если логрег можно найти в сотне реализаций под все платформы, то уж разнообразие инструментов работы с текстами под питоном больше. К счастью, тексты уже идут не только в сыром виде, но и обработанные нашей штатной системой препроцессинга на базе спарка и Lucene. Поэтому можно сразу брать список токенов и не париться токенизацией/лематизацией/стемингом.
Некоторое время я сомневался, что же взять: отечественный BigARTM или импортный Gensim. В итоге остановился на втором и скопипастил их doc2vec туториал :). Надеюсь, коллеги из команды BigARTM не преминут воспользоваться шансом и покажут преимущества своей библиотеки на конкурсе.
У нас опять простой план: берём все тексты из теста, обучаем модель Doc2Vec, далее инферим её на трэйне и на её результатах учим логрег (стэкинг — наше всё!). Но, как обычно, сразу начались проблемы. Несмотря на относительно скромный объём текстов в обучающей выборке (всего полтора гига), при попытке втянуть их в пандас питон отожрал 20 (20, Карл!) гигабайтов памяти, ушел в своп и не вернулся. Пришлось есть слона по частям.
При чтении всегда указываем, какие именно колонки поднять из паркета, что позволяет не читать сырой текст в память. Это экономит её использование вдвое, документы тестового множества грузятся в память для обучения без проблем. С обучающей выборкой такого трюка мало, поэтому за раз прогружаем не более одного «паркетного» файла. Далее в загруженном файле оставляем только ID из тех дней, которые хотим использовать для обучения, и уже на них инферим эмбединги.
Логрег поверх этого работает так же быстро, и в итоге получаем 14 параграфов и score 0.54 :)
Картиночный baseline
Что может быть популярнее deep learning? Только котики! Поэтому для картиночного baseline-а мы составили гениально простой план: прогоняем на картинках из тестового множества детектор котиков, а потом ранжируем контент по полученному score :)
И опять есть из чего выбирать. Классификация или детекция? pyTorch или Tensorflow? Главный критерий — простота реализации методом копипаста. And the winner is… YOLOv3 на MXNet :). Лаконичность их демки пленила с первого взгляда, но потом, как обычно, начались проблемы.
С чего обычно начинается работа с большими данными? С оценки производительности и необходимого машинного времени! Хотелось сделать baseline максимально автономным, поэтому научили его работать напрямую с tar-файлом и быстро поняли, что при скорости извлечения из tar в tmpfs 200 фото в секунду на обработку 352 758 картинок понадобится порядка получаса, норм. Добавляем загрузку и предобработку фоток — 100 в секунду, примерно час на всё, норм. Добавляем вычисление нейросетки — 20 фоток в секунду, 5 часов, нуууу ок… Добавляем извлечение результата — 1 фотка в секунду, неделя, WTF?
После пары часов танцев с бубнами приходит понимание, что NDArray, который мы наблюдаем, это ни разу ни numpy, а внутренняя структура MXNet-а, который проводит все вычисления лениво. Бинго! Что же делать? Любой начинающий диплернер знает, что всё дело в размере батча! Если MXNet вычисляет score лениво, то если мы сначала запросим их для пары десятков фото, а потом начнём их извлекать, то, быть может, обработка фоток пойдёт батчем? И да, после добавления батчинга со скоростью 10 фото в секунду удалось найти всех котиков :).
Дальше применяем уже известную машинерию и за 10 параграфов получаем score 0.504 :).
Выводы
Когда одного мудрого сэнсея спросили «Кто победит, мастер по айкидо или по карате?», он ответил «Победит мастер». К примерно таким же выводам нас привёл и этот эксперимент: нет и не может быть идеального языка на все случаи жизни. С Python вы можете быстро собрать решение из готовых блоков, но попытка отойти от них при достаточно больших объёмах данных принесёт много боли. В Java и Scala вы тоже можете найти много готовых инструментов и легко реализуете собственные идеи, но сами языки будут более требовательны к качеству кода.
Ну и, конечно, удачи всем участникам SNA Hackathon 2019!
Автор: dmitrybugaychenko