02 / 27 · Постановка задачи обучения
Данные, признаки и утечки
От сырых столбцов к матрице «объекты–признаки»: типы признаков, one-hot и целевое кодирование категорий и почему целевое кодирование даёт утечку, если не вычислять его вне фолда, масштабирование и почему оно нужно расстояниям и градиентному спуску, пропущенные значения, конструирование признаков и два способа обмануть оценку качества: утечка целевой переменной, когда признак несёт ответ, и загрязнение выборки, когда шаг предобработки видел данные для оценки.
Что такое машинное обучение · Математическое ожидание · Условная вероятность
На этой странице 13
Вводная заметка считала каждый объект вектором , а каждую обучающую строку — независимым наблюдением из того распределения, с которым встретится модель. Настоящие данные в исходном виде не удовлетворяют ни одному из этих предположений. В таблице кредитных заявок есть название района, дата, доход, который часть заявителей не указала, цель кредита свободным текстом и столбцы, заполненные через несколько месяцев после решения по заявке. Эта заметка превращает такие столбцы в числа, которыми может пользоваться модель, и показывает два способа, которыми информация о целевой переменной просачивается в признаки и выдаёт бесполезную модель за отличную.
Обозначения. В матрице «объекты–признаки» (object–feature matrix) одна строка на объект и один столбец на признак; — признак объекта , — значения целевой переменной. Категориальный признак принимает значения из конечного множества категорий; — число обучающих строк в категории , — их среднее значение целевой переменной; — общее среднее.
Какие бывают признаки#
| Тип | Пример | Что нужно модели |
|---|---|---|
| Числовой | доход, площадь, число покупок | часто масштаб и преобразование против асимметрии |
| Бинарный | есть ли автомобиль | ничего: 0 или 1 |
| Категориальный (номинальный) | район, товар, браузер | кодирование; у категорий нет порядка |
| Порядковый | уровень образования, оценка 1–5 | порядок, но не обязательно равные шаги |
| Дата и время | дата заявки | части (час, день недели, месяц) и разности («дней с момента») |
| Текст, множества | цель кредита, теги | вектор, например счётчики слов |
Тип определяет, какая арифметика имеет смысл. Среднее двух районов — не район, а кодирование районов числами 1, 2, 3 сообщает линейной модели, что район 2 лежит посередине между 1 и 3. Текст превращается в вектор счётчиков, мешок слов (bag of words); курс NLP строит его в заметках о тексте как данных и о мешке слов, и получающаяся матрица — это матрица «объекты–признаки» с десятками тысяч разреженных столбцов.
Кодирование категорий#
One-hot кодирование (one-hot encoding) заменяет признак с категориями на бинарных столбцов, по одному на категорию, с единственной единицей в каждой строке. Тогда линейная модель выучивает по одному весу на категорию — ровно ту свободу, которая должна быть у номинального признака. Если в модели есть свободный член, столбцов в сумме всегда дают 1 и дублируют его, поэтому один столбец обычно отбрасывают. Без штрафа это нужно для единственности решения: столбцов и свободный член линейно зависимы, и бесконечно много векторов весов дают одни и те же прогнозы. Со штрафом ridge или lasso можно оставить все столбцов, потому что штраф сам выбирает одно решение; отбрасывание столбца тогда меняет подгонку, ведь остальные веса сжимаются к отброшенной опорной категории, а не к общему значению. One-hot хорошо работает до нескольких сотен категорий; для признака со 100 000 значений (пользователь, магазин, почтовый индекс) матрица становится огромной, а у большинства категорий лишь горстка строк, так что их веса оцениваются почти ни по чему.
Целевое кодирование (target encoding) заменяет каждую категорию одним числом — оценкой среднего значения целевой переменной в этой категории, (Micci-Barreca, 2001). Для редкой категории сырое среднее шумное, поэтому его стягивают к общему среднему:
с весом априорного распределения , который действует как дополнительных строк со значением, равным общему среднему. Для бинарной целевой переменной это апостериорное среднее в бета-биномиальной модели с априорным распределением , центрированным в . Для числовой целевой переменной та же формула — общая оценка со сжатием (shrinkage); её байесовское прочтение — нормальная модель: нормальное априорное распределение на среднее категории с центром в и нормальный шум, где — отношение дисперсии шума к априорной дисперсии. В обоих случаях категория с большим числом строк сохраняет своё среднее, редкая остаётся близко к априорному значению. Разобранный пример. Категория из 3 строк, 2 из них положительные, в данных с общей долей положительных 0.2, закодированная с : вместо сырых .
Почему наивное целевое кодирование даёт утечку#
Если вычислено по обучающим строкам, оно включает целевое значение самой строки. В крайнем случае категория, встретившаяся один раз, кодируется собственной меткой, и модель, читающая кодировку, читает ответ. Prokhorenkova et al. (2018), раздел 3.2, приводят чистый вариант: если каждая категория уникальна, а метка — честная монета, закодированные обучающие строки идеально разделяются одним порогом, тогда как каждая тестовая строка получает априорное значение, и модель выдаёт 0.5. Распределение закодированного признака при заданной метке различается для обучающих и тестовых строк — а именно на таком различии модель обучать нельзя. Исключение только самой строки не помогает: для одной категории, общей для всех строк, среднее с исключением одного объекта (leave-one-out) принимает одно значение для положительных строк и другое для отрицательных, и снова их разделяет один порог (тот же раздел).
Исправление — кодировать каждую обучающую строку статистиками, вычисленными по другим строкам, как в кросс-валидации. Кодирование вне фолда (out-of-fold encoding) делит обучающую выборку на фолды и кодирует строки каждого фолда средними по остальным фолдам; тестовые строки получают средние по всей обучающей выборке. Восемь строк, две категории, два фолда, без сглаживания:
| Строка | Категория | Фолд | Наивное | вне фолда | |
|---|---|---|---|---|---|
| 1 | A | 1 | 1 | 0.75 | 1.0 |
| 2 | A | 0 | 1 | 0.75 | 1.0 |
| 3 | A | 1 | 2 | 0.75 | 0.5 |
| 4 | A | 1 | 2 | 0.75 | 0.5 |
| 5 | B | 0 | 1 | 0.25 | 0.5 |
| 6 | B | 1 | 2 | 0.25 | 0.0 |
| 7 | B | 0 | 2 | 0.25 | 0.0 |
| 8 | B | 0 | 1 | 0.25 | 0.5 |
Строки 1 и 2 кодируются по строкам 3 и 4 (обе положительные), строки 5 и 8 — по строкам 6 и 7. Значения вне фолда шумнее: это честная цена того, что мы не смотрим на собственную метку строки, и причина, по которой на практике их сглаживают. CatBoost использует упорядоченный вариант: он перемешивает строки и кодирует каждую по строкам, стоящим перед ней, так что каждая строка закодирована по данным, которые она могла бы увидеть в потоке (Prokhorenkova et al., 2018, раздел 3.2).
На данных, где категория не несёт никакой информации (2000 строк, 500 категорий примерно по 4 строки, случайные метки), логистическая регрессия на наивной кодировке достигает ROC-AUC 0.774 по кросс-валидации; с кодированием вне фолда, обученным внутри каждого обучающего фолда, она получает 0.48 — заслуженный уровень случайного угадывания (раздел «Код»).
Масштабирование#
Признак в рублях и признак в годах различаются по разбросу в тысячи раз, а некоторые методы обращаются с числами так, будто их единицы сравнимы.
- Расстояния. Два заявителя с доходами 50 000 и 52 000 и возрастом 25 и 60 лет находятся на евклидовом расстоянии : разница в возрасте почти ничего не добавляет. В наборе данных wine, входящем в scikit-learn, стандартное отклонение содержания пролина равно 314, а нефлавоноидных фенолов — 0.12; классификатор по 5 ближайшим соседям на сырых столбцах имеет долю правильных ответов (accuracy) по кросс-валидации 0.663, а на стандартизованных — 0.961. Заметка о методе ближайших соседей к этому вернётся.
- Градиентный спуск. Для метода наименьших квадратов с двумя независимыми центрированными признаками со стандартными отклонениями 1 и 100 поверхность потерь — эллипс, оси которого различаются в отношение масштабов, а число обусловленности около ; после стандартизации оно равно 1.02. Число шагов, которое нужно градиентному спуску, растёт с числом обусловленности, как показывает заметка о градиентном спуске.
- Штрафы. Регуляризация штрафует все коэффициенты одинаково, поэтому признак в крупном масштабе (с малым коэффициентом) штрафуется слабее, чем та же информация в мелком масштабе.
Стандартизация вычитает среднее каждого столбца и делит на его стандартное отклонение; min–max-масштабирование переводит каждый столбец в ; робастный вариант использует медиану и межквартильный размах, чтобы масштаб не задавали несколько экстремальных значений. Деревья делят выборку по порогам одного признака за раз, и на них не влияет никакое монотонное преобразование признака. Масштабирование не всегда улучшает результат: когда все признаки уже измеряются в одних единицах, как интенсивности пикселей, стандартизация раздувает шум почти постоянных столбцов. Каким бы ни был метод, его средние и разбросы — статистики данных, и вычислять их нужно только по обучающим строкам; раздел о загрязнении выборки уточняет это.
Пропущенные значения#
Значение может отсутствовать по важным причинам. Rubin (1976) различает значения, пропущенные полностью случайно (missing completely at random; сбой датчика, ни с чем не связанный), пропущенные случайно (missing at random) при заданных наблюдаемых признаках и пропущенные неслучайно (missing not at random), когда пропуск зависит от самого пропущенного значения (люди с высоким доходом отказываются его указывать). В последнем случае сам факт пропуска информативен.
Большинству моделей нужно число в каждой ячейке. Стандартный рецепт заполняет пропуск статистикой обучающего столбца (медианой для асимметричных признаков, самой частой категорией для категориальных) и добавляет бинарный индикатор пропуска (missing indicator). Для линейной модели вес индикатора сдвигает прогноз для строк с пропуском, так что модель фактически сама выучивает лучшее значение для заполнения, какую бы константу ни поставили в ячейку. Библиотеки градиентного бустинга обрабатывают пропуски сами: отправляют их в ту ветвь разбиения, которая сильнее уменьшает потери.
Конструирование признаков#
Модели находят закономерности в тех признаках, которые им дали; признаки определяют, какие закономерности найти легко. Типичные конструкции на табличных данных:
- Отношения и разности: долг, делённый на доход, цена квадратного метра, число дней между регистрацией и первой покупкой.
- Части дат: час суток, день недели, месяц, флаг праздника; и у спроса, и у мошенничества сильные недельные циклы.
- Преобразования асимметричных сумм: для доходов и сумм покупок, чтобы линейная модель видела относительные, а не абсолютные различия.
- Агрегаты по истории: число покупок за последние 30 дней, средний размер корзины, доля возвращённых заказов. Каждый из них нужно вычислять на момент прогноза, только по событиям до него.
- Взаимодействия: произведение двух признаков или пересечение двух категорий — для моделей, которые не могут построить их сами. Деревья и нейронные сети строят взаимодействия самостоятельно, и это одна из причин, по которым им нужно меньше ручного конструирования.
Утечка целевой переменной#
Утечка целевой переменной (target leakage) — это признак, несущий информацию о целевой переменной, которая не будет доступна в момент прогноза (Kaufman et al., 2012). Типичные формы:
- Вычислено после события. Предсказывать дефолт по кредиту по «числу звонков из отдела взыскания» или повторную госпитализацию по коду диагноза, внесённому при этой повторной госпитализации. Признак есть в исторической таблице потому, что исход уже произошёл.
- Замаскированная копия целевой переменной. «Общая выплаченная сумма» при предсказании того, будет ли кредит выплачен; «заполнена дата закрытия счёта» при предсказании оттока.
- Агрегаты, включающие будущее. «Средние траты клиента», вычисленные по всей истории, включая месяцы после даты прогноза.
- Артефакты сбора данных. Строки отсортированы по исходу, так что идентификатор строки предсказывает метку, или положительные примеры собраны из другого источника в другом формате файлов.
Симптом — модель, которая намного лучше, чем кто-либо ожидал, причём большую часть работы делают один-два признака. Лекарство — отметка времени для каждого признака и правило, что признак может использовать только информацию до момента прогноза. Kapoor and Narayanan (2023) нашли утечки в опубликованных работах из 17 научных областей, затрагивающие 294 статьи; в их повторном анализе прогнозирования гражданских войн сложные модели потеряли преимущество перед логистической регрессией, как только утечку устранили.
Загрязнение выборки#
Второму виду утечки плохой признак не нужен. Загрязнение выборки (train–test contamination) происходит, когда любой обучаемый на данных шаг конвейера, а не только модель, видит строки, используемые для оценки:
- масштабирование или заполнение пропусков, средние для которых вычислены по всем строкам;
- словарь, взвешивание TF-IDF или модель векторных представлений слов, обученные на всех текстах, — для текстов это обсуждается в заметке об оценке качества в NLP;
- целевое кодирование, вычисленное по всем строкам;
- отбор признаков (feature selection) по всем строкам: оставить признаки, сильнее всего коррелирующие с меткой, а затем оценить на них модель кросс-валидацией;
- дубликаты или почти дубликаты одного объекта в обучающей и оценочной выборках.
Насколько вреден шаг, зависит от того, сколько он узнаёт о целевой переменной. Масштабирование узнаёт только средние и разбросы; на данных wine стандартизация всех строк до кросс-валидации даёт ту же долю правильных ответов, что и стандартизация внутри каждого обучающего фолда, — 0.961 в обоих случаях. Отбор признаков узнаёт, какие признаки согласуются с метками всех строк, включая оценочные, и эффект получается драматическим. Классическая демонстрация использует данные вообще без сигнала (Hastie, Tibshirani and Friedman, 2009, раздел 7.10.2; Ambroise and McLachlan, 2002):
Отбор признаков вне кросс-валидации находит сигнал в чистом шуме
При 1000 шумовых признаков и 50 объектах некоторые признаки случайно согласуются с метками, и отбор по всем строкам выбирает именно те случайные совпадения, которые отложенные фолды затем «подтвердят». Утечка растёт с числом отобранных признаков, вплоть до оценённой доли правильных ответов 0.99 при 200 признаках, и исчезает, когда оставлены все 1000, потому что тогда ничего не отбиралось. Повторение отбора внутри каждого обучающего фолда устраняет её полностью: оценка остаётся на истинных 0.5. Раздел «Код» повторяет эксперимент с 10 000 шумовых признаков, 20 лучшими из них и логистической регрессией: 0.87 при отборе по всем строкам, 0.49 при отборе внутри фолдов.
Практическое правило — оформить каждый зависящий от данных шаг как часть модели, конвейер (pipeline), который обучается на обучающей части каждого разбиения и только применяется к оценочной части. В scikit-learn это make_pipeline(StandardScaler(), SelectKBest(...), LogisticRegression()), целиком переданный функции кросс-валидации. То же правило касается и человека, настраивающего модель: выбирать признаки, глядя на результаты на тестовой выборке, — это отбор признаков на тестовой выборке.
Что изменилось#
| Сырая таблица | Подготовленные признаки | |
|---|---|---|
| Категории | строки или произвольные коды | one-hot столбцы или целевое кодирование, вычисленное вне фолда |
| Масштабы | единицы измерения | стандартизованы там, где используются расстояния, градиенты или штрафы |
| Пропуски | пустые ячейки | заполнены по статистикам обучающей выборки, плюс индикатор |
| Время | столбец с датой | части, разности, агрегаты по истории на момент прогноза |
| Оценка качества | любое разбиение итоговой таблицы | каждый обучаемый шаг внутри обучающего фолда; признаки ограничены информацией, доступной в момент прогноза |
Ограничения#
- Конструирование признаков зависит от задачи. Никакой рецепт не скажет, какие отношения или агрегаты важны; решают знание предметной области и валидация, и каждый опробованный признак — ещё один шанс переобучиться под валидационную выборку.
- Утечку нельзя обнаружить по одной таблице. Заполнен ли столбец до или после исхода — факт о процессе, породившем данные, а не о числах.
- Кодирование вне фолда стоит дисперсии. Оно шумнее наивного и требует сглаживания; при очень редких категориях one-hot с регуляризацией или хеширование может оказаться лучше.
- Предположения о пропусках непроверяемы. Неслучайные пропуски нельзя отличить от случайных по одним наблюдаемым значениям.
Что нужно запомнить#
- Тип признака определяет допустимую арифметику; категориям нужно кодирование, датам — части и разности, тексту — вектор.
- One-hot даёт каждой категории свой вес; целевое кодирование заменяет категорию её сглаженным средним значением целевой переменной — апостериорным средним для бинарной целевой переменной и оценкой со сжатием в общем случае.
- Наивное целевое кодирование даёт утечку собственной метки строки, и leave-one-out тоже; вычисляйте его вне фолда или в случайном порядке.
- Масштабируйте признаки для моделей на расстояниях, для градиентного спуска и для штрафов; деревьям это безразлично.
- Заполняйте пропуски по статистикам обучающей выборки и добавляйте индикатор; сам пропуск может быть информативен.
- Утечка целевой переменной — признак, недоступный в момент прогноза. Загрязнение выборки — обучаемый шаг, видевший оценочные строки. Оба почти всегда делают оценки оптимистичными; отбор признаков по всем строкам может превратить чистый шум в 99% правильных ответов.
- Помещайте каждый обучаемый шаг в конвейер, который заново обучается внутри каждого обучающего фолда.
Код#
Сглаженное целевое кодирование и кодирование вне фолда, отбор признаков внутри и вне кросс-валидации, масштабирование. Напечатанные числа получены с NumPy 2.1.3 и scikit-learn 1.9.1. Передать разбиватель в cv у TargetEncoder можно только в scikit-learn 1.9 и новее; в версиях от 1.3 до 1.8 TargetEncoder(cv=5, shuffle=True, random_state=0) делает такое же кодирование вне фолда со стратифицированными внутренними фолдами (его AUC в scikit-learn 1.9.1 равен 0.493):
import numpy as np
from sklearn.datasets import load_wine
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import KFold, StratifiedKFold, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler, TargetEncoder
rng = np.random.default_rng(0)
cv = StratifiedKFold(5, shuffle=True, random_state=0)
# smoothed target encoding of a category with 3 objects, 2 positive, global rate 0.2, prior weight 10
print(round((2 + 10 * 0.2) / (3 + 10), 3)) # 0.308
# pure noise: 100 objects, 10,000 features, labels independent of everything; keep the 20 "best" features
X = rng.normal(size=(100, 10_000))
y = rng.permutation(np.repeat([0, 1], 50))
X20 = SelectKBest(f_classif, k=20).fit_transform(X, y) # selected on all 100 rows: leak
wrong = cross_val_score(LogisticRegression(), X20, y, cv=cv).mean()
right = cross_val_score(make_pipeline(SelectKBest(f_classif, k=20), LogisticRegression()), X, y, cv=cv).mean()
print(round(wrong, 3), round(right, 3)) # 0.87 0.49
# target encoding of a noise category with 500 values (about 4 rows each), 2,000 rows, random labels
cat = rng.integers(0, 500, size=(2000, 1))
yb = rng.integers(0, 2, size=2000)
counts, sums = np.bincount(cat[:, 0], minlength=500), np.bincount(cat[:, 0], weights=yb, minlength=500)
naive = (sums / np.maximum(counts, 1))[cat] # mean label of the category, own row included
auc_naive = cross_val_score(LogisticRegression(), naive, yb, cv=cv, scoring='roc_auc').mean()
oof = make_pipeline(TargetEncoder(cv=KFold(5, shuffle=True, random_state=0)), LogisticRegression()) # разбиватель в cv: scikit-learn >= 1.9
auc_oof = cross_val_score(oof, cat, yb, cv=cv, scoring='roc_auc').mean() # out-of-fold encoding inside each fold
print(round(auc_naive, 3), round(auc_oof, 3)) # 0.774 0.48
# scaling: kNN on the wine data, raw, standardized on all rows, standardized inside each training fold
Xw, yw = load_wine(return_X_y=True)
print(Xw.std(axis=0).max().round(2), Xw.std(axis=0).min().round(2)) # 314.02 0.12 (proline, nonflavanoid phenols)
raw = cross_val_score(KNeighborsClassifier(5), Xw, yw, cv=cv).mean()
outside = cross_val_score(KNeighborsClassifier(5), StandardScaler().fit_transform(Xw), yw, cv=cv).mean()
inside = cross_val_score(make_pipeline(StandardScaler(), KNeighborsClassifier(5)), Xw, yw, cv=cv).mean()
print(round(raw, 3), round(outside, 3), round(inside, 3)) # 0.663 0.961 0.961
# the condition number of X^T X for two independent features on scales 1 and 100, raw and standardized
Z = rng.normal(size=(10_000, 2)) * [1, 100]
Zs = StandardScaler().fit_transform(Z)
print(f'{np.linalg.cond(Z.T @ Z):.2g}', f'{np.linalg.cond(Zs.T @ Zs):.3g}') # 1e+04 1.02
Дальше#
Утечка и загрязнение — это провалы оценки качества: модель проверяют на данных, которые она так или иначе уже видела. Избежать их необходимо, но недостаточно. Даже при чистом разбиении оценка на валидации — случайное число с погрешностью, модель, выбранная как лучшая из многих, выглядит лучше, чем она есть, а способ разбиения данных должен соответствовать тому, как модель будут применять. Следующая заметка строит протокол оценки: отложенные выборки, кросс-валидацию, разбиения по группам и по времени и вложенную кросс-валидацию для выбора моделей.
Литература#
- E. Sokolov et al. Машинное обучение (HSE), семинар 4: предобработка данных и лекция 1: введение.
- D. Micci-Barreca. A preprocessing scheme for high-cardinality categorical attributes in classification and prediction problems. ACM SIGKDD Explorations, 3(1), 2001.
- L. Prokhorenkova, G. Gusev, A. Vorobev, A. V. Dorogush, A. Gulin. CatBoost: unbiased boosting with categorical features. NeurIPS, 2018. Раздел 3.2 (целевые статистики: жадные, на отложенной выборке, leave-one-out и упорядоченные).
- D. B. Rubin. Inference and missing data. Biometrika, 63(3), 1976.
- S. Kaufman, S. Rosset, C. Perlich, O. Stitelman. Leakage in data mining: formulation, detection, and avoidance. ACM Transactions on Knowledge Discovery from Data, 6(4), 2012.
- S. Kapoor, A. Narayanan. Leakage and the reproducibility crisis in machine-learning-based science. Patterns, 4(9), 2023.
- C. Ambroise, G. J. McLachlan. Selection bias in gene extraction on the basis of microarray gene-expression data. PNAS, 99(10), 2002.
- T. Hastie, R. Tibshirani, J. Friedman. The Elements of Statistical Learning, 2nd ed. Springer, 2009. Раздел 7.10.2 (неправильный и правильный способы кросс-валидации).