От Soda Luv до BTS: как ВКонтакте рекомендует музыку миллионам пользователей

Всем привет! Это Даня Самойлов и Женя Замятин, мы из команды Core ML, занимаемся рекомендациями VK Музыки. В этой статье мы хотим поделиться с вами, как устроена наша система музыкальных рекомендаций (на примере алгоритмического плейлиста «Для вас»), и рассказать об интересных решениях, принятых на каждом этапе.

6fbdd9750b89d4730e31e5a1075ceb1b.png

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

abbb8ae7df46911a8ae1d4e56a202fbe.jpg

Первый уровень

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

ALS

Для отбора кандидатов мы используем эмбеддинги, полученные с помощью ALS. Кроме него, мы пробовали w2v (статья Deezer про w2v в рекомендациях музыки), но ALS показал себя значительно лучше. Факторизация у нас implicit, раскладываем матрицу взаимодействий пользователя с треками — на пересечении стоит количество запусков трека. На выходе получаем две матрицы — матрицу пользователей и матрицу трековR=U^T * I. Дальше нам понадобится сделать шаг ALS — одну итерацию расчёта ALS-факторизации матрицы A-B при зафиксированной матрице A. После получения матрицы треков мы делаем из неё шаг ALS для получения матрицы плейлистов (раскладываем матрицу «плейлист — трек»), матрицы стилей (раскладываем матрицу «стиль — трек») и матрицы артистов (раскладываем матрицу «артист — трек»). А из матрицы юзеров мы делаем шаг ALS для получения матрицы сообществ (раскладываем матрицу «юзер — сообщество»). Все эти разложения нам нужны в дальнейшем для использования эмбеддингов в рекомендациях. Так как они все из одного векторного пространства, то мы можем найти к необходимому плейлисту ближайшего артиста, к пользователю — ближайший стиль и так далее.

Индексы эмбеддингов

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

3e725cebb79058251364a23163c3d402.jpg

Онлайн ALS

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

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

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

f9a4ebe901c487758155047a591566cc.jpg

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

Кластеризация

Для кластеризации мы используем spherical k-means. Таким образом мы кластеризуем прослушки и добавления пользователя и для каждого кластера вычисляем one-step ALS из треков в пользователя. Благодаря этому теперь для юзера у нас есть несколько его эмбеддингов, которые в совокупности полностью покрывают его вкусы. Для каждого из них мы находим ближайших соседей и используем полученные треки в качестве кандидатов для ранжирования. Получившийся набор треков для ранжирования в полной мере охватывает предпочтения пользователя. 

Второй уровень

На второй уровень поступают кандидаты с первого уровня — набор треков, которые должны понравиться пользователю, и для которых осталось только провести ранжирование. Этот этап также очень важен, потому что после открытия алгоритмического плейлиста пользователь видит лишь часть верхних треков, которые должны его заинтересовать, чтобы он начал слушать плейлист. Поэтому для набора треков после нахождения «ближайших соседей» мы должны прокинуть в топ самые релевантные треки.

Модель ранжирования

В качестве модели ранжирования мы используем дописанный нами pairwise-мультитаргет XGB, который обучается распределённо на нескольких машинах. В качестве таргетов мы используем сигнал добавления трека в «Мою музыку», сигнал прослушивания трека (>60% трека прослушано) и сигнал скипа трека. Подробнее про эту модель мы расскажем в отдельной статье.

Признаки для ранжирования

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

Признаки пользователя

Самые базовые признаки — информация о пользователе. Его пол, возраст, страна, подписки и другая полезная информация — всё это использует модель.

Признаки, завязанные на прослушивания трека и артиста, а также истории прослушиваний пользователя

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

Признаки, завязанные на коллаборативные эмбеддинги

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

Анализ звучания песен. Признаки на основе контентных данных

Для контентных признаков мы используем данные, которые были получены в результате обработки аудиозаписей с помощью нейросети PANNs. Нейросеть имеет 527 выходов, каждый из которых отвечает за отдельный вид «звука», будь то мужское пение, барабаны или шум воды. Нейросеть может работать только с ограниченными по длине отрезками аудиозаписей, поэтому для каждого трека есть несколько записей с результатами обработки. Так перед нами встала задача агрегации этих данных. В качестве агрегирующих функций мы решили взять среднее значение, максимальное значение и энтропию. Рассматривались также минимальные значения, мода, медиана, но они не получили должного importance в модели, поэтому от них было решено отказаться в пользу производительности. Наша модель ранжирования использует эти агрегированные признаки из PHP-кода.

Но было недостаточно добавить контентные признаки только для аудиозаписей. Чтобы модель могла работать корректно, необходимо было добавить пользовательские контентные признаки исходя из истории взаимодействия юзера с треками. Во время генерации рекомендаций на этапе извлечения признаков для пользователя берётся история его прослушиваний, и контентные признаки треков агрегируются для получения пользовательских признаков. Для признаков, которые были рассчитаны с помощью среднего значения, мы считали «честное среднее» по следующей формуле:

\dfrac{\sum_i x_i * n_i}{\sum_i n_i},

где x_i— значение признака трека, n_i— количество отрезков трека.

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

Внедрение данных признаков дало прирост прослушиваний и таймспента.

Признаки на основе данных Discogs

После успеха внедрения контентных данных было решено продолжить двигаться в направлении использования контентной информации для улучшения рекомендаций. Мы выбрали open-source базу данных Discogs, в которой собрана метаинформация о треках — жанры, стили, страна, год релиза и другая полезная информация. Из этих данных мы хотели использовать жанры и стили — их можно было бы применить в различных местах рекомендательной системы: в качестве признаков для модели ранжирования или в кластеризации в качестве опорных векторов. Также с помощью информации о жанрах и стилях можно более точно определить вкус и предпочтения пользователя, а значит, рекомендовать всё более релевантные треки. Плюсом к этому шло то, что стилей в этой базе данных более 500, а значит, есть возможность довольно детально описать вкусы пользователя.

В сыром виде использовать эти данные было невозможно, потому что они не покрывали нашу базу треков. Тогда было решено обучить модели для предсказания жанров и стилей из контентного эмбеддинга с прошлого этапа. Контентный эмбеддинг — предпоследний слой нейросети PANNs, упомянутой ранее. Мы обработали данные Discogs и сопоставили её с нашей базой, а затем обучили модели. Далее разметили с помощью этих моделей всю нашу базу треков. Теперь для каждого трека в нашей базе указаны его жанр и стили. Эти данные мы протащили в признаки, считая в онлайне предпочтения пользователя по жанрам и стилям, а затем, сравнивая с жанром и стилем трека, получили попарные признаки трека и юзера.

Внедрение этих признаков также дало прирост прослушиваний и таймспента.

Разнообразие и фильтры

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

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

В заключение мы фильтруем треки по доступности, корректности id и прочим техническим ограничениям.

Подготовка ответа

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

Заключение

Данная статья — лишь часть нашей музыкальной рекомендательной системы. Здесь мы не описывали рекомендации UGC-плейлистов, «Показать похожие» треки и прочее, но постарались осветить основные детали и интересные решения, которые пронизывают нашу систему. Надеемся, что вы найдёте в этой статье полезные рекомендации и узнаете что-то новое для себя.

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

© Habrahabr.ru