← Назад к вопросам

Как проводилась итоговая оценка качества в рабочем 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 StabilityGap < 0.05Нет переобучения
Inference time< 100msРеальное время ответа
ROC-AUC> 0.85Качество при разных порогах
FairnessMax group diff < 5%Нет дискриминации

Итог

Итоговая оценка включает: стратифицированный split, K-Fold CV, расширенный набор метрик, анализ ошибок, проверку fairness, тест производительности и интерпретируемость. Только после всех проверок модель готова к production.

Как проводилась итоговая оценка качества в рабочем ML-проекте? | PrepBro