Темпоральная валидация
Как ты разбиваешь данные на train/test, решает не меньше, чем сама модель. Ниже — вживую: random vs leave-last-out vs global-temporal split на одном датасете. Урок: хорошая offline-метрика без правильного split — самообман.
⚠ Таблица сравнения посчитана офлайн (сравнить протоколы — значит переобучить модели под каждый split); страница её только показывает. Ползунок отсечки считается вживую, но дёшево (popularity — это счётчики).
1 · Глобальная отсечка по времени — вживую
Двигай отсечку: всё до неё — train, всё после — test. Смотри, как меняются размеры выборок, доля cold пользователей (кого не было в train) и метрика popularity. Popularity — это просто счётчики, поэтому считается мгновенно.
2 · Один датасет, три протокола сплита
Те же модели, посчитанные под тремя способами разбиения (обучены офлайн под каждый протокол — сравнить их — значит переобучить). Смотри, как random завышает числа и как под честным temporal ранжирование моделей переворачивается.
Теория простым языком
Как ты разбиваешь данные на train/test, влияет на метрику не меньше, чем сама модель. Неправильный split даёт красивые, но лживые числа: модель «подглядывает будущее» и на бумаге выглядит лучше, чем будет в проде.
Три протокола
Что показывает эксперимент
В таблице выше random даёт метрику примерно вдвое-втрое выше leave-last-out для большинства моделей — но не для всех: у content-based всё наоборот (0.0029 против 0.0083), так что «random всегда завышает» — упрощение. И это совокупный эффект, а не чистая цена утечки: вместе с ней меняется таргет (случайное событие вместо последнего), а с ним горизонт и сложность предсказания; вдобавок порог релевантности отбирает у двух протоколов разные подмножества юзеров (342 против 363, общих 230). Чтобы вычленить именно утечку, нужны одинаковые запросы и две версии фич — PIT против leaky; так это сделано в D2b. Под global temporal ранжирование переворачивается: popularity/NeuMF выходят вперёд, а MF/BPR падают почти в ноль. Разбивка warm / cold ниже показывает почему.
Виды утечки (leakage)
Важно: один и тот же user в train и test — это не утечка (warm-start это нормальный сценарий: есть история, предсказываем следующий айтем). Плохо другое:
В нашем эксперименте всё считается строго по train-окну: каждая модель обучается только на train-матрице протокола, popularity и novelty (self-information) — тоже по train, а TF-IDF content-модели берёт статичные признаки айтемов (жанры/теги), не тестовые interactions. Так утечки через фичи не возникает.
val ≠ test
Нужны три части, а не две: train (учим), validation (крутим гиперпараметры, раннюю остановку, отбор модели) и test (трогаем один раз в самом конце). Если подбирать гиперпараметры по тесту — ты в него утёк, и финальная оценка снова оптимистична.
|— train —————|— val —|— test —| → по времени
обучение тюнинг финальный
замер (1 раз)▸Rolling-window / time-based CV (для полноты)
В проде часто делают несколько последовательных срезов по времени (rolling / expanding window): обучились на [0..t], проверили на [t..t+Δ], сдвинули окно, повторили. Это даёт устойчивую оценку во времени и ловит сезонность/дрейф. Здесь не реализуем — идея та же: никогда не тестировать на прошлом относительно train.
Сильные стороны
- Global temporal честнее всего отражает прод: учимся на прошлом, предсказываем будущее.
- Правильный split ловит cold-start и дрейф — то, что random прячет.
- Отдельный validation бережёт test от утечки при тюнинге.
Слабые стороны
- Темпоральный split строже и «неудобнее»: числа ниже, cold-start больно бьёт.
- Меньше тестовых данных в позднем окне (маленькая когорта → шумная метрика).
- Global temporal-когорта не сравнима напрямую с 1-на-юзера протоколами (разный состав).
⚠️ Что может пойти не так
- Random split на временных данных — почти всегда оптимистичный самообман (модель видит будущее).
- Тюнинг гиперпараметров по тесту = утечка; заведи отдельный validation.
- Сравнивать метрики между протоколами «в лоб» нельзя, если у них разный состав/размер теста.
- Leave-last-out честнее random, но cross-user leakage всё ещё есть.
- Cold-start под global temporal — это фича, а не баг: так и выглядит реальный прод.
🧠 Проверь себя: Почему random split на данных со временем даёт оптимистичную метрику?
Где это в жизни
В любой продовой системе бэктест делают по времени (обучились на прошлом, замерили на следующем окне), а перед раскаткой — A/B (Фаза 14). Правильная валидация — это мост между «красивым offline» и «работает в проде»; следом идут feature store (те же фичи на обучении и в serving, без утечки) и мониторинг.