性能评估
这一章讲的是怎么科学地给模型"体检"——也就是性能评估(Model Evaluation)。模型在训练数据上考满分不算本事,重要的是在没见过的数据上依然靠谱。学完这一章,你会明白训练集、验证集、测试集为什么要"三权分立",会亲眼看到准确率是怎么被骗的,能分清精确率与召回率这对"查准 vs 查全"的冤家,看懂 ROC 曲线和 AUC,还会用 RMSE、MAE 给回归任务打分。
三组数据:训练、验证、测试
先看一个真实案例。书里的 MNIST-1D 手写数字数据集上,一个神经网络在训练集里几乎做到了完美分类,可一到测试集,错误率却稳定在 40% 左右——训练时把样本"背"得太熟,遇到新样本就露馅了,这就是过拟合。所以评估模型,必须在它从没见过的数据上进行,这样的分数才可信。
为了让评估既诚实又够用,我们把数据分成三份,各管一摊:
- 训练集(Training Set):给模型学习参数用的,相当于平时的随堂练习。
- 验证集(Validation Set):用来挑超参数——比如模型该多大、学习率设多少。超参数不是模型自己学出来的,得靠我们试,试完在验证集上看效果。
- 测试集(Test Set):最后的"高考",只在全部调好之后考一次,用来估计模型的真实水平。
为什么非要分这么细?因为如果调超参数时也偷偷看测试集的分数,我们就会不自觉地把模型调得"专门适合测试集"——这等于偷看了考题,分数就再也不可信了。规矩只有一条:测试集只能碰一次。
学车就像训练模型:训练集是驾校练车场,反复练基本功;验证集是科目二模拟考,用来调整练车策略(比如打轮的时机);测试集是正式路考——考前绝不能提前去考场踩点背路线,否则考出来的分数代表不了你真实的驾驶水平。
数据量少的时候,三份一分就更紧张了。这时可以用交叉验证(Cross-Validation):把训练加验证的数据切成 $K$ 份(比如 5 份),每次拿 $K-1$ 份训练、1 份验证,轮着做 $K$ 次,把 $K$ 次验证分数平均,用平均分来选超参数。每一份数据既当过"学生"又当过"考官",数据利用得更充分,选出来的超参数也更稳。
准确率的陷阱:类别不平衡与混淆矩阵
最直观的评估指标是准确率(Accuracy)——预测对的样本占总数的比例:
$$\text{Accuracy} = \frac{TP + TN}{TP + FP + FN + TN}$$这个式子意思是:四格里"预测正确"的两格除以总数。听起来没毛病,但在类别不平衡的时候,准确率会骗人。设想一种罕见病:1000 人里只有 1 个病人。你训练一个"体检模型",它为了省事,对所有人一律回答"健康"。准确率高达 99.9%,可它一个病人都没查出来——这模型等于没用。
要看清真相,得把预测结果和真实情况放进一张混淆矩阵(Confusion Matrix),按"真实 vs 预测"分成四格:
| 预测:生病 | 预测:健康 | |
|---|---|---|
| 真实:生病 | 真阳性 TP(查对了) | 假阴性 FN(漏诊) |
| 真实:健康 | 假阳性 FP(误报) | 真阴性 TN(查对了) |
四格各有名字:TP(真阳性)是"真生病、预测生病",FP(假阳性)是"没生病却被说生病"(误报),FN(假阴性)是"真生病却说健康"(漏诊),TN(真阴性)是"没生病、预测健康"。后面要讲的精确率、召回率,都是这四个格子算出来的。
精确率与召回率:查准还是查全
回到体检场景。模型说"你生病了",我们最关心的是:它说的是不是真的?精确率(Precision),也叫查准率,回答的正是这个问题——在"预测为生病"的人里,真正生病的占多少:
$$\text{Precision} = \frac{TP}{TP + FP}$$式子意思是:说"生病"的人里,别冤枉太多好人。反过来,召回率(Recall),也叫查全率,关心的是:在所有真病人里,模型查出来了多少:
$$\text{Recall} = \frac{TP}{TP + FN}$$式子意思是:真病人别漏掉太多。麻烦在于,这两个指标常常此消彼长:模型越严格(很少说"生病"),精确率越高,但容易漏诊;越宽松(经常说"生病"),召回率越高,但误报变多。
快递公司的两个考核指标:召回率是"真正该到的包裹,系统报到了多少"——漏派是大事故;精确率是"系统说已送达的包裹,真的送到了多少"——乱报"已送达"会挨骂。想一个不落地全送到(高召回),就可能出现误报(低精确);想每个都确认无误(高精确),就可能漏报(低召回)。怎么取舍,取决于哪个错误更贵。
什么时候优先查全?癌症筛查就是典型:漏诊一个病人后果严重,宁可多复查(允许一些误报),也要把病人全找出来。什么时候优先查准?垃圾邮件拦截、广告点击预测这类场景误报成本高——把正常邮件扔进垃圾箱,比漏掉一封垃圾邮件更惹人烦。想用一个数同时照顾两头,可以用 F1 分数——精确率和召回率的调和平均:
$$F_1 = \frac{2 \times \text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}$$F1 只有在精确率、召回率都不错时才高,任何一头被拖后腿都会明显拉低它。这些指标用 sklearn 几行就能算出来:
from sklearn.metrics import confusion_matrix, precision_score, recall_score
# 真实标签与模型预测(1 = 生病,0 = 健康)
y_true = [0, 0, 1, 1, 0, 1, 0, 1, 0, 0]
y_pred = [0, 0, 1, 0, 0, 1, 1, 1, 0, 0]
# 混淆矩阵:行是真实类别,列是预测类别
cm = confusion_matrix(y_true, y_pred)
print("混淆矩阵:\n", cm)
# 精确率:说"生病"的人里,真生病的比例
print("精确率:", round(precision_score(y_true, y_pred), 2))
# 召回率:真生病的人里,被查出来的比例
print("召回率:", round(recall_score(y_true, y_pred), 2))
跑一下这段代码,你会看到混淆矩阵的四个数字,以及由它们算出的精确率、召回率。在真实项目里,这几行代码就是分类器的"体检报告"。
ROC 曲线与 AUC:拧动阈值看全貌
很多分类器输出的其实不是"生病/健康",而是一个 0 到 1 之间的概率。到底超过多少算"生病"?这个分界线叫阈值。阈值一降,被判"生病"的人变多,召回率上去,误报也上去;阈值一升,预测变谨慎,误报减少,但漏诊变多。
与其在某个阈值上纠结,不如把所有可能的阈值都试一遍,画一条ROC 曲线:横轴是假阳性率 FPR(误报率),纵轴是真阳性率 TPR(也就是召回率):
$$FPR = \frac{FP}{FP + TN} \qquad TPR = \frac{TP}{TP + FN} = \text{Recall}$$FPR 是"没生病却被说生病"的比例,TPR 是"真生病被查出来"的比例。每个阈值对应曲线上一个点,把所有点连起来,就得到模型从"最严格"到"最宽松"的完整画像。ROC 曲线下方围出的面积叫 AUC(Area Under the Curve):随机瞎猜的模型,ROC 是条对角线,AUC = 0.5;完美模型 AUC = 1。直觉上,AUC 可以理解为:随机抽一个正样本和一个负样本,模型把正样本排在前面的概率——AUC 越高,模型越能把"生病"和"健康"分开。
回归任务:用 RMSE 和 MAE 打分
前面讲的是分类问题(预测类别)。如果模型预测的是连续数值——比如房价、温度——怎么评估?最常用的两个指标是 MAE 和 RMSE。设真实值是 $y_i$,预测值是 $\hat{y}_i$,样本共 $I$ 个。先看 MAE:
$$\text{MAE} = \frac{1}{I}\sum_{i=1}^{I} |\hat{y}_i - y_i|$$这个式子意思是:把所有"预测误差的绝对值"加起来取平均,得到平均绝对误差——平均每单差多少,直白好懂。再看 RMSE:
$$\text{RMSE} = \sqrt{\frac{1}{I}\sum_{i=1}^{I} (\hat{y}_i - y_i)^2}$$它把误差平方、取平均、再开根号,叫均方根误差。平方这一步会放大惩罚大错误:同样是平均误差 5 万,如果里面藏着一个离谱到偏了 20 万的预测,RMSE 会明显变大,而 MAE 看不出差别。所以当大错误特别要命时——比如房价估错一倍——RMSE 更贴合你的担心。
最后别忘了:不管用哪个指标,都要在没参与训练、也没参与调参的测试集上计算才有意义。评估的终极问题只有一个:这个模型,换一批没见过的新数据,还行不行?