问题 1
为什么不能反复根据测试集分数选择模型?
TRACK A · COMPOSITE SOURCE · 复合内容来源 · 对应课程 1.2.4、1.2.6、1.4.3 和 1.5.1–1.5.7
学习如何公平评估模型:划分训练、验证和测试数据,选择回归与分类指标,理解类别不平衡、阈值、过拟合和数据泄漏。
模型可能在训练样本上几乎完美,却无法处理新数据。因此,质量评估必须使用未参与参数拟合的数据。评估是一项实验,目的是尽可能模拟模型未来的实际使用条件。
训练集用于拟合模型参数;验证集用于比较配置、选择超参数和决策阈值;测试集保留到最后,对选定方案进行独立评估。如果反复根据测试分数调整模型,测试集就参与了开发过程,结果会产生偏差。
Baseline(基线)是用于比较的简单参考方案。回归任务可以预测训练集均值或中位数;分类任务可以始终预测出现频率最高的类别。复杂模型必须证明有可测量的收益,才能说明其成本合理。
MAE是原始单位下的平均绝对误差;MSE对误差平方求平均,对较大的错误惩罚更强;RMSE是MSE的平方根,单位回到目标变量原单位;R²将模型与均值预测基线比较。R²可以为负数,也不代表预测正确率百分比。
MAE:平均绝对误差;MSE:均方误差;RMSE:均方根误差;R²:相对均值基线的决定系数。
混淆矩阵包含TP(真正例)、TN(真负例)、FP(假正例)和FN(假负例)。Accuracy是预测正确的比例。Precision = TP/(TP+FP),衡量正向预测的可靠性;Recall = TP/(TP+FN),衡量实际正例被发现的比例;F1是两者的调和平均数。
Accuracy = (TP+TN)/N · Precision = TP/(TP+FP) · Recall = TP/(TP+FN) · F1 = 2PR/(P+R)
如果1000次设备检查中只有10次故障,始终预测“无故障”的模型也能获得99%的准确率,但故障召回率为0%。面对稀有事件,必须检查混淆矩阵、precision、recall、PR曲线以及错误的实际代价。
分类器通常输出分数或概率,阈值将其转换为类别。降低阈值通常会提高recall,同时增加误报;提高阈值可能提高precision,却会漏掉更多事件。阈值应根据FP和FN的后果选择,而不是默认0.5永远正确。
欠拟合意味着模型过于简单;过拟合意味着模型过度适应训练数据;泛化指模型在新数据上的表现。数据泄漏是未来信息或测试集信息进入特征或预处理。缺失值填补、标准化和特征选择都只能在训练集上拟合。
随机划分并非总是合适。预测未来时,应按时间划分:用过去训练,用未来评估。如果多行数据属于同一用户、设备或建筑,可能需要按组划分,避免相关记录同时出现在训练集和测试集。小数据集可以采用合适策略的交叉验证。
ROC曲线展示阈值变化时真正例率(Recall)与假正例率(False Positive Rate)的关系。ROC-AUC是曲线下面积,用于衡量模型将正例排在负例之前的能力。接近0.5通常表示随机排序,1.0表示在当前评估样本上完全区分。ROC-AUC并不能告诉我们实际应用中应选择哪个阈值。
Precision–Recall曲线展示不同阈值下Precision与Recall的变化。Average Precision(AP)概括排序质量。当正类非常稀少时,PR曲线往往比ROC更有解释力:大量真负例可能使假正例率看起来很低,但实际误报数量仍然很多。AP的基线取决于正类比例。
ROC-AUC和AP都不能代替混淆矩阵、概率校准、错误成本分析以及独立测试集评估。比较模型时必须使用相同的数据划分策略。
K折交叉验证将训练数据分成K份,重复训练K次:每次使用K−1份训练,并在剩余一份上验证。指标的平均值和波动反映结果对单次划分的敏感程度。类别不平衡时可使用StratifiedKFold,使各折中的类别比例大致一致。若同一对象有多条相关记录,应考虑GroupKFold;时间序列则应使用TimeSeriesSplit等按时间前进的策略。
交叉验证只在训练部分内部进行。测试集应保持独立,直到最终评估。若用交叉验证选择超参数,不应再反复根据测试集分数调参。预处理步骤应放入Pipeline,以便每个训练折独立拟合,避免数据泄漏。
在同一数据划分下比较基线模型和两个候选模型。回归计算MAE与RMSE;分类计算混淆矩阵、precision和recall。记录实验局限。指标不是绝对真理,它只回答特定数据上的特定问题。
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.linear_model import LinearRegression
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
model = LinearRegression().fit(X_train, y_train)
pred = model.predict(X_test)
print("MAE:", mean_absolute_error(y_test, pred))
print("RMSE:", mean_squared_error(y_test, pred) ** 0.5)
print("R2:", r2_score(y_test, pred))注意:所有数据转换步骤都必须只在训练集上拟合。
本实验使用合成数据,演示分层抽样、基线分类器、预处理Pipeline和多种指标。实际项目中应替换为真实特征,并提前明确误报与漏报的代价。
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.dummy import DummyClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (confusion_matrix, precision_score, recall_score,
f1_score, roc_auc_score, average_precision_score)
X, y = make_classification(
n_samples=1200, n_features=12, weights=[0.9, 0.1],
class_sep=0.8, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, stratify=y, random_state=42
)
models = {
"baseline": DummyClassifier(strategy="prior"),
"logistic": make_pipeline(StandardScaler(),
LogisticRegression(max_iter=1000))
}
for name, model in models.items():
model.fit(X_train, y_train)
pred = model.predict(X_test)
score = model.predict_proba(X_test)[:, 1]
print("\\n", name)
print("confusion matrix:\\n", confusion_matrix(y_test, pred))
print("precision:", precision_score(y_test, pred, zero_division=0))
print("recall:", recall_score(y_test, pred, zero_division=0))
print("f1:", f1_score(y_test, pred, zero_division=0))
print("roc_auc:", roc_auc_score(y_test, score))
print("average_precision:", average_precision_score(y_test, score))
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(models["logistic"], X_train, y_train,
cv=cv, scoring="average_precision")
print("CV AP mean/std:", cv_scores.mean(), cv_scores.std())请在相同测试样本上比较基线模型与逻辑回归。检查混淆矩阵,并判断0.5阈值是否符合误报与漏报的实际代价。交叉验证AP衡量训练数据内部的稳定性;测试集分数仍是最终独立评估。
为什么不能反复根据测试集分数选择模型?
类别不平衡时,为什么 Accuracy 可能产生误导?
预测未来事件时,应如何划分数据?
如果 Accuracy 为 99%,而少数类的 Recall 为 0%,模型是否可能漏掉该类的全部事件?