Как устроен эксперимент

Все числа базовой части — от popularity до matrix factorization — получены по одному протоколу на MovieLens. Инженерный трек считается на другом логе (Brightkite) и по своему протоколу, и сравнивать числа между треками нельзя. Здесь — как делим данные, что считаем «правильным ответом», какой контракт замера и где этот протокол хитрит.

▸Почему нужен честный протокол оценки
БылоМожно было просто перемешать оценки и посчитать метрику.
ПроблемаRandom split легко даёт утечку и слишком оптимистичную оценку.
ИдеяИмитировать будущее хотя бы внутри пользователя: учимся на его прошлых взаимодействиях, проверяем на последнем.
Стало лучшеОценка ближе к настоящей задаче — «что рекомендовать дальше».
Осталось слабымLeave-last-out не равен полноценному global temporal split (события разных пользователей пересекаются во времени); offline ≠ online; один test-айтем упрощает реальность.
ДальшеTemporal split — темпоральная валидация; impression-aware негативы — данные и логи; и A/B (в следующих модулях).

Данные

Датасет ml-latest-small (его же называют MovieLens-small): 610 пользователей, ~100 000 оценок по шкале 0.5–5. Точная версия, счётчики и контрольные суммы — в блоке «Версия датасета» ниже: GroupLens не архивирует прежние выпуски, так что без этого протокол невоспроизводим.

Explicit feedback (явный отклик) — пользователь прямо выставил оценку (звёзды). Это не то же самое, что просто «посмотрел» — у нас есть и факт взаимодействия, и его сила.

Что считается «правильным ответом»

Чтобы проверить модель, надо знать, что для пользователя было бы хорошей рекомендацией. Мы берём за «правильный ответ» фильм, который он реально оценил высоко.

Порог релевантности — минимальная оценка, при которой фильм считается «понравившимся». По умолчанию ≥ 4 из 5. Если последний фильм пользователя оценён ниже — это не «релевантный» айтем, и такой пользователь в подсчёте точности не участвует. Порог можно менять на странице метрик и смотреть, как плывут числа.

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

Как делим на train и test

Leave-last-out — у каждого пользователя прячем его последнее по времени взаимодействие — оно уходит в test, всё остальное остаётся в train. Модель учится на прошлом и пытается предсказать «следующий» фильм. Это имитирует реальную задачу: предсказать будущее, а не подсмотреть его.

Из самих рекомендаций мы убираем то, что пользователь уже видел (любой оценки). Это политика этого эксперимента, а не общее правило: в музыке, продуктовых корзинах и коротких лентах повторное потребление — норма, и там «убрать увиденное» выкинуло бы как раз правильный ответ. В инженерном треке мы на это и натыкаемся: 89% следующих чек-инов в Brightkite — повторные визиты.

⚠️ Где наш протокол хитрит (и это нормально для учебного offline)

Утечка времени между пользователями. Мы прячем последний фильм у каждого человека по его личной хронологии. Но спрятанный у Ани фильм мог попасть в train к Боре, который посмотрел его позже Ани. Глобально по времени это не совсем честно. Более строгий вариант — единый временной срез по всем пользователям (global temporal split).

Подбор гиперпараметров. У большинства моделей (число факторов, регуляризация, эпохи) значения заданы руками и по метрикам не перебирались. Исключение — три ручки item-CF: усадка, число соседей и профиль из лайков. Их приходится выбирать, и выбирать их по test нельзя — иначе опубликованная метрика перестаёт быть оценкой на новых данных. Поэтому валидация откусывается от train тем же leave-last-out: у каждого пользователя прячется предпоследнее событие. Таблица отбора и цена ошибки — на странице item-CF.

Раньше здесь было написано, что гиперпараметры «ни под что не подбираются, поэтому утечки от тюнинга нет». Для item-CF это было неверно, и ревью поймало противоречие: λ выбиралась по тем же ndcg@10 / hit_rate@10, которые рядом печатались как результат.

Как считаем и усредняем

Для каждого оцениваемого пользователя строим топ-K рекомендаций, проверяем, попал ли туда спрятанный фильм, считаем метрики и усредняем по пользователям. K по умолчанию = 10.

⚠️ Одна метрика — разные реализации

«Recall@10» или «NDCG@10» в разных библиотеках и статьях считаются по-разному: как обрабатывают ничьи в ранжировании, как усредняют, что считают релевантным, как нормируют. Поэтому цифры из двух источников нельзя сравнивать вслепую — всегда нужно знать точный протокол. Сравнивать осмысленно можно только модели внутри одного протокола — как здесь.

Неожиданный вывод про popularity

Когда «релевантно» = «понравилось» (порог ≥ 4), простой popularity становится на удивление сильным и обходит matrix factorization по NDCG. Долгое время на этой странице стояло объяснение «популярные фильмы чаще получают высокие оценки» — без единого замера. Теперь замер есть, и он показывает две вещи сразу.

Связь популярности с оценкой действительно есть: в верхнем дециле каталога доля оценок ≥ 4 заметно выше, чем в хвосте (таблица ниже). Но это факт про оценки, а не объяснение результата: вместе с порогом меняются сразу и определение релевантности, и состав оцениваемых людей, так что свод по порогам сам по себе ничего не изолирует. Поэтому рядом стоит третья таблица — метрика внутри непересекающихся групп по оценке скрытого таргета. На фиксированном составе видно, что у моделей просто разные профили: MF сильнее там, где таргет оценён средне, popularity — там, где он оценён на пятёрку. Честная формулировка: порог выбирает подгруппу, и на ней расклад моделей другой.

Пример. Отсюда практическое правило: на одну accuracy-метрику нельзя смотреть в вакууме — рядом должны стоять определение релевантности, состав когорты и beyond-accuracy метрики.
Загружаем данные

Чем посчитаны интервалы и p-value

На страницах блока несколько раз встречаются доверительные интервалы и одно p-value. Числа повторов мало: чтобы их можно было повторить независимо, нужны единица ресэмплинга, статистика, способ построения интервала и seed. Всё это здесь и приезжает из того же артефакта, что и сами расчёты.

Загружаем данные

Что дальше

Опирается на этот модуль — здесь он нужен как предпосылка

Можно читать параллельно — тот же блок, порядок между ними не важен

До финальной сборки не хватает ещё 12 модулей по этому пути.

Порядок здесь — рекомендация из карты курса, ничего не блокируется. Отметка «прочитано» хранится только в этом браузере.

Источники