2.4 模型评估 (Model Evaluation) Scikit-learn 模型评估 (Model Evaluation) 详解 模型评估的重要性 模型评估的核心目的是衡量模型泛化能力,即模型在训练数据以外的新数据上的表现。一个优秀的模型不仅需要在训练集上表现良好,更重要的是在面对未知数据时依然能够保持优秀的预测能力。模型评估的重要性体现在以下几个方面: 模型选择: 当我们尝试多种模型或同一模型的不同配置时,模型评估可以帮助我们客观地比较它们的优劣,从而选择最适合当前任务的模型。 超参数调优: 模型的性能往往受到超参数的影响。模型评估可以作为超参数调优的指导,帮助我们找到最优的超参数组合,提升模型性能。 问题诊断: 模型评估结果可以帮助我们诊断模型存在的问题。
模型评估的核心目的是衡量模型泛化能力,即模型在训练数据以外的新数据上的表现。一个优秀的模型不仅需要在训练集上表现良好,更重要的是在面对未知数据时依然能够保持优秀的预测能力。模型评估的重要性体现在以下几个方面:
模型选择: 当我们尝试多种模型或同一模型的不同配置时,模型评估可以帮助我们客观地比较它们的优劣,从而选择最适合当前任务的模型。
超参数调优: 模型的性能往往受到超参数的影响。模型评估可以作为超参数调优的指导,帮助我们找到最优的超参数组合,提升模型性能。
问题诊断: 模型评估结果可以帮助我们诊断模型存在的问题。例如,如果模型在训练集上表现良好,但在测试集上表现糟糕,可能意味着模型过拟合;反之,如果模型在训练集和测试集上都表现不佳,可能意味着模型欠拟合或者特征工程不足。
性能监控: 在模型部署上线后,模型评估可以用于监控模型性能的衰退,及时发现问题并进行模型维护和更新。
简而言之,模型评估是确保我们构建的机器学习模型真正有效且可靠的关键步骤。
Scikit-learn 提供了全面的模型评估工具,主要围绕以下几个核心概念展开:
评估指标 (Metrics): 用于量化模型性能的指标。不同的任务类型 (分类、回归、聚类等) 和业务目标需要选择不同的评估指标。Scikit-learn 提供了丰富的内置评估指标,例如准确率 (Accuracy)、精确率 (Precision)、召回率 (Recall)、F1-score、均方误差 (Mean Squared Error, MSE)、R 平方 (R-squared) 等。
评估方法 (Evaluation Methods): 用于分割数据集和评估模型性能的方法,例如留出法 (Hold-out method)、交叉验证 (Cross-validation) 等。交叉验证是 Scikit-learn 中常用的评估方法,可以更可靠地评估模型的泛化能力。
评分函数 (Scoring Functions): Scikit-learn 使用评分函数来封装评估指标,方便在交叉验证、网格搜索等过程中使用。评分函数通常接受真实值和预测值作为输入,返回一个评估指标值。
模型选择工具 (Model Selection Tools): Scikit-learn 提供了如 cross_val_score, cross_validate, GridSearchCV, RandomizedSearchCV 等工具,结合评估指标和评估方法,帮助我们进行模型选择、超参数调优和性能评估。
分类任务的目标是将数据样本划分到不同的类别中。Scikit-learn 提供了多种评估分类模型性能的指标。
定义: 准确率是最直观的分类指标,指预测正确的样本数占总样本数的比例。
公式:
Accuracy = (TP + TN) / (TP + TN + FP + FN)
其中:
TP (True Positive): 真正例,预测为正例且实际为正例
TN (True Negative): 真反例,预测为反例且实际为反例
FP (False Positive): 假正例,预测为正例但实际为反例 (Type I Error)
FN (False Negative): 假反例,预测为反例但实际为正例 (Type II Error)
代码实践:
from sklearn.metrics import accuracy_score from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 训练逻辑回归模型 model = LogisticRegression(random_state=42) model.fit(X_train, y_train) # 预测测试集 y_pred = model.predict(X_test) # 计算准确率 accuracy = accuracy_score(y_test, y_pred) print(f"Accuracy: {accuracy:.4f}")
内容详解:
accuracy_score(y_true, y_pred) 函数计算准确率,其中 y_true 是真实标签,y_pred 是模型预测标签。
示例代码使用 Iris 数据集进行二分类 (实际上 Iris 数据集是多分类,这里为了演示方便,可以将其视为二分类问题,或者直接使用多分类准确率)。
准确率简单易懂,但在类别不平衡的数据集中,准确率可能会产生误导。例如,如果一个二分类问题中,90% 的样本是正例,即使模型总是预测为正例,也能达到 90% 的准确率,但这显然不是一个好的模型。
定义: 混淆矩阵是用于可视化分类模型性能的表格,它展示了模型预测结果与真实标签之间的对应关系。
代码实践:
from sklearn.metrics import confusion_matrix import matplotlib.pyplot as plt import seaborn as sns # ... (前面代码与准确率示例相同,加载数据,划分数据集,训练模型,预测) ... # 计算混淆矩阵 cm = confusion_matrix(y_test, y_pred) # 可视化混淆矩阵 plt.figure(figsize=(8, 6)) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=iris.target_names, yticklabels=iris.target_names) plt.xlabel('Predicted labels') plt.ylabel('True labels') plt.title('Confusion Matrix') plt.show()
内容详解:
confusion_matrix(y_true, y_pred) 函数计算混淆矩阵。
sns.heatmap 用于可视化混淆矩阵,annot=True 显示每个单元格的数值,fmt="d" 指定数值格式为整数,cmap="Blues" 指定颜色映射。
混淆矩阵的行表示真实类别,列表示预测类别。对角线上的元素表示预测正确的样本数,非对角线上的元素表示预测错误的样本数。
通过混淆矩阵,我们可以更直观地了解模型在不同类别上的表现,以及模型容易混淆的类别。
定义: 这三个指标都基于混淆矩阵,用于更细致地评估分类模型在正例上的表现。
精确率 (Precision): 指预测为正例的样本中,实际为正例的比例。衡量模型预测正例的准确程度,关注预测的质量。
Precision = TP / (TP + FP)
召回率 (Recall): 指实际为正例的样本中,被模型预测为正例的比例。衡量模型捕捉正例的能力,关注预测的完整性。也称为灵敏度 (Sensitivity) 或真正例率 (True Positive Rate, TPR)。
Recall = TP / (TP + FN)
F1-score: 是精确率和召回率的调和平均值,综合考虑了精确率和召回率,是更全面的评价指标。
F1-score = 2 * (Precision * Recall) / (Precision + Recall)
代码实践:
from sklearn.metrics import precision_score, recall_score, f1_score, classification_report # ... (前面代码与准确率示例相同,加载数据,划分数据集,训练模型,预测) ... # 计算精确率, 召回率, F1-score (默认针对二分类的正例) precision = precision_score(y_test, y_pred, average='weighted') # 多分类需要指定 average 参数 recall = recall_score(y_test, y_pred, average='weighted') f1 = f1_score(y_test, y_pred, average='weighted') print(f"Precision: {precision:.4f}") print(f"Recall: {recall:.4f}") print(f"F1-score: {f1:.4f}") # 使用 classification_report 生成更全面的报告 report = classification_report(y_test, y_pred, target_names=iris.target_names) print("\nClassification Report:\n", report)
内容详解:
precision_score(y_true, y_pred, average=...), recall_score(y_true, y_pred, average=...), f1_score(y_true, y_pred, average=...) 分别计算精确率、召回率和 F1-score。
average 参数用于处理多分类问题,常用的取值有:
'binary' (默认): 仅适用于二分类,计算正例的指标。
'micro': 全局计算 TP, FP, FN,然后计算指标。适用于类别不平衡的情况。
'macro': 分别计算每个类别的指标,然后取平均值 (不考虑类别样本数量)。
'weighted': 分别计算每个类别的指标,然后根据类别样本数量进行加权平均。
classification_report(y_true, y_pred, target_names=...) 函数生成包含精确率、召回率、F1-score 和支持度 (每个类别的样本数量) 的分类报告,更全面地展示模型在每个类别上的表现。
定义: ROC (Receiver Operating Characteristic) 曲线和 AUC (Area Under the ROC Curve) 用于评估二分类模型预测概率的性能,尤其是在类别不平衡的情况下。
ROC 曲线: 以假正例率 (False Positive Rate, FPR) 为横轴,真正例率 (True Positive Rate, TPR) (即召回率) 为纵轴绘制的曲线。通过调整分类阈值,可以得到不同的 FPR 和 TPR 组合,从而绘制出 ROC 曲线。
FPR = FP / (FP + TN) TPR = TP / (TP + FN) (即 Recall)
AUC: ROC 曲线下的面积,取值范围为 [0, 1]。AUC 值越大,模型的性能越好。AUC 值可以理解为模型将正例排在反例前面的概率。
代码实践:
from sklearn.metrics import roc_curve, roc_auc_score import matplotlib.pyplot as plt # ... (前面代码与准确率示例相同,加载数据,划分数据集,训练逻辑回归模型) ... # 获取模型预测概率 (注意:需要模型支持 predict_proba 方法) y_prob = model.predict_proba(X_test)[:, 1] # 取正例的概率 # 计算 ROC 曲线 fpr, tpr, thresholds = roc_curve(y_test, y_prob) # 计算 AUC 值 auc = roc_auc_score(y_test, y_prob) # 绘制 ROC 曲线 plt.figure(figsize=(8, 6)) plt.plot(fpr, tpr, label=f'ROC curve (AUC = {auc:.4f})') plt.plot([0, 1], [0, 1], 'k--') # 绘制对角线作为随机分类器的基准 plt.xlabel('False Positive Rate (FPR)') plt.ylabel('True Positive Rate (TPR)') plt.title('ROC Curve') plt.legend(loc="lower right") plt.show() print(f"AUC: {auc:.4f}")
内容详解:
roc_curve(y_true, y_prob) 函数计算 ROC 曲线的 FPR, TPR 和阈值。
roc_auc_score(y_true, y_prob) 函数计算 AUC 值。
predict_proba 方法返回模型预测的每个类别的概率,对于二分类问题,我们通常取正例的概率 (索引为 1 的列)。
ROC 曲线和 AUC 适用于评估模型对样本排序的能力,即使在类别不平衡的情况下也能提供更可靠的评估结果。AUC 值越接近 1,模型性能越好;AUC 值为 0.5 相当于随机分类器。
定义: 对数损失 (Log Loss) 也称为交叉熵损失 (Cross-Entropy Loss),用于评估分类模型预测概率的性能。Log Loss 衡量模型预测概率与真实标签之间的差异,值越小,模型性能越好。
公式 (二分类):
Log Loss = - [y * log(p) + (1 - y) * log(1 - p)]
其中:
y 是真实标签 (0 或 1)
p 是模型预测为正例的概率
代码实践:
from sklearn.metrics import log_loss # ... (前面代码与准确率示例相同,加载数据,划分数据集,训练逻辑回归模型) ... # 获取模型预测概率 y_prob = model.predict_proba(X_test) # 计算 Log Loss logloss = log_loss(y_test, y_prob) print(f"Log Loss: {logloss:.4f}")
内容详解:
log_loss(y_true, y_prob) 函数计算 Log Loss。
y_prob 需要是模型预测的概率值,可以是 predict_proba 的输出。
Log Loss 惩罚模型预测概率的置信度,对于错误分类且置信度高的预测,Log Loss 会很高。因此,Log Loss 更关注模型预测概率的准确性,而不仅仅是最终的类别预测结果。
回归任务的目标是预测一个连续的数值。Scikit-learn 提供了多种评估回归模型性能的指标。
定义: 均方误差 (MSE) 是回归模型最常用的评估指标之一,指预测值与真实值之差的平方的平均值。MSE 值越小,模型性能越好。
公式:
MSE = (1 / n) * Σ (y_i - ŷ_i)^2
其中:
n 是样本数量
y_i 是第 i 个样本的真实值
ŷ_i 是第 i 个样本的预测值
代码实践:
from sklearn.metrics import mean_squared_error from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.datasets import load_boston # 加载 Boston 房价数据集 boston = load_boston() X, y = boston.data, boston.target # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 训练线性回归模型 model = LinearRegression() model.fit(X_train, y_train) # 预测测试集 y_pred = model.predict(X_test) # 计算 MSE mse = mean_squared_error(y_test, y_pred) print(f"Mean Squared Error (MSE): {mse:.2f}")
内容详解:
mean_squared_error(y_true, y_pred) 函数计算 MSE。
MSE 对误差进行平方,放大了误差较大的样本的影响,因此对异常值比较敏感。
定义: 均绝对误差 (MAE) 指预测值与真实值之差的绝对值的平均值。MAE 值越小,模型性能越好。
公式:
MAE = (1 / n) * Σ |y_i - ŷ_i|
代码实践:
from sklearn.metrics import mean_absolute_error # ... (前面代码与 MSE 示例相同,加载数据,划分数据集,训练模型,预测) ... # 计算 MAE mae = mean_absolute_error(y_test, y_pred) print(f"Mean Absolute Error (MAE): {mae:.2f}")
内容详解:
mean_absolute_error(y_true, y_pred) 函数计算 MAE。
MAE 对所有误差都给予相同的权重,对异常值不如 MSE 敏感。MAE 的值更贴近实际误差的大小,更易于理解。
定义: 均方根误差 (RMSE) 是 MSE 的平方根。RMSE 与真实值的量纲相同,更易于解释。RMSE 值越小,模型性能越好。
公式:
RMSE = √(MSE) = √[(1 / n) * Σ (y_i - ŷ_i)^2]
代码实践:
import numpy as np # ... (前面代码与 MSE 示例相同,加载数据,划分数据集,训练模型,预测) ... # 计算 RMSE (可以直接开方 MSE) rmse = np.sqrt(mse) print(f"Root Mean Squared Error (RMSE): {rmse:.2f}")
内容详解:
RMSE 可以通过对 MSE 开方得到。
RMSE 的量纲与真实值相同,例如,如果预测房价,RMSE 的单位也是元/平方米,更易于理解预测误差的实际意义。
定义: R 平方 (R-squared) 衡量回归模型解释因变量变异的程度。R 平方的取值范围为 [0, 1],R 平方值越接近 1,模型拟合效果越好。
公式:
R² = 1 - (SS_res / SS_tot)
其中:
SS_res (Residual Sum of Squares): 残差平方和,即 MSE 的分子部分。
SS_tot (Total Sum of Squares): 总平方和,衡量因变量的总体变异程度。
代码实践:
from sklearn.metrics import r2_score # ... (前面代码与 MSE 示例相同,加载数据,划分数据集,训练模型,预测) ... # 计算 R 平方 r2 = r2_score(y_test, y_pred) print(f"R-squared: {r2:.4f}")
内容详解:
r2_score(y_true, y_pred) 函数计算 R 平方。
R 平方的含义可以理解为,模型能够解释因变量多少比例的变异。例如,R 平方为 0.8,表示模型可以解释 80% 的因变量变异。
R 平方可以用来比较不同模型的拟合效果,但不能完全反映模型的预测能力,尤其是在外推预测时。
交叉验证是一种更可靠的模型评估方法,它将数据集划分为多个子集 (fold),轮流使用其中一个子集作为验证集,其余子集作为训练集进行模型训练和评估,最终将多次评估结果进行汇总,得到更稳定的模型性能估计。
K 折交叉验证是最常用的交叉验证方法。它将数据集划分为 K 个大小相近的子集,每次选择其中一个子集作为验证集,其余 K-1 个子集作为训练集,重复 K 次,每次使用不同的子集作为验证集。
代码实践:
from sklearn.model_selection import KFold, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 定义 K 折交叉验证 kf = KFold(n_splits=5, shuffle=True, random_state=42) # 划分为 5 折,打乱数据 # 训练逻辑回归模型,并使用交叉验证评估准确率 model = LogisticRegression(random_state=42) cv_scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy') # 使用准确率作为评分指标 print(f"Cross-validation scores: {cv_scores}") print(f"Mean cross-validation score: {cv_scores.mean():.4f}")
内容详解:
KFold(n_splits=K, shuffle=True, random_state=...) 创建 K 折交叉验证对象。n_splits 指定折数 K,shuffle=True 表示在划分前打乱数据,random_state 用于控制随机性。
cross_val_score(estimator, X, y, cv=kf, scoring='metric') 函数使用交叉验证评估模型性能。estimator 是模型对象,X, y 是数据和标签,cv 是交叉验证对象 (或折数),scoring 是评分指标 (字符串或评分函数)。
cross_val_score 返回一个包含 K 次验证结果的数组。通常取平均值作为模型的交叉验证得分。
分层 K 折交叉验证在 K 折交叉验证的基础上,保证每个子集中各类别的样本比例与原始数据集相同。适用于类别不平衡的数据集。
代码实践:
from sklearn.model_selection import StratifiedKFold # ... (前面代码与 K 折交叉验证示例相同,加载数据,划分数据集) ... # 定义分层 K 折交叉验证 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 划分为 5 折,分层打乱数据 # ... (后续代码与 K 折交叉验证示例相同,使用 cross_val_score 评估模型) ... cv_scores = cross_val_score(model, X, y, cv=skf, scoring='accuracy') print(f"Stratified cross-validation scores: {cv_scores}") print(f"Mean stratified cross-validation score: {cv_scores.mean():.4f}")
内容详解:
StratifiedKFold(n_splits=K, shuffle=True, random_state=...) 创建分层 K 折交叉验证对象,参数与 KFold 类似。
对于分类问题,推荐使用 StratifiedKFold,尤其是在类别不平衡的情况下。
cross_validate 函数cross_validate 函数是比 cross_val_score 更强大的交叉验证工具。它不仅可以计算多个评分指标,还可以返回训练时间和评分时间等信息。
代码实践:
from sklearn.model_selection import cross_validate from sklearn.metrics import precision_score, recall_score # ... (前面代码与 K 折交叉验证示例相同,加载数据,定义交叉验证对象,训练模型) ... # 定义多个评分指标 scoring = ['accuracy', 'precision_weighted', 'recall_weighted'] # 可以使用字符串或评分函数 # 使用 cross_validate 进行交叉验证 cv_results = cross_validate(model, X, y, cv=kf, scoring=scoring, return_train_score=True) # 返回训练集得分 print("Cross-validation results:\n", cv_results) print(f"\nMean test accuracy: {cv_results['test_accuracy'].mean():.4f}") print(f"Mean test precision: {cv_results['test_precision_weighted'].mean():.4f}") print(f"Mean test recall: {cv_results['test_recall_weighted'].mean():.4f}")
内容详解:
cross_validate(estimator, X, y, cv=kf, scoring=scoring, return_train_score=True) 函数进行更全面的交叉验证。
scoring 参数可以接受一个评分指标字符串列表或一个评分指标字典。
return_train_score=True 参数表示返回训练集上的评分结果。
cross_validate 返回一个字典,包含 fit_time, score_time, test_<scorer_name>, train_<scorer_name> 等键,分别表示训练时间、评分时间、测试集评分和训练集评分。
模型评估在超参数调优中扮演着至关重要的角色。常用的超参数调优方法如网格搜索 (Grid Search) 和随机搜索 (Randomized Search) 都需要使用模型评估来选择最优的超参数组合。
代码实践 (GridSearchCV 结合交叉验证和评估指标):
from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC # ... (前面代码与 K 折交叉验证示例相同,加载 Iris 数据集) ... # 定义参数网格 param_grid = {'C': [0.1, 1, 10], 'gamma': ['scale', 0.1, 0.01]} # SVM 的超参数 # 创建 GridSearchCV 对象 grid_search = GridSearchCV(SVC(), param_grid, cv=kf, scoring='accuracy') # 使用准确率作为评估指标 # 进行网格搜索 grid_search.fit(X_train, y_train) # 最优模型和最优参数 best_model = grid_search.best_estimator_ best_params = grid_search.best_params_ best_score = grid_search.best_score_ print(f"Best parameters: {best_params}") print(f"Best cross-validation score: {best_score:.4f}") # 在测试集上评估最优模型 test_accuracy = best_model.score(X_test, y_test) # SVC 的 score 方法默认使用准确率 print(f"Test accuracy of best model: {test_accuracy:.4f}")
内容详解:
GridSearchCV(estimator, param_grid, cv=kf, scoring='metric') 函数进行网格搜索。param_grid 定义要搜索的超参数网格,cv 是交叉验证对象,scoring 是评估指标。
grid_search.fit(X_train, y_train) 进行网格搜索,训练模型并评估不同超参数组合的性能。
grid_search.best_estimator_ 返回最优模型,grid_search.best_params_ 返回最优超参数组合,grid_search.best_score_ 返回最优模型的交叉验证得分。
最后在测试集上评估最优模型的泛化能力。
Scikit-learn 允许用户自定义评分函数,以满足更特定的评估需求。自定义评分函数需要满足一定的接口规范,例如接受真实值和预测值作为输入,返回一个数值型的评分结果。
代码实践 (自定义 F2-score 评分函数):
from sklearn.metrics import make_scorer, fbeta_score # 定义 F2-score 评分函数 (beta=2 表示更关注召回率) def f2_scorer(y_true, y_pred): return fbeta_score(y_true, y_pred, beta=2) # 使用 make_scorer 创建评分器对象 my_f2_scorer = make_scorer(f2_scorer) # ... (前面代码与 GridSearchCV 示例类似,加载数据,定义参数网格,交叉验证对象) ... # 创建 GridSearchCV 对象,使用自定义评分器 grid_search_f2 = GridSearchCV(SVC(), param_grid, cv=kf, scoring=my_f2_scorer) # ... (后续代码与 GridSearchCV 示例相同,进行网格搜索,评估最优模型) ...
内容详解:
fbeta_score(y_true, y_pred, beta=...) 函数计算 F-beta score,beta 参数控制精确率和召回率的权重。beta=2 表示 F2-score,更关注召回率。
make_scorer(scoring_func) 函数将自定义评分函数转换为 Scikit-learn 的评分器对象,可以用于 cross_val_score, cross_validate, GridSearchCV 等函数。
模型评估是机器学习流程中至关重要的一环。本文详细介绍了 Scikit-learn 中模型评估的核心概念、常用评估指标 (分类和回归)、交叉验证方法以及模型评估在超参数调优中的应用。
关键要点回顾:
选择合适的评估指标: 根据任务类型和业务目标选择合适的评估指标。例如,类别不平衡时,准确率可能不可靠,可以考虑使用精确率、召回率、F1-score、ROC 曲线和 AUC 等指标。回归任务常用 MSE, MAE, RMSE, R 平方等指标。
使用交叉验证: 交叉验证能够更可靠地评估模型的泛化能力,避免过拟合和欠拟合。常用的交叉验证方法有 K 折交叉验证和分层 K 折交叉验证。
模型评估指导超参数调优: 利用模型评估结果,通过网格搜索、随机搜索等方法进行超参数调优,找到最优的模型配置。
自定义评分函数: Scikit-learn 允许用户自定义评分函数,满足更特定的评估需求。