Перейти к содержанию

Модуль 7. Честное сравнение

После этого модуля вы сможете

  • Объяснить, зачем нужны три выборки, а не две, и что происходит, когда их две.
  • Измерить на симуляции, насколько тест портится от подглядывания.
  • Найти утечку по симптому «метрика подозрительно хороша».
  • Разбить временной ряд так, чтобы результат не был выдумкой.
  • Вести журнал прогонов, из которого через полгода понятно, откуда взялось число.

Время: около недели. Пререквизиты: модуль 6. Ноутбук: notebooks/07-honest-comparison.ipynb

Зачем это

Самый короткий модуль части II и самый дорогой. Всё, что было раньше, производит числа. Этот модуль решает, значат ли они что-нибудь.

Модуль 1 перечислил шесть способов получить улучшение, которого нет. Три из них — подглядывание в тест, утечка и остановка по результату — живут именно здесь, в процедуре сравнения. Здесь они и лечатся.

Три выборки, а не две

Обучающая. На ней подбираются веса.

Валидационная. На ней принимаются решения: какая модель, какая глубина, какой \(\lambda\), когда остановиться.

Тестовая. На ней меряется итог. Открывается один раз.

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

Это то же множественное сравнение, что в модуле 1, шестой пункт. Из двадцати моделей одна окажется удачливой просто по построению.

Тест существует, чтобы измерить победителя на данных, которые не участвовали в его выборе. Если тест использовался для выбора, он больше не тест, а вторая валидация.

Тест открывается один раз

Посмотрели на тест, поменяли модель, посмотрели снова — тест испорчен. Не кодом, а вами: вы стали каналом, через который информация из теста просочилась в модель.

Через двадцать таких итераций тестовая метрика завышена так же, как валидационная. Это не теория — это измеряется, и в ноутбуке это третий сюжет.

Кросс-валидация

Одно разбиение даёт одно число, а из модуля 1 известно, чего стоит одно число.

K-fold: разбить данные на \(K\) частей, обучиться на \(K-1\) и померить на оставшейся, повторить \(K\) раз. Получается \(K\) измерений: есть среднее, есть разброс, есть доверительный интервал по бутстрэпу из модуля 1.

Что здесь важно и часто игнорируется: разброс между фолдами обычно больше, чем разница между сравниваемыми моделями. Модель А со средним 0.85 против модели Б со средним 0.84 при разбросе фолдов 0.03 — это не победа А, а отсутствие данных для вывода. Правило перекрытия интервалов из модуля 1 работает и здесь.

Выбор \(K\): пять или десять. Больше — дороже и почти без пользы. Меньше — обучающая выборка заметно урезается.

Если гиперпараметры подбираются кросс-валидацией, а качество меряется ею же, вы вернулись к двум выборкам. Честный вариант — вложенная кросс-валидация: внешний цикл меряет, внутренний подбирает. Дорого, поэтому чаще держат отдельный тест.

Утечка

Утечка — попадание в обучение информации, которой в момент предсказания не будет.

Симптом всегда один и тот же: метрика внезапно стала гораздо лучше. Приятная неожиданность в машинном обучении почти всегда означает ошибку, и первым делом надо искать её, а не радоваться.

Каталог случаев, каждый из которых встречается регулярно:

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

Дубликаты. Один объект в обучении и в тесте. На пользовательских данных это обычное дело: один человек оставил несколько записей.

Признак из будущего. «Дата закрытия сделки» в задаче предсказания закрытия сделки. Проверка одна: был бы этот признак известен в момент, когда предсказание реально нужно?

Признак-следствие. Не будущее формально, но порождён целью. Число обращений в поддержку как признак оттока: обращаются потому, что уже уходят.

Групповая утечка. Записи одного пациента, одного магазина, одной сессии разъехались по обучению и тесту. Модель узнаёт группу, а не закономерность. Лечится группировкой при разбиении.

Временные ряды

Отдельный случай, где стандартное случайное разбиение просто неверно.

Случайно перемешав данные во времени, вы обучаете модель на будущем и меряете на прошлом. Метрика получится отличной и не будет значить ничего.

Правильно — разбиение по времени: обучение на прошлом, проверка на будущем, окно двигается вперёд. Обучились на январе–марте, померили на апреле. Обучились на январе–апреле, померили на мае.

Цена ошибки измерима. В ноутбуке на ряде, где связь прошлого с будущим медленно дрейфует — а так ведут себя спрос, цены и поведение пользователей, — случайное разбиение показывает ошибку в два с половиной раза меньше настоящей.

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

Журнал прогонов

Последний сюжет, и он про через полгода.

Число в отчёте живёт дольше, чем память о том, как оно получено. Через полгода вопрос «почему здесь 0.87» не имеет ответа, если не записано.

Минимум, который стоит записывать по каждому прогону:

  • что за модель и все её параметры,
  • зерно и все зёрна, если их несколько,
  • какие данные — версия набора, а не просто имя файла,
  • git-коммит кода,
  • версии ключевых библиотек,
  • метрики на валидации и на тесте, если тест открывался,
  • дата и время.

Почему в списке версии библиотек

Ноутбук к модулю 5 этого курса считал площадь под кривой через np.trapz. На машине автора стоял NumPy 1.26, и всё работало. В NumPy 2 эту функцию убрали, и первый же прогон в CI упал с AttributeError.

Код не менялся, данные не менялись, зерно то же. Сломала прогон строчка в чужом списке изменений. Без записи версий такое расследование начинается с нуля.

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

Формат вторичен. CSV работает; для больших проектов есть инструменты — в частности mlango, где прогон открывается одной командой и записывается вместе с коммитом и артефактом. Но CSV, который вы действительно ведёте, лучше инструмента, который вы поставили и забыли.

Проверка качества журнала одна: можно ли по записи воспроизвести число. Не можете — журнал не работает.

Практика

Часть 1. Ноутбук

Откройте notebooks/07-honest-comparison.ipynb.

Что внутри:

  1. Выбор лучшей из двадцати моделей по валидации: насколько завышен результат победителя.
  2. K-fold руками. Разброс между фолдами против разницы между моделями.
  3. Подглядывание в тест: шестьдесят итераций «улучшений», измеряем деградацию.
  4. Утечка через нормализацию до разбиения: измеряем, сколько она дарит.
  5. Групповая утечка на данных с повторяющимися пользователями.
  6. Временной ряд: случайное разбиение против разбиения по времени на одних данных.
  7. Журнал прогонов в двадцать строк.

Часть 2. Аудит своей задачи

Возьмите любую свою задачу с обученной моделью.

  1. Пройдите по каталогу утечек и по каждому пункту напишите «нет, потому что…». Именно письменно: устно всегда всё в порядке.
  2. Посчитайте, сколько раз вы смотрели на тестовую метрику. Честно.
  3. Пересчитайте качество кросс-валидацией и постройте доверительный интервал.
  4. Сравните с числом, которое вы раньше считали результатом.

Четвёртый пункт бывает неприятным. Это и есть польза модуля.

Задание

  1. Реализуйте K-fold с группировкой: объекты одной группы не расходятся по фолдам.
  2. Симулируйте подглядывание: пятьдесят случайных моделей, выбор лучшей по тесту. Постройте разрыв между тестовой оценкой и честной.
  3. Возьмите набор с датой. Померьте качество случайным разбиением и разбиением по времени. Разница и есть цена ошибки.
  4. Внесите утечку намеренно: добавьте признак, слегка коррелированный с целью через будущее. Найдите его пермутационной важностью из модуля 6.
  5. Заведите журнал прогонов и прогоните через него все эксперименты этого модуля. Через неделю попробуйте воспроизвести любое число только по записи.

Проверка себя

  1. Зачем три выборки? Что именно завышается, когда их две?
  2. Почему тест открывается один раз?
  3. Разброс между фолдами больше разницы между моделями. Какой вывод?
  4. Каков единственный надёжный симптом утечки?
  5. Какой вопрос задать про каждый признак, чтобы поймать признак из будущего?
  6. Почему случайное разбиение временного ряда даёт бессмысленную метрику?
  7. Что такое групповая утечка и чем она лечится?
  8. Что должно быть в записи прогона, чтобы число можно было воспроизвести?

Дальше

Часть II закончена. Есть модели, метрики и процедура, которая делает их сравнение честным. Дальше — часть III: нейросети, начиная с обратного распространения, написанного с нуля на NumPy.

Всё, что там появится, будет сравниваться с бустингом из модуля 6 по правилам модуля 7. Иначе это будет не результат, а впечатление.

Приятная неожиданность в машинном обучении почти всегда означает утечку. Ищите её, а не радуйтесь.