模型好不好,不能靠「看着还行」。 这一节把评估拆成两件事:分对情况——分清 TP/FP/FN/TN 四种结果; 分对数据集——训练、验证、测试各管一段。 Demo 里三类的准确率 0.8167 看着平常,但混淆矩阵立刻指出「类 2 有 9 个被认成了类 1」; 而一个全判负类的废模型,在不均衡数据上准确率能到 0.9800,召回率却是 0。
第 2、3 章讲了十七个算法,每个 Demo 都报了「准确率」「纯度」「邻居保持率」这些数。 这一节把它们统一说清楚: 这些数各自在回答什么问题、什么时候会骗人。 它是全书最实用的一节——调参、选模型、写报告, 全靠这里的几个数字。2.7 节随机森林那个「训练 0.9929、测试 0.7917」的对照,就是这一节的动机。
一、数据要分成三份,不是两份
最直觉的做法是分「训练集」和「测试集」。 但只分两份有个致命问题:你要调的超参数往哪儿试? 在测试集上试,等于拿考卷当练习册, 最后那个分数就是假的。 所以标准做法是分三份。训练集用来拟合参数;验证集用来挑超参数、挑模型; 测试集只在最后用一次,报出来的才是「新数据上的表现」。 常见的比例是 6 比 2 比 2 或者 8 比 1 比 1。 数据少的时候三份分不动,那就用交叉验证(下面会讲),本质是把验证这一步重复多次、轮流当验证集。 这里有一条铁律:测试集只能碰一次。 碰了第二次,它就不再是「没见过的数据」, 你报的分数就是在自欺欺人。每次想「再看一眼测试集」,都等于往过拟合挪一步。
二、四种结果:TP、FP、FN、TN
二分类里,预测和真实一交叉,只有四种情况。TP(真正例):判正,实际也正; FP(假正例):判正,实际是负——俗称「误报」;FN(假负例):判负,实际是正——俗称「漏报」; TN(真负例):判负,实际也负。 在这四个数上,长出了四把尺子。准确率 = (TP+TN) / 全部,回答「一共猜对多少」。 精确率 = TP / (TP+FP),回答「判成正的里面有多少真对」。 召回率 = TP / (TP+FN),回答「真正的正例有多少被抓到」。 F1 是精确率和召回率的调和平均,用来在两者冲突时给一个折中数。 为什么精确率和召回率会冲突? 因为你可以把阈值调得很松——宁可错杀一千,不可放过一个,召回率上去,精确率掉下来; 也可以调得很严,反过来。这两个指标是同一个旋钮的两端,不可能同时最优。 多分类时这四把尺子要按类算。 Demo 里三类各算一遍,你会发现每一类的精确率和召回率都不一样——这说明模型对某些类更偏心。 只看总准确率是看不出这件事的。
三、准确率什么时候会骗人
Demo 里造了一份 1000 个样本、只有 20 个正类的数据,然后做一个「什么都不学、全判负类」的分类器。 准确率 0.9800。 看着像 98 分,实际上它一个正类都没抓到,召回率 0.0000。 在垃圾邮件过滤、欺诈检测、疾病筛查这些场景里,这种模型毫无用处,可它能在「准确率排行榜」上排很前。 类别不平衡时,准确率是废的指标。 该看的是精确率、召回率、F1,或者干脆把混淆矩阵打出来看。 还有一个更狠的办法:跟「全判多数类」的基线比。 如果模型的准确率连这个基线都没超过, 那它等于没学。 顺带说一句平衡的做法:过采样少数类、欠采样多数类、调类别权重、换评估指标(比如 AUC、 PR 曲线下的面积)。最不该做的就是只盯着准确率调参。
四、交叉验证:把「运气」摊平
数据少的时候,一次划分的结果波动很大。 Demo 里五折的准确率分别是0.7833、0.8167、0.7833、0.7667、0.7333, 最高和最低差了 8 个点。 只做一次留出法,你拿到的可能正好是最差那一折。 交叉验证的办法是:把数据切成 k 份,每次拿一份当测试、其余训练,重复 k 次,最后报平均值和标准差。 平均值更稳,标准差告诉你「这个模型对数据划分有多敏感」。 标准差的用途常被忽略。两个模型平均分一样时,标准差小的那个更可靠——它不挑数据。 反过来,如果某个模型标准差特别大,说明它可能只是碰巧在某一折上运气好。 还有两个变体值得知道。分层 k 折:切分时保持每折的类别比例和整体一致,类别不平衡时几乎必用。 时间序列不能随机切——拿未来预测过去是作弊,必须按时间顺序切,用前面训练、后面测试。
五、回归的指标:没有「准确率」
回归预测的是连续值,所以不能用「猜对猜错」来评价。这一节量了四个指标。 MSE(均方误差)是误差平方的平均,最常用,但它有个毛病:量纲是原来的平方, 「误差 0.2677 平方米」这种话没法说。RMSE 是 MSE 开方,回到原来的量纲,好解释。 MAE(平均绝对误差)是误差绝对值的平均。 它和 MSE 的差别在「对离群点的态度」: MSE 把误差平方,一个大错顶很多小错,所以更怕离群点;MAE 一视同仁。 数据里离群点多时, MAE 更稳。 R² 最容易读错。它算的是「比全程猜平均值,误差少了多少比例」。 Demo 里 0.9769 的意思是误差减少了 97.69%,不是「准确率 97.69%」。 它可以是负的——模型比直接猜平均值还差的时候,R² 就是负数。
六、动手 Demo:一个准确率数字,什么都说明不了
五段实验,逐层拆开「评估」这件事。前三段用分类:先造三类的预测结果,算混淆矩阵和四指标; 再造一份极度不平衡的数据,看准确率怎么骗人。第四段做五折交叉验证,第五段换回归指标。 代码不长,每一段都是「算一个数、看一眼」。重点是看这些数之间的关系,不是背公式。
import numpy as np
# 混淆矩阵:行是真实类别,列是预测类别
cm = np.zeros((3, 3), int)
for t, q in zip(y, p):
cm[t, q] += 1
# 从矩阵里读四个指标
for c in range(3):
tp = cm[c, c]
fn = cm[c].sum() - tp
fp = cm[:, c].sum() - tp
prec = tp / max(tp + fp, 1)
rec = tp / max(tp + fn, 1)
f1 = 2 * prec * rec / max(prec + rec, 1e-9)
# 五折交叉验证:每次留一折当测试集
for f in range(5):
te = idx[f * 60:(f + 1) * 60]
tr = np.setdiff1d(idx, te)
w = np.linalg.lstsq(np.column_stack([np.ones(len(tr)), X[tr]]), yy[tr] * 2.0 - 1.0, rcond=None)[0]
s = np.column_stack([np.ones(len(te)), X[te]]) @ w
print("第 %d 折 准确率 %.4f" % (f + 1, ((s > 0).astype(int) == yy[te]).mean()))
实跑输出:
--------------------------------------------------------------------
评估:先分清「猜对了」的四种情况
--------------------------------------------------------------------
这一节不讲新算法,讲**怎么判断一个模型好不好**。它比算法更重要:
**指标选错了,调参就成了往错的方向使劲。**
【一】三类的混淆矩阵
真实\预测 类 0 类 1 类 2 合计
------------------------------------------
类 0 52 5 3 60
类 1 2 50 8 60
类 2 6 9 45 60
合计 60 64 56 180
**对角线是猜对的,其他地方都是猜错的。**
这个表比一个准确率数字信息量大得多:它告诉你「错在哪儿」——是把类 1 认成了类 2,
还是把类 0 认成了类 1。
【二】从混淆矩阵里读出四个指标
类别 TP FP FN 精确率 召回率 F1
------------------------------------------------------
类 0 52 8 8 0.8667 0.8667 0.8667
类 1 50 14 10 0.7812 0.8333 0.8065
类 2 45 11 15 0.8036 0.7500 0.7759
**准确率 0.8167 = 对角线之和除以总数。**
精确率和召回率是两把不同的尺子:精确率问「判成这一类的里面有多少是真对的」,
召回率问「这一类的里面有多少被判出来了」。**两者经常此消彼长。**
【三】准确率为什么会骗人
造一个 1000 个样本、只有 20 个正类的极度不平衡数据。
**然后做一个什么都不学的分类器:全判成负类。**
指标 值 怎么算
--------------------------------------------
准确率 0.9800 (TP+TN) / 全部
精确率 0.0000 TP / (TP+FP)
召回率 0.0000 TP / (TP+FN)
F1 0.0000 精确率与召回率的调和平均
**准确率 0.9800,看着很棒;召回率 0.0000,一个正类都没抓到。**
这个分类器在生产上毫无用处,可它能在「准确率排行榜」上排很前。
**类别不平衡时,准确率是废的指标。** 该看的是精确率、召回率、F1,或者直接看混淆矩阵。
【四】留出法 vs 交叉验证
第几折 测试准确率
----------------------
1 0.7833
2 0.8167
3 0.7833
4 0.7667
5 0.7333
平均 0.7767
标准差 0.0271
**五折的准确率从 0.7333 到 0.8167,标准差 0.0271。**
只做一次留出法,你拿到的可能正好是最差或者最好的那一折——
**这就是为什么论文里都报交叉验证。**
平均之后更稳,标准差还告诉你「这个模型对数据划分有多敏感」。
【五】回归不能看准确率,看这几个
指标 值 它回答什么
--------------------------------------------------
MSE 0.2677 平均平方误差,量纲是原来的平方
RMSE 0.5174 MSE 开方,回到原来的量纲
MAE 0.3970 平均绝对误差,对离群点更宽容
R² 0.9769 比「全猜平均值」好多少,1 是完美
**R² 最容易读错。**
0.9769 不是「准确率 97.69%」,而是「比拿平均值瞎猜,误差少了 97.69%。它可以为负——
**模型比平均值还差的时候,R² 就是负的。**
混淆矩阵要横着看、竖着看。横着看一行,是这个真实类被分到哪儿去了; 竖着看一列,是判成这个类的都从哪儿来。 类 2 那一行有 9 个跑到类 1 去了,这就是「错在哪儿」的具体位置。 四指标表里,类 0 的三个数一样(0.8667),类 1 和类 2 就不一样了。 三分类时这三把尺子只有在「两类样本一样多、错误也对称」时才碰巧相等。 所以别指望一个数字概括一切。 第三段那两行数字是这一节最该记住的:准确率 0.9800、召回率 0.0000。一个数字夸它,一个数字骂它。 它们都对——只是回答的问题不同。 五折那组:0.7333 到 0.8167,标准差 0.0271。 换句话说, 你只做一次留出法报出来的数,误差可能有好几个点。 论文里报交叉验证不是为了显得严谨, 是因为不这么报根本不可比。
30 秒实验
- 把第三段的负类比例从 98% 改成 80%,那个「全判负类」的准确率会变成多少?
- 把五折改成十折,平均值会变吗?标准差会变大还是变小?
- 构造一个 R² 为负的例子:用一条斜率相反的直线去拟合,看看 R² 掉到多少。
常见误区
- 用测试集调参。这是最常见的错误,也是最难自查的——你会不知不觉拿测试集的结果去改超参数, 改到满意为止。那时报出来的分数已经没有意义了。测试集只能碰一次。
- 类别不平衡还只看准确率。99% 的负类数据里,全判负类就有 99% 准确率。 先算一个「全判多数类」的基线,再比。
- 把 R² 当准确率念。R² = 0.9 不是「准确率 90%」,而是「比猜平均值少 90% 的误差」。 它可以是负的,这件事很多人不知道。
自测三问(点开对答案)
1. 为什么需要验证集,不能只用训练集和测试集? —— 因为调超参数需要一个「可以反复看」的数据集。 用测试集调,测试集就被污染了,最后报的分数不能代表新数据上的表现。 验证集就是专门用来被反复看的。 2. 精确率和召回率分别在回答什么问题? —— 精确率问「判成正例的里面,有多少是真的」;召回率问「真正的正例里,有多少被抓到了」。 两者随阈值此消彼长。 3. R² 为负意味着什么? —— 意味着模型的预测比「全部猜训练集平均值」还差。这不是笔误,用错方向的模型很容易出现负 R²。评估讲完了,接下来两节讲「数据怎么进来」。先看最常见也最麻烦的一种——文本。
- ☐ (本节完,下一节 4.2 文本数据的转换处理)