Наше решение для осмысления крупномасштабных экспериментальных результатов

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

В какой-то момент каждому исследователю необходимо задать вопрос: как управлять всеми этими результатами и понимать их?

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

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

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

Сообщения Kafka

Мы отправляем различные сообщения журнала во время обучения модели в заранее заданную тему Kafka. Эта тема настроена на постоянное хранение сообщений, поэтому она действует как единственный источник истины. Поскольку сообщения Kafka представляют собой просто байтовые последовательности, мы кодируем данные сообщения как объекты JSON.

Мы обнаружили, что очень важно придерживаться схемы. Spark не обязательно этого требует (хотя Spark Streaming немного сложнее), но наличие согласованного формата для сообщений заставило нас согласиться на относительно стабильную структуру сообщений, что, в свою очередь, помогло сохранить согласованность.

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

Вот пример сообщения, которое содержит текущий f-показатель проверки модели во время обучения:

{
  "_runId": "e59816af-14b6-4827-a68e-cba1720e277f",
  "_messageId": "e59816af-14b6-4827-a68e-cba1720e277f/2103",
  "_timestamp": "2019-06-15T16:30:31.000+0000",
  "metric": "f1-score",
  "value": 0.746,
  "batchNumber": 150000,
  "stage: "training",
  "step": "validation"
}

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

Мы также храним метаданные эксперимента в виде сообщений Kafka. Описательные метаданные необходимы для будущих запросов, поэтому мы регистрируем их довольно много:

  • идентификатор запуска эксперимента, тип задачи
  • пользовательские теги (метки)
  • данные о главной машине, версиях инструментария, среде…

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

Запрос данных

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

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

Допустим, теперь мы хотим построить f-баллы. Используя Databricks, мы можем легко создавать графики на основе SQL-запросов. (В качестве дополнительного бонуса при использовании Spark Streaming графики обновляются в реальном времени.)

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

Используя аналогичные короткие запросы, мы можем легко ответить на другие вопросы или получить различные визуализации:

  • Сколько из моих экспериментов в группе завершились неудачно / завершились успешно?
  • Каков статус всех экспериментов, которые были начаты вчера?
  • Сколько времени занимает эпоха на моей рабочей станции по сравнению с облачным экземпляром?
  • График точности модели и размера скрытого слоя.

Мы даже подумали о том, чтобы иметь постоянно работающий ноутбук с потоковой передачей Spark для мониторинга всех моделей, которые в настоящее время проходят обучение, и отображения их производительности в виде живой панели инструментов в нашем офисе. Опять же, Databricks в сочетании с Kafka и Spark Streaming сделали бы эту задачу тривиальной.

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

Выводы

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

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

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

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