Вопрос 1
Почему нельзя многократно выбирать модель по результату test set?
TRACK A · COMPOSITE SOURCE · Составной материал · уроки 1.2.4, 1.2.6, 1.4.3 и 1.5.1–1.5.7
Разберём, как честно оценить модель: разделение данных, метрики регрессии и классификации, дисбаланс классов, порог решения, переобучение и утечка данных.
Модель может почти идеально работать на обучающих примерах и плохо — на новых. Поэтому качество нельзя оценивать на тех же данных, по которым настраивали параметры. Нужен независимый эксперимент, отражающий предполагаемое применение.
Training set используется для настройки параметров. Validation set помогает сравнивать варианты, выбирать гиперпараметры и порог решения. Test set оставляют для итоговой оценки выбранного решения. Если многократно подбирать модель по test score, тест перестаёт быть независимым.
Baseline — простое решение для сравнения. Для регрессии это прогноз средним или медианой обучающей выборки; для классификации — наиболее частый класс. Сложная модель должна показать измеримое преимущество, иначе её стоимость не оправдана.
MAE — средняя абсолютная ошибка в исходных единицах; MSE усредняет квадраты ошибок и сильнее штрафует большие промахи; RMSE — корень MSE, снова в исходных единицах; R² сравнивает модель с прогнозом средним. R² может быть отрицательным и не означает процент правильных прогнозов.
MAE — средняя абсолютная ошибка; MSE — средняя квадратичная ошибка; RMSE — корень MSE; R² — коэффициент детерминации относительно базового прогноза средним.
Матрица ошибок содержит TP (верно найденный положительный случай), TN (верно найденный отрицательный), FP (ложная тревога) и FN (пропуск). Accuracy — доля правильных ответов. Precision = TP/(TP+FP) показывает надёжность положительных сигналов; Recall = TP/(TP+FN) — долю обнаруженных положительных случаев; F1 — гармоническое среднее precision и recall.
Accuracy = (TP+TN)/N · Precision = TP/(TP+FP) · Recall = TP/(TP+FN) · F1 = 2PR/(P+R)
Если из 1000 проверок только 10 аварийные, модель «аварии нет» во всех случаях получит accuracy 99%, но recall аварий будет 0%. При редких событиях обязательно смотрите confusion matrix, precision, recall, PR-кривую и последствия ошибок.
Классификатор часто выдаёт вероятность или score, а threshold превращает его в класс. Снижение порога обычно увеличивает recall и число ложных тревог; повышение может увеличить precision, но привести к пропускам. Порог выбирают исходя из стоимости FP и FN, а не автоматически равным 0.5.
Underfitting — модель слишком проста; overfitting — чрезмерно подстроена под train; generalization — способность работать на новых данных. Data leakage возникает, когда в признаки или подготовку попадает информация из будущего или из тестовой части. Масштабирование, заполнение пропусков и отбор признаков должны обучаться только на train.
Случайный split подходит не всегда. Для временного прогноза используйте разделение по времени: прошлое для обучения, будущее для проверки. Для нескольких строк одного пользователя, устройства или здания может потребоваться group split, чтобы связанные записи не попали одновременно в train и test. При малом наборе используйте cross-validation с корректной схемой.
ROC-кривая показывает зависимость True Positive Rate (Recall) от False Positive Rate при изменении порога. ROC-AUC — площадь под этой кривой; она характеризует способность модели ранжировать положительные примеры выше отрицательных. Значение около 0.5 соответствует случайному ранжированию, а 1.0 — идеальному разделению на оцениваемой выборке. ROC-AUC не показывает, насколько хорошо выбран конкретный рабочий порог.
Precision–Recall curve показывает изменение Precision и Recall при разных порогах. Average Precision (AP) суммирует качество precision-recall ранжирования. При редком положительном классе PR-кривая часто информативнее ROC: большое количество истинно отрицательных примеров может сделать False Positive Rate небольшим даже при значительном числе ложных тревог. Базовый уровень AP зависит от доли положительного класса.
Ни ROC-AUC, ни AP не заменяют анализ confusion matrix, калибровки вероятностей, стоимости ошибок и качества на независимых данных. Сравнивайте модели на одной и той же схеме разбиения.
K-fold cross-validation делит обучающую часть на K блоков: модель K раз обучается на K−1 блоках и проверяется на оставшемся. Среднее и разброс метрики показывают, насколько результат зависит от конкретного разбиения. Для классификации с дисбалансом применяют StratifiedKFold, сохраняющий приблизительную долю классов в каждом блоке. Для связанных наблюдений одного объекта используют GroupKFold. Для временных рядов используют последовательную схему, например TimeSeriesSplit, не допуская обучения на будущем.
Кросс-валидацию выполняют внутри обучающей части. Тестовую выборку оставляют нетронутой до финальной оценки. Если по результатам CV выбирают гиперпараметры, тест нельзя использовать для повторной подстройки. Все операции предобработки следует включать в Pipeline, чтобы они обучались заново только на соответствующем тренировочном fold.
Сравните baseline и две модели на одной схеме разделения. Для регрессии посчитайте MAE и RMSE; для классификации — confusion matrix, precision и recall. Запишите ограничения эксперимента. Метрика отвечает только на конкретный вопрос о конкретных данных.
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.linear_model import LinearRegression
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
model = LinearRegression().fit(X_train, y_train)
pred = model.predict(X_test)
print("MAE:", mean_absolute_error(y_test, pred))
print("RMSE:", mean_squared_error(y_test, pred) ** 0.5)
print("R2:", r2_score(y_test, pred))Важно: все преобразования данных должны обучаться только на тренировочной выборке.
Эксперимент на синтетических данных демонстрирует стратифицированное разбиение, базовый классификатор, конвейер предобработки Pipeline и несколько метрик. В реальном проекте замените генератор на подготовленные признаки и заранее определите цену ложных тревог и пропусков.
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.dummy import DummyClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (confusion_matrix, precision_score, recall_score,
f1_score, roc_auc_score, average_precision_score)
X, y = make_classification(
n_samples=1200, n_features=12, weights=[0.9, 0.1],
class_sep=0.8, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, stratify=y, random_state=42
)
models = {
"baseline": DummyClassifier(strategy="prior"),
"logistic": make_pipeline(StandardScaler(),
LogisticRegression(max_iter=1000))
}
for name, model in models.items():
model.fit(X_train, y_train)
pred = model.predict(X_test)
score = model.predict_proba(X_test)[:, 1]
print("\\n", name)
print("confusion matrix:\\n", confusion_matrix(y_test, pred))
print("precision:", precision_score(y_test, pred, zero_division=0))
print("recall:", recall_score(y_test, pred, zero_division=0))
print("f1:", f1_score(y_test, pred, zero_division=0))
print("roc_auc:", roc_auc_score(y_test, score))
print("average_precision:", average_precision_score(y_test, score))
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(models["logistic"], X_train, y_train,
cv=cv, scoring="average_precision")
print("CV AP mean/std:", cv_scores.mean(), cv_scores.std())Сначала сравните baseline и logistic по одним и тем же тестовым объектам. Затем изучите матрицу ошибок и проверьте, соответствует ли порог 0.5 реальной цене FP и FN. Значение CV AP оценивает устойчивость внутри обучающей выборки; тестовая оценка остаётся финальной и независимой.
Почему нельзя многократно выбирать модель по результату test set?
Почему accuracy может вводить в заблуждение при дисбалансе классов?
Как следует разделять данные для прогноза будущих событий?
Если accuracy равна 99%, а recall редкого класса равен 0%, может ли модель пропускать все события этого класса?