为什么评估很重要
模型评估是机器学习流程中不可或缺的一环。没有科学的评估方法,我们无法判断模型是否真正有效,也无法比较不同模型的优劣。
核心评估指标
分类指标
以二分类为例,基于混淆矩阵(Confusion Matrix)定义:
- TP(True Positive) — 正类被正确预测为正类
- TN(True Negative) — 负类被正确预测为负类
- FP(False Positive) — 负类被错误预测为正类(误报)
- FN(False Negative) — 正类被错误预测为负类(漏报)
准确率(Accuracy)
所有预测中正确的比例:
Accuracy = (TP + TN) / (TP + TN + FP + FN)
在类别不平衡的情况下准确率可能产生误导。例如 95% 负类、5% 正类的数据集,模型全部预测为负类也能达到 95% 的准确率。
精确率(Precision)与召回率(Recall)
Precision = TP / (TP + FP) — 预测为正类中有多少是真正的正类
Recall = TP / (TP + FN) — 所有正类中有多少被找了出来
精确率和召回率往往此消彼长。具体选择哪个指标取决于业务场景:
- 垃圾邮件检测:更看重 Precision(误删正常邮件不可接受)
- 癌症筛查:更看重 Recall(漏诊后果严重)
F1 分数
精确率和召回率的调和平均数:
F1 = 2 × (Precision × Recall) / (Precision + Recall)
from sklearn.metrics import (
accuracy_score, precision_score,
recall_score, f1_score, classification_report
)
y_true = [0, 1, 1, 0, 1, 0]
y_pred = [0, 1, 0, 0, 1, 1]
print(f"准确率: {accuracy_score(y_true, y_pred):.2f}")
print(f"精确率: {precision_score(y_true, y_pred):.2f}")
print(f"召回率: {recall_score(y_true, y_pred):.2f}")
print(f"F1: {f1_score(y_true, y_pred):.2f}")
print(classification_report(y_true, y_pred))
ROC 曲线与 AUC
ROC 曲线展示不同阈值下 TPR(True Positive Rate)与 FPR(False Positive Rate)的关系。AUC(Area Under Curve)是曲线下面积,值越大说明模型分类能力越强。
回归指标
- MAE(平均绝对误差) — 直观反映预测误差大小
- MSE(均方误差) — 对大误差施加更大惩罚
- RMSE — MSE 的平方根,与原始单位一致
- R²(决定系数) — 模型解释了多少方差
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
y_true = [3.0, 2.5, 4.0, 3.5]
y_pred = [2.8, 2.7, 3.8, 3.6]
print(f"MAE: {mean_absolute_error(y_true, y_pred):.3f}")
print(f"MSE: {mean_squared_error(y_true, y_pred):.3f}")
print(f"R²: {r2_score(y_true, y_pred):.3f}")
交叉验证
交叉验证是评估模型泛化能力的核心技术,通过反复将数据划分为训练集和验证集来获得更稳健的性能估计。
K-Fold 交叉验证
将数据分成 K 份,轮流将其中一份作为验证集,其余 K-1 份作为训练集,最终取 K 次评估的平均值。
from sklearn.model_selection import cross_val_score, KFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
iris = load_iris()
model = RandomForestClassifier()
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, iris.data, iris.target,
cv=kfold, scoring="accuracy")
print(f"每折准确率: {scores}")
print(f"平均准确率: {scores.mean():.3f} (+/- {scores.std() * 2:.3f})")
其他交叉验证方法
- Stratified K-Fold — 保持每折的类别分布与整体一致
- Leave-One-Out(LOO) — 每次只留一个样本做验证
- Time Series Split — 时间序列数据专用,避免未来信息泄露
超参数调优
超参数是训练开始前需要设定的参数,不同于模型在训练过程中学习的参数。
网格搜索(Grid Search)
穷举所有超参数组合,简单但计算成本高。
from sklearn.model_selection import GridSearchCV
param_grid = {
"n_estimators": [50, 100, 200],
"max_depth": [None, 10, 20],
"min_samples_split": [2, 5, 10]
}
grid_search = GridSearchCV(
RandomForestClassifier(),
param_grid,
cv=5,
scoring="f1",
n_jobs=-1
)
grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳得分: {grid_search.best_score_:.3f}")
随机搜索(Random Search)
在参数空间中随机采样,通常比网格搜索更高效。在同样的预算下能探索更多的参数组合。
贝叶斯优化
使用高斯过程或 Tree-Structured Parzen Estimators(TPE)来建模超参数与模型性能的关系,在参数空间中智能地选择下一个要尝试的点。库:Hyperopt、Optuna。
import optuna
def objective(trial):
n_estimators = trial.suggest_int("n_estimators", 50, 300)
max_depth = trial.suggest_int("max_depth", 5, 30)
lr = trial.suggest_float("lr", 1e-4, 1e-1, log=True)
model = SomeModel(n_estimators, max_depth, lr)
score = cross_val_score(model, X, y, cv=3).mean()
return score
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=50)
print(f"最佳参数: {study.best_params}")
过拟合与欠拟合
- 过拟合 — 模型在训练集上表现好但验证集上差。解决:增加数据量、降低模型复杂度、正则化、早停
- 欠拟合 — 模型在训练集上表现就不够好。解决:增强特征、增加模型复杂度、减少正则化
模型评估与调优是一个迭代的过程,需要结合领域知识和实验经验才能达到理想的效果。