← Назад к вопросам

Как отберешь фичи, если их слишком много?

1.0 Junior🔥 151 комментариев
#Машинное обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как отберешь фичи, если их слишком много?

Имеем слишком много признаков (high-dimensional data) — это классическая задача feature selection. Существует несколько стратегических подходов, которые я использовал в practice.

1. Фильтровые методы (Filter Methods)

Это самый быстрый подход — отбираем фичи на основе статистических тестов, не обучая модель.

Корреляция с целевой переменной

import pandas as pd
import numpy as np
from sklearn.feature_selection import SelectKBest, f_classif, f_regression, mutual_info_classif

# Датасет с 1000 признаков
X = pd.DataFrame(np.random.randn(10000, 1000), columns=[f'feature_{i}' for i in range(1000)])
y = (X['feature_0'] + X['feature_1'] + np.random.randn(10000) * 0.1 > 0).astype(int)

# Способ 1: Отбираем топ-20 фич по корреляции с целевой переменной
correlations = []
for col in X.columns:
    corr = np.abs(np.corrcoef(X[col], y)[0, 1])
    correlations.append((col, corr))

correlations_sorted = sorted(correlations, key=lambda x: x[1], reverse=True)
top_features = [feat[0] for feat in correlations_sorted[:20]]
X_filtered = X[top_features]

print("Top 20 correlated features:")
for feat, corr in correlations_sorted[:20]:
    print(f"{feat}: {corr:.4f}")

Использование SelectKBest

from sklearn.feature_selection import SelectKBest, f_classif, chi2

# SelectKBest выбирает топ-K фич на основе статистического теста
# f_classif: ANOVA F-value (для regression f_regression)
# chi2: Chi-squared test (только для неотрицательных признаков)
# mutual_info_classif: Mutual Information (не требует нормального распределения)

# Для классификации
selector = SelectKBest(score_func=f_classif, k=50)  # выбираем 50 лучших
X_selected = selector.fit_transform(X, y)

# Получаем индексы выбранных фич
selected_mask = selector.get_support()
selected_features = X.columns[selected_mask]
print(f"Selected {len(selected_features)} features")
print(selected_features)

# Для регрессии
from sklearn.feature_selection import f_regression
selector_reg = SelectKBest(score_func=f_regression, k=50)
X_selected_reg = selector_reg.fit_transform(X, y_continuous)

Mutual Information

from sklearn.feature_selection import mutual_info_classif

# Mutual Information не предполагает линейную зависимость
# Хорошо работает с нелинейными паттернами

mi_scores = mutual_info_classif(X, y, random_state=42)
mi_scores_df = pd.DataFrame({
    'feature': X.columns,
    'MI_score': mi_scores
}).sort_values('MI_score', ascending=False)

print(mi_scores_df.head(20))

# Отбираем фичи с MI_score > threshold
threshold = np.percentile(mi_scores, 95)  # топ 5%
top_mi_features = X.columns[mi_scores > threshold]
X_mi_filtered = X[top_mi_features]

2. Встроенные методы (Embedded Methods)

Моделируем и анализируем какие фичи модель считает важными.

Tree-based Feature Importance

from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler

# Обучаем модель на всех признаках
model = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
model.fit(X, y)

# Получаем важность признаков
feature_importance = pd.DataFrame({
    'feature': X.columns,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)

print(feature_importance.head(20))

# Отбираем топ-50 фич
top_features_tree = feature_importance['feature'].head(50).values
X_tree_filtered = X[top_features_tree]

# Визуализируем
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.barh(feature_importance['feature'].head(20), feature_importance['importance'].head(20))
plt.xlabel('Feature Importance')
plt.title('Top 20 Features by Importance')
plt.tight_layout()
plt.show()

L1 Regularization (Lasso)

from sklearn.linear_model import LogisticRegression, Lasso

# Lasso добавляет L1 penalty, обнуляя коэффициенты неважных фич
# Это естественно отбирает важные признаки

X_scaled = StandardScaler().fit_transform(X)

model_l1 = LogisticRegression(penalty='l1', solver='liblinear', C=1.0, random_state=42)
model_l1.fit(X_scaled, y)

# Коэффициенты, которые не обнулились = важные признаки
feature_coefs = pd.DataFrame({
    'feature': X.columns,
    'coefficient': np.abs(model_l1.coef_[0])
}).sort_values('coefficient', ascending=False)

print(f"Non-zero coefficients: {(feature_coefs['coefficient'] > 0).sum()}")
print(feature_coefs.head(30))

# Автоматическое количество фич
selected_by_l1 = feature_coefs[feature_coefs['coefficient'] > 0]['feature'].values
X_l1_filtered = X[selected_by_l1]

3. Методы обёртки (Wrapper Methods)

Отбираем фичи на основе performance модели.

Рекурсивное исключение признаков (RFE)

from sklearn.feature_selection import RFE

# RFE: обучаем модель, исключаем менее важные фичи, повторяем

selector = RFE(
    estimator=RandomForestClassifier(n_estimators=50, random_state=42),
    n_features_to_select=50,  # выбираем 50 фич
    step=50  # исключаем по 50 фич за итерацию (ускорение)
)

X_rfe = selector.fit_transform(X, y)
rfe_features = X.columns[selector.support_]

print(f"RFE selected {len(rfe_features)} features")
print(rfe_features)

Последовательный отбор (Sequential Feature Selection)

from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# Forward Selection: начинаем с нуля, добавляем лучшие фичи
selector_forward = SequentialFeatureSelector(
    estimator=RandomForestClassifier(n_estimators=50, random_state=42),
    n_features_to_select=50,
    direction='forward',  # 'forward' или 'backward'
    cv=5,  # 5-fold cross-validation для оценки
    n_jobs=-1
)

X_sfs = selector_forward.fit_transform(X, y)
sfs_features = X.columns[selector_forward.support_]

print(f"SFS selected {len(sfs_features)} features")
print(sfs_features)

4. Комбинированный подход

# Стратегия: используем несколько методов и берём пересечение

# Список признаков из разных методов
features_from_methods = {
    'corr': set(top_features),
    'mi': set(top_mi_features),
    'tree': set(top_features_tree),
    'l1': set(selected_by_l1),
    'rfe': set(rfe_features),
    'sfs': set(sfs_features)
}

# Фичи, выбранные большинством методов
feature_counts = {}
for feature in X.columns:
    count = sum(1 for method_features in features_from_methods.values() if feature in method_features)
    feature_counts[feature] = count

# Берём фичи, выбранные как минимум 3 методами
ensemble_features = [feat for feat, count in feature_counts.items() if count >= 3]
X_ensemble = X[ensemble_features]

print(f"Ensemble selection: {len(ensemble_features)} features")
print(f"Agreement: {len(ensemble_features)} features selected by >=3 methods")

5. Dimensionality Reduction методы

PCA (Principal Component Analysis)

from sklearn.decomposition import PCA

# PCA создаёт новые синтетические признаки (principale components)
# Сохраняет 95% дисперсии

pca = PCA(n_components=0.95)  # сохранять 95% дисперсии
X_pca = pca.fit_transform(X)

print(f"Original features: {X.shape[1]}")
print(f"PCA components: {X_pca.shape[1]}")
print(f"Variance explained: {pca.explained_variance_ratio_.sum():.4f}")

Feature Agglomeration

from sklearn.cluster import FeatureAgglomeration

# Группируем похожие признаки, представляя каждую группу одним признаком

feat_agg = FeatureAgglomeration(n_clusters=50)
X_agg = feat_agg.fit_transform(X)

print(f"Original features: {X.shape[1]}")
print(f"Agglomerated features: {X_agg.shape[1]}")

6. Практический workflow для больших датасетов

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score

# Шаг 1: Быстрое удаление бесполезных признаков (filter)
selector_kbest = SelectKBest(score_func=f_classif, k=500)
X_step1 = selector_kbest.fit_transform(X, y)
features_step1 = X.columns[selector_kbest.get_support()]
print(f"Step 1: {len(features_step1)} features (from {len(X.columns)})")

# Шаг 2: L1 регуляризация для дополнительного отбора
X_step1_scaled = StandardScaler().fit_transform(X_step1)
model_l1 = LogisticRegression(penalty='l1', solver='liblinear', C=0.1, random_state=42)
model_l1.fit(X_step1_scaled, y)
features_step2 = features_step1[model_l1.coef_[0] != 0]
X_step2 = X[features_step2]
print(f"Step 2: {len(features_step2)} features (after L1)")

# Шаг 3: RFE с быстрой моделью
selector_rfe = RFE(
    estimator=GradientBoostingClassifier(n_estimators=50, random_state=42),
    n_features_to_select=100,
    step=20
)
X_step3 = selector_rfe.fit_transform(X_step2, y)
features_final = features_step2[selector_rfe.support_]
print(f"Step 3: {len(features_final)} features (final)")

# Шаг 4: Валидация на тестовом наборе
X_train, X_test, y_train, y_test = train_test_split(X[features_final], y, test_size=0.2, random_state=42)

model_final = GradientBoostingClassifier(n_estimators=200, random_state=42)
model_final.fit(X_train, y_train)

auc_score = roc_auc_score(y_test, model_final.predict_proba(X_test)[:, 1])
print(f"\nFinal model AUC: {auc_score:.4f}")

7. Рекомендации по выбору метода

СитуацияРекомендуемый методПлюсыМинусы
Мало времени, много фич (>10K)Filter (MI + correlation)Очень быстроМожет пропустить взаимодействия
Нелинейные зависимостиTree importance + RFEЗахватывает взаимодействияМедленнее
Интерпретируемость важнаL1 RegularizationПонятно почемуПредполагает линейность
Нужна точностьEnsemble методовНадёжнееМедленнее
Очень большой датасетPCA или Feature AggБыстро, уменьшает размерТеряет интерпретируемость

Ключевые выводы

  1. Filter методы — быстрые для больших датасетов
  2. Tree importance — хорошо работают на практике
  3. L1 регуляризация — естественный отбор признаков
  4. Ensemble подход — отбираем фичи, выбранные несколькими методами
  5. Валидация — всегда проверяем качество на тестовом наборе
  6. Итерационно — сначала грубый отбор, потом детальный
  7. Нельзя забывать — не использовать информацию из тестового набора!