Важность признаков (Feature Importance) — инструмент для понимания того, какие переменные вносят наибольший вклад в предсказания модели. Методы оценки важности признаков решают несколько практических задач: отбор релевантных предикторов, детекцию утечек данных, снижение размерности для ускорения инференса. Выбор метода зависит от типа модели, структуры данных и требований к интерпретируемости.
Разные подходы к оценке важности дают разные результаты:
- Встроенные методы древовидных моделей быстры, но смещены к высококардинальным признакам;
- Метод Permutation importance универсален, но чувствителен к корреляциям;
- SHAP предоставляет теоретически обоснованные оценки, но требует больших вычислительных ресурсов.
Понимание этих различий позволяет выбрать оптимальный подход для конкретной задачи.
Методы оценки важности признаков
Встроенные методы древовидных моделей
Древовидные алгоритмы (Random Forest, XGBoost, LightGBM) вычисляют важность признаков в процессе обучения. Метод основан на измерении улучшения критерия разбиения (Gini impurity, Information gain) при использовании признака в узлах дерева. Чем чаще признак выбирается для разбиения и чем больше улучшение метрики, тем выше его важность.
Random Forest использует среднее снижение Gini или энтропии по всем деревьям. XGBoost и LightGBM предлагают три варианта подсчета:
- weight — количество использований признака;
- gain — среднее улучшение метрики;
- cover — среднее количество наблюдений в разбиениях.
Gain предпочтительнее для интерпретации, так как отражает реальный вклад в качество предсказаний.
Основная проблема встроенных методов — смещенность (bias) к признакам с высокой кардинальностью. Переменные с большим количеством уникальных значений получают завышенные оценки важности, даже если их предсказательная способность низка. Категориальные признаки после one-hot encoding также искажают результаты. Для корректной оценки требуется сравнение нескольких методов или использование альтернативных подходов.
Давайте рассмотрим это на следующем примере:
import yfinance as yf
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
import matplotlib.pyplot as plt
pd.set_option('display.expand_frame_repr', False)
# Загрузка данных
ticker = yf.Ticker("2330.TW") # TSMC
data = ticker.history(period="2y", interval="1d")
# Проверка на Multiindex
if isinstance(data.columns, pd.MultiIndex):
data.columns = data.columns.droplevel(1)
# Создание признаков
df = pd.DataFrame()
df['returns'] = data['Close'].pct_change()
df['volume_change'] = data['Volume'].pct_change()
df['high_low_spread'] = (data['High'] - data['Low']) / data['Close']
df['close_open_spread'] = (data['Close'] - data['Open']) / data['Open']
# Лаговые признаки
for lag in [1, 2, 3, 5, 10]:
df[f'returns_lag_{lag}'] = df['returns'].shift(lag)
df[f'volume_lag_{lag}'] = df['volume_change'].shift(lag)
# Rolling признаки
df['volatility_5d'] = df['returns'].rolling(5).std()
df['volatility_20d'] = df['returns'].rolling(20).std()
df['volume_ma_ratio'] = data['Volume'] / data['Volume'].rolling(20).mean()
# Таргет: доходность через 1 день
df['target'] = df['returns'].shift(-1)
# Очистка данных
df = df.replace([np.inf, -np.inf], np.nan).dropna()
# Разделение на признаки и таргет
X = df.drop('target', axis=1)
y = df['target']
# Обучение моделей
rf_model = RandomForestRegressor(n_estimators=100, max_depth=8, random_state=42)
xgb_model = XGBRegressor(n_estimators=100, max_depth=6, learning_rate=0.1, random_state=42)
lgbm_model = LGBMRegressor(n_estimators=100, max_depth=6, learning_rate=0.1, random_state=42)
rf_model.fit(X, y)
xgb_model.fit(X, y)
lgbm_model.fit(X, y)
rf_model.fit(X, y)
xgb_model.fit(X, y)
lgbm_model.fit(X, y)
# Извлечение важности признаков
rf_importance = pd.DataFrame({
'feature': X.columns,
'importance': rf_model.feature_importances_
}).sort_values('importance', ascending=False)
xgb_importance = pd.DataFrame({
'feature': X.columns,
'importance': xgb_model.feature_importances_
}).sort_values('importance', ascending=False)
lgbm_importance = pd.DataFrame({
'feature': X.columns,
'importance': lgbm_model.feature_importances_
}).sort_values('importance', ascending=False)
# Визуализация топ-10 признаков для каждой модели
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
axes[0].barh(rf_importance.head(10)['feature'], rf_importance.head(10)['importance'], color='#2C3E50')
axes[0].set_title('Random Forest Feature Importance')
axes[0].invert_yaxis()
axes[1].barh(xgb_importance.head(10)['feature'], xgb_importance.head(10)['importance'], color='#2C3E50')
axes[1].set_title('XGBoost Feature Importance (Gain)')
axes[1].invert_yaxis()
axes[2].barh(lgbm_importance.head(10)['feature'], lgbm_importance.head(10)['importance'], color='#2C3E50')
axes[2].set_title('LightGBM Feature Importance')
axes[2].invert_yaxis()
plt.tight_layout()
plt.show()
# Вывод топ-5 для сравнения
print("Random Forest Top 5:")
print(rf_importance.head())
print("\nXGBoost Top 5:")
print(xgb_importance.head())
print("\nLightGBM Top 5:")
print(lgbm_importance.head())
[LightGBM] [Info] Total Bins 2508
[LightGBM] [Info] Number of data points in the train set: 460, number of used features: 17
[LightGBM] [Info] Start training from score 0.002040
Random Forest Top 5:
feature importance
13 volume_lag_10 0.138693
0 returns 0.119082
3 close_open_spread 0.111151
6 returns_lag_2 0.102413
8 returns_lag_3 0.058379
XGBoost Top 5:
feature importance
13 volume_lag_10 0.134891
4 returns_lag_1 0.093672
6 returns_lag_2 0.079875
8 returns_lag_3 0.068672
11 volume_lag_5 0.063433
LightGBM Top 5:
feature importance
3 close_open_spread 98
4 returns_lag_1 80
14 volatility_5d 77
7 volume_lag_2 69
8 returns_lag_3 61

Рис. 1: Визуализация важности признаков по 3 моделям: Random Forest, XGBoost, LightGBM
Код загружает дневные котировки TSMC, создает набор технических признаков (лаги доходности и объема, волатильность, спреды) и обучает три модели. После обучения извлекаются встроенные оценки важности и визуализируются топ-10 признаков для каждого алгоритма.
Результаты показывают различия между моделями. В целом, 2 из 3 моделей выбрали наиболее важным единый признак — volume_lag_10, что хорошо. Однако в остальном их рейтинги важности существенно отличаются. Это происходит потому, что:
- Random Forest старается отдавать предпочтение признакам с высокой вариативностью;
- XGBoost с параметром gain фокусируется на признаках с наибольшим улучшением метрики качества;
- LightGBM выдает совершенно другой рейтинг важности признаков, что может сигнализировать о том, что был выбран другой способ построения деревьев (leaf-wise vs level-wise).
Совет: при работе с финансовыми данными предпочтительнее XGBoost или LightGBM с метрикой gain. Random Forest склонен переоценивать шумные признаки. Для финальных выводов об важности признаков стоит усреднить результаты нескольких моделей или применить дополнительные методы валидации.
Метод Permutation Importance
Метод Permutation importance измеряет снижение качества модели при случайном перемешивании значений признака. Метод работает следующим образом:
- Для каждого признака его значения перемешиваются;
- После чего модель делает предсказания на измененных данных;
- Затем вычисляется разница в метрике качества. Чем сильнее ухудшается метрика, тем важнее признак.
Основное преимущество подхода — универсальность. Метод применим к любым моделям, включая нейросети и ансамбли. В отличие от встроенных методов, permutation importance не зависит от структуры алгоритма и показывает реальное влияние признака на предсказания. Метод учитывает взаимодействия между признаками, так как перемешивание одного признака может затронуть другие.
Главная проблема — коррелированные признаки. Если два признака сильно коррелируют, перемешивание одного из них компенсируется другим, и оба получают заниженные оценки важности. Для обнаружения таких ситуаций используется кластеризация признаков по корреляции или анализ variance inflation factor (VIF).
У Permutation importance есть еще одна проблема — вычислительная сложность для больших датасетов, так как требуется переобучение модели для каждого признака.
Давайте рассмотрим как работает этот метод:
from sklearn.inspection import permutation_importance
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# Разделение данных
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
# Обучение модели
model = XGBRegressor(n_estimators=100, max_depth=6, learning_rate=0.1, random_state=42)
model.fit(X_train, y_train)
# Baseline метрика
y_pred = model.predict(X_test)
baseline_mse = mean_squared_error(y_test, y_pred)
print(f"Baseline MSE: {baseline_mse:.6f}")
# Вычисление permutation importance
perm_importance = permutation_importance(
model, X_test, y_test,
n_repeats=10, # Количество перемешиваний для каждого признака
random_state=42,
scoring='neg_mean_squared_error'
)
# Создание датафрейма с результатами
perm_importance_df = pd.DataFrame({
'feature': X.columns,
'importance_mean': perm_importance.importances_mean,
'importance_std': perm_importance.importances_std
}).sort_values('importance_mean', ascending=False)
# Визуализация с доверительными интервалами
fig, ax = plt.subplots(figsize=(10, 8))
top_features = perm_importance_df.head(10)
ax.barh(top_features['feature'], top_features['importance_mean'],
xerr=top_features['importance_std'], color='#2C3E50', capsize=5)
ax.set_xlabel('Decrease in MSE')
ax.set_title('Permutation Importance (Top 10 Features)')
ax.invert_yaxis()
plt.tight_layout()
plt.show()
print("\nPermutation Importance Top 10:")
print(perm_importance_df.head(10))
Permutation Importance Top 10:
feature importance_mean importance_std
13 volume_lag_10 0.000076 0.000017
7 volume_lag_2 0.000010 0.000009
4 returns_lag_1 0.000006 0.000006
8 returns_lag_3 0.000005 0.000007
5 volume_lag_1 0.000005 0.000007
12 returns_lag_10 0.000005 0.000006
10 returns_lag_5 0.000003 0.000008
1 volume_change 0.000003 0.000010
11 volume_lag_5 0.000002 0.000006
0 returns 0.000001 0.000005

Рис. 2: Топ-10 признаков, отобранных методом Permutation Importance. Отрыв признака volume_lag_10 становится еще более выраженным, что подтверждает его важность
Представленный выше код вычисляет permutation importance для модели XGBoost на тестовой выборке. Параметр n_repeats=10 означает, что каждый признак перемешивается 10 раз для получения стабильной оценки. Метод возвращает среднее снижение метрики и стандартное отклонение.
Визуализация включает error bars, отражающие вариативность оценок. Интерпретация следующая:
- Признаки с высокой вариативностью могут быть нестабильными или взаимодействовать с другими переменными;
- Низкая вариативность указывает на устойчивое влияние признака.
Сравнение с встроенной важностью XGBoost выявляет расхождения. Встроенный метод может переоценивать признаки, часто используемые в ранних разбиениях дерева, но не влияющие на финальные предсказания. Permutation importance фокусируется на реальном вкладе в качество модели на новых данных.
Совет: используйте permutation importance для финального отбора признаков после обучения модели. Если признак имеет низкую permutation importance при высокой встроенной важности, это сигнал о переобучении или коллинеарности. Удаление таких признаков упрощает модель без потери качества.
Метод SHAP (SHapley Additive exPlanations)
Метод SHAP базируется на концепции значений Шепли из теории кооперативных игр. Метод отвечает на вопрос: какой вклад каждого признака в отклонение предсказания от среднего значения?
Значения SHAP удовлетворяют свойствам симметрии, эффективности и линейности, что делает их теоретически обоснованным способом интерпретации моделей. Для каждого наблюдения метод SHAP вычисляет вклад каждого признака в предсказание.
Сумма всех значений SHAP плюс базовое значение (среднее предсказание по выборке) равна итоговому предсказанию модели. Глобальная важность признака определяется как среднее абсолютных значений SHAP по всем наблюдениям.
Существуют разные алгоритмы расчета SHAP:
- TreeExplainer для древовидных моделей (точный и быстрый);
- KernelExplainer для любых моделей (медленный, использует аппроксимацию);
- DeepExplainer для нейросетей.
TreeExplainer оптимален для XGBoost, LightGBM, CatBoost — он использует структуру деревьев для точного вычисления за полиномиальное время.
import shap
# Обучение модели
model = XGBRegressor(n_estimators=100, max_depth=6, learning_rate=0.1, random_state=42)
model.fit(X_train, y_train)
# Создание SHAP explainer для древовидных моделей
explainer = shap.TreeExplainer(model)
shap_values = explainer(X_test)
# Глобальная важность признаков
shap_importance = pd.DataFrame({
'feature': X.columns,
'importance': np.abs(shap_values.values).mean(axis=0)
}).sort_values('importance', ascending=False)
print("SHAP Feature Importance Top 10:")
print(shap_importance.head(10))
# Визуализация 1: Summary plot (показывает распределение SHAP values)
shap.summary_plot(shap_values, X_test, show=False)
plt.tight_layout()
plt.show()
# Визуализация 2: Bar plot глобальной важности
shap.plots.bar(shap_values, show=False)
plt.tight_layout()
plt.show()
# Визуализация 3: Waterfall plot для конкретного наблюдения
# Выбираем наблюдение с наибольшим абсолютным предсказанием
max_pred_idx = np.abs(model.predict(X_test)).argmax()
shap.plots.waterfall(shap_values[max_pred_idx], show=False)
plt.tight_layout()
plt.show()
# Визуализация 4: Dependence plot для топового признака
top_feature = shap_importance.iloc[0]['feature']
shap.dependence_plot(top_feature, shap_values.values, X_test, show=False)
plt.tight_layout()
plt.show()
SHAP Feature Importance Top 10:
feature importance
13 volume_lag_10 0.002486
6 returns_lag_2 0.002229
3 close_open_spread 0.001710
14 volatility_5d 0.001490
7 volume_lag_2 0.001375
8 returns_lag_3 0.001370
9 volume_lag_3 0.001143
1 volume_change 0.001053
4 returns_lag_1 0.001043
11 volume_lag_5 0.000802

Рис. 3: Сводный график значений SHAP (summary plot), показывающий распределение влияния каждого признака на предсказания модели XGBoost. Цветовая шкала отражает значения признаков, а положение по оси X — силу и направление их вклада

Рис. 4: Столбчатая диаграмма глобальной важности признаков на основе средних абсолютных значений SHAP, иллюстрирующая совокупное влияние факторов на результаты модели

Рис. 5: Waterfall-график разложения предсказания для одного наблюдения, демонстрирующий, как каждый признак увеличивает или уменьшает итоговое значение прогноза

Рис. 6: График зависимости SHAP для наиболее важного признака, показывающий связь между значением признака и его вкладом в предсказание модели
Вышеуказанный код создает TreeExplainer для модели XGBoost и вычисляет значения SHAP для тестовой выборки. Глобальная важность определяется как среднее абсолютных значений по всем наблюдениям.
Summary plot показывает распределение значений SHAP для каждого признака:
- Каждая точка — одно наблюдение, цвет отражает значение признака (красный — высокое, синий — низкое);
- Горизонтальная позиция точки — SHAP value (вклад в предсказание);
- Признаки упорядочены по важности сверху вниз. Визуализация позволяет увидеть не только важность, но и характер влияния: положительный или отрицательный, линейный или нелинейный.
Waterfall plot показывает декомпозицию предсказания для конкретного наблюдения. Начинается с базового значения (среднее по выборке), затем последовательно добавляются вклады признаков. Финальное значение — предсказание модели. Визуализация отвечает на вопрос «почему модель сделала такое предсказание для этого объекта?».
Dependence plot показывает зависимость SHAP value от значения признака:
- По оси X — значение признака;
- По оси Y — его SHAP value;
- Цвет точек может отражать значение другого признака (автоматически выбирается наиболее взаимодействующий).
График Dependence plot выявляет нелинейные зависимости и взаимодействия между признаками.
Метод Drop Column Importance
Метод Drop column importance измеряет важность признака через разницу в качестве модели с признаком и без него. Метод прямолинеен: для каждого признака обучается две модели — с полным набором признаков и с удаленным целевым признаком. Разница в метрике качества на валидационной выборке определяет важность.
Метод вычислительно затратен, так как требует обучения N+1 моделей, где N — количество признаков. Для больших датасетов или сложных моделей это непрактично. Тем не менее, данный метод часто используют в прогнозировании, так как он дает наиболее точную оценку реального влияния признака, учитывая все взаимодействия и компенсации.
Применение Drop column importance оправдано в следующих случаях:
- Финальная валидация наиболее важных признаков перед продакшеном;
- Детекция признаков с marginal value (модель их использует, но их можно удалить без потери качества);
- Анализ cost-benefit для дорогих в получении признаков (например, альтернативные данные).
Для отбора из сотен признаков метод не подходит — используйте встроенные методы или permutation importance для предварительного отсева.
Реализация Drop column importance на Python:
from sklearn.model_selection import cross_val_score
def drop_column_importance(X, y, model, cv=5, scoring='neg_mean_squared_error'):
"""
Вычисляет важность признаков через удаление каждого признака
и измерение изменения качества модели
"""
# Baseline: качество с полным набором признаков
baseline_scores = cross_val_score(model, X, y, cv=cv, scoring=scoring)
baseline_mean = baseline_scores.mean()
importance_scores = {}
for column in X.columns:
# Удаляем признак
X_dropped = X.drop(columns=[column])
# Оцениваем качество без этого признака
dropped_scores = cross_val_score(model, X_dropped, y, cv=cv, scoring=scoring)
dropped_mean = dropped_scores.mean()
# Важность = насколько ухудшилось качество
# Для neg_mean_squared_error: чем меньше значение, тем хуже
# Поэтому importance = baseline - dropped
importance = baseline_mean - dropped_mean
importance_scores[column] = importance
return pd.DataFrame({
'feature': importance_scores.keys(),
'importance': importance_scores.values()
}).sort_values('importance', ascending=False)
# Используем подвыборку для ускорения (первые 300 наблюдений)
X_subset = X.head(300)
y_subset = y.head(300)
model = XGBRegressor(n_estimators=50, max_depth=5, learning_rate=0.1, random_state=42)
drop_importance = drop_column_importance(X_subset, y_subset, model, cv=3)
print("Drop Column Importance:")
print(drop_importance.head(10))
# Визуализация
fig, ax = plt.subplots(figsize=(10, 8))
top_features = drop_importance.head(10)
ax.barh(top_features['feature'], top_features['importance'], color='#2C3E50')
ax.set_xlabel('Decrease in Score (higher = more important)')
ax.set_title('Drop Column Importance (Top 10 Features)')
ax.invert_yaxis()
plt.tight_layout()
plt.show()
Drop Column Importance:
feature importance
13 volume_lag_10 0.000027
4 returns_lag_1 0.000023
12 returns_lag_10 0.000018
16 volume_ma_ratio 0.000018
11 volume_lag_5 0.000017
8 returns_lag_3 0.000010
6 returns_lag_2 0.000007
3 close_open_spread 0.000007
14 volatility_5d 0.000005
15 volatility_20d 0.000004

Рис. 7: Диаграмма важности признаков, рассчитанная методом исключения признаков (Drop Column Importance). График показывает, насколько ухудшается качество модели XGBoost при удалении каждого признака, что позволяет оценить его вклад в общую предсказательную способность модели
Код реализует drop column importance через кросс-валидацию. Для каждого признака модель обучается на данных без этого признака, вычисляется среднее качество по фолдам. Разница между baseline качеством и качеством без признака определяет важность.
Метод показывает признаки, без которых модель теряет качество:
- Отрицательные значения важности означают, что удаление признака улучшило модель — такие признаки вносят шум или переобучение;
- Положительные значения отражают реальную пользу признака.
Сравнение с другими методами выявляет расхождения. Признак может иметь высокую встроенную важность или permutation importance, но низкую drop column importance. Это происходит, когда другие признаки полностью компенсируют его удаление. Такие признаки — кандидаты на удаление для упрощения модели.
Практические аспекты применения
Стабильность оценок важности
Оценки важности признаков варьируются между запусками модели из-за стохастичности обучения. Random Forest и Gradient Boosting используют случайные подвыборки данных и признаков, что приводит к разным деревьям. Для получения устойчивых оценок требуется усреднение по нескольким запускам или фолдам кросс-валидации.
Признаки, у которых важность сильно меняется между фолдами называют нестабильными. Их появление — сигнал о проблемах в модели. Причины нестабильности могут быть разными:
- Коллинеарность с другими признаками;
- Чувствительность к выбросам;
- Взаимодействия высокого порядка, которые модель не улавливает стабильно.
Такие признаки повышают риск переобучения и непредсказуемого поведения модели в продакшене.
from sklearn.model_selection import KFold
def cross_validated_importance(X, y, model, cv=5):
"""
Вычисляет важность признаков на каждом фолде кросс-валидации
и возвращает среднее значение и стандартное отклонение
"""
kfold = KFold(n_splits=cv, shuffle=False)
importance_list = []
for train_idx, val_idx in kfold.split(X):
X_train_fold, X_val_fold = X.iloc[train_idx], X.iloc[val_idx]
y_train_fold, y_val_fold = y.iloc[train_idx], y.iloc[val_idx]
model.fit(X_train_fold, y_train_fold)
importance_list.append(model.feature_importances_)
importance_array = np.array(importance_list)
return pd.DataFrame({
'feature': X.columns,
'importance_mean': importance_array.mean(axis=0),
'importance_std': importance_array.std(axis=0),
'cv_coefficient': importance_array.std(axis=0) / (importance_array.mean(axis=0) + 1e-10)
}).sort_values('importance_mean', ascending=False)
model = XGBRegressor(n_estimators=100, max_depth=6, learning_rate=0.1, random_state=42)
cv_importance = cross_validated_importance(X, y, model, cv=5)
print("Cross-Validated Feature Importance:")
print(cv_importance.head(15))
# Визуализация с error bars
fig, ax = plt.subplots(figsize=(10, 8))
top_features = cv_importance.head(10)
ax.barh(top_features['feature'], top_features['importance_mean'],
xerr=top_features['importance_std'], color='#2C3E50', capsize=5)
ax.set_xlabel('Feature Importance (mean ± std)')
ax.set_title('Cross-Validated Feature Importance (Top 10)')
ax.invert_yaxis()
plt.tight_layout()
plt.show()
# Выявление нестабильных признаков
unstable_features = cv_importance[cv_importance['cv_coefficient'] > 0.5]
print(f"\nUnstable features (CV > 0.5): {len(unstable_features)}")
print(unstable_features[['feature', 'importance_mean', 'cv_coefficient']])
Cross-Validated Feature Importance:
feature importance_mean importance_std cv_coefficient
13 volume_lag_10 0.137225 0.031500 0.229551
4 returns_lag_1 0.081185 0.016949 0.208777
3 close_open_spread 0.074710 0.020138 0.269550
6 returns_lag_2 0.068826 0.006165 0.089578
8 returns_lag_3 0.065704 0.010538 0.160385
14 volatility_5d 0.060929 0.012391 0.203364
7 volume_lag_2 0.060439 0.006705 0.110940
11 volume_lag_5 0.056599 0.010507 0.185639
10 returns_lag_5 0.052230 0.008998 0.172284
16 volume_ma_ratio 0.050730 0.008113 0.159919
12 returns_lag_10 0.049305 0.008984 0.182222
9 volume_lag_3 0.048266 0.006120 0.126796
15 volatility_20d 0.046576 0.012109 0.259983
2 high_low_spread 0.045212 0.006113 0.135202
5 volume_lag_1 0.041862 0.005503 0.131461
Unstable features (CV > 0.5): 0
Empty DataFrame
Columns: [feature, importance_mean, cv_coefficient]
Index: []

Рис. 8: График важности признаков, рассчитанной с использованием кросс-валидации. Столбцы отражают средние значения важности признаков модели XGBoost по всем фолдам, а горизонтальные линии погрешностей (error bars) показывают стандартное отклонение, характеризующее устойчивость вклада каждого признака в предсказание
Представленный пример кода вычисляет важность признаков на каждом фолде кросс-валидации и агрегирует результаты. Метрика cv_coefficient (коэффициент вариации) показывает относительную нестабильность: значения выше 0.5 указывают на высокую вариативность оценок.
Если есть признаки с высоким cv_coefficient, то нужно провести дополнительный анализ:
- Проверка корреляции с другими признаками через матрицу корреляций или VIF. Если признак сильно коррелирует с другими и имеет схожую предсказательную способность, то следует удалить один из них;
- Проверка влияния выбросов. Если признак нестабилен из-за выбросов, то можно попробовать применить масштабирование или обрезку аномалий по квантилям.
Совет: при отборе признаков для продакшена используйте пороговые значения не только по средней важности, но и по стабильности. Признак с importance_mean=0.05 и cv_coefficient=0.2 предпочтительнее признака с importance_mean=0.08 и cv_coefficient=0.8. Стабильность важнее маргинального прироста качества.
Интеграция в feature selection
Feature selection на основе важности признаков реализуется рекурсивным удалением наименее важных переменных. Процесс итеративный:
- Обучаем модель;
- Оцениваем важность;
- Удаляем N наименее важных признаков;
- Повторяем.
Критерий остановки — заданное количество признаков или падение метрики качества ниже порога.
Пороговые значения для отбора зависят от задачи:
- Для задач с сотнями признаков (например, генетические данные) обычно используют агрессивный отсев: топ 10-20% признаков;
- Для финансовых данных с десятками признаков предпочтительнее консервативный подход: удаляются только признаки с важностью близкой к нулю. Порог определяется эмпирически через валидацию на hold-out выборке.
Альтернативный подход — метод threshold-based selection. Суть его в следующем: установить минимальный уровень важности (например, importance > 0.01) и удалить все признаки ниже порога. Метод быстрее рекурсивного удаления, однако менее точен, так как не учитывает взаимодействия между признаками после удаления части из них.
from sklearn.metrics import mean_squared_error, mean_absolute_error
def recursive_feature_elimination_custom(X, y, model, min_features=5, step=0.1):
"""
Рекурсивное удаление признаков на основе важности
step: доля признаков для удаления на каждой итерации
"""
X_current = X.copy()
results = []
while X_current.shape[1] >= min_features:
# Кросс-валидация
kfold = KFold(n_splits=5, shuffle=False)
scores = []
for train_idx, val_idx in kfold.split(X_current):
X_train_fold = X_current.iloc[train_idx]
X_val_fold = X_current.iloc[val_idx]
y_train_fold = y.iloc[train_idx]
y_val_fold = y.iloc[val_idx]
model.fit(X_train_fold, y_train_fold)
y_pred = model.predict(X_val_fold)
scores.append(mean_squared_error(y_val_fold, y_pred))
mean_mse = np.mean(scores)
# Записываем результат
results.append({
'n_features': X_current.shape[1],
'mse': mean_mse,
'features': list(X_current.columns)
})
# Обучаем на всех данных для получения важности
model.fit(X_current, y)
# Определяем количество признаков для удаления
n_to_remove = max(1, int(X_current.shape[1] * step))
# Получаем важность и удаляем наименее важные
importance = pd.DataFrame({
'feature': X_current.columns,
'importance': model.feature_importances_
}).sort_values('importance', ascending=True)
features_to_remove = importance.head(n_to_remove)['feature'].tolist()
X_current = X_current.drop(columns=features_to_remove)
print(f"Features: {X_current.shape[1]}, MSE: {mean_mse:.6f}")
return pd.DataFrame(results)
# Рекурсивное удаление признаков
model = XGBRegressor(n_estimators=100, max_depth=6, learning_rate=0.1, random_state=42)
rfe_results = recursive_feature_elimination_custom(X, y, model, min_features=5, step=0.15)
print("\nRFE Results:")
print(rfe_results[['n_features', 'mse']])
# Визуализация зависимости качества от количества признаков
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(rfe_results['n_features'], rfe_results['mse'], marker='o', color='#2C3E50', linewidth=2)
ax.set_xlabel('Number of Features')
ax.set_ylabel('Cross-Validated MSE')
ax.set_title('Model Performance vs Number of Features')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Определение оптимального количества признаков
optimal_idx = rfe_results['mse'].idxmin()
optimal_n_features = rfe_results.loc[optimal_idx, 'n_features']
optimal_features = rfe_results.loc[optimal_idx, 'features']
print(f"\nOptimal number of features: {optimal_n_features}")
print(f"Optimal MSE: {rfe_results.loc[optimal_idx, 'mse']:.6f}")
print(f"\nOptimal feature set:")
print(optimal_features)
Features: 15, MSE: 0.000502
Features: 13, MSE: 0.000497
Features: 12, MSE: 0.000479
Features: 11, MSE: 0.000482
Features: 10, MSE: 0.000478
Features: 9, MSE: 0.000523
Features: 8, MSE: 0.000517
Features: 7, MSE: 0.000514
Features: 6, MSE: 0.000535
Features: 5, MSE: 0.000502
Features: 4, MSE: 0.000538
RFE Results:
n_features mse
0 17 0.000502
1 15 0.000497
2 13 0.000479
3 12 0.000482
4 11 0.000478
5 10 0.000523
6 9 0.000517
7 8 0.000514
8 7 0.000535
9 6 0.000502
10 5 0.000538
Optimal number of features: 11
Optimal MSE: 0.000478
Optimal feature set:
['close_open_spread', 'returns_lag_1', 'returns_lag_2', 'volume_lag_2', 'returns_lag_3', 'returns_lag_5', 'volume_lag_5', 'returns_lag_10', 'volume_lag_10', 'volatility_5d', 'volatility_20d']

Рис. 9: График зависимости качества модели от количества используемых признаков, полученный методом рекурсивного исключения признаков (RFE). Кривая иллюстрирует изменение среднеквадратичной ошибки (MSE) при поэтапном удалении наименее значимых признаков, что позволяет определить оптимальное количество признаков для построения модели
Код реализует рекурсивное удаление признаков с кросс-валидацией на каждом шаге. Параметр step=0.15 означает удаление 15% наименее важных признаков на каждой итерации. Процесс продолжается до достижения минимального количества признаков (min_features=5).
Интересно отметить, что при использовании этого метода наглядно видно как качество улучшается при удалении шумных признаков, достигает минимума, затем начинает ухудшаться при удалении информативных переменных. Оптимальное количество признаков соответствует минимуму MSE.
Результаты показывают, что модель часто достигает лучшего качества с 30-50% исходных признаков. Удаление шумных переменных снижает переобучение и улучшает генерализацию. Финальный набор признаков содержит только стабильные, информативные предикторы.
Совет: запускайте RFE на обучающей выборке, выбирайте оптимальное количество признаков, финально валидируйте на отложенной тестовой выборке. Если качество на тесте значительно хуже, чем на валидации внутри RFE, это сигнал о переобучении — уменьшите количество признаков или примените регуляризацию.
Применение в алгоритмическом трейдинге
Feature importance в алгоритмическом трейдинге решает задачу отбора предикторов из множества потенциальных сигналов. Рыночные данные содержат высокий уровень шума, большинство технических индикаторов коррелируют между собой. Модели склонны переобучаться на случайные паттерны, которые не повторяются в будущем. Анализ важности признаков выявляет действительно значимые факторы.
Лаговые зависимости — ключевой аспект временных рядов. Feature importance показывает, какие лаги доходности, объема или волатильности наиболее предсказательны. Если модель присваивает высокую важность лагу 1 и низкую лагам 5-10, это указывает на краткосрочную momentum-стратегию. Обратная картина (высокая важность дальних лагов) может сигнализировать о стремлении возврата к среднему (mean reversion).
Перед деплоем модели важно провести анализ возможных утечек данных в будущее (look-ahead bias). Look-ahead bias возникает, когда признак содержит информацию из будущего, недоступную в момент принятия торгового решения. Feature importance выявляет такие признаки: если переменная имеет аномально высокую важность (в 2-3 раза выше остальных), проверьте ее расчет на утечку данных.
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
from sklearn.model_selection import TimeSeriesSplit
from xgboost import XGBRegressor
import shap
# Загрузка данных
tickers = ['JNJ', 'PFE', 'MRK', 'ABT', 'LLY'] # Акции компаний сектора здравоохранения
data = yf.download(tickers, start='2022-09-01', end='2025-09-01')['Close']
data = data.dropna(how='all')
# Функция генерации признаков
def create_features(df, ticker):
df = df.copy()
df = df.rename(columns={ticker: 'close'})
df['ticker'] = ticker
df['return'] = df['close'].pct_change()
df['vol'] = df['return'].rolling(5).std()
df['momentum'] = df['return'].rolling(10).mean()
# Лаги доходности и объема
for lag in [1, 2, 3, 5, 10]:
df[f'return_lag_{lag}'] = df['return'].shift(lag)
df[f'vol_lag_{lag}'] = df['vol'].shift(lag)
# Целевая переменная — накопленная доходность на 5 дней вперед
df['target'] = df['return'].shift(-5).rolling(5).sum()
# Удаление NaN
df = df.dropna()
return df
# Объединение данных по всем тикерам
all_data = []
for ticker in tickers:
features_df = create_features(data[[ticker]], ticker)
if len(features_df) > 100: # пропускаем слишком короткие ряды
all_data.append(features_df)
combined_df = pd.concat(all_data, ignore_index=True)
combined_df = combined_df.replace([np.inf, -np.inf], np.nan).dropna()
print("Combined data shape:", combined_df.shape)
# Разделение на признаки и цель
X = combined_df.drop(columns=['target', 'ticker'])
y = combined_df['target']
# Кросс-валидация по временным срезам
tscv = TimeSeriesSplit(n_splits=5)
feature_importances = []
for fold, (train_idx, val_idx) in enumerate(tscv.split(X)):
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
model = XGBRegressor(
n_estimators=100,
max_depth=6,
learning_rate=0.1,
random_state=42,
n_jobs=-1
)
model.fit(X_train, y_train)
feature_importances.append(model.feature_importances_)
# Средняя и стандартная важность признаков
importance_array = np.array(feature_importances)
cv_importance = pd.DataFrame({
'feature': X.columns,
'importance_mean': importance_array.mean(axis=0),
'importance_std': importance_array.std(axis=0),
'cv_coefficient': importance_array.std(axis=0) / (importance_array.mean(axis=0) + 1e-10)
}).sort_values('importance_mean', ascending=False)
print("\nCross-Validated Feature Importance (Top 15)")
print(cv_importance.head(15))
# Визуализация важности признаков
fig, ax = plt.subplots(figsize=(10, 8))
top_features = cv_importance.head(10)
ax.barh(top_features['feature'], top_features['importance_mean'],
xerr=top_features['importance_std'], color='#2C3E50', capsize=5)
ax.set_xlabel('Feature Importance (mean ± std)')
ax.set_title('Cross-Validated Feature Importance (Top 10)')
ax.invert_yaxis()
plt.tight_layout()
plt.show()
# SHAP-анализ для последнего фолда
explainer = shap.TreeExplainer(model)
shap_values = explainer(X_val)
# Summary plot
shap.summary_plot(shap_values, X_val, show=False)
plt.tight_layout()
plt.show()
# Waterfall для самого значимого наблюдения
max_pred_idx = np.abs(model.predict(X_val)).argmax()
shap.plots.waterfall(shap_values[max_pred_idx], show=False)
plt.tight_layout()
plt.show()
# Dependence plot для топового признака
top_feature = cv_importance.iloc[0]['feature']
shap.dependence_plot(top_feature, shap_values.values, X_val, show=False)
plt.tight_layout()
plt.show()
# Выявление нестабильных признаков
unstable_features = cv_importance[cv_importance['cv_coefficient'] > 0.5]
print(f"\nUnstable features (CV > 0.5): {len(unstable_features)}")
print(unstable_features[['feature', 'importance_mean', 'cv_coefficient']])
data = yf.download(tickers, start='2022-09-01', end='2025-09-01')['Close']
[*********************100%***********************] 5 of 5 completed
Combined data shape: (3655, 16)
Cross-Validated Feature Importance (Top 15)
feature importance_mean importance_std cv_coefficient
13 vol_lag_10 0.097870 0.010513 0.107422
11 vol_lag_5 0.092625 0.002959 0.031942
3 momentum 0.085974 0.003869 0.045004
0 close 0.082631 0.008803 0.106534
9 vol_lag_3 0.082259 0.007485 0.090992
7 vol_lag_2 0.076350 0.010486 0.137343
5 vol_lag_1 0.075557 0.007180 0.095023
2 vol 0.074290 0.006878 0.092582
12 return_lag_10 0.062405 0.003305 0.052961
10 return_lag_5 0.060910 0.011940 0.196028
8 return_lag_3 0.060688 0.006945 0.114433
6 return_lag_2 0.054017 0.009124 0.168915
4 return_lag_1 0.049505 0.006018 0.121571
1 return 0.044920 0.005280 0.117538
Unstable features (CV > 0.5): 0
Empty DataFrame
Columns: [feature, importance_mean, cv_coefficient]
Index: []

Рис. 10: График средней важности признаков модели XGBoost с временной кросс-валидацией для акций сектора здравоохранения. Отображены средние значения и стандартные отклонения важности по фолдам, что позволяет оценить устойчивость влияния признаков на предсказания модели

Рис. 11: Сводный график SHAP (summary plot), показывающий вклад топ-15 признаков в предсказания модели на последнем фолде временной валидации. Цветовая шкала отражает значения признаков, а распределение по оси X — силу их влияния

Рис. 12: График зависимости важности лагов доходности от периода лага. Демонстрирует какие лаговые переменные оказывают наибольшее влияние на прогнозируемую доходность

Рис. 13: График зависимости важности лагов объема торгов от периода лага, иллюстрирующий, насколько прошлые изменения объема влияют на предсказания модели
Давайте разберемся что делает этот код:
- Сначала загружаются котировки 5 тикеров из сектора здравоохранения, для которых создается набор признаков;
- Затем обучается модель XGBoost для предсказания доходностей акций на 5 дней вперед. Используется метод TimeSeriesSplit для корректной временной валидации — каждый последующий фолд содержит более поздние данные;
- Далее происходит оценка важности признаков. Feature importance агрегируется по всем фолдам временной валидации. Стандартное отклонение показывает стабильность признаков во времени. Высокая вариативность может указывать на режимные изменения рынка или нестационарность предикторов.
Анализ лаговых зависимостей показывает, какие временные горизонты наиболее предсказательны. Для избежания look-ahead bias в примере выше таргет корректно смещен на 5 дней назад через shift(-5).
Заключение
Важность признаков, получаемая из ML-моделей (Feature Importance), позволяет определить ключевые факторы, влияющие на результаты машинного обучения, и оценить их вклад в предсказание. Это служит основой для повышения интерпретируемости модели и помогает принять решения о том, какие признаки стоит оптимизировать или исключить для улучшения устойчивости и прозрачности модели.
Интерпретация моделей через анализ важности признаков позволяет не только повысить доверие к результатам машинного обучения, но и выявить ключевые факторы, влияющие на целевые показатели. Это делает такие методы ценными не только в исследовательских задачах, но и в прикладных сценариях — например, при оптимизации бизнес-процессов, оценке рисков и стратегическом планировании.