Как бороться с даталиками при использовании target encoding
Что проверяют
Понимаете ли вы механизм утечки, а не просто помните слово «кросс-валидация». Target encoding подставляет в признак среднее таргета, в котором участвует таргет самой строки, и модель учится его считывать. Следом спрашивают про leave-one-out и про то, как не допустить утечку при подборе гиперпараметров.
Как отвечать
Сначала механизм. Если у категории три строки, её код на трейне почти равен их таргету, а у категории из одной строки совпадает с ним точно. На трейне признак выглядит сверхсильным, на новых данных нет, и валидация обещает качество, которого в проде не будет.
Способы защиты:
- out-of-fold кодирование: трейн делится на K фолдов, каждый фолд кодируется статистиками, посчитанными по остальным. Тестовая выборка кодируется по всему трейну;
- сглаживание: среднее категории смешивается с глобальным с весом по числу наблюдений,
(n * mean_cat + m * global) / (n + m). Редкие категории стягиваются к общему среднему; - шум: небольшой гауссов шум в закодированный признак на трейне, как дополнение, а не замена;
- упорядоченные статистики: CatBoost считает код строки только по строкам, стоящим раньше в случайной перестановке, и делает это из коробки;
- энкодер в пайплайне: он обучается внутри
Pipeline, иначе утечка переедет уже в оценку на кросс-валидации.
Из инструментов: sklearn.preprocessing.TargetEncoder сам делает кросс-фиттинг в fit_transform и сглаживание через smooth="auto". В category_encoders есть TargetEncoder со сглаживанием, LeaveOneOutEncoder с параметром sigma и CatBoostEncoder.
Где спотыкаются
Кодируют весь датасет до разбиения на трейн и тест. После этого любая метрика на тесте завышена.
Вызывают у sklearn fit(X, y).transform(X) на трейне вместо fit_transform. Второй вариант кодирует по фолдам, первый нет, и утечка возвращается.
Считают leave-one-out безопасным. Для бинарного таргета внутри категории код строки с единицей чуть ниже, чем у строки с нулём, и деревья это разделяют. Ещё одна ловушка: случайные фолды там, где данные упорядочены во времени или сгруппированы по пользователю. Нужны TimeSeriesSplit или GroupKFold.