Об авторе
Приветствую вас! Меня зовут Эрик, хочу поделиться личным опытом и знаниями. Я практикующий дата-сайентист с опытом участия и судейства в чемпионатах по прогнозированию, а также аналитик-исследователь, которому нравится искать, находить и делиться.
Эта статья основана на ситуации, с которой я столкнулся лично. Моё мнение может не совпадать с чьим-то мнением, но я постараюсь донести свою точку зрения и помочь вам лучше понять и решать аналогичные задачи. Предлагаю начать.
Введение
Аналитики данных, создающие прогностические модели, часто сталкиваются с необходимостью обеспечить качество модели в условиях ограниченного времени. Сохранить баланс становиться труднее после этапа генерации новых признаков, когда количество данных увеличивается. В таких условиях важно отбирать значимые признаки, чтобы обеспечить эффективность как модели, так и её обслуживания.
Фильтрационные методы, как правило, не улучшают метрики модели, тогда как обёрточные методы требуют слишком много времени. Рекурсивный отбор признаков, использующий встроенные методы ранжирования, предлагает компромисс, который позволяет маневрировать между Сциллой недостаточного качества и Харибдой временных затрат.
Выбор размера шагов для рекурсивного отбора признаков играет важную роль в этом процессе. Слишком мелкие шаги делают процесс неоправданно дорогим, в то время как большие шаги рискуют пропустить критически значимые признаки. В качестве решения помимо константного шага предлагается рассмотреть два дополнительных подхода к определению размера шага: динамический шаг, зависящий от текущего количества признаков, и динамический шаг, зависящий от значимости признаков.
О недостатках реализации алгоритма в Scikit-learn
В популярной библиотеке Scikit-learn реализован рекурсивный отбор признаков, но существующая реализация позволяет использовать только фиксированный шаг. Шаг отбора, задаваемый параметром step, может быть целым числом (например, step=10 означает удаление десяти признаков на каждом шаге) или долей (например, step=0.2 означает, что на каждом шаге исключается 20% от изначального числа признаков). В обоих случаях, размер шага остаётся константным на протяжении всего процесса и не адаптируется к изменениям в информативности признаков или количестве признаков на любой из итераций отбора.
Также, критерием останова отбора в библиотеке рассматривается единственный вариант — это остановка после достижения определенного количества признаков. Этот подход может не быть оптимальным: например, когда перед аналитиком стоит задача в первую очередь достичь максимальной метрики, а во вторую - сократить количество признаков. В таких случаях аналитик рискует не дойти до достижимого максимума метрики или потратить слишком много времени на отбор в той области, когда метрика в среднем предсказуемо и неизменно снижается. Несмотря на то, что в статье я не рассматриваю способы задания критерия останова, зависящего от метрики, подчеркну, что этот элемент тоже важен, так как он может ощутимо сэкономить время.
Постановка задачи
Оценим эффективность отбора в зависимости от выбора размера шагов: фиксированного шага, динамического шага, зависящего от текущего количества признаков, и динамического шага, основанного на информативности признаков. Проведём исследование на двух типах данных: реальных данных из чемпионата по прогнозированию и сгенерированных данных, моделирующих аналогичную задачу классификации. По ходу итераций отбора признаков зафиксируем изменение количества признаков, метрику на кросс-валидации и совокупную информативность отбрасываемых признаков. На основании расчетов построим графики и проведем анализ результатов.
Такой подход позволит оценить алгоритмы отбора в контролируемых условиях и оценить возможность их применения на практике.
Описание рассматриваемых методов отбора
ФШ или фиксированный шаг: этот метод определяет удаление фиксированного количества наименее информативных признаков на каждой итерации. Например, при 1000 признаках и доле 0.02 удаляется 20 признаков на каждом шаге.
ДШ(К) или динамический шаг, зависящий от количества признаков: подразумевает, что доля удаляемых признаков зависит от их текущего количества. Например, если изначально есть 1000 признаков и задана доля 0.1, то на первом шаге удаляется 100 признаков, на следующем — 90, затем 81 и так далее. На каждом этапе отбрасывается не менее одного признака.
ДШ(З) или динамический шаг, зависящий от значимости признаков: шаг отбора определяется на основе информативности признаков. Если задана доля отбора 0.01, то удаляются наименее информативные признаки, чья совокупная важность составляет не более 1% от общей важности всех признаков. На каждом этапе отбрасывается не менее одного признака, даже если общий критерий информативности не выполнен.
Основные параметры задачи
Классификация с тремя классами.
Метрика качества: F1_score (macro)
Дополнительный критерий оценки качества: время на достижение лучшей метрики.
Дополнительный критерий оценки качества: количество отобранных признаков при лучшей метрике.
Количество объектов: 10 000 для сгенерированных данных и 13 584 для реальных данных.
Количество признаков: 1 000 для сгенерированных данных и 1 385 для реальных данных.
-
Размерность шагов:
- Фиксированный шаг: 2% от изначального количества признаков.
- Динамический шаг от количества признаков: 10% от количества признаков для каждой итерации.
- Динамический шаг от значимости признаков: 1% от суммарной информативности признаков для каждой итерации.
Задача 1. Сгенерированный набор данных. Меньше информативных признаков, больше шума.
Сгенерируем набор данных.
код
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=10000,
n_features=1000,
n_informative=100,
n_classes=3,
n_clusters_per_class=2,
weights=[0.6, 0.35, 0.05],
random_state=42)
Проведём отбор признаков с использованием кросс-валидации и соберём результаты. В качестве предиктора используем градиентный бустинг над деревьями решений.
код
import numpy as np
from sklearn.model_selection import cross_val_score, RepeatedStratifiedKFold
from time import time
import lightgbm
import pandas as pd
gbm = lightgbm.LGBMClassifier(random_state=42, n_jobs=-1, importance_type='gain')
cv = RepeatedStratifiedKFold(n_splits=5, random_state=42, n_repeats=5)
scoring = 'f1_macro'
# достаточное количество итераций
n_iterations = 51
initial_features = X.shape[1]
# размеры и доли шагов
fixed_step = int(initial_features * 0.02)
dynamic_step = 0.1
super_dynamic_step = 0.01
# функция формирования новой обучающей выборки
def select_important_features(X, model, num_features):
importances = model.feature_importances_
indices = np.argsort(importances)[::-1]
return X.iloc[:, indices[:num_features]]
# ДШ(З) динамическое уменьшение от значимости
super_dynamic_f1_scores, super_dynamic_f1_times, features_super_dynamic, info_out_super_dynamic = [], [], [], []
current_features = initial_features
X_selected = X
gbm.fit(X_selected, y)
for i in range(n_iterations):
if current_features > 1:
#валидация и сбор результатов
start_time = time()
X_selected = select_important_features(X_selected, gbm, current_features)
f1_scores = cross_val_score(gbm, X_selected, y, cv=cv, scoring=scoring)
super_dynamic_f1_scores.append(f1_scores.mean())
super_dynamic_f1_times.append(time() - start_time)
features_super_dynamic.append(current_features)
# обучение на уменьшенном наборе, сбор результатов и расчет нового количества признаков
gbm.fit(X_selected, y)
cums = pd.Series(gbm.feature_importances_, gbm.feature_name_).sort_values().cumsum()
cums_normalized = cums / cums.max()
super_dinam_feats_step_count = len(cums_normalized[cums_normalized <= super_dynamic_step].index)
if super_dinam_feats_step_count < 1:
super_dinam_feats_step_count = 1
info_out_super_dynamic.append(cums_normalized[super_dinam_feats_step_count-1])
current_features = max(current_features - super_dinam_feats_step_count, 1)
# ДШ(К) динамическое уменьшение от количества признаков
dynamic_f1_scores, dynamic_f1_times, features_dynamic, info_out_dynamic = [], [], [], []
current_features = initial_features
X_selected = X
gbm.fit(X_selected, y)
for i in range(n_iterations):
if current_features > 1:
#валидация и сбор результатов
start_time = time()
X_selected = select_important_features(X_selected, gbm, current_features)
f1_scores = cross_val_score(gbm, X_selected, y, cv=cv, scoring=scoring)
dynamic_f1_scores.append(f1_scores.mean())
dynamic_f1_times.append(time() - start_time)
features_dynamic.append(current_features)
# обучение на уменьшенном наборе, сбор доп.результатов и расчет нового количества признаков
gbm.fit(X_selected, y)
cums = pd.Series(gbm.feature_importances_, gbm.feature_name_).sort_values().cumsum()
cums_normalized = cums / cums.max()
out_features = max(int(current_features * (dynamic_step)), 1)
info_out_dynamic.append(cums_normalized[out_features-1])
current_features = max(int(current_features * (1-dynamic_step)), 1)
# ФШ фиксированное уменьшение
fixed_f1_scores, fixed_f1_times, features_fixed, info_out_fixed = [], [], [], []
current_features = initial_features
X_selected = X
gbm.fit(X_selected, y)
for i in range(n_iterations):
if current_features > 1:
#валидация и сбор результатов
start_time = time()
X_selected = select_important_features(X_selected, gbm, current_features)
f1_scores = cross_val_score(gbm, X_selected, y, cv=cv, scoring=scoring)
fixed_f1_scores.append(f1_scores.mean())
fixed_f1_times.append(time() - start_time)
features_fixed.append(current_features)
# обучение на уменьшенном наборе, сбор доп.результатов и расчет нового количества признаков
gbm.fit(X_selected, y)
cums = pd.Series(gbm.feature_importances_, gbm.feature_name_).sort_values().cumsum()
cums_normalized = cums / cums.max()
info_out_fixed.append(cums_normalized[fixed_step-1])
current_features = max(current_features - fixed_step, 1)
Построим графики.
код
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
best_dynamic_iter = np.array(dynamic_f1_scores).argmax()
best_super_dynamic_iter = np.array(super_dynamic_f1_scores).argmax()
best_fixed_iter = np.array(fixed_f1_scores).argmax()
#График 1.1 Количество отобранных признаков в зависимости от числа итераций
fig, ax = plt.subplots(figsize=(24, 8))
ax.plot(range(len(features_dynamic)), features_dynamic, label='ДШ(К)', marker='o')
ax.plot(range(len(features_super_dynamic)), features_super_dynamic, label='ДШ(З)', marker='v')
ax.plot(range(len(features_fixed)), features_fixed, label='ФШ', marker='s')
ax.scatter([best_dynamic_iter], [features_dynamic[best_dynamic_iter]], c='red', s=80, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
ax.scatter([best_super_dynamic_iter], [features_super_dynamic[best_super_dynamic_iter]], c='red', s=80, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
ax.scatter([best_fixed_iter], [features_fixed[best_fixed_iter]], c='red', s=80, marker='s', zorder=3, label='f1_score_max_ФШ')
ax.xaxis.set_major_locator(MaxNLocator(4 * len(ax.get_xticks())))
ax.yaxis.set_major_locator(MaxNLocator(3 * len(ax.get_yticks())))
ax.set_title('Number of Features Selected vs. Iterations')
ax.set_xlabel('Iterations')
ax.set_ylabel('Number of Features Selected')
ax.legend()
ax.grid(True)
plt.show()
# Графики 1.2. Метрика и затраченное время на обучение и валидацию от порядкового номера итерации.
fig, axs = plt.subplots(1, 2, figsize=(24, 8))
# F1-Score от количества итераций
axs[0].plot(range(len(dynamic_f1_scores)), dynamic_f1_scores, label='ДШ(К)', marker='o', markersize=4)
axs[0].plot(range(len(super_dynamic_f1_scores)), super_dynamic_f1_scores, label='ДШ(З)', marker='v', markersize=4)
axs[0].plot(range(len(fixed_f1_scores)), fixed_f1_scores, label='ФШ', marker='s', markersize=4)
axs[0].scatter([best_dynamic_iter], [dynamic_f1_scores[best_dynamic_iter]], c='red', s=30, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
axs[0].scatter([best_super_dynamic_iter], [super_dynamic_f1_scores[best_super_dynamic_iter]], c='red', s=30, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
axs[0].scatter([best_fixed_iter],[fixed_f1_scores[best_fixed_iter]], c='red', s=30, marker='s', zorder=3, label='f1_score_max_ФШ')
axs[0].xaxis.set_major_locator(MaxNLocator(4 * len(axs[0].get_xticks())))
axs[0].yaxis.set_major_locator(MaxNLocator(2 * len(axs[0].get_yticks())))
axs[0].set_title('F1-Score vs. Number of Iterations')
axs[0].set_xlabel('Iterations')
axs[0].set_ylabel('Cross-Validation F1-Score')
axs[0].legend()
axs[0].grid(True)
# Время от количества итераций
axs[1].plot(range(len(dynamic_f1_times)), np.cumsum(dynamic_f1_times), label='ДШ(К)', marker='o', markersize=4)
axs[1].plot(range(len(super_dynamic_f1_times)), np.cumsum(super_dynamic_f1_times), label='ДШ(З)', marker='s', markersize=4)
axs[1].plot(range(len(fixed_f1_times)), np.cumsum(fixed_f1_times), label='ФШ', marker='v', markersize=4)
axs[1].scatter([best_dynamic_iter], [np.cumsum(dynamic_f1_times)[best_dynamic_iter]], c='red', s=30, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
axs[1].scatter([best_super_dynamic_iter], [np.cumsum(super_dynamic_f1_times)[best_super_dynamic_iter]], c='red', s=30, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
axs[1].scatter([best_fixed_iter],[np.cumsum(fixed_f1_times)[best_fixed_iter]], c='red', s=30, marker='s', zorder=3, label='f1_score_max_ФШ')
axs[1].xaxis.set_major_locator(MaxNLocator(4 * len(axs[1].get_xticks())))
axs[1].yaxis.set_major_locator(MaxNLocator(2 * len(axs[1].get_yticks())))
axs[1].set_title('Cumulative CV Time vs. Number of Iterations')
axs[1].set_xlabel('Iterations')
axs[1].set_ylabel('Cumulative CV Time (seconds)')
axs[1].legend()
axs[1].grid(True)
plt.show()
# Графики 1.3.1 Метрика и затраченное время на обучение и валидацию от количества признаков.
fig, axs = plt.subplots(1, 2, figsize=(24, 8))
# F1-Score от количества признаков
axs[0].plot(features_dynamic, dynamic_f1_scores, label='ДШ(К)', marker='o', markersize=4)
axs[0].plot(features_super_dynamic, super_dynamic_f1_scores, label='ДШ(З)', marker='v', markersize=4)
axs[0].plot(features_fixed, fixed_f1_scores, label='ФШ', marker='s', markersize=4)
axs[0].scatter([features_dynamic[best_dynamic_iter]], [dynamic_f1_scores[best_dynamic_iter]], c='red', s=30, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
axs[0].scatter([features_super_dynamic[best_super_dynamic_iter]], [super_dynamic_f1_scores[best_super_dynamic_iter]], c='red', s=30, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
axs[0].scatter([features_fixed[best_fixed_iter]], [fixed_f1_scores[best_fixed_iter]], c='red', s=30, marker='s', zorder=3, label='f1_score_max_ФШ')
axs[0].xaxis.set_major_locator(MaxNLocator(4 * len(axs[0].get_xticks())))
axs[0].yaxis.set_major_locator(MaxNLocator(2 * len(axs[0].get_yticks())))
axs[0].set_ylim(0.54, 0.64)
axs[0].set_title('F1-Score vs. Number of Features')
axs[0].set_xlabel('Number of Features')
axs[0].set_ylabel('Cross-Validation F1-Score')
axs[0].legend()
axs[0].invert_xaxis()
axs[0].grid(True)
# Время от количества признаков
axs[1].plot(features_dynamic, np.cumsum(dynamic_f1_times), label='ДШ(К)', marker='o', markersize=4)
axs[1].plot(features_super_dynamic, np.cumsum(super_dynamic_f1_times), label='ДШ(З)', marker='v', markersize=4)
axs[1].plot(features_fixed, np.cumsum(fixed_f1_times), label='ФШ', marker='s', markersize=4)
axs[1].scatter([features_dynamic[best_dynamic_iter]], [np.cumsum(dynamic_f1_times)[best_dynamic_iter]], c='red', s=30, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
axs[1].scatter([features_super_dynamic[best_super_dynamic_iter]], [np.cumsum(super_dynamic_f1_times)[best_super_dynamic_iter]], c='red', s=30, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
axs[1].scatter([features_fixed[best_fixed_iter]], [np.cumsum(fixed_f1_times)[best_fixed_iter]], c='red', s=30, marker='s', zorder=3, label='f1_score_max_ФШ')
axs[1].xaxis.set_major_locator(MaxNLocator(4 * len(axs[1].get_xticks())))
axs[1].yaxis.set_major_locator(MaxNLocator(2 * len(axs[1].get_yticks())))
axs[1].set_title('Cumulative CV Time vs. Number of Features')
axs[1].set_xlabel('Number of Features')
axs[1].set_ylabel('Cumulative CV Time (seconds)')
axs[1].legend()
axs[1].invert_xaxis()
axs[1].grid(True)
plt.show()
# График 1.3.2 Метрика от количества признаков. Увеличенный масштаб
fig, ax = plt.subplots(figsize=(24, 8))
ax.plot(features_dynamic, dynamic_f1_scores, label='ДШ(К)', marker='o')
ax.plot(features_super_dynamic, super_dynamic_f1_scores, label='ДШ(З)', marker='v')
ax.plot(features_fixed, fixed_f1_scores, label='ФШ', marker='s')
ax.scatter([features_dynamic[best_dynamic_iter]], [dynamic_f1_scores[best_dynamic_iter]], c='red', s=80, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
ax.scatter([features_super_dynamic[best_super_dynamic_iter]], [super_dynamic_f1_scores[best_super_dynamic_iter]], c='red', s=80, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
ax.scatter([features_fixed[best_fixed_iter]], [fixed_f1_scores[best_fixed_iter]], c='red', s=80, marker='s', zorder=3, label='f1_score_max_ФШ')
ax.xaxis.set_major_locator(MaxNLocator(4 * len(ax.get_xticks())))
ax.yaxis.set_major_locator(MaxNLocator(2 * len(ax.get_yticks())))
ax.set_title('F1-Score vs. Number of Features')
ax.set_xlabel('Number of Features')
ax.set_ylabel('Cross-Validation F1-Score')
ax.legend()
ax.invert_xaxis()
ax.grid(True)
ax.set_xlim(180, 20)
ax.set_ylim(0.58, 0.64)
plt.show()
#График 1.4 Объем отбрасываемой значимости в зависимости от номера итераци
fig, axs = plt.subplots(figsize=(24, 12))
axs.plot(range(1, len(features_dynamic)+1)[:50], info_out_dynamic[:50], label='ДШ(К)', marker='o')
axs.plot(range(1,len(features_super_dynamic)+1)[:50], info_out_super_dynamic[:50], label='ДШ(З)', marker='v')
axs.plot(range(1, len(features_fixed)+1)[:50], info_out_fixed[:50], label='ФШ', marker='s')
axs.scatter([best_dynamic_iter], [info_out_dynamic[best_dynamic_iter-1]], c='red', s=80, marker='o', zorder=3, label='f1_score_max_ДШ(К)')
axs.scatter([best_super_dynamic_iter], [info_out_dynamic[best_super_dynamic_iter-1]], c='red', s=80, marker='v', zorder=3, label='f1_score_max_ДШ(З)')
axs.scatter([best_fixed_iter], [info_out_fixed[best_fixed_iter-1]], c='red', s=80, marker='s', zorder=3, label='f1_score_max_ФШ')
axs.xaxis.set_major_locator(MaxNLocator(4 * len(axs.get_xticks())))
axs.yaxis.set_major_locator(MaxNLocator(3 * len(axs.get_yticks())))
axs.set_title('Ratio of discarded importance vs. Iterations')
axs.set_xlabel('Iterations')
axs.set_ylabel('ratio of discarded importance')
axs.legend()
axs.grid(True)
plt.show()
![График 1.1 Количество отобранных признаков в зависимости от числа итераций График 1.1 Количество отобранных признаков в зависимости от числа итераций](https://habrastorage.org/getpro/habr/upload_files/25f/fba/7ba/25ffba7ba2604d8b3f7469acc42fb39e.png)
График отображает количество отобранных признаков в зависимости от числа итераций:
ФШ уменьшает количество признаков с одной и той же скоростью.
ДШ(К) уменьшает количество признаков тем медленнее, чем меньше остаётся признаков.
ДШ(З) уменьшает количество признаков в зависимости от значимости признаков на каждой итерации. На данном графике нет конкретных значений информативности, но на его основании предполагается, что большинство признаков малозначительны или избыточны для модели.
![Графики 1.2. Метрика и затраченное время на обучение и валидацию от порядкового номера итерации. Графики 1.2. Метрика и затраченное время на обучение и валидацию от порядкового номера итерации.](https://habrastorage.org/getpro/habr/upload_files/1a0/ed5/ef5/1a0ed5ef54197717805a790613bc6e5b.png)
Графики отображают метрику и затраченное время признаков в зависимости от номера итерации:
ФШ достигает улучшения метрики на последних итерациях отбора и затрачивает наибольшее время на отбор и валидацию
ДШ(З) достигает улучшения метрики на ранних итерациях отбора и затрачивает наименьшее время на отбор и валидацию.
После нахождения максимума метрики для каждого из методов по мере отбрасывания признаков на каждой итерации, метрика начинает ухудшаться:
для ФШ метрика падает с высокой скоростью, т.к. отбрасываются существенное количество значимых признаков.
для ДШ(З) метрика снижается медленнее с каждой итерацией, т.к. метод учитывает, что отбрасываются значимые признаки, и поэтому количество отбрасываемых признаков сокращается.
![Графики 1.3.1 Метрика и затраченное время на обучение и валидацию от количества признаков Графики 1.3.1 Метрика и затраченное время на обучение и валидацию от количества признаков](https://habrastorage.org/getpro/habr/upload_files/fb7/500/23c/fb750023c7ff16437eaad32b094359a1.png)
![Графики 1.3.2 Метрика от количества признаков в важной области исследования Графики 1.3.2 Метрика от количества признаков в важной области исследования](https://habrastorage.org/getpro/habr/upload_files/e34/d66/c4c/e34d66c4c367c0fae7765d397e570ee7.png)
Графики отображают метрику и затраченное время признаков в зависимости от количества признаков:
методы отбора достигают максимальных значений метрик в одной и той же области исследования.
ДШ(З) достигает улучшения метрики на ранних итерациях отбора и затрачивает наименьшее время на отбор и валидацию.
![График 1.4 Объем отбрасываемой значимости в зависимости от номера итерации График 1.4 Объем отбрасываемой значимости в зависимости от номера итерации](https://habrastorage.org/getpro/habr/upload_files/40c/aa3/63f/40caa363fdafaccac7a88769332d7f04.png)
ДШ(З) отбрасывает признаки, пока это возможно, сохраняя объем отбрасываемой совокупной значимости для каждой итерации на уровне 1%
В таблице представлены численные результаты и условная оценка качества работы алгоритма.
Задача 2. Сгенерированный набор данных. Больше информативных признаков, меньше шума.
Сгенерируем набор данных, усложним задачу отбора и, добавив больше информативных признаков, проведём отбор, соберём и визуализируем результаты. Прокомментируем те моменты, когда поведение моделей отбора будет отличаться от их поведения в первой задаче.
код генерации новых данных
X, y = make_classification(n_samples=10000, n_features=1000, n_informative=1000, n_redundant=0,
n_classes=3, n_clusters_per_class=4, weights=[0.6, 0.35, 0.05],
class_sep=0.8, random_state=42)
![График 2.1 Количество отобранных признаков в зависимости от числа итераций График 1.1 Количество отобранных признаков в зависимости от числа итераций](https://habrastorage.org/getpro/habr/upload_files/f1c/1b6/3c7/f1c1b63c735347f4f69715f2cd803ee4.png)
ДШ(З) уменьшает количество признаков в зависимости от информативности признаков на каждой итерации. На основании линии ДШ(З) предполагается, что в этой задаче меньше шумовых признаков с крайне низкой значимостью.
![Графики 2.2. Метрика и затраченное время на обучение и валидацию от порядкового номера итерации. Графики 1.2. Метрика и затраченное время на обучение и валидацию от порядкового номера итерации.](https://habrastorage.org/getpro/habr/upload_files/db5/583/f21/db5583f2157d30698f9d2d2c33db76b9.png)
ДШ(З) достигает улучшения метрики на поздних итерациях отбора и затрачивает наибольшее время на отбор. Метрика принимает наибольшее значение среди моделей с наглядным отрывом.
![Графики 2.3.1 Метрика и затраченное время на обучение и валидацию от количества признаков Графики 1.3.1 Метрика и затраченное время на обучение и валидацию от количества признаков](https://habrastorage.org/getpro/habr/upload_files/03e/cc9/cee/03ecc9ceee800975c76832fa87f857c4.png)
![График 2.3.2 Метрика от количества признаков в важной области исследования Графики 1.3.2 Метрика от количества признаков в важной области исследования](https://habrastorage.org/getpro/habr/upload_files/609/a87/d35/609a87d357f6de4028707b16bd7cbcf6.png)
в области 120-40 отобранных признаков ДШ(З) удерживает метрику высокой надёжнее других моделей
![График 2.4 Объем отбрасываемой значимости в зависимости от номера итерации График 1.4 Объем отбрасываемой значимости в зависимости от номера итерации](https://habrastorage.org/getpro/habr/upload_files/c41/11e/7b4/c4111e7b44bd28b4394bd8009f0e1fd9.png)
Задача 3. Реальная задача. Краткий обзор результатов.
Для реальной задачи представим графические и табличные результаты, чтобы подтвердить, что логика рассмотренная в тепличных условиях сгенерированных данных совпадает с логикой решения задачи в боевом режиме. Детали и решение задачи доступно по ссылке.
![График 3.1 Количество отобранных признаков в зависимости от числа итераций График 1.1 Количество отобранных признаков в зависимости от числа итераций](https://habrastorage.org/getpro/habr/upload_files/277/91e/7da/27791e7da6a92e6688a5498fcdc39ac0.png)
![Графики 3.2.1 Метрика и затраченное время на обучение и валидацию от количества признаков Графики 1.3.1 Метрика и затраченное время на обучение и валидацию от количества признаков](https://habrastorage.org/getpro/habr/upload_files/252/8cc/4c8/2528cc4c875f2c5d79327ce37202a8ac.png)
![График 3.2.2 Метрика от количества признаков в важной области исследования Графики 1.3.2 Метрика от количества признаков в важной области исследования](https://habrastorage.org/getpro/habr/upload_files/aac/46a/17d/aac46a17d61f6efba269b70580221353.png)
Результаты и выводы
В ходе исследования оценены три метода отбора признаков на основе их способности улучшать и удерживать точность модели, затрачивать минимальное количество времени и сокращать количество признаков:
-
Фиксированный шаг (ФШ):
Удаляет фиксированное количество признаков на каждом шаге
Чувствителен к неправильной настройке начальной доли отбора, что рискует привести к избыточному исключению признаков на поздних этапах отбора.
Способен достигать достаточной метрики, но затрачивает больше времени.
-
Динамический шаг, зависящий от количества признаков (ДШ(К)):
Адаптирует скорость уменьшения признаков в зависимости от их количества на итерациях отбора
Чувствителен к неправильной настройке начальной доли отбора, что рискует привести к преждевременному исключению важных признаков на начальных этапах отбора.
Достижение достаточной метрики с меньшими временными затратами по сравнению с ФШ, что делает привлекательным вариантом для уменьшения времени отбора
-
Динамический шаг, зависящий от значимости признаков (ДШ(З)):
Опирается на информативность признаков для определения количества удаляемых элементов, обеспечивает эффективность отбора, в первую очередь с точки зрения метрики.
Устойчив к ошибкам в настройке доли, так как отбрасывает признаки на основе фактической значимости, что минимизирует риск снижения метрики даже при неидеальных настройках. Выбор доли характеризует скорость работы алгоритма, сохраняя логику: признаки с низкой значимостью обрабатываются быстро и грубо, признаки с высокой значимостью обрабатываются детализировано
При большом количестве шума достигает достаточной метрики на ранних этапах и требует наименьшее количество времени
При малом количестве шума и большом количестве информативных признаков достигает лучшей метрики за счет детального, но длительного отбора признаков.
Предложенные подходы к выбору размера шага не только улучшают оперативность работы с данными, но и обеспечивает адаптивность к изменяющимся условиям анализа данных, делая ДШ(З) предпочтительным выбором для типовых задач, где критичны точность моделирования и время.
Применение методов отбора в иных условиях
В статье не рассмотрены все существующие методы отбора признаков, но примеры, рассмотренные здесь, дают базу для творческих размышлений. Дополнительное внимание при отборе признаков рассмотренными методами следует уделить настройке размерности долей для каждого из типов шагов. Понимание сути данных и предварительный анализ облегчат эту задачу. Надёжная кросс-валидация поможет точнее определить, что лучшая метрика найдена, а также обеспечит ясность представления результатов отбора на графиках. Кроме того, в статье не рассмотрен критерий останова отбора, который может быть определён требуемым количеством признаков по техническому заданию, достижением максимума метрики или иным способом.
Учёт этих нюансов позволяет подходить к отбору признаков творчески и адаптировать методы под конкретные задачи и условия, создавая эффектный танец с точными шагами и своевременными остановками. Желаю удачи!
economist75
Отличная работа! Однозначно посыл к более смелому фич-инжинирингу и безумным джойнам данных всего со всем.
Оценки времени на отбор в очередной раз дают повод для ~грусти~ upgrade ПК аналитиков, но это как раз то самое немногое, на что можно и нужно влиять.
ErikKagarmanov Автор
Благодарю! Меткие замечания!