Условие
В датасете 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 фичей)
- VarianceThreshold → убрать константные.
- Корреляция > 0.95 → оставить одну из пары.
- MI на y → топ-200 (грубый фильтр).
- LightGBM importance на 200 фич → топ-50.
- SHAP для финальной интерпретации.
- Валидировать на 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)Подводные камни
- Filter без модели даёт «информативные» фичи, но они могут быть взаимозаменяемы. Бустинг сам разберётся; селекция нужна для интерпретации/инференса.
- Корреляция Пирсона ловит только линейные связи; для нелинейных — MI / Spearman.
feature_importancesна категориальных с высокой cardinality (user_id) — раздуты. Использовать permutation / SHAP.- Selection на full data до train/test split = leakage. Делать только на train.
- L1 + сильно коррелирующие фичи: выберет одну случайным образом — между запусками меняется.
- Wrapper (RFECV) на 500 фич, 50К строк, 5 fold — часы/сутки. Использовать только после filter-этапа.
- «Меньше фич = лучше» — миф. Главное — не 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.