Мы хотим упростить для участников Netflix поиск отличного контента, отвечающего их уникальным вкусам. Для этого мы следуем алгоритмическому подходу на основе данных, основанному на машинном обучении, который мы описывали в прошлых постах и других публикациях. Мы стремимся к тому дню, когда каждый сможет сесть, включить Netflix, и лучший контент для него автоматически начнет воспроизводиться. Хотя мы все еще далеки от этой цели, она ставит перед нами задачу улучшить алгоритмы, охватывающие наш сервис: от того, как мы оцениваем видео до того, как мы создаем домашнюю страницу, и как мы предоставляем результаты поиска. Чтобы улучшить наши алгоритмы, мы придерживаемся двухэтапного подхода. Во-первых, мы пробуем идею в автономном режиме, используя исторические данные, чтобы увидеть, могут ли она дать лучшие рекомендации. Если это так, мы затем запускаем живой A / B-тест, чтобы увидеть, хорошо ли он работает в действительности, что мы измеряем с помощью статистически значимых улучшений основных показателей, таких как вовлеченность участников, удовлетворенность и удержание.
Хотя есть много способов улучшить подходы к машинному обучению, возможно, наиболее важным является предоставление более качественных входных данных. Модель может быть настолько хороша, насколько хороши данные, которые мы ей даем. Таким образом, мы проводим много времени, экспериментируя с новыми видами входных сигналов для наших моделей. Большинство моделей машинного обучения ожидают, что входные данные будут представлены в виде вектора чисел, известного как вектор признаков. Каким-то образом нам нужно взять произвольный входной объект (например, кортеж из профиля участника, видео, страны, времени, устройства и т. Д.) С его связанными, богато структурированными данными и предоставить вектор признаков, представляющий этот объект для алгоритма машинного обучения. использовать. Мы называем это преобразованием функции генерации, и оно является центральным для предоставления данных, необходимых для обучения. Примеры функций включают в себя, сколько минут пользователь просмотрел видео, популярность видео, его прогнозируемый рейтинг, к какому жанру принадлежит видео или сколько видео находится в ряду. Мы используем термин «функция» в широком смысле, поскольку функция может быть простым индикатором или иметь за собой полную модель, например матричную факторизацию.
Мы опишем, как мы создали машину времени для генерации функций с помощью Apache Spark, которая позволяет нашим исследователям быстро опробовать идеи для новых функций на исторических данных, чтобы запуск автономных экспериментов и переход к онлайн-тестам A / B происходили без проблем.
Зачем строить машину времени?
Есть много способов подойти к созданию функций, некоторые из которых мы использовали в прошлом. Один из способов - использовать зарегистрированные данные о событиях, которые мы храним на S3 и получаем доступ через Hive, выполняя запросы к этим таблицам для определения функций. Хотя это гибко для исследовательского анализа, у него есть несколько проблем. Во-первых, чтобы запустить A / B-тест, нам нужно, чтобы расчет функций выполнялся в нашей онлайн-архитектуре микросервисов. Мы запускаем модели в режиме онлайн, потому что знаем, что актуальность и оперативность наших рекомендаций важны для впечатлений участников. Это означает, что нам нужно будет повторно реализовать создание функций для получения данных из онлайн-сервисов вместо таблиц Hive. Трудно точно сопоставить две такие реализации, тем более что любые расхождения между автономными и онлайн-источниками данных могут привести к неожиданным различиям в выходных данных модели. Кроме того, не все наши данные доступны в автономном режиме, особенно результаты моделей рекомендаций, поскольку они включают преобразование из разреженного в плотный, что создает большой объем данных.
С другой стороны, мы могли бы регистрировать наши функции в Интернете, где будет использоваться модель. Хотя это устраняет несоответствие офлайн / онлайн и упрощает переход к A / B-тесту, это означает, что нам нужно внедрить каждую идею для новой функции в производство и дождаться сбора данных, прежде чем мы сможем определить, полезна ли функция. Это замедляет цикл итераций новых идей. Это также требует, чтобы все данные для функции были доступны в Интернете, что может означать создание новых систем для обслуживания этих данных, опять же, прежде чем мы определим их ценность. Нам также необходимо вычислить функции для гораздо большего количества участников или запросов, чем нам может действительно потребоваться для обучения в зависимости от того, как мы выбираем данные меток.
Мы также попробовали компромисс, когда мы используем код функции, который вызывает онлайн-сервисы, например тот, который предоставляет историю просмотров, и отфильтровывает все данные с отметками времени за определенный момент времени. Однако это работает только в тех случаях, когда служба записывает журнал всех исторических событий; сервисы, которые просто предоставляют текущее состояние, не могут быть использованы. Это также создает дополнительную нагрузку на онлайн-сервисы каждый раз, когда мы создаем функции.
Во всех этих подходах чрезвычайно важно управление временем. Нам нужен подход, который уравновешивает преимущества всех вышеперечисленных подходов без недостатков. В частности, нам нужна система, которая:
- Позволяет быстро переходить от идеи к моделированию и запускать A / B-тест
- Использует данные, предоставляемые нашими онлайн-микросервисами, не перегружая их
- Точно представляет входные данные для модели в определенный момент времени для имитации онлайн-использования
- Обрабатывает наш масштаб данных, когда многие исследователи проводят эксперименты одновременно, не используя более 1,21 гигаватт энергии.
- Хорошо работает в интерактивной среде, например при использовании записной книжки для экспериментов, а также надежно в пакетной среде, например, для периодического переобучения.
- Необходимо написать код функции только один раз, чтобы нам не приходилось тратить время на проверку того, что две реализации в точности эквивалентны.
- Самое главное, недопустимо никаких парадоксов (например, метка не может быть в функциях).
Столкнувшись с трудными проблемами, часто хочется, чтобы их решила машина времени. Вот что мы решили построить. Наша машина времени создает моментальные снимки онлайн-сервисов и использует данные моментальных снимков в автономном режиме, чтобы реконструировать входные данные, которые модель могла бы видеть онлайн, для создания функций. Таким образом, когда экспериментаторы разрабатывают новые кодеры функций - функции, которые принимают необработанные данные в качестве входных и вычисляют функции, - они могут немедленно использовать их для вычисления новых функций в любое время в прошлом, поскольку машина времени может извлекать соответствующие снимки и передавать их в кодеры функций.
Как построить машину времени
Вот различные компоненты, необходимые для машины времени, которая делает снимки онлайн-сервисов:
- Выберите контексты для снимка
- Снимки данных различных микросервисов для выбранного контекста
- Создавайте API-интерфейсы для обслуживания этих данных за заданную временную координату в прошлом.
Выбор контекста
Создание снимков данных для всех контекстов (например, всех профилей участников, устройств, времени суток) было бы очень дорогостоящим. Вместо этого мы выбираем образцы контекстов для периодических снимков (обычно ежедневно), хотя разные алгоритмы могут нуждаться в обучении на разных дистрибутивах. Например, некоторые используют стратифицированные выборки, основанные на таких свойствах, как шаблоны просмотра, устройства, время, потраченное на службу, регион и т. Д. Чтобы справиться с этим, мы используем Spark SQL, чтобы выбрать соответствующую выборку контекстов для каждого эксперимента из Hive. Мы объединяем контекст, установленный для экспериментов, и сохраняем его в S3 вместе с соответствующими идентификаторами экспериментов.
Снимки данных
Следующий компонент машины времени получает данные из различных онлайн-сервисов и сохраняет моментальный снимок возвращенных данных для выбранных контекстов. Netflix использует детализированную сервис-ориентированную архитектуру для нашей облачной модели развертывания. Существуют сотни таких микросервисов, которые несут коллективную ответственность за взаимодействие с участниками. Данные из различных таких сервисов, как История просмотра, Мой список и Прогнозируемые рейтинги, используются в качестве входных данных для функций в наших моделях.
Мы используем специфические для Netflix компоненты, такие как Eureka, Hystrix и Archaius, для получения данных из онлайн-сервисов через их клиентские библиотеки. Однако некоторые из этих клиентских библиотек выполняют массовую загрузку данных, поэтому они занимают много места в памяти и имеют большое время запуска. Spark не очень подходит для загрузки таких компонентов внутри JVM. Более того, требование создания uber jar для запуска заданий Spark может вызвать проблемы несовместимости jar во время выполнения с другими библиотеками Netflix. Чтобы решить эту проблему, мы использовали Prana, работающую вне Spark JVM, в качестве прокси-сервера данных для экосистемы Netflix.
Spark распараллеливает вызовы Prana, который извлекает данные из различных микросервисов для каждого из этих контекстов. Мы выбрали Thrift в качестве протокола двоичной связи между Spark и Prana. Мы сохраняем моментальные снимки данных в S3, используя Parquet, сжатый двоичный формат, ориентированный на столбцы, для экономии времени и пространства, и сохраняем местоположение данных S3 в Cassandra.
Обеспечение безупречного качества данных этих снимков критически важно для правильной оценки наших моделей. Следовательно, мы сохраняем уровень достоверности для каждой службы моментальных снимков, который представляет собой процент успешных выборок данных из микросервисов, исключая любые откаты из-за тайм-аутов или сбоев службы. Мы предоставляем ее нашим клиентам, которые могут использовать эту информацию для своих экспериментов.
И для моментальных снимков, и для выбора контекста нам нужно было запланировать периодическое выполнение нескольких заданий Spark с зависимостями между ними. С этой целью мы создали платформу для оркестровки и планирования рабочих процессов общего назначения под названием Meson, которая оптимизирована для конвейеров машинного обучения, и использовали ее для выполнения заданий Spark для компонентов машины времени. В будущем мы планируем открыть Meson с открытым исходным кодом и расскажем о нем более подробно в одном из следующих постов в блоге.
API для путешествий во времени
Мы создали API-интерфейсы, которые позволяют путешествовать во времени и извлекать данные моментальных снимков из S3 за определенный промежуток времени в прошлом. Вот пример API для получения данных моментального снимка для службы журнала просмотров.
Учитывая время назначения в прошлом, API извлекает соответствующее местоположение S3 данных моментального снимка из Cassandra и загружает данные моментального снимка в Spark. Кроме того, при присвоении идентификатора A / B-теста API фильтрует данные моментального снимка, чтобы вернуть только те контексты, которые были выбраны для этого A / B-теста. Система преобразует данные моментальных снимков обратно в объекты Java соответствующих сервисов (POJO), так что кодеры функций работают с одними и теми же POJO как для автономных экспериментов, так и для создания функций в режиме онлайн в производственной среде.
На следующей диаграмме показана общая архитектура машины времени и то, где Spark используется при ее создании: от выбора участников для экспериментов, создания моментальных снимков данных различных служб для выбранных участников, до окончательного обслуживания данных в течение некоторого времени в прошлом.
DeLorean: создание характеристик с помощью путешествий во времени
DeLorean - это наш внутренний проект по созданию системы, которая берет план эксперимента, путешествует во времени, чтобы собрать все необходимые данные из снимков, и генерирует набор данных с функциями и метками для того времени в прошлом для обучения моделей машинного обучения. Конечно, первым делом нужно выбрать время прибытия, довести скорость до 88 миль в час, а потом DeLorean позаботится обо всем остальном.
Проведение эксперимента
DeLorean позволяет исследователю проводить широкий спектр экспериментов, автоматически определяя, как запустить машину времени, какие временные координаты необходимы, какие данные нужно получить и как структурировать вывод. Таким образом, чтобы провести новый эксперимент, экспериментатору нужно только предоставить следующее:
- Данные метки: схема для получения набора контекстов со связанными координатами времени, элементами и метками для каждого из них. Обычно это создается с помощью SQL-запроса Hive, Pig или Spark.
- Модель функций, содержащая необходимые конфигурации кодировщика функций.
- Реализации любых новых кодировщиков функций, которых еще нет в нашей библиотеке.
DeLorean предоставляет возможность писать и изменять новый кодировщик функций во время эксперимента, например, в Блокноте Zeppelin или в Spark Shell, чтобы его можно было сразу использовать для генерации функций. Если мы сочтем этот новый кодировщик функций полезным, мы сможем позже реализовать его, добавив в нашу библиотеку кодировщиков функций.
Высокоуровневый процесс создания функций изображен на следующей диаграмме, где блоки, выделенные светло-зеленым цветом, обычно настраиваются для новых экспериментов. В этом сценарии экспериментаторы могут также реализовать новые кодировщики функций, которые используются вместе с существующими.
Этикеточные данные и кодеры функций
Одним из основных входных данных для DeLorean являются данные меток, которые содержат информацию о контекстах, элементах и связанных метках, для которых необходимо создать элементы. Контексты, как следует из названия, могут описывать настройку того, где должна использоваться модель (например, кортежи профилей участников, страна, время, устройство и т. Д.). Элементы - это элементы, которые нужно обучать, оценивать и / или оценивать (например, видео, строки, объекты поиска). Ярлыки обычно являются целями, используемыми в контролируемом обучении для каждой комбинации контекст-элемент. Для подходов к обучению без учителя метка не требуется. Например, для персонализированного ранжирования контекст может быть определен как идентификатор профиля участника, код страны и время, а элемент - как видео, а метки - как воспроизведение или не воспроизведение. В этом примере данные метки создаются путем присоединения набора контекстов моментальных снимков к зарегистрированным действиям воспроизведения.
Когда у нас есть этот набор данных меток, нам нужно вычислить функции для каждой комбинации контекст-элемент в наборе данных, используя желаемый набор кодировщиков функций. Каждый кодировщик функций принимает контекст и каждый из целевых элементов, связанных с контекстом, вместе с некоторыми необработанными элементами данных в форме POJO для вычисления одной или нескольких функций.
Каждый тип элемента, переменной контекста или элемента данных имеет связанный с ним ключ данных. Каждый кодировщик функций имеет метод, который возвращает набор ключей для данных, которые он потребляет. DeLorean использует эти ключи для идентификации требуемых типов данных, извлекает данные и передает их кодировщику функций в виде карты данных, которая представляет собой карту от ключей данных к объектам данных.
Мы сделали DeLorean достаточно гибким, чтобы в экспериментах можно было использовать различные типы контекстов и элементов без необходимости настраивать систему генерации функций. DeLorean можно использовать не только для рекомендаций, но и для эксперимента с упорядочением строк, в котором кортеж профиль-устройство используется в качестве контекста, а строки видео - в качестве элементов. Другим вариантом использования может быть поисковый эксперимент, в котором кортеж query-profile-country используется в качестве контекста, а отдельные видео - в качестве элементов. Для этого DeLorean автоматически определяет тип контекстов и элементов из данных метки и ключей данных, необходимых кодировщикам функций.
Элементы данных
Элементы данных - это ингредиенты, которые кодировщик функций преобразует в функции. Некоторые из них зависят от контекста, например, история просмотров профиля, а другие используются во всех контекстах, например, метаданные видео. Мы обрабатываем эти два типа элементов данных по-разному.
Для контекстно-зависимых элементов данных мы используем снимки, описанные выше, и связываем каждый из них с ключом данных. Мы объединяем все необходимые источники данных моментальных снимков вместе со значениями, элементами и метками для каждого контекста, чтобы данные для одного контекста отправлялись одному исполнителю Spark. Различные контексты разбиты, чтобы обеспечить создание распределенных функций. Моментальные снимки загружаются как RDD (контекст, карта (ключ данных - ›элемент данных)) ленивым способом, а серия объединений между данными метки и всеми необходимыми контекстно-зависимыми элементами данных выполняется с помощью Spark.
Для контекстно-независимых элементов данных DeLorean рассылает эти массовые элементы данных каждому исполнителю. Поскольку эти элементы данных имеют управляемый размер и часто имеют медленную скорость изменения с течением времени, мы ведем запись каждого обновления, которое используем для возврата к соответствующей предыдущей версии. Они хранятся в памяти как одноэлементные объекты и становятся доступными для генераторов функций для каждого контекста, обрабатываемого исполнителем. Таким образом, для каждого контекста создается полная карта данных, содержащая контекстные данные, контекстно-зависимые элементы данных моментальных снимков и единичные экземпляры общих данных.
После создания функций в Spark данные представляются в виде Spark DataFrame со встроенной схемой. Для многих приложений персонализации нам необходимо ранжировать несколько элементов для каждого контекста. Чтобы избежать путаницы в процессе ранжирования, характеристики элементов в выходных данных сгруппированы по контексту. Окончательные функции сохраняются в Hive в формате Parquet.
Обучение, проверка и тестирование модели
Мы используем функции, созданные с помощью нашей машины времени, для обучения моделей, которые мы используем в различных частях наших рекомендательных систем. Мы используем стандартизированную схему для передачи DataFrames обучающих функций в алгоритмы машинного обучения, а также вычисляем прогнозы и метрики для обученных моделей для функции проверки и тестирования DataFrames. Мы также стандартизировали формат для сериализации моделей, которые мы используем для публикации моделей, которые впоследствии будут использоваться онлайн-приложениями или в других будущих экспериментах.
На следующей диаграмме показано, как мы проводим типичный эксперимент с машинным обучением. После того, как эксперимент будет спланирован, мы собираем набор данных о контекстах, элементах и метках. Затем создаются объекты для набора данных этикеток. Затем мы обучаем модели с использованием одно-машинных, многоядерных или распределенных алгоритмов и выполняем настройку параметров путем вычисления показателей на проверочном наборе. Затем мы выбираем лучшие модели и сравниваем их на тестовой выборке. Когда мы видим значительное улучшение автономных показателей по сравнению с производственной моделью и что результаты достаточно разные, мы разрабатываем A / B-тест, используя варианты модели, и запускаем его в интерактивном режиме. Если A / B-тест показывает статистически значимое увеличение основных показателей, мы его широко применяем. В противном случае мы учимся на результатах, чтобы перейти к следующей идее.
Выход в Интернет
Одним из основных мотивов создания DeLorean является совместное использование одних и тех же кодировщиков функций в автономных экспериментах и онлайн-системах оценки, чтобы гарантировать отсутствие расхождений между функциями, созданными для обучения, и теми, которые вычисляются онлайн в производственной среде. Когда идея готова для онлайн-тестирования, модель упаковывается с той же конфигурацией функций, которая использовалась DeLorean для создания функций.
Чтобы вычислить функции в производственной системе, мы напрямую вызываем наши онлайн-микросервисы для сбора элементов данных, необходимых для всех кодировщиков функций, используемых в модели, вместо того, чтобы получать их из моментальных снимков, как мы это делаем в автономном режиме. Затем мы собираем их в карты данных и передаем кодировщикам функций. Затем вектор признаков передается модели, обученной в автономном режиме, для вычисления прогнозов, которые используются для создания наших рекомендаций. На следующей диаграмме показан высокоуровневый процесс перехода от автономного эксперимента к онлайн-производственной системе, где блоки, выделенные желтым цветом, являются онлайн-системами, а те, которые выделены синим, - автономными системами. Обратите внимание, что кодировщики функций используются совместно в интерактивном и автономном режиме, чтобы гарантировать согласованность создания функций.
Заключение и дальнейшая работа
Собирая состояние онлайн-мира в определенный момент времени для избранного набора контекстов, мы смогли создать механизм, позволяющий повернуть время вспять. Распределенная, устойчивая вычислительная мощность Spark позволила нам делать снимки миллионов контекстов в день и реализовывать создание функций, обучение моделей и проверку в любом масштабе. DeLorean сейчас используется в производстве для генерации функций в некоторых из последних A / B-тестов нашей рекомендательной системы.
Однако это только начало, и есть много способов улучшить этот подход. Вместо периодического периодического создания снимков состояния мы можем создавать снимки на основе событий, например, в то время, когда конкретный участник посещает нашу службу. Чтобы избежать дублирования сбора данных, мы также можем фиксировать изменения данных вместо того, чтобы каждый раз делать полные снимки. Мы также планируем использовать возможности машины времени для других нужд при оценке новых алгоритмов и тестировании наших систем. Конечно, мы оставляем возможность путешествовать во времени в качестве будущей работы.
Быстрое экспериментирование - отличительная черта инновационной культуры. Сокращение времени разработки идеи - ключевой показатель, который мы используем для измерения успеха наших инфраструктурных проектов. Мы продолжим укреплять этот фундамент, чтобы обеспечить лучшую персонализацию Netflix в наших усилиях порадовать пользователей и выиграть моменты истины. Если вас интересуют такие временные инженерные задачи, присоединяйтесь к нам.
- от Хоссейн Тагави, Прасанна Падманабхан, ДБ Цай, Фейсал Закария Сиддики и Джастин Базилико
Смотрите также:
Первоначально опубликовано на сайте techblog.netflix.com 12 февраля 2016 г.