Модуль 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.
Что внутри:
- Выбор лучшей из двадцати моделей по валидации: насколько завышен результат победителя.
- K-fold руками. Разброс между фолдами против разницы между моделями.
- Подглядывание в тест: шестьдесят итераций «улучшений», измеряем деградацию.
- Утечка через нормализацию до разбиения: измеряем, сколько она дарит.
- Групповая утечка на данных с повторяющимися пользователями.
- Временной ряд: случайное разбиение против разбиения по времени на одних данных.
- Журнал прогонов в двадцать строк.
Часть 2. Аудит своей задачи¶
Возьмите любую свою задачу с обученной моделью.
- Пройдите по каталогу утечек и по каждому пункту напишите «нет, потому что…». Именно письменно: устно всегда всё в порядке.
- Посчитайте, сколько раз вы смотрели на тестовую метрику. Честно.
- Пересчитайте качество кросс-валидацией и постройте доверительный интервал.
- Сравните с числом, которое вы раньше считали результатом.
Четвёртый пункт бывает неприятным. Это и есть польза модуля.
Задание¶
- Реализуйте K-fold с группировкой: объекты одной группы не расходятся по фолдам.
- Симулируйте подглядывание: пятьдесят случайных моделей, выбор лучшей по тесту. Постройте разрыв между тестовой оценкой и честной.
- Возьмите набор с датой. Померьте качество случайным разбиением и разбиением по времени. Разница и есть цена ошибки.
- Внесите утечку намеренно: добавьте признак, слегка коррелированный с целью через будущее. Найдите его пермутационной важностью из модуля 6.
- Заведите журнал прогонов и прогоните через него все эксперименты этого модуля. Через неделю попробуйте воспроизвести любое число только по записи.
Проверка себя¶
- Зачем три выборки? Что именно завышается, когда их две?
- Почему тест открывается один раз?
- Разброс между фолдами больше разницы между моделями. Какой вывод?
- Каков единственный надёжный симптом утечки?
- Какой вопрос задать про каждый признак, чтобы поймать признак из будущего?
- Почему случайное разбиение временного ряда даёт бессмысленную метрику?
- Что такое групповая утечка и чем она лечится?
- Что должно быть в записи прогона, чтобы число можно было воспроизвести?
Дальше¶
Часть II закончена. Есть модели, метрики и процедура, которая делает их сравнение честным. Дальше — часть III: нейросети, начиная с обратного распространения, написанного с нуля на NumPy.
Всё, что там появится, будет сравниваться с бустингом из модуля 6 по правилам модуля 7. Иначе это будет не результат, а впечатление.
Приятная неожиданность в машинном обучении почти всегда означает утечку. Ищите её, а не радуйтесь.