机器学习新手笔记:训练考满分,一新数据就崩?可靠是考出来的

先想象一个场景。你戴着运动手环,它记录了三个月里你的步数、心率、睡眠,然后给你报了个"健康评分 99 分"。你一高兴买了一年的会员。可换了一周剧烈的新行程,手环又开始一本正经地给出完全离谱的预测。

这不怪手环。它学会的,很可能只是把你这三个月的旧数据背了下来,而不是理解了"什么样的人会健康地活"。机器学习和这个手环面对的,是同一个问题:模型在没见过的新数据上还靠不靠得住,而不是在练过的旧数据上得多少分。

这篇笔记就围绕这一句主线讲完全部六个考点:先立下"测试必须用没见过的数据"这条纪律,再看喂给它什么料、做哪两种任务,接着弄清楚为什么越使劲反而越容易崩,最后学会用一整套数字证据说清"它到底答得对不对"。


1. 一份数据凭什么要分成三份用,而不是全拿去训练?

直觉上,数据越多越好学,应该有什么全拿来训练才对。但机器学习和考试有一个共同点:答案一旦被看见,"考试"就不再是考试。

假设你的复习资料只有题目库,你把题库背得滚瓜烂熟,用同一本题库自测次次满分。可一到真正的新考题就崩。这套说法的出处是 scikit-learn 官方文档,它讲得非常直白:在同一批数据上训练又用它给自己打分,是方法论错误,因为一个"只会复读刚看见过的标签"的模型,能得满分,却在没见过的数据上什么都预测不出来。这个现象有个名字,叫过拟合(overfitting),机制我们放到第 5 章再细讲,这里先记住它的诱因就够了。sklearnCV

所以负责任的流程,是把数据集按用途分家。先看课程给出的这幅分工图:

图注:一个 DATASET 按职责分成三段。TRAIN 段负责训练多个模型;VALIDATION 段用来调超参(hyperparameter)并从几个模型里挑最好的;TEST 段在最后用混淆矩阵等指标给选中模型做最终评估。关键在分工:训练段真的去"学"东西,验证段只是偶尔被看用来调参,测试段则全程考完才碰。

"偶尔被看,但绝不学习"这句话是验证集(validation set)的核心。当你想比较两个不同设定(比如正则化强度 C 取多大)哪个更好时,如果在测试集上来回调参,就等于把测试题答案偷看进了选择过程,这叫验证信息泄漏(information leak)。修正办法是多划出一份验证集:在训练集上训练,在验证集上评估和调参,等实验看起来都成功了,才在测试集上做最后一次评判。sklearnCV

那如果数据本身很少,划成三份会显得"训练料不够"?这时就请出另一种做法:k 折交叉验证(k-fold cross-validation)。它先把真正留作最后考核的测试集放一边不看,然后把训练集切成 k 份,轮流把其中一折当作"临时没见过的样本"来测,其余 k-1 折用来训练,一共得到 k 个分数,最后用它们的均值 ± 标准差来汇报模型水平。下面这张官方示意把"先留测试、再在训练里做 5 折轮换"的两层结构画得很清楚:

图注:这是一张结构示意图,不代表任何样本量比例。顶部 All Data 先被分出较长的 Training data 和单独留出的 Test data;下方 5 行 5 列的方块表示把训练区再切成 5 折,每一行留出蓝色那一折当验证,蓝色块从左到右轮换,正好是课件里"6 个样本、k=3、每折 2 个观测值轮流当留出集"的放大版。sklearnCV

经验上 5 到 10 折是常见选择。这一章其实立下的是全书的规矩:评价模型靠不靠谱,前提是能看考题答案的口子都已经堵死。调参用的验证集和最终考核的测试集,绝不能混用。


2. 喂给模型的"料"将就不来:质量差、或想多要一点时怎么办?

纪律管住了"数据怎么分",但还得问另一件事:数据本身的料够不够好?近年数据方向的好几个热门课题其实都是"料"的问题,比如大规模收集数据、类别不平衡(imbalanced / 长尾分布)、弱标注和噪声标签,以及区分简单样本和困难样本。每一条都在说同一种困境:光有数据的"量"不够,还得有"质"和"多样性"。

拿类别不平衡来说,现实数据集里常有几个类别占了绝大多数样本、另一些类别只有零星几条(所谓长尾),模型很容易学会"无脑猜多数",少数类却学不会。再看噪声标签,如果一批样本的标注本身就是错的,模型硬去拟合,就是把错误当成了规律在背。这些挑战的影响方式各有不同,但都不难理解:模型最后学出来的本领,跳不出你喂给它的那批料里真实、干净、够多样的一部分。

当你手里数据不够、想让样本更丰富一些时,常用的招数是数据增强(data augmentation)。它的核心约束只有一句话:只有当增强操作保留了标签和任务语义时,增强才有效。

比如图像可以水平翻转、旋转、随机裁剪、做颜色抖动。把这些操作换一句话说,就是"在不改变真实信息的前提下,给模型提供更多信号"。看图最直观:

图注:Cutout(随机擦除)这一种增强长这样:训练时在一批 CIFAR-10 小图上随机盖上一块方形黑块,而图里原本是什么类别(猫、狗…)并没有变,所以这张"遮了一块"的新样本仍然是同一个标签,属于合法增强。Cutout

Cutout 是这篇论文给出的一种具体随机擦除套路。它的价值在"标签没变":即便网络看不见那只猫的整张脸,它还是要判断出这只猫是猫,于是被迫去学别的更稳固的特征,不容易只依赖某一小块,这正是对抗过拟合、压制方差的思路。论文还强调它和已有的翻转、裁剪等增强以及别的正则化手段可以叠加着一起用,互不冲突。这部分内容现在只是埋个伏笔,到第 5 章我们会回访"增强其实是减方差的一味药"。Cutout


3. 第一个任务:预测一个连续数,回归想找的那条线

把数据纪律和"料"的问题交代清楚后,我们开始回答真正的核心问题:模型到底在学什么?先看第一类问题,它长成"答案是一个连续的数"。

假设你走进一个租房网站,看到每套房源都带着居住面积、房间数、建成年代这些信息。你妈妈想让你估一估,这样一套两居室一个月大概多少钱租得下来。这就是回归(regression):它的任务是找到那个把输入变量映射到连续输出变量的规律,典型如预测价格、工资、温度这类没有"断点"的量。sklearn 线性模型文档

最朴素、也最适合入门的一种回归是线性回归:假设价格和各个因素之间用一条直线(在高维是超平面)近似,形如公式

$$\hat{y} = \mathbf{w}\cdot\mathbf{x} + b$$

其中 $\mathbf{x}$ 是输入特征,$\mathbf{w}$ 是每个特征对应的权重(从数据里学出来),$b$ 是偏置,$\hat{y}$ 是预测的价格。学习线性回归,就是找一组 $\mathbf{w}$、$b$,让那条线尽量贴近真实数据,常用均值平方误差(mean squared error, MSE)来惩罚偏差,MSE 越大说明预测离真值越远。

有个很容易帮你把"线性回归"和它全家理解起来的视角:线性回归其实就是只有输入输出两层、中间没有隐藏层的一条线的神经网络。看下面课件这张结构图就很清楚:

图注:一个只有两层的线性模型:输入层是若干"factors"(居住面积、房间数…,图中用 x1、x2、xd 代表许多个),它们各自乘上自己的权重,箭头汇到一个输出节点 o1,输出就是 Price。换句话说,输出只是对一堆输入做了一次"逐项乘权重再加总"。

课件要用一个真实数据集把这个例子钉死,用的是 California Housing,即预测加州每个街区的房价中位数。按 scikit-learn 官方口径,它来自 1990 年美国人口普查,一共有 20,640 个样本、8 个输入特征(MedInc 收入中位数、房屋年龄、户均房间数、户均卧室数、街区人口、户均人数、纬度和经度),目标是房价中位数,单位是 $100,000。CalHousing

一个直觉判断口诀:当问题的答案是一个连续的、可以取中间值的量叫回归,它找的是一条线。这个数据集官方就明确把它标成回归(regression)任务。


4. 第二个任务:判别离散类别,名字带"回归"、干的却是分类

同一位房东,如果换一个问题:这封新邮件是垃圾邮件还是正常邮件?这个客户账户是不是被黑了?答案不是"多少",而是"哪一个/要不要",是离散的类别。这类问题叫分类(classification)。它找的不是一条穿过数据点的线,而是一条能把不同类别分开的"界"。

课件里有个小测验正好拿来分辨:想预测未来三个月某款商品会卖出去多少件,答案是"多少",这是回归;想写程序判断某个客户账户是不是被黑客入侵了,答案是"是不是",这是分类。一个往连续的数,一个往离散的类,别搞反。

回归和分类的区别,用两张小图一眼就能分清。左边那张是分类,绿色的点和青蓝色的点各自抱团,中间画了一道 S 形的曲线把它们剖开;右边那张是回归,红色散点大致沿一条直线趋势上升,黑色的直线要穿过它们找到最贴合的走向。

图注:同一页把两兄弟摆开对照。左子图标注 Classification:两类散点被一条 S 形(sigmoid 形状)的边界分开;右子图标注 Regression:散点把一条直线"夹"出上升趋势。一个画界、一个穿点,这就是离散判别与连续映射的差别。

现在到了新手最容易卡壳的一处:逻辑回归(Logistic Regression)为什么名字里带着"回归",干的确是分类的活?scikit-learn 官方对此有一句很干净的解释:尽管它叫 regression,它实现的却是一个用于分类的线性模型,它输出的不是连续目标值,而是属于某个类别的概率。sklearn linear

它的机制其实还是上一章那个线性内核。先像线性回归那样算出线性组合 $z = \mathbf{w}\cdot\mathbf{x} + b$,再把它塞进一个叫 sigmoid(又叫 logistic)的函数,硬生生压到 [0, 1] 之间:

$$\sigma(z) = \frac{1}{1 + e^{-z}}$$

sigmoid 挤出来的东西可以当概率读:$\sigma(z) \ge 0.5$ 就判成类别 1,否则判成类别 0,这就是 0.5 那根决策边界。所以逻辑回归"为什么是分类",答案就一句话:它把 z 变成了概率,再靠一根阈值线来切。

那类别超过两个怎么办?比如 CIFAR-10 这种十类图像问题,里面是飞机、汽车、鸟、猫、鹿、狗、蛙、马、船、卡车各 6,000 张,共 60,000 张 32×32×3 的彩色小图,训练测试各 50,000/10,000 张。CIFAR

方法就变成"给每个类各发一个分数,谁最大就猜谁"。整条流程是:把图像拉成一个长长的向量 → 过一个打分函数 f(x,W,b) → 得到 10 个类别的分数 → 取最大那个当作预测。所谓学习,就是去找一组参数 W、b,让正确类别的分数普遍最高。这条管线长这样:

图注:图像线性分类的完整流程。左边一张猫图是 x∈R^{32×32×3},经"图像化向量"展平成 x∈R^{3072×1}(一张 32×32×3 的图像正好 3072 个数),喂给打分函数 f(x,W,b) 得到 10 个类别分数,最后以最大分数对应的类为预测。

课件在旁边补了一个实现上的小技巧来"消掉偏置 b":在 3072 维特征后面再补齐一位 1,把它当成一个虚拟输入,于是向量变成 3073 维,参数函数写作只有 W、不带 b 的 f(x,W)。两种写法等价,只是后者把偏置也收进了权重矩阵,实现更整齐。

那么问题来了:光说"让正确分数最高"还不够,得有一个具体的、可求出梯度的惩罚才能训练。课件给了一个很直观的损失,叫多分类 SVM(hinge)损失。它的大白话是:只要错误类别的分数没有"高出正确类别一个间隔(margin,通常取 1)",就记一点损失,否则这件事就不算错。用 CIFAR 里猫 cat / 车 car / 蛙 frog 三个类举例,某个样本拿到的分数是:

类别分数
cat(正确,真实类别)3.2
car5.1
frog−1.7

对正确类别 cat 算损失,要逐个看错误类:

  • 对 car:max(0, 5.1 − 3.2 + 1) = max(0, 2.9) = 2.9,car 高过正确分数还超出间隔,挨罚 2.9;
  • 对 frog:max(0, −1.7 − 3.2 + 1) = max(0, −3.9) = 0,frog 远低于正确分数而且没越过间隔,不罚。

scikit-learn 的官网公式把这件事写成 $\max(0,, 1 - y(\mathbf{w}^T\mathbf{x}+b))$,意思是只要分数又对方向又够大,损失就是 0;一旦越了界,就按"差多少"线性惩罚。sklearn linear

顺带一句术语提醒:程序里常见把权重写成 Coef_、偏置写成 Intercept_,逻辑回归也叫 Logit 或 Logistic,考试时别被绕昏。


5. 训练下越大的功夫越容易崩:欠拟合与过拟合到底卡在哪?

现在两类任务我们都能"跑通"了。可恰恰在模型能跑起来之后,真正的陷阱才浮出来:训练时下的功夫越大,越可能在新数据上崩。第 1 章点到的那句 overfitting,这一章要兑现成一套能治的诊断框架。

框架的第一步,是把模型的误差拆开看。机器学习教材通常把期望损失分解成三部分:偏差、方差和噪声。直觉是这样给的:偏差(bias)衡量模型平均预测跟"正确值"差了多少;方差(variance)衡量同一种问题,喂进不同的随机训练样本时,模型输出波动有多大;剩下那股来自数据本身的、怎么都消不掉的随机性,叫噪声(noise)。sklearn 的官方例子把单棵决策树和它的 bagging 集成并排比较后发现,集成把方差明显压下去了、偏差几乎持平,而噪声那部分完全没变,这正是"噪声不可约、方差可降"的直观来源。sklearnBiasVar

怎么把"偏差高↔欠拟合、方差高↔过拟合"落到能实际看的图上?最顺手的是 validation curve。scikit-learn 用一个回归模型扫了一串正则强度,把训练分和验证分同时画出来:

图注:这是 scikit-learn 用来讲"正则强度↔过/欠拟合"的一张 validation curve。横轴是正则强度 alpha(对数刻度,越靠右正则越强、越约束模型),纵轴是模型分数,蓝线是训练分、橙线是验证/测试分。alpha 很小时几乎不加正则、蓝色训练分贴到很高(把训练数据近乎背下来)而橙色验证分掉下去,这段就是过拟合;当 alpha 大到过头,蓝色橙色一起塌,就是欠拟合;平衡点落在中段双方都不难看、验证分见峰的地方。sklearnBiasVar

上一章线性模型讲到的那个正则强度 C 也沿这根横轴走:C 越大约束越少、越贴训练数据、越偏过拟合端,C 越小正则越强、越偏欠拟合端。这张曲线图其实给出了一个非常好用的诊断口诀:训练分数高、验证分被甩开,是过拟合(模型太灵活,把训练数据连同噪声一起背了);训练和验证分一样低,是欠拟合(模型太简单,连训练数据的走势都没吃进)。

课件配的那张三格图把"欠拟合/合适/过拟合"三种拟合形态画得更形象一些:

图注:三个没有刻度的示意格子。左边 Under-fitting 标着 too simple,一条过于平滑的边界按捺不住数据自身的分散,这是高偏差低方差;中间合适;右边 Over-fitting 的边界过分曲折,把边角一个离群点也都圈了进去,于是在见过的点上分很高,换新点就失灵,这是高方差低偏差。

有了诊断,就能对症下药。欠拟合是模型太简单,应对的思路是给它更大的容量:多加数据、提高模型复杂度、多做特征工程、把训练多跑几轮。过拟合则是模型太灵活,应对思路正好相反:增加并净化训练数据、压低复杂度、用早停和 dropout、做数据增强。可以看到,第 2 章埋下的增强,此刻正好作为减方差的药方之一回收。读到这里你应该能让整套错位对上了:为什么"训练上功夫下得越大"反而越崩,因为把数据背下来不等于理解规律,而真正值得夸的能力,永远是那句回答:对没见过的数据稳得住。


6. 凭什么说它答得对:从混淆矩阵滑到 ROC 与 AUC

最后一步,把"答得对"落实成一份防自欺的定量证据。假设你手上有两个概率分类器,比如逻辑回归和一个神经网络,你到底凭什么说哪个更好?光凭直觉和"我记得它挺准"都不算数,得拿出数字。

先从最基础的格子讲起:混淆矩阵(confusion matrix)。它把每个样本的结果归进四种去向:真阳性(TP,真患病且被判患病)、假阳性(FP,没病却被报有病)、真阴性(TN,没病且被判没病)、假阴性(FN,真患病却没被查出)。课件拿"心脏病发作检测"来举例,行放的是金标准真相,列放的是模型预测,注意不同工具行/列约定可能相反,读图前先分清谁是真相谁是预测。下面是 scikit-learn 官方一张真实的混淆矩阵展示,用来建立"行=真实类、列=预测类"的几何坐标系:

图注:scikit-learn 画的混淆矩阵,纵轴 True label(真实类)、横轴 Predicted label(预测类),对角线上数字越大颜色越深说明判得越准。注意这是 Iris 三分类的 3×3 演示,不是课件那个心脏病二分类的四格,用它只认坐标结构即可。sklearnMetrics

有了四种计数,就可以抽出两个最常用的比率:

  • 敏感度(sensitivity / recall / true positive rate):真患病的人里,被检出了几个?公式是 TP / (TP + FN)。
  • 特异度(specificity / true negative rate):真没病的人里,被判成没病的有几个?公式是 TN / (TN + FP)。

注意一个术语精确点:假阳性率(FPR)是"没病却被误报有病"的比例,等于 1 − 特异度。课件某一行写着"FPR 也称 Specificity",那是让人卡壳的措辞,按标准的 Fawcett 定义,特异度是能正确识别真阴性的能力,FPR 反而随它下降而上升。Fawcett

课件给过一个乳腺癌诊断的实操题,正好能把这俩算一遍。金标准里 100 人是真患者、900 人真没病;模型诊断得 80 个真阳性、漏掉 20 个患者,又误报了 100 个没病的人、正确地放了 800 个没病的人走。于是敏感度 = 80 / (80+20) = 80%,特异度 = 800 / (800+100) ≈ 89%。一个高灵敏的检测漏掉患者少,适合初步筛查;一个高特异的检测误报健康人少,适合确诊后不吓唬人。

那"准确率"这个最直觉的指标为什么常常不可靠?关键在于它遇到类别不平衡(class imbalance)时会骗人。课件的 COVID 例子一针见血:某社区有 280,000 例正常人、只有 50 例真的感染。一个把所有 28 万人统统判成"正常"的模型,准确率 = 280,000 / 280,050 ≈ 99.9%,听着近乎完美,可它一个患者都没查出来。原因在于准确率把"绝大多数是没病的人"这个先验当成了能力,而它本质上没有做任何辨别。sklearnMetrics

对付这种骗术的正确武器是 ROC 曲线和它下面的面积 AUC。ROC 的思路很妙:把"判正/判负"那根 0.5 的阈值当成一个可以滑动的旋钮,从极严到极松一路拖过去,每个阈值点都对应一个 (false positive rate, true positive rate),在平面上滑出一条很"陡越靠近左上方越好"的曲线,曲线越贴近左上角、AUC 越大说明分类越可靠。下图是 Fawcett 那篇经典论文里的示意图,把"每个离散分类器只是 ROC 平面上一个点"讲得最直白:

图注:基础 ROC 平面,纵轴 True positive rate、横轴 False positive rate。从 (0,0) 到 (1,1) 的虚线对角线是"随机瞎猜"的基准,A、B、C、D、E 五个离散分类器各是一个点,越靠左上角的点越好。图上不承载任何真实性能数值。Fawcett

而如果用带概率输出的分类器,把阈值一路滑动,就会得到一条连续的 ROC 曲线。scikit-learn 用 Iris 数据集跑出来的一条真实曲线如下,可以看到橙色曲线明显鼓在随机对角线之上,说明这个分类器确实有判别力:

图注:一条真实的 ROC 曲线(Iris one-vs-rest 的一个二分类子任务)。横轴 False Positive Rate、纵轴 True Positive Rate,黑色虚线对角线是随机基准(AUC=0.5),橙色曲线鼓在对角线上方,图例标注其 AUC≈0.78。曲线越高过对角线越好。sklearnMetrics

ROC 为什么偏偏不怕类别不平衡?Fawcett 的原话是它只依赖 TPR/FPR 这两组"每一列各自算比例"的统计量,所以对类分布、错分成本不敏感;而不平衡测试集上虚高的那些单值准确率恰恰相反。回到第 1 章的问题就顺了:不管用哪串数字当证据,前提都是这些数字落在真正留出来的、没见过的测试数据上。正确划分和合适指标合在一起,才构成了"对没见过的新数据可靠"的完整证明。

一句话收个尾:请永远记住,机器学习的门槛不是"在训练数据上把题做对",而是在连出题人都没见过的数据上仍能答对,而"靠不靠得住"这件事,是考出来的,不是估出来的。


参考文献

本笔记引用的核心资料如下,建议复习考点时对照原文阅读:

  1. scikit-learn user guide: Cross-validation & Validation curves — https://scikit-learn.org/stable/modules/cross_validation.html
  2. scikit-learn user guide: Metrics and scoring — https://scikit-learn.org/stable/modules/model_evaluation.html
  3. Fawcett, An introduction to ROC analysis, Pattern Recognition Letters 27(8) — https://www.sciencedirect.com/science/article/abs/pii/S016786550500303X
  4. scikit-learn user guide: Linear Models & Support Vector Machines — https://scikit-learn.org/stable/modules/linear_model.html
  5. scikit-learn example: Validation curves & bias-variance decomposition — https://scikit-learn.org/stable/auto_examples/ensemble/plot_bias_variance.html
  6. CIFAR-10 / CIFAR-100 dataset (official page) — https://www.cs.toronto.edu/~kriz/cifar.html
  7. California Housing dataset (scikit-learn real world datasets) — https://scikit-learn.org/stable/datasets/real_world.html#california-housing-dataset
  8. DeVries & Taylor, Improved Regularization of Convolutional Neural Networks with Cutout — https://arxiv.org/abs/1708.04552
机器学习新手笔记:训练考满分,一新数据就崩?可靠是考出来的