← Назад к вопросам
Как отберешь фичи, если их слишком много?
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 | Быстро, уменьшает размер | Теряет интерпретируемость |
Ключевые выводы
- Filter методы — быстрые для больших датасетов
- Tree importance — хорошо работают на практике
- L1 регуляризация — естественный отбор признаков
- Ensemble подход — отбираем фичи, выбранные несколькими методами
- Валидация — всегда проверяем качество на тестовом наборе
- Итерационно — сначала грубый отбор, потом детальный
- Нельзя забывать — не использовать информацию из тестового набора!