← Назад к вопросам
Как проводилась итоговая оценка качества в рабочем ML-проекте?
2.0 Middle🔥 221 комментариев
#Метрики и оценка моделей#Опыт и проекты
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Итоговая оценка качества ML-проекта: Best Practices
В рабочих ML-проектах итоговая оценка качества требует комплексного подхода с несколькими уровнями валидации и метриками. Расскажу о проверенной методологии, которую использую в production.
Фаза 1: Подготовка и базовые метрики
Разделение данных
from sklearn.model_selection import train_test_split, StratifiedKFold
import numpy as np
# Стратифицированный split с учётом классов
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
stratify=y,
random_state=42
)
# Дополнительный валидационный сет
X_train, X_val, y_train, y_val = train_test_split(
X_train, y_train,
test_size=0.2,
stratify=y_train,
random_state=42
)
print(f"Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}")
print(f"Train pos rate: {y_train.mean():.2%}")
print(f"Val pos rate: {y_val.mean():.2%}")
print(f"Test pos rate: {y_test.mean():.2%}")
Фаза 2: Многоуровневая валидация
K-Fold Cross-Validation
from sklearn.model_selection import cross_validate
from sklearn.metrics import make_scorer
scoring_metrics = {
'accuracy': 'accuracy',
'precision_macro': 'precision_macro',
'recall_macro': 'recall_macro',
'f1_macro': 'f1_macro',
'roc_auc': 'roc_auc_ovr_weighted',
}
model = GradientBoostingClassifier(n_estimators=100, random_state=42)
cv_scores = cross_validate(
model,
X_train,
y_train,
cv=5,
scoring=scoring_metrics,
return_train_score=True,
n_jobs=-1
)
print("\n=== Cross-Validation Results ===")
for metric in scoring_metrics.keys():
train_scores = cv_scores[f'train_{metric}']
test_scores = cv_scores[f'test_{metric}']
print(f"{metric}:")
print(f" Train: {train_scores.mean():.4f} ± {train_scores.std():.4f}")
print(f" CV: {test_scores.mean():.4f} ± {test_scores.std():.4f}")
print()
Фаза 3: Финальная оценка на test set
Полный набор метрик
from sklearn.metrics import (
classification_report,
confusion_matrix,
roc_auc_score,
roc_curve,
auc,
precision_recall_curve,
f1_score,
matthews_corrcoef,
)
import matplotlib.pyplot as plt
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
print("\n=== Test Set Evaluation ===")
print(f"\nAccuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"F1-Score: {f1_score(y_test, y_pred, average='weighted'):.4f}")
print(f"Precision: {precision_score(y_test, y_pred, average='weighted'):.4f}")
print(f"Recall: {recall_score(y_test, y_pred, average='weighted'):.4f}")
print(f"Matthews Correlation Coef: {matthews_corrcoef(y_test, y_pred):.4f}")
print(f"\nROC-AUC: {roc_auc_score(y_test, y_pred_proba, multi_class='ovr'):.4f}")
print("\n=== Classification Report ===")
print(classification_report(y_test, y_pred))
print("\n=== Confusion Matrix ===")
print(confusion_matrix(y_test, y_pred))
Фаза 4: Анализ ошибок и bias
Confusion Matrix анализ
import pandas as pd
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
print(f"\nTrue Negatives: {tn}")
print(f"False Positives: {fp} (Type I error)")
print(f"False Negatives: {fn} (Type II error)")
print(f"True Positives: {tp}")
print(f"\nSpecificity (TNR): {tn / (tn + fp):.4f}")
print(f"Sensitivity (TPR): {tp / (tp + fn):.4f}")
print(f"False Positive Rate: {fp / (fp + tn):.4f}")
print(f"False Negative Rate: {fn / (fn + tp):.4f}")
ROC и PR кривые
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# ROC кривая
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
roc_auc = auc(fpr, tpr)
axes[0].plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.4f})')
axes[0].plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='Random Classifier')
axes[0].set_xlabel('False Positive Rate')
axes[0].set_ylabel('True Positive Rate')
axes[0].set_title('ROC Curve')
axes[0].legend(loc='lower right')
axes[0].grid(True)
# Precision-Recall кривая
precision, recall, _ = precision_recall_curve(y_test, y_pred_proba)
axes[1].plot(recall, precision, color='blue', lw=2, label='PR curve')
axes[1].set_xlabel('Recall')
axes[1].set_ylabel('Precision')
axes[1].set_title('Precision-Recall Curve')
axes[1].legend(loc='upper right')
axes[1].grid(True)
plt.tight_layout()
plt.savefig('evaluation_curves.png', dpi=300, bbox_inches='tight')
plt.show()
Фаза 5: Анализ по подгруппам (Fairness)
# Проверяем, нет ли bias по защищённым признакам
for protected_attr in ['gender', 'age_group']:
print(f"\n=== Performance by {protected_attr} ===")
for group in X_test[protected_attr].unique():
mask = X_test[protected_attr] == group
group_pred = y_pred[mask]
group_true = y_test[mask]
f1 = f1_score(group_true, group_pred, average='weighted', zero_division=0)
recall = recall_score(group_true, group_pred, average='weighted', zero_division=0)
print(f"{group}: F1={f1:.4f}, Recall={recall:.4f}, N={mask.sum()}")
Фаза 6: Производительность и стабильность
import time
# Скорость инференса
start = time.time()
y_pred_batch = model.predict(X_test)
inference_time = (time.time() - start) / len(X_test) * 1000 # ms per sample
print(f"\nInference time: {inference_time:.2f} ms per sample")
print(f"Throughput: {1000 / inference_time:.0f} samples/sec")
# Стабильность: разница между CV и test
cv_f1 = cv_scores['test_f1_macro'].mean()
test_f1 = f1_score(y_test, y_pred, average='weighted')
gap = abs(cv_f1 - test_f1)
print(f"\nCV F1: {cv_f1:.4f}")
print(f"Test F1: {test_f1:.4f}")
print(f"Gap: {gap:.4f} (должна быть < 0.05 для хорошей стабильности)")
if gap > 0.05:
print("WARNING: Большой gap между CV и Test - возможно переобучение")
Фаза 7: Интерпретируемость
# Feature importance
feature_importance = pd.DataFrame({
'feature': X_train.columns,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print("\n=== Top 10 Important Features ===")
print(feature_importance.head(10))
# Проверяем, что важные признаки логичны
if feature_importance.iloc[0]['importance'] < 0.05:
print("WARNING: Топ-признак имеет низкий вес - возможно переобучение")
Финальный отчёт
final_report = {
'model': model.__class__.__name__,
'cv_f1': cv_scores['test_f1_macro'].mean(),
'cv_f1_std': cv_scores['test_f1_macro'].std(),
'test_accuracy': accuracy_score(y_test, y_pred),
'test_f1': f1_score(y_test, y_pred, average='weighted'),
'test_roc_auc': roc_auc_score(y_test, y_pred_proba, multi_class='ovr'),
'inference_ms': inference_time,
'model_size_mb': model.__sizeof__() / 1024 / 1024,
'cv_test_gap': gap,
'status': 'READY_FOR_PRODUCTION' if gap < 0.05 and test_f1 > 0.85 else 'NEEDS_IMPROVEMENT'
}
print("\n" + "="*50)
print("FINAL EVALUATION REPORT")
print("="*50)
for key, value in final_report.items():
print(f"{key}: {value}")
Ключевые метрики для production
| Метрика | Порог | Описание |
|---|---|---|
| F1-Score | > 0.80 | Баланс precision и recall |
| CV Stability | Gap < 0.05 | Нет переобучения |
| Inference time | < 100ms | Реальное время ответа |
| ROC-AUC | > 0.85 | Качество при разных порогах |
| Fairness | Max group diff < 5% | Нет дискриминации |
Итог
Итоговая оценка включает: стратифицированный split, K-Fold CV, расширенный набор метрик, анализ ошибок, проверку fairness, тест производительности и интерпретируемость. Только после всех проверок модель готова к production.