Собесов

DataLearn ML-101: Отбор признаков — filter / wrapper / embedded

ML / Data ScienceFeature selectionСредняяMiddle

Условие

В датасете 500 фичей и 50К строк. Бизнес просит модель «без лишних колонок» (поддержка/инференс). Какие методы отбора применить? Когда какой подходит? Опишите минимальный pipeline и подводные камни.

Решение

Три семейства

Семейство Идея Примеры Стоимость
Filter независимая статистика corr, χ², MI, ANOVA дёшево
Wrapper перебор подмножеств RFE, forward/backward дорого
Embedded внутри модели L1, feature_importances, SHAP средне

Filter

from sklearn.feature_selection import (VarianceThreshold,
                                       mutual_info_classif,
                                       SelectKBest, f_classif)
 
# 1) убрать почти-константные
vt = VarianceThreshold(threshold=0.01).fit(X)
X = X.loc[:, vt.get_support()]
 
# 2) мульти-коллинеарность через corr
corr = X.corr().abs()
to_drop = [c for c in corr.columns
           if any(corr[c].drop(c) > 0.95)]
X = X.drop(columns=to_drop)
 
# 3) MI (нелинейная зависимость)
mi = mutual_info_classif(X, y, random_state=0)
top = X.columns[np.argsort(mi)[::-1][:100]]

Embedded

L1 (Lasso) — обнуляет коэффициенты:

from sklearn.linear_model import LogisticRegressionCV
clf = LogisticRegressionCV(penalty='l1', solver='saga', Cs=10, cv=5, max_iter=2000).fit(X, y)
sel = np.where(clf.coef_[0] != 0)[0]

feature_importances в бустинге:

import lightgbm as lgb
model = lgb.LGBMClassifier().fit(X, y)
imp = pd.Series(model.feature_importances_, index=X.columns).sort_values(ascending=False)

SHAP — глобальная важность через TreeExplainer:

import shap
expl = shap.TreeExplainer(model)
shap_values = expl(X.sample(2000))
mean_abs = np.abs(shap_values.values).mean(axis=0)

Wrapper

from sklearn.feature_selection import RFECV
selector = RFECV(estimator=lgb.LGBMClassifier(),
                 step=5, min_features_to_select=20,
                 scoring='roc_auc', cv=5, n_jobs=-1).fit(X, y)
print("Optimal:", selector.n_features_)

Дорого, но даёт оптимальный набор для конкретной модели.

Pipeline под кейс (500 → ~50 фичей)

  1. VarianceThreshold → убрать константные.
  2. Корреляция > 0.95 → оставить одну из пары.
  3. MI на y → топ-200 (грубый фильтр).
  4. LightGBM importance на 200 фич → топ-50.
  5. SHAP для финальной интерпретации.
  6. Валидировать на out-of-fold: AUC не должен упасть > 1 п.п. vs full feature set.

Permutation importance vs gain

  • Gain (feature_importances): смещён к фичам с высокой cardinality.
  • Permutation importance: модель-агностична, дороже, честнее.
from sklearn.inspection import permutation_importance
r = permutation_importance(model, X_val, y_val, n_repeats=10, random_state=0)

Подводные камни

  1. Filter без модели даёт «информативные» фичи, но они могут быть взаимозаменяемы. Бустинг сам разберётся; селекция нужна для интерпретации/инференса.
  2. Корреляция Пирсона ловит только линейные связи; для нелинейных — MI / Spearman.
  3. feature_importances на категориальных с высокой cardinality (user_id) — раздуты. Использовать permutation / SHAP.
  4. Selection на full data до train/test split = leakage. Делать только на train.
  5. L1 + сильно коррелирующие фичи: выберет одну случайным образом — между запусками меняется.
  6. Wrapper (RFECV) на 500 фич, 50К строк, 5 fold — часы/сутки. Использовать только после filter-этапа.
  7. «Меньше фич = лучше» — миф. Главное — не upper bound сложности, а целевая метрика.

Эталонный ответ

Pipeline: (1) VarianceThreshold + удаление коррелирующих > 0.95; (2) MI на y → top-200; (3) LightGBM importance + permutation → top-50; (4) SHAP для интерпретации; (5) проверить, что AUC на OOF не упал > 1 п.п.

Когда что: filter — дешёвый префильтр; embedded (L1, importances, SHAP) — стандарт для табличных моделей; wrapper (RFECV) — для жёсткой оптимизации малого набора фич. Селекцию — только на train, иначе leakage.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти