Рекомендации музыки: двухэтапная система на данных Яндекс Музыки

Отбор кандидатов из пяти источников и ранжирование LightGBM на открытом датасете Yambda-50M. Честная проверка по времени, две задачи — «прослушает» и «откроет», доверительные интервалы, смещение обратной связи. Отчёт собран 25.10.2026; код — GitHub.

1. Данные

Yambda-50M — открытый датасет Яндекс Музыки: 47 470 030 событий 10 000 пользователей с 930 788 треками за 1 498 дней — прослушивания с долей прослушанного, лайки и дизлайки. У каждого события есть флаг: нашёл пользователь трек сам или трек показала рекомендательная система (48 % прослушиваний).

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

Положительный отклик — трек прослушан больше чем наполовину (определение авторов датасета) или лайкнут. 27 % прослушиваний обрываются на первых 10 % трека.

2. Как проверяем

План записан до обучения моделей (PLAN.md). Разбиение только по времени: модели учатся на истории, проверяются на следующей неделе. Ранкер учится на одной неделе, подбирается на следующей (валидация), а финальные цифры ниже — на последней неделе (тест), которая открывалась один раз. Метрика — NDCG@10; разницы моделей — 95 % доверительные интервалы бутстрепом по пользователям (парные: одни и те же пользователи).

Двухэтапная схема, как в индустрии: несколько источников кандидатов (популярное, «ещё раз своё», треки любимых исполнителей, «слушают вместе», ALS) дают около 500 треков на пользователя, затем LightGBM (LambdaRank) переранжирует их по признакам трека, пользователя и пары.

3. Результат на тестовой неделе

«Прослушает»: ранкер 0,247 против «ещё раз своё» 0,226: +0,021 (95 % ДИ [+0,017; +0,025]).
«Откроет»: ранкер 0,054 против ALS 0,031: +0,023 [+0,020; +0,027], то есть +75 %. В кандидаты попадает 44 % и 23 % будущих откликов — это потолок, выше которого ранкер подняться не может.

4. Смещение обратной связи

Модели учатся на логах, которые сами частично создали: половина откликов — на треки, показанные прошлой рекомендательной системой. Если модель лишь воспроизводит старые рекомендации, она выиграет на откликах «из рекомендаций» и проиграет на треках, которые люди находят сами.

«Прослушает»: на треках, найденных самостоятельно, ранкер 0,292 против «ещё раз своё» 0,292, на пришедших из рекомендаций — 0,057 против 0,022. Почти весь выигрыш (+155 %) — на треках, которые показывала старая система: ранкер во многом учится предсказывать прошлые рекомендации, а на самостоятельных находках не лучше простого правила.
«Откроет»: сам нашёл — 0,034 против 0,018 у ALS, из рекомендаций — 0,057 против 0,033. Выигрыш есть на обоих типах: +85 % на самостоятельных находках и +71 % на треках из рекомендаций — это не только эхо старой системы.

5. Шире точности

Популярное почти ничего не покрывает: всем показывается один и тот же топ. Ранкер в задаче «откроет» показывает 1,3 % каталога, а доля «хитов» (топ-1 % треков) в его топ-10 — 39 %.

Абляция: значимо нужны только «ALS», «ещё раз своё» — без них качество падает. Источники «исполнители», «популярное», «слушают вместе» сверх них ничего не добавляют (интервал накрывает ноль): систему можно упростить, а вклад исполнителей сохраняется через признаки пары «пользователь — исполнитель».

6. Ловушка: случайное разбиение

Частая ошибка — делить взаимодействия на обучение и проверку случайно: модель видит будущие прослушивания соседей и «предсказывает» то, что уже знает.

Та же ALS со случайным разбиением показывает Recall@100 в 1,6 раза выше в задаче «откроет». Эффект меньше, чем часто пишут, но достаточно, чтобы выбрать не ту модель.

7. Что нести в A/B-тест