После добавления SMOTE вырос CV-скор, а качество в проде не изменилось
Антифрод, примерно 1 положительный на 1000 сессий. После добавления техники oversampling'а SMOTE кросс-валидационный ROC-AUC вырос с 0.81 до 0.95, но качество алертов в проде не изменилось совсем.
Ограничение: модель, признаки и схема CV не менялись; добавлен только шаг ресемплинга.
X_res, y_res = SMOTE(random_state=0).fit_resample(X, y)
scores = cross_val_score(model, X_res, y_res,
cv=StratifiedKFold(5), scoring="roc_auc")
print(scores.mean()) # 0.95, было 0.81
Определите причину.
Ресемплинг сделан до разбиения: синтетические точки из обучающих строк попали в валидационный фолд, поэтому CV оценивает почти копии виденного и завышает результат. Ресемплить надо внутри каждого фолда, только на обучающей части — через pipeline, переобучаемый на каждом фолде.
- ✗Вызывать
fit_resampleна всём датасете до разбиения на train и валидацию - ✗Считать, что синтетические строки не могут утечь, раз это новые объекты
- ✗Ресемплить валидационный или тестовый фолд под баланс обучающей части
- →Какой класс библиотеки
imbalanced-learnдержит ресемплер внутри фолда? - →Избежали бы вы этой утечки полностью, взяв веса классов вместо SMOTE?
Утечка возникает из-за порядка операций. fit_resample вызывается на всём датасете до разбиения: SMOTE строит синтетическую точку по отрезку между двумя реальными миноритарными объектами, и оба «родителя» вместе со своим потомком расходятся по разным фолдам. Валидационный фолд получает почти копии обучающих строк, ROC-AUC растёт, а в проде такой пары нет — и качество не меняется.
Лечится переносом ресемплинга внутрь фолда: Pipeline из imbalanced-learn применяет ресемплер только на обучающей части каждого разбиения и не трогает валидационную.
from imblearn.pipeline import Pipeline
from imblearn.over_sampling import SMOTE
pipe = Pipeline([("smote", SMOTE(random_state=0)), ("model", model)])
scores = cross_val_score(pipe, X, y, cv=StratifiedKFold(5), scoring="roc_auc")
print(scores.mean()) # снова около 0.81 — честная оценка