Реактивный

Машинное обучение

Может ли приложение-убийца больших данных действительно быть реактивным?

Этот пост был моим первым наброском набора идей по реактивному машинному обучению. С тех пор эти идеи претерпели значительные изменения и теперь лучше всего представлены в книге Реактивные системы машинного обучения. Самую свежую информацию о реактивном машинном обучении, такую ​​как выступления и другие материалы, можно найти на сайте reactivemachinelearning.com.

Реактивные системы - одно из самых захватывающих достижений в разработке программного обеспечения за последние несколько лет. Для краткого введения в тему я бы указал вам на Манифест реакции. Это краткое и откровенное изложение многих ключевых стратегий выполнения требований современных приложений с использованием сложных принципов проектирования распределенных систем.

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

Все это захватывающий и увлекательный материал, но я вижу одну большую ошибку: машинное обучение. Хотя я работаю в веб-стартапе, я лично не создаю веб-приложения. Я использую современные инструменты для работы с большими данными для создания массивных конвейеров машинного обучения. Итак, мой очевидный вопрос после того, как я углубился в эту тему, был:

Как принципы реактивных систем соотносятся с приложениями машинного обучения?

Не найдя ответа в Google, я решил попробовать ответить на него сам. Далее следует довольно грубый первоначальный анализ, который я, вероятно, обновлю со временем, когда буду больше интересоваться вопросом. Но в духе того, чтобы начать до того, как вы будете готовы, вот моя попытка ответить на свой собственный вопрос.

Приложения для машинного обучения

Во-первых, что я имею в виду, когда говорю «приложение машинного обучения» (приложение машинного обучения)? Собственно, я имею в виду несколько вещей.

Модель обучения конвейера

Конвейер обучения модели - это обычно то, о чем люди думают, когда думают о приложении машинного обучения. Обычно, но не обязательно, это пакетное задание, которое обрабатывает набор данных, состоящий из обучающих и, возможно, проверочных и / или тестовых наборов экземпляров. Конечный результат - это своего рода модель.

Модель сервера

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

Экстракторы функций

Это часть приложения, которая берет необработанные данные из некоторого источника и превращает их в производные семантически значимые данные, известные как функции. Проницательный читатель заметит, что нам нужны экстракторы признаков в обеих вышеупомянутых подсистемах: один раз, когда мы изучаем модель, и второй раз, когда мы оцениваем модель. В идеале эти два использования будут просто двумя вызовами одной и той же реализации экстрактора функций, но этого не требуется. Реализация экстракторов функций может быть сложной задачей, даже если она выглядит простой. Чтобы глубже погрузиться в трудности, связанные с этим, прочтите мой пост о сбоях в машинном обучении. И последнее, что следует отметить в отношении экстракторов функций, заключается в том, что они имеют аналогичную проблему с сервером модели: они могут быть связаны с процессом фактического сбора необработанных данных из внешнего мира.

Имена тяжелые

Приведенная выше номенклатура, вероятно, не соответствует терминам, которые вы слышали в школе или используете в своем приложении машинного обучения. Я обнаружил, что их использование в академических и промышленных кругах довольно неоднородно. Итак, пока давайте примем мои условия как данность.

Чье это вообще приложение?

В своем анализе я собираюсь использовать гипотетическое приложение, предназначенное для решения одной из самых насущных проблем в современном мире: собачьих какашек. Для нас, жителей Нью-Йорка, перспектива выходить каждое утро и ступать прямо в дымящуюся кучу вполне реальна. Итак, мы представим модный стартап в Сохо с приложением для экономии обуви под названием Dookie. Люди в Dookie - любители собак, которые хотят создать глобальную социальную сеть, чтобы выявлять и сообщать о собачьих фекалиях.

Первоначальная версия приложения была основана на картографировании и уведомлении о встречах собачьих какашек. С помощью мобильного приложения Дуки жители Нью-Йорка могли отмечать места, где они находили собачий помет. Благодаря этому в реальном времени можно было увидеть все недавние наблюдения собачьего помета в городе. В конце концов, люди смогут планировать свои маршруты в места и обратно, основываясь на наличии или отсутствии собачьих какашек на улице. Приложение имело огромный успех среди богатых пользователей, заботящихся о своем имидже, на которых рекламодатели любят нацеливаться.

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

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

Предсказать, где может оказаться фекалий, оказалось проблемой, сложной не только с аналитической точки зрения. Этот новый стек машинного обучения требовал всевозможного нового кода приложения.

В первой версии приложения машинного обучения под названием Poop Predictor команда использовала в основном инструменты из стека PyData: scikit-learn, pandas и т. Д. Во второй версии приложения машинного обучения под названием Scoop Sense, команда использовала Spark и Scala.

Специфика этих двух технических стеков на самом деле не является темой этого поста. Цель состоит в том, чтобы просто охарактеризовать различия между внутренне однопоточной реализацией с одним узлом (Poop Predictor) и многопоточной распределенной реализацией (Scoop Sense).

Я знаю, что есть способы создания больших потрясающих приложений с использованием стека PyData, которые не ограничиваются только возможностями CPython; Я так и сделал. На самом деле я большой поклонник Python, особенно для машинного обучения. Но выбор того или иного из двух лучших технологических стеков для машинного обучения имеет серьезные архитектурные последствия, и я хочу выделить их как способ выделить принципы, стратегии, шаблоны и т. Д., Которые позволят нам создать реактивный система.

Наконец, в материале ниже я попытаюсь охарактеризовать поведение и дизайн, применимые к широкому кругу приложений. Для начала я в основном сосредоточусь на конвейере обучения модели и экстракторах признаков. Я постараюсь отбросить любые опасения по поводу сервера модели, рассматривая его как отдельное, потенциально полностью реактивное приложение.

Принципы реактивных систем

Пройдя через четыре принципа реактивных систем, я попытаюсь оценить, насколько наши примеры систем будут соответствовать идеалу реактивной системы.

Как следует из диаграммы, принципы взаимодействуют и позволяют друг другу, поэтому методы, используемые в одном разделе, несомненно, будут играть роль в поддержке принципа другого раздела.

Отзывчивый

Первый принцип реактивных систем - они должны быть отзывчивыми. Несколько удивительно, но именно этот момент часто заставляет людей вроде Джея Крепса сомневаться в полезности парадигмы реактивных систем. Если вы занимаетесь инфраструктурой больших данных, отзывчивость звучит слишком близко к тому адаптивному веб-дизайну, о котором иногда беспокоятся разработчики пользовательского интерфейса.

Насколько я понимаю, скорость отклика зависит от того, возвращает ли приложение последовательные своевременные ответы на вводимые пользователем данные. Что это означает для конвейера машинного обучения?

Начнем с пользовательского ввода. Что пользователь вводит в наш конвейер машинного обучения? Обработка волнистой руки в учебнике машинного обучения, вероятно, могла бы сказать что-то вроде «особенности», но мы знаем лучше, не так ли? Детали не падают с неба; они извлекаются из необработанных данных, и их необходимо собирать. Давайте определим пользовательский ввод как вызов для инициирования процесса извлечения признаков и обучения модели, который потребляет необработанные входные данные, оставляя на данный момент в стороне проблемы сбора необработанных данных.

Тогда что представляет собой ответ? В случае конвейера машинного обучения очевидным кандидатом является только что изученная модель.

Итак, есть ли у нас надежные верхние границы времени ответа, обеспечивающие стабильное качество обслуживания? Я бы сказал, что во многих проектах систем ответ отрицательный.

Например, когда закончится трубопровод? В зависимости от приложения ответ может быть от нескольких минут до нескольких часов (или больше, в принципе). Но какое бы число ни было приведено, это число почти всегда будет просто оценкой, основанной на прошлых историях. Редко существует концепция SLA для конвейера машинного обучения. Обычно они просто настраиваются для работы с любыми данными, на которые они указывают, и результирующая задержка между запуском и получением новой модели как раз и есть. Если что-то начинает затягиваться, люди используют более крупное оборудование или пытаются каким-то образом улучшить реализацию. Может быть, они даже сделают выборку данных, если это возможно. Но ни один из этих вариантов на самом деле не даст пользователю конвейерной системы постоянный уровень обслуживания за счет надежной верхней границы скорости реагирования.

Улучшения

Однако есть варианты уменьшения неопределенности времени отклика. Хотя это может существенно повлиять на качество изученной модели, мы можем установить количество итераций.

Явная установка количества итераций, которые будет использовать алгоритм, не совсем ограничивает время отклика; он просто ограничивает количество проходов по обучающим данным. Но это определенно прогресс в создании предсказуемого распределения времени отклика.

Если мы хотим иметь истинную верхнюю границу времени отклика нашего конвейера обучения модели, нам подойдет упаковка нашего конвейера обучения модели в Scala Future:

В этом подходе используется концепция модели по умолчанию. Модель по умолчанию может быть ключевым компонентом, позволяющим вашему ML-приложению плавно перейти в какое-то разумное состояние в случае сбоя. В этом конкретном примере мы пытаемся изучить настоящую модель линейной регрессии Spark, но если обучение модели занимает больше времени, чем тайм-аут, равный одному часу, мы вместо этого вернем модель по умолчанию. Примеры стандартной модели даже не обязательно должны включать «настоящие» модели. Они могут быть просто классом по умолчанию (например, «здесь нет какашек») или последней моделью, которая была изучена для этой конфигурации. Во многом это зависит от специфики вашего приложения, стабильности процесса обучения вашей модели, ваших бизнес-требований и т. Д. Но у большинства команд есть возможность постепенного перехода к какой-либо модели по умолчанию.

Функциональность тайм-аута обучения модели значительно приближает нас к концепции адаптивного конвейера машинного обучения. Хотя мне нравится, насколько это аккуратно и чисто в примере Scala, фьючерсы доступны на большинстве языков, поддерживающих многопоточность (часто через библиотеки). Более того, достижение семантики, подобной Futures, в любом приложении, способном к асинхронной обработке, должно быть достижимо. Его не нужно реализовывать на уровне языка, и он может легко работать в коде вашего приложения.

Устойчивый

Следующий принцип реактивных систем - их устойчивость. В этом контексте устойчивость определяется как поддержание способности реагировать на неудачи. Очевидно, это желательное свойство в современном приложении, но как этого добиться?

Авторы Реактивного манифеста описывают три конкретных стратегии обеспечения устойчивости:

  • Репликация
  • Сдерживание и изоляция
  • Делегация

Давайте рассмотрим каждый по очереди и рассмотрим, как мы могли бы включить их в наши две реализации нашего приложения машинного обучения.

Репликация

Реактивный манифест определяет репликацию следующим образом:

Выполнение компонента одновременно в разных местах…

Для Poop Predictor, реализации нашего приложения машинного обучения на Python, это сложно. Без встроенных возможностей многопоточности мы должны возложить ответственность за одновременное выполнение нескольких вызовов нашего конвейера машинного обучения на какую-то другую внешнюю систему. Да, Python имеет некоторую поддержку многопроцессорности, но пока мы проигнорируем это. Это последнее добавление к языку, полностью привязанное к глобальной блокировке интерпретатора. Я думаю, что правильнее рассматривать многопроцессорность Python как средство, предоставляемое ОС, а не языком Python. Кроме того, Poop Predictor представляет собой одноузловое приложение. Таким образом, даже если бы мы выполняли наш конвейер машинного обучения в многопоточном режиме, эти исполнения не были бы в разных местах, что резко ограничило бы полезность такого рода репликации.

Со стороны Scoop Sense история совсем другая. Благодаря использованию Spark и MLlib наша реализация Scala работает в кластере. Spark внутренне полагается на Akka. Его системная архитектура гарантирует, что данный компонент нашего конвейера (задача) будет выполняться на нескольких узлах в кластере, когда это необходимо. По умолчанию в нашей работе может не так много репликации, но эффективное программирование на Spark почти наверняка будет включать широковещательные переменные. Это будет означать, что части нашего набора данных будут реплицированы по всему кластеру.

Мы можем пойти еще дальше, воспользовавшись возможностями Spark спекулятивное выполнение, мы можем добиться буквально одновременного выполнения частей нашей работы, точно выполняя определение репликации, данное выше.

Теперь, когда одна задача в нашей работе выполняется медленнее, чем хотелось бы, возможно, приближаясь к сбою, мы начнем спекулятивное выполнение этой задачи в другом месте, давая нашему конвейеру некоторую хорошую отзывчивость перед лицом сбоев задач низкого уровня.

Все это звучит довольно полезно, и широковещательные переменные и спекулятивное исполнение определенно могут сыграть роль в реализации других принципов и стратегий для систем реактивного машинного обучения.

Сдерживание и изоляция

Стратегия сдерживания и изоляции означает, что компоненты приложения должны быть достаточно отделены друг от друга, чтобы отказ одного компонента не повлиял на другие компоненты. Кун и Аллен используют метафору переборки из конструкции корабля. Приложения не должны делиться ничем. Ошибки должны быть только сигналами, на которые можно отреагировать, а не частью общего состояния, которое может привести к каскадному отказу.

Poop Predictor оказывается здесь не в лучшей форме. Поскольку он восходит к более ранним дням Dookie, когда у них было меньше масштабов, он просто перебирает все необходимые сконфигурированные модели и изучает каждую по очереди. Это означает, что катастрофический сбой в модели прогнозирования фекалий Чикаго может помешать изучению модели предсказания фекалий Нью-Йорка. Poop Predictor - неплохо реализованное приложение; он имеет функцию обработки ошибок. Но у него нет возможности обнаруживать тайм-аут и параллельно запускать процесс извлечения новых функций или обучения модели для компенсации. Таким образом, любой отказ конвейера, вероятно, будет иметь каскадные последствия ниже по потоку.

Scoop Sense здесь выглядит лучше. Spark внутренне разделяет задачи, предотвращая сбой одной задачи из-за сбоя всего задания (в нашем случае конвейеры извлечения функций и обучения модели). Поскольку мы отправляем задания по обучению модели в Нью-Йорке и Чикаго отдельно, у мошеннической работы нет реального способа выплеснуться на кормовую палубу S. S. Dookie или в другое отделение.

Но что делать, если вся работа терпит неудачу? Неужели вся наша способность к обучению модели тоже терпит неудачу? Это зависит от взаимосвязи между вызовом задания и выполнением задания.

Предположим, что изначально разработчики Dookie выполняют свои задания Scoop Sense Spark в облаке, в частности AWS EMR. Это означает, что приложение вызова задания просто выполняет вызовы API и получает статусы назад. Существует очень небольшой риск того, что простой код состояния распространит какой-либо сбой на другие независимо выполняющиеся задания. Отказ прекрасно сдерживается, потому что компоненты приложения достаточно изолированы.

По мере того, как Dookie продолжает масштабироваться, они чувствуют необходимость снизить свои ежемесячные счета за AWS, поэтому они обращаются к размещению постоянного кластера для всех своих задач по обработке данных. Независимо от того, выбирают ли они Mesos или YARN для управления своими рабочими местами, оба менеджера кластера используют сложные стратегии по сдерживанию и изоляции, чтобы гарантировать, что сбои не распространятся на рабочие места.

А как насчет самого вызова задания? Во всех этих сценариях работа должна чем-то начинаться. Должна присутствовать какая-то программа-драйвер, чтобы запускать задания и получать их сигналы состояния, чтобы знать, не удалось ли выполнить задание. Хорошей стратегией для обеспечения большей устойчивости может быть использование распределенного отказоустойчивого планировщика, такого как Chronos. Если команда Dookie использует Chronos для управления своими заданиями Scoop Sense Spark (или любыми другими типами заданий, которые у него могут быть), они получат возможность настраивать логику повторных попыток для своих заданий и управления зависимостями, и все это находится на вершине отказоустойчивой инфраструктуры Mesos. . Использование такой системы позволит им спроектировать свою систему таким образом, чтобы приложение для вызова заданий не становилось единственной точкой отказа, которая каскадно переходила в остальную часть приложения машинного обучения.

Делегация

Наша последняя стратегия обеспечения устойчивости нашего приложения - это делегирование. Еще раз из реактивного манифеста:

Асинхронное делегирование задачи другому компоненту означает, что выполнение задачи будет происходить в контексте этого другого компонента.

Очевидно, у нас снова проблемы с Poop Predictor. Об асинхронности ничего не может быть и речи, если мы не покинем территорию Python (например, с использованием какой-либо очереди сообщений). И все наши рабочие места выполняются в одном и том же контексте. Poop Predictor запускает каждое задание в одном и том же процессе Python. Невозможно, чтобы какой-то независимый компонент приложения сидел поверх различных заданий, наблюдая за тем, что не удалось, таким образом, который позволил бы ему действительно отделить свой контекст от контекста выполнения задания. Конечно, может быть иерархия и обработка ошибок, но только в пределах одного и того же контекста выполнения. Таким образом, любое «делегирование», которое мы можем иметь в Poop Predictor, будет особенностью кода нашего приложения, а не его архитектуры, что резко ослабит любые гарантии, которые оно может предложить в отношении контроля выполнения.

Поскольку Scoop Sense построен на основе Spark, мы получаем некоторое делегирование прямо из коробки. Делегирование - это основная концепция дизайна Akka, который является ключевым компонентом Spark, и Erlang, от которого происходит большая часть дизайна Akka. Помимо концепций делегирования уровня Akka, Spark также использует иерархическую архитектуру, позволяющую обрабатывать сбои как на уровне диспетчера кластера, так и на уровне программы драйвера. Подобно предыдущему разделу, у нас есть возможность добавить еще больше делегирования к нашему проекту, обеспечив механизм вызова задания живым вне самой программы драйвера через отказоустойчивый планировщик, такой как Chronos.

Успех?

Удалось ли нам обеспечить устойчивость хотя бы в одной из наших реализаций? Очевидно, мы столкнулись с множеством реальных ограничений на стороне Poop Predictor, в основном из-за того, что Python был однопоточным, а Poop Predictor был развернут только на одном узле. У нас были варианты улучшения нашего приложения машинного обучения, но все они, казалось, предполагали довольно фундаментальные изменения в архитектуре Poop Predictor.

Похоже, что Scoop Sense был настроен на гораздо больший успех, в основном из-за повсеместного использования принципов реактивного дизайна при реализации Spark и других вспомогательных инструментов, таких как Mesos и Chronos. Но каково здесь определение успеха?

Из реактивного манифеста:

Клиент компонента не обременен обработкой его сбоев.

Исходя из этого, я бы сказал, что если мы увидим исключения, которые приводят к падению нашего приложения с запросом о вакансии верхнего уровня, мы потерпели неудачу. Scoop Sense предлагает множество явных вариантов дизайна, в которых используются стратегии репликации, сдерживания / изоляции и делегирования, что позволяет нам занять хорошее место с точки зрения устойчивости.

Основываясь на уже используемых методах, я собираюсь предложить еще одно усовершенствование нашего дизайна. Давайте представим наши задания таким образом, чтобы наши программы драйверов контролировались менеджером кластера.

Основываясь в первую очередь на стратегиях делегирования и изоляции, эта стратегия вызова гарантирует, что любой сбой в программе драйвера будет обнаружен контролирующим менеджером кластера и вызовет перезапуск программы драйвера. Эта функция в сочетании со всеми другими вышеупомянутыми функциями должна сделать нашу реализацию Scoop Sense достаточно отзывчивой, несмотря на любые возможные сбои.

Эластичный

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

В случае с Poop Predictor непонятно, как мы это сделаем. Наша реализация Python последовательно запускает конвейеры извлечения функций и обучения модели для каждой настроенной модели. Даже если предположить, что внешний уровень приема сообщений принимает запросы на новые модели, Poop Predictor может изучать только одну модель за раз. Таким образом, хотя Poop Predictor может в конечном итоге удовлетворить поток новых входящих запросов на модели (из-за запуска Dookie на заполненных дерьмом улицах Парижа), Poop Predictor не будет поддерживать постоянный уровень отзывчивости. этого скачка трафика.

Вторая характеристика эластичной системы состоит в том, что она может изменять количество процессоров в ответ на изменяющуюся нагрузку. В оптимистической версии этого сценария мы просто хотим, чтобы наше приложение машинного обучения запускало кучу новых ресурсов обработки, когда все эти парижане начинают загружать приложение и хотят знать, куда они могут перейти. В пессимистическом сценарии речь идет о сокращении наших общих расходов на AWS, когда Северная Корея отключает наше приложение, чтобы предсказать, что в Центральном роскошном особняке Ким Чен Ына могут быть собачьи какашки на крыльце. Учитывая, что наше приложение Poop Predictor работает только на одном сервере, у него не будет этой полезной характеристики.

Оба являются важными компонентами для того, чтобы приложение вписалось в общую систему ценностей компании Dookie. Дуки - это стартап. Команда хочет, чтобы приложение стало невероятно популярным и по-прежнему радует пользователей. И если в этом месяце роста клюшек не произойдет, они хотят сократить расходы на инфраструктуру, чтобы максимально удлинить взлетно-посадочную полосу. Может ли реализация Scoop Sense работать лучше?

Распределенный дизайн нашего приложения Spark ML делает нас лучше. На уровне задачи, в рамках данного экземпляра конвейера, мы определенно разделяем наши входные данные между несколькими процессорами. Если мы предполагаем наличие либо независимой службы выполнения, такой как EMR, либо диспетчера кластеров, то наши различные призывы начать изучение новых моделей прогнозирования фекалий будут сопоставляться с еще большим количеством ресурсов.

Фреймворк Spark, лежащий в основе нашего приложения Scoop Sense, также дает нам возможность автоматически сокращать наши ресурсы, когда верховный лидер отключает наше многообещающее азиатское расширение. Если Scoop Sense выполняется с YARN в качестве диспетчера кластеров в последней версии Spark. Эта функция называется динамическое распределение ресурсов, что звучит как более подробный способ сказать эластичность.

Однако это не волшебный трюк. Spark не покупает и не настраивает наши собственные серверы. Если мы запускаем кластер Hadoop, настроенный на использование YARN, мы все равно так или иначе платим и делаем большую часть тяжелой работы. Дополнительные серверы не появятся волшебным образом, если мы не работаем в облаке и не выполняем правильные вызовы API для их создания. Эластичная масштабируемость в ответ на переменный трафик - невероятно сложная проблема, и ни один вызов API не может ее решить.

По идее, мы могли бы захотеть, если Dookie размещает кластер Hadoop, чтобы иметь возможность отправлять лишнюю работу в облако, когда локальный кластер перегружен. У меня нет примера кода, как реализовать этот конкретный трюк, потому что, честно говоря, это сложно сделать. Сложные менеджеры кластеров, такие как YARN и Mesos, имеют возможность отслеживать использование кластера и передавать сигналы об этом состоянии в какое-либо другое контролирующее приложение. Но допустим, что этот дополнительный уровень контроля не реализован в Scoop Sense, и оставим его на будущее.

Функциональное программирование

Стоит потратить время на то, чтобы понять, почему эластичность было так сложно достичь в реализации Poop Predictor, но намного проще в реализации Scoop Sense. Конечно, отчасти причина в том, что мы предполагали кластерное развертывание Scoop Sense. Spark и связанные с ним менеджеры кластеров делают большую часть тяжелой работы для нашего приложения. Но стоит углубиться в то, как и почему Spark, учитывая, насколько ценными оказались многие из его дизайнерских решений в нашем стремлении к реактивным приложениям машинного обучения.

Как и в других реактивных системах, наш конвейер машинного обучения построен на основе принципов функционального программирования. Spark реализован на Scala и широко использует возможности функционального программирования, предоставляемые этим языком.

Например, в Spark мы используем функции высшего порядка для передачи функции нашему набору данных, например, когда мы загружаем наши данные и анализируем их:

Это не просто метафора. Заданная часть наших данных находится на заданном узле в нашем кластере Spark. Инфраструктура Spark отправит инструкции для выполнения разделения строк и создания LabeledPoint на каждый из этих узлов; программа перейдет к данным, а не наоборот. Это одна из фундаментальных идей, лежащих в основе модели программирования сокращения карты, популяризированной Hadoop. Как мы видели выше, разделение данных окажет огромное влияние на нашу способность увеличивать или уменьшать масштаб по мере необходимости.

Функции высшего порядка - не единственный метод функционального программирования, который позволяет нашему приложению Scoop Sense иметь такой реактивный дизайн. Еще одна важная стратегия использования - это неизменяемость. Не углубляясь слишком глубоко в тему, можно сказать, что мышление неизменными терминами позволяет нам мыслить в более математическом смысле, рассматривая наш конвейер машинного обучения как серию преобразований, которые приводят к новым наборам данных, не изменяя исходные наборы данных. Эта ментальная модель и стратегия выполнения значительно упрощают мышление в терминах, которые имеют смысл на портативном компьютере разработчика, но могут масштабироваться до массивных кластеров.

Я мог бы легко начать разглагольствовать о том, почему функциональное программирование имеет решающее значение для современной инженерии данных и что это значит для вашей карьеры, но я не буду. Если вы видите меня на встрече или в баре, вероятно, я все равно этим и занимаюсь, и шоу намного лучше лично. Вместо этого я перейду к нашему последнему принципу реактивных систем, чтобы увидеть, насколько наши приложения машинного обучения соответствуют требованиям.

На основе сообщений

В предыдущих разделах мы подробно объяснили, почему наше приложение машинного обучения необходимо разбить на более мелкие части, которые четко разделены, чтобы их можно было контролировать и реагировать на них в случае различной нагрузки или сбоя. Последний принцип, управляемость сообщениями, является ключевым элементом того, как на самом деле можно реализовать систему со всеми этими свойствами. В реактивной системе асинхронная передача сообщений используется в качестве механизма связи между слабосвязанными компонентами.

Передача сообщений играет ключевую роль в обеспечении гибкости, устойчивости системы и, в конечном итоге, способности реагировать на нее. Когда наш конвейер машинного обучения увеличивает время выполнения, ожидая, что информация может быть передана обратно нашему диспетчеру кластера, чтобы получить больше ресурсов для таких вещей, как спекулятивное выполнение. Когда новая модель Парижа взрывается на некоторых данных, которые включают accent aigu, это не будет проблемой для нашей модели Чикаго.

Нам не придется ступать в эту пылающую кучу, соскребать грязь с нашей обуви и угрожать жизни Билли Мэдисона.

В системе, управляемой сообщениями, отказ - это просто еще одно сообщение.

На него можно ответить или нет, в зависимости от требований вашего приложения. Если мы правильно спроектировали устойчивость, возможно, мы уже справляемся с ошибкой обучения модели с помощью модели по умолчанию. Или, может быть, бизнес-правило состоит в том, чтобы просто не показывать новые прогнозы фекалий в случае неудачи. Сообщенных Пэрис уровней какашек может быть достаточно для поддержания ожидаемого пользовательского опыта, поэтому предсказанные dookies могут просто не потребоваться. В качестве альтернативы, сбой можно передать обратно на контрольный уровень нашего приложения для дальнейших действий в виде сообщения, а не в качестве исключения времени выполнения, которое необходимо обработать немедленно. Тогда у нас есть выбор, что делать дальше, когда сбои - это просто сообщения.

Сбор данных

Похоже, мы можем быть в хорошей форме для реализации нашего последнего принципа, но давайте рассмотрим несколько более хитрый пример. Давайте посмотрим, сможем ли мы подробнее рассказать о роли, которую управление сообщениями играет в реакции.

Когда пользователь взаимодействует с мобильным приложением Dookie, он или она отправляет данные обратно в системы сбора данных Dookie. Эти данные представляют собой такие вещи, как введенные пользователем данные об обнаружении фекалий или (чаще) отправляемые пассивные данные о местах, которые пользователь прошел, без сообщения об оставленных без присмотра фекалиях. В конечном итоге эти данные будут использоваться частью конвейера извлечения признаков для изучения новых моделей. Итак, как эти данные загружаются в наше приложение машинного обучения?

В случае Poop Predictor эти данные отправляются нерегулярными пакетами из мобильного приложения обратно в API сбора данных на внутреннем сервере. Это приложение для сбора данных на самом деле теперь является частью нашего приложения машинного обучения и вовсе не управляется сообщениями. Данные фактически отправляются в виде извлеченных функций для прямой записи в базу данных значений реляционных функций. Хуже того, он напрямую записывает эти экземпляры функций в базу данных, прежде чем подтвердить мобильному приложению, что запрос был обработан.

Это означает, что эта связь не асинхронна. Кроме того, мы, вероятно, также находимся в плохом положении с точки зрения устойчивости и эластичности. Мы не обрабатываем ошибки или заставляем их обрабатывать обратно в наше мобильное приложение! Безусловно, нет никаких функций для управления нагрузкой на наши записи в базу данных или нашим использованием самого приложения для сбора данных.

К счастью, команда Dookie опиралась на все, чему они научились с Poop Predictor, когда создавали Scoop Sense. В последней версии платформы Scoop Sense ML они фактически построены на основе PredictionIO, сложной платформы машинного обучения, построенной на основе Spark (и нескольких других интересных технологий).

В версии платформы Scoop Sense на основе PredictionIO данные отправляются обратно на сервер событий через вызовы API из клиентских библиотек. Это приближает нас к идеалу передачи сообщений. Сервер событий в первую очередь отвечает только за получение этих сообщений. Нет необходимости в какой-то блокирующей проверке того, соответствуют ли сообщения о событиях некоторой ожидаемой бизнес-логике. Это просто сообщения.

Это полностью отделяет этап сбора данных от логики конвейера извлечения признаков обучения модели. Если новая версия мобильного клиентского приложения начинает отправлять обратно бессмысленные события, сервер событий все равно может получать их все, не отклоняя их при получении, потому что они не вписываются в реляционную модель данных того, какой должна быть функция. Пока они являются действительными сообщениями о событиях, эти сообщения могут сохраняться, и ничто не заставляет конвейер извлечения функций использовать их, если они не проходят логику проверки данных.

Возможно, самое главное, это полностью отделяет вопросы вертикального и горизонтального масштабирования нашего процесса сбора данных от нашего конвейера извлечения функций. Пока мы просто передаем сообщения через некоторый уровень интеграции персистентности (например, Kafka), у нас могут быть совершенно разные следы для сбора данных и конвейеров извлечения функций и обучения модели. Затем это позволяет гибко масштабировать эти ресурсы, поэтому команда Dookie может каждую ночь наращивать кучу ресурсов для изучения новых моделей, сохраняя при этом постоянную площадь приложения для сбора данных.

Готово, не сделано

Теперь, когда я попытался ответить на свой вопрос, должен сказать, что мне он показался еще более интересным.

Как принципы реактивных систем соотносятся с приложениями машинного обучения?

Я попытался дать этот вопрос настолько тщательно, насколько это поместится в сообщении в блоге, но у меня все еще есть много вопросов, в которых я хотел бы углубиться:

  • Можем ли мы найти лучшее определение отзывчивости?
  • Что на самом деле означает «отзывчивость» в контексте цикла «сбор данных - извлечение функций - изучение модели - повторение»?
  • Можем ли мы вывести принципы реактивного дизайна исключительно из принципов функционального программирования? Или возможно построить реактивную систему машинного обучения без использования функций более высокого порядка, неизменяемости и т. Д.?
  • Как лучше всего применить эти методы к языкам без встроенных возможностей многопоточности?
  • Можете ли вы использовать Spark и по-прежнему останавливаться на нереактивной системе?
  • Существуют ли антипаттерны дизайна, которые приводят людей к созданию систем машинного обучения, которые, по их мнению, являются реактивными, но на самом деле таковыми не являются?
  • Действительно ли прогнозирование местоположения собачьих фекалий является наилучшим и лучшим применением сложных методов машинного обучения?
  • Где должна находиться логика эластичности при развертывании вашего приложения в облаке?
  • Как реализация модельного сервера влияет на реактивность всего приложения?
  • Кто наблюдает за сторожами? Нужно ли нам контролировать наш распределенный отказоустойчивый планировщик?
  • Как меняется наше приложение реактивного машинного обучения, когда наш алгоритм обучения модели является алгоритмом онлайн-обучения или обучения с подкреплением?
  • Как концепция лямбда-архитектуры соотносится с реактивной архитектурой в контексте системы машинного обучения?

Здесь определенно есть что подумать, и я обещаю вернуться к этим вопросам. Я хотел бы услышать ваше мнение по этой теме, поэтому ознакомьтесь с контактной информацией ниже и дайте мне знать, что вы думаете. А пока смотрите, куда вы ступаете!

Если вы хотите узнать больше о реактивном машинном обучении, подпишитесь на мою рассылку на reactivemachinelearning.com.

Я инженер по обработке данных и иногда карикатурист из Нью-Йорка. Я занимаюсь системами машинного обучения в x.ai. Я веду блог в основном здесь, на Medium. Вы можете найти больше моих комиксов и совместных арт-проектов в Comics in Beta collection.

Если вы хотите поговорить о реактивных системах, машинном обучении или их сочетании с арахисовым маслом и желе, вы можете связаться со мной через Twitter или LinkedIn.

Наконец, я обещаю, что всегда собираю какашки своей собаки.

Ссылки и кредиты

Большая часть материала реактивных систем, очевидно, основана на Манифесте реактивности. Я также часто ссылался на Паттерны реактивного дизайна Куна и Аллена и настоятельно рекомендую купить и прочитать книгу! Я также нашел очень полезными презентации Dean Wampler и Henrik Engström. Я очень рекомендую курс Принципы реактивных систем на Coursera.

Чтобы изучить и понять Spark, я рекомендую начать с Learning Spark. Мне очень понравилась Расширенная аналитика с помощью Spark, так как платформа больше ориентирована на машинное обучение. Конечно, вы можете (и должны!) Погрузиться в Spark прямо с документацией.

Исходя из несколько иного ракурса, Oryx пытается построить полное приложение машинного обучения с использованием лямбда-архитектуры, и полученные в результате принципы проектирования, похоже, во многом пересекаются с реактивными системами.

Наконец, для интересного взгляда на современную архитектуру машинного обучения, построенную с использованием Spark, определенно стоит разобраться в PredictionIO.

Заключительное примечание Python

Я действительно люблю Python, и я понимаю, что этот пост можно рассматривать как обвинительный акт против Python. Это не. Когда меня посадят в землю, я надеюсь, что на моем надгробии будет значительный пробел, а не одна точка с запятой.

Но я создаю и люблю мощные распределенные системы. Лучший способ, которым я знаю, как это сделать в Python, - это Spark. Серьезно, это вполне реальный вариант. Проблемы несоответствия импеданса более значительны, чем при использовании Scala или Java для общения со Spark, но когнитивные издержки могут окупиться из-за возможностей, которые они привносят в ваше приложение. Это вам решать. Я бы просто сказал, что попытка создать приложение с реактивным машинным обучением - амбициозная цель, к которой стоит стремиться.