🧮 第4章 评估方法和各种数据的处理
📄 一句话概括

模型好不好,不能靠「看着还行」。 这一节把评估拆成两件事:分对情况——分清 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 秒实验

  1. 把第三段的负类比例从 98% 改成 80%,那个「全判负类」的准确率会变成多少?
  2. 把五折改成十折,平均值会变吗?标准差会变大还是变小?
  3. 构造一个 R² 为负的例子:用一条斜率相反的直线去拟合,看看 R² 掉到多少。

常见误区

  1. 用测试集调参。这是最常见的错误,也是最难自查的——你会不知不觉拿测试集的结果去改超参数, 改到满意为止。那时报出来的分数已经没有意义了。测试集只能碰一次。
  2. 类别不平衡还只看准确率。99% 的负类数据里,全判负类就有 99% 准确率。 先算一个「全判多数类」的基线,再比。
  3. 把 R² 当准确率念。R² = 0.9 不是「准确率 90%」,而是「比猜平均值少 90% 的误差」。 它可以是负的,这件事很多人不知道。
自测三问(点开对答案) 1. 为什么需要验证集,不能只用训练集和测试集? —— 因为调超参数需要一个「可以反复看」的数据集。 用测试集调,测试集就被污染了,最后报的分数不能代表新数据上的表现。 验证集就是专门用来被反复看的。 2. 精确率和召回率分别在回答什么问题? —— 精确率问「判成正例的里面,有多少是真的」;召回率问「真正的正例里,有多少被抓到了」。 两者随阈值此消彼长。 3. R² 为负意味着什么? —— 意味着模型的预测比「全部猜训练集平均值」还差。这不是笔误,用错方向的模型很容易出现负 R²。

评估讲完了,接下来两节讲「数据怎么进来」。先看最常见也最麻烦的一种——文本。

相关笔记