训练完 ≠ 完事。没有评估的训练是自嗨

混淆矩阵、Precision/Recall/F1、ROC/AUC、交叉验证、过拟合诊断全掌握。

[toc]

模型评估与验证


1️⃣ 分类任务评估指标

混淆矩阵 (Confusion Matrix)

             预测值
           正类   负类
实际  正类  TP    FN     ← 灵敏度 (Recall)
值    负类  FP    TN     ← 特异度
           ↑     ↑
         精确率  负类精确率
         (Precision)
指标公式含义
准确率 (Accuracy)(TP+TN)/(TP+TN+FP+FN)整体对了多少
精确率 (Precision)TP/(TP+FP)判为对的里面,真对的比例
召回率 (Recall)TP/(TP+FN)真的里面找出来了多少
F1 Score2×P×R/(P+R)精确率+召回率的调和平均
from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, 
    f1_score, confusion_matrix, classification_report,
    roc_auc_score
)
 
# 假設有预测和真实标签
y_true = [0, 1, 1, 0, 1, 0, 1, 1]
y_pred = [0, 1, 0, 0, 1, 0, 1, 0]
 
print(f"Accuracy:  {accuracy_score(y_true, y_pred):.4f}")
print(f"Precision: {precision_score(y_true, y_pred):.4f}")
print(f"Recall:    {recall_score(y_true, y_pred):.4f}")
print(f"F1 Score:  {f1_score(y_true, y_pred):.4f}")
print("\n分类报告:")
print(classification_report(y_true, y_pred))
print(f"混淆矩阵:\n{confusion_matrix(y_true, y_pred)}")

ROC 曲线与 AUC

from sklearn.metrics import RocCurveDisplay
 
# ROC 曲线衡量模型在不同阈值下的性能
# AUC = Area Under Curve,越接近 1 越好
y_scores = model.predict_proba(X_test)[:, 1]  # 概率
auc = roc_auc_score(y_test, y_scores)
print(f"AUC: {auc:.4f}")
 
RocCurveDisplay.from_predictions(y_test, y_scores)

什么时候用哪个指标?

场景关注指标例子
类别均衡Accuracy手写数字识别
类别严重不平衡Precision/Recall/F1欺诈检测(正例很少)
宁错杀不放过的场景高 Recall癌症筛查
宁放过不错杀的场景高 Precision垃圾邮件过滤(误判不可接受)
综合评估F1 Score/AUC通用场景

2️⃣ 回归任务评估指标

from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
 
y_true = [1.0, 2.5, 3.2, 4.1, 5.6]
y_pred = [1.1, 2.3, 3.0, 4.3, 5.8]
 
mse = mean_squared_error(y_true, y_pred)   # 均方误差
rmse = np.sqrt(mse)                        # 根均方误差(更直观)
mae = mean_absolute_error(y_true, y_pred)  # 平均绝对误差
r2 = r2_score(y_true, y_pred)              # R² 决定系数
 
print(f"MSE:  {mse:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"MAE:  {mae:.4f}")
print(f"R²:   {r2:.4f}")  # 越接近 1 越好

3️⃣ 交叉验证 (Cross-Validation)

K-Fold Cross Validation

from sklearn.model_selection import KFold, cross_val_score
 
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = []
 
for fold, (train_idx, val_idx) in enumerate(kf.split(X)):
    X_train, X_val = X[train_idx], X[val_idx]
    y_train, y_val = y[train_idx], y[val_idx]
    
    # 训练和验证
    model = train_model(X_train, y_train)
    acc = evaluate_model(model, X_val, y_val)
    scores.append(acc)
    print(f"Fold {fold+1}: {acc:.4f}")
 
print(f"平均: {np.mean(scores):.4f} ± {np.std(scores):.4f}")

K 的选择

K 值适用场景
K=3大数据集 (>100K),节省时间
K=5⭐ 通用推荐
K=10小数据集,更稳定但更慢
K=N (留一法)极少量数据 (<100)

4️⃣ 过拟合诊断

如何判断过拟合?

Train Loss ↘↘↘     (一直下降)
Val   Loss ↘→↗     (先降后升)  ← ⚠️ 过拟合信号!

过拟合典型症状

# 训练准确率 99%,验证准确率 70% → 严重过拟合
# 训练 Loss 一直在降,验证 Loss 开始上升 → 过拟合开始
# 模型在训练集上表现完美,泛化差 → 过拟合

解决过拟合的方法

方法说明效果
增加数据收集更多/数据增强⭐⭐⭐ 最佳方案
降低模型复杂度减少层数/隐藏层维度⭐⭐⭐
正则化L1/L2 正则化⭐⭐
Dropout随机丢弃神经元⭐⭐⭐
早停验证 Loss 不再下降时停⭐⭐⭐
Batch Normalization每层归一化⭐⭐

5️⃣ 模型对比实验规范

import json
from datetime import datetime
 
class ExperimentTracker:
    """规范化记录实验"""
    def __init__(self, experiment_name):
        self.experiment_name = experiment_name
        self.results = {}
    
    def log_params(self, **params):
        self.results['params'] = params
    
    def log_metrics(self, **metrics):
        self.results['metrics'] = metrics
        self.results['timestamp'] = datetime.now().isoformat()
    
    def save(self, path='experiments.json'):
        try:
            with open(path, 'r') as f:
                all_exp = json.load(f)
        except:
            all_exp = {}
        
        all_exp[self.experiment_name] = self.results
        with open(path, 'w') as f:
            json.dump(all_exp, f, indent=2)

🔗 下一篇

模型保存与部署