← Назад к вопросам
Как определить критерии остановки forward-backward selection?
2.0 Middle🔥 61 комментариев
#Машинное обучение#Метрики и оценка моделей
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Критерии остановки Forward-Backward Selection
Forward-Backward Selection (пошаговая селекция признаков) — это алгоритм выбора подмножества признаков для модели. Критерии остановки определяют, когда алгоритм должен прекратить добавлять или удалять признаки.
1. Виды пошаговой селекции и их критерии
1.1 Forward Selection (добавление признаков)
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.datasets import load_breast_cancer
import numpy as np
# Загружаем данные
X, y = load_breast_cancer(return_X_y=True)
print(f"Всего признаков: {X.shape[1]}")
# Forward selection: добавляем признаки один за другим
# Критерий остановки: когда метрика перестаёт улучшаться
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
selected_features = []
best_score = -np.inf
scores = []
for i in range(X.shape[1]):
best_new_score = -np.inf
best_feature = -1
for j in range(X.shape[1]):
if j in selected_features or j == -1:
continue
# Пытаемся добавить признак j
features = selected_features + [j]
model = LogisticRegression(max_iter=1000)
score = cross_val_score(
model, X[:, features], y,
cv=5, scoring='accuracy'
).mean()
if score > best_new_score:
best_new_score = score
best_feature = j
# Критерий остановки 1: улучшение меньше threshold
if best_new_score - best_score < 0.001:
print(f"Остановка: улучшение < 0.1%")
break
selected_features.append(best_feature)
best_score = best_new_score
scores.append(best_score)
print(f"Итерация {len(selected_features)}: добавлен признак {best_feature}, score={best_new_score:.4f}")
1.2 Backward Elimination (удаление признаков)
# Backward elimination: убираем худшие признаки
from sklearn.preprocessing import StandardScaler
selected_features = list(range(X.shape[1])) # Начинаем со всех
best_score = -np.inf
scores = []
for iteration in range(X.shape[1]):
# Вычисляем score текущего набора
model = LogisticRegression(max_iter=1000)
current_score = cross_val_score(
model, X[:, selected_features], y,
cv=5, scoring='accuracy'
).mean()
scores.append(current_score)
# Критерий остановки 1: если убавление признаков ухудшает модель
if iteration > 0 and current_score < scores[-2] - 0.001:
print(f"Остановка: удаление признака ухудшает модель")
break
if len(selected_features) == 1:
print(f"Остановка: остался 1 признак")
break
# Пробуем удалить каждый признак
worst_feature = -1
worst_score = current_score
for feature in selected_features:
features_without = [f for f in selected_features if f != feature]
model = LogisticRegression(max_iter=1000)
score = cross_val_score(
model, X[:, features_without], y,
cv=5, scoring='accuracy'
).mean()
# Худший признак — тот, удаление которого лучше всего сказывается на score
if score > worst_score:
worst_score = score
worst_feature = feature
if worst_feature != -1:
selected_features.remove(worst_feature)
print(f"Итерация {iteration}: удалён признак {worst_feature}")
2. Типовые критерии остановки
# Критерий 1: Минимальный прирост метрики
def stop_criterion_min_improvement(previous_score, current_score, threshold=0.001):
"""
Остановка если улучшение меньше threshold
"""
improvement = current_score - previous_score
return improvement < threshold
# Критерий 2: Максимальное количество признаков
def stop_criterion_max_features(num_features, max_features=20):
"""
Остановка если добавили max_features признаков
"""
return num_features >= max_features
# Критерий 3: Статистическая значимость
from scipy.stats import f_oneway
def stop_criterion_statistical_significance(p_value, alpha=0.05):
"""
Остановка если p-value > alpha (не статистически значим)
"""
return p_value > alpha
# Критерий 4: Информационный критерий (AIC, BIC)
def stop_criterion_aic(model, X, y):
"""
Используем AIC: ниже — лучше
AIC = 2k - 2*ln(L)
k = количество параметров
L = максимальное значение likelihood
"""
from sklearn.metrics import log_loss
k = X.shape[1] # Количество признаков
n = X.shape[0] # Количество наблюдений
y_pred = model.predict_proba(X)[:, 1]
log_likelihood = -n * log_loss(y, y_pred)
aic = 2 * k - 2 * log_likelihood
return aic
# Критерий 5: Раннее остановление (Early Stopping)
def stop_criterion_early_stopping(train_score, val_score, patience=3):
"""
Если val_score не улучшается patience итераций подряд
"""
return patience <= 0 # Счётчик обнулился
3. Полный пример с несколькими критериями
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import StandardScaler
class ForwardSelectionWithStoppingCriteria:
def __init__(self,
min_improvement=0.001,
max_features=None,
cv=5,
patience=3):
self.min_improvement = min_improvement
self.max_features = max_features or float('inf')
self.cv = cv
self.patience = patience
self.selected_features = []
self.scores_history = []
def fit(self, X, y):
n_features = X.shape[1]
patience_counter = self.patience
best_score = -np.inf
for iteration in range(n_features):
# Критерий 1: Достигнули max_features
if len(self.selected_features) >= self.max_features:
print(f"STOP: Достигнут максимум {self.max_features} признаков")
break
# Критерий 2: Patience исчерпана
if patience_counter <= 0:
print(f"STOP: Early stopping (no improvement for {self.patience} iterations)")
break
best_new_feature = -1
best_new_score = best_score
# Пробуем добавить каждый оставшийся признак
for feature_idx in range(n_features):
if feature_idx in self.selected_features:
continue
features_to_test = self.selected_features + [feature_idx]
model = LogisticRegression(max_iter=1000, random_state=42)
scores = cross_val_score(
model, X[:, features_to_test], y,
cv=self.cv, scoring='f1_weighted'
)
cv_score = scores.mean()
if cv_score > best_new_score:
best_new_score = cv_score
best_new_feature = feature_idx
# Критерий 3: Минимальное улучшение
improvement = best_new_score - best_score
if improvement < self.min_improvement:
print(f"STOP: Улучшение {improvement:.4f} < {self.min_improvement}")
break
if best_new_feature == -1:
print(f"STOP: Нет новых признаков для добавления")
break
# Добавляем лучший признак
self.selected_features.append(best_new_feature)
best_score = best_new_score
self.scores_history.append(best_score)
print(f"Итерация {iteration+1}: добавлен признак {best_new_feature}, "
f"улучшение={improvement:.4f}, текущий score={best_score:.4f}")
# Обновляем patience counter
if improvement > self.min_improvement:
patience_counter = self.patience
else:
patience_counter -= 1
return self
# Использование
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=200, n_features=20, n_informative=10, random_state=42)
fs = ForwardSelectionWithStoppingCriteria(
min_improvement=0.005,
max_features=10,
patience=3,
cv=5
)
fs.fit(X, y)
print(f"\nВыбранные признаки: {fs.selected_features}")
print(f"История scores: {fs.scores_history}")
4. Критерии, основанные на информационных мерах
# AIC (Akaike Information Criterion)
def calculate_aic(n_samples, n_features, rss):
"""
AIC = n*log(RSS/n) + 2*p
p = количество параметров (признаков + intercept)
"""
p = n_features + 1
return n_samples * np.log(rss / n_samples) + 2 * p
# BIC (Bayesian Information Criterion)
def calculate_bic(n_samples, n_features, rss):
"""
BIC = n*log(RSS/n) + p*log(n)
BIC строже штрафует сложность, чем AIC
"""
p = n_features + 1
return n_samples * np.log(rss / n_samples) + p * np.log(n_samples)
# Регуляризированные модели: используют встроенные критерии
from sklearn.linear_model import LinearRegression, Ridge, Lasso
# Ridge/Lasso автоматически выбирают alpha на основе cross-validation
from sklearn.linear_model import RidgeCV
model = RidgeCV(alphas=[0.1, 1.0, 10.0], cv=5)
model.fit(X, y)
print(f"Optimal alpha: {model.alpha_}")
5. Практические рекомендации
Выбор критериев остановки зависит от задачи:
1. Для быстрого результата:
└─ min_improvement = 0.01 (1%)
└─ max_features = 5-10
└─ cv = 3
2. Для аккуратного анализа:
└─ min_improvement = 0.001 (0.1%)
└─ max_features = количество_признаков / 2
└─ cv = 10
└─ patience = 5
3. Для production:
└─ Используй p_value (статистическая значимость)
└─ Используй AIC/BIC (информационные критерии)
└─ Лучше недообучиться, чем переобучиться
4. Для интерпретируемости:
└─ max_features = 3-7 (можно интерпретировать)
└─ min_improvement = 0.005 (значимое улучшение)
6. Сравнение с регуляризацией
# Вместо forward selection можно использовать:
# 1. L1 регуляризация (Lasso) — автоматически зануляет признаки
from sklearn.linear_model import LassoCV
lasso = LassoCV(cv=5)
lasso.fit(X_scaled, y)
selected_lasso = np.where(lasso.coef_ != 0)[0]
print(f"Признаки с Lasso: {selected_lasso}")
# 2. Tree-based feature importance
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)
feature_importance = rf.feature_importances_
selected_rf = np.argsort(feature_importance)[-10:] # Top 10
print(f"Top признаки (Random Forest): {selected_rf}")
# 3. Permutation importance
from sklearn.inspection import permutation_importance
perm_importance = permutation_importance(rf, X, y, n_repeats=10)
selected_perm = np.argsort(perm_importance.importances_mean)[-10:]
print(f"Top признаки (Permutation): {selected_perm}")
Заключение
Критерии остановки Forward-Backward Selection:
- Минимальное улучшение метрики — самый частый (0.1-1%)
- Максимальное количество признаков — для интерпретируемости
- Статистическая значимость (p-value) — для формальной статистики
- Информационные критерии (AIC/BIC) — для моделей с likelihood
- Early Stopping (Patience) — когда улучшение застопорилось
- Без улучшения валидационной метрики — защита от переобучения
Рекомендуемый подход:
- Используй комбинацию критериев
- Начни с простого (min_improvement + max_features)
- Визуализируй кривую scores для анализа
- Проверь результаты на валидационном сете
- Рассмотри альтернативы (Lasso, Tree-based selection)