[omscs-ml] 数据划分、kNN、和什么都不学的 Dummy

OMSCS CS7641 · Supervised Learning · Covertype 七分类笔记

做监督学习作业时,大家的注意力很容易全压在"调参调到 macro-F1 多少"上。但真正决定一份报告是否站得住脚的,往往是三件不起眼的小事:你怎么切数据、你有没有看穿某个模型的"假强大"、以及你有没有一条诚实的地板线。这篇就用 Covertype 这个数据集把这三件事讲清楚。


一、数据是怎么切的:三层 split,以及 validation 的"功成身退"

Covertype 全量有 58 万行。我们没有全用,而是做了一条确定性的三层切分(全程由 SEED=42 固定,任何一段代码独立重跑都得到同一份数据):

fetch_covtype 全量 581,012 行
   └─ 分层抽样 20,000 行                     (random_state=42)
        ├─ 80% → X_train_full 16,000 行     (test_size=0.20, random_state=43)
        │         ├─ 75% → X_tune 12,000    (调参时 fit 用)
        │         └─ 25% → X_val  4,000     (调参时选超参用)
        └─ 20% → X_test 4,000 行   ← 留出测试集,只在最后碰一次

这里有个很多人第一次会绕进去的点:

validation 只负责"选超参",选完就退场;最终模型是在完整的 16,000(train+val 合并)上重训的,而不是只用 12,000。

也就是说,流程其实是两个阶段:

阶段fit 用的数据打分用的数据目的
调参X_tune (12,000)X_val (4,000)选出最优超参
最终评估X_train_full (16,000)X_test (4,000)报告泛化成绩

超参一旦定下来,验证集就"功成身退",把它合并回训练集能让最终模型多见 4,000 个样本、表现更好。X_test 则从头到尾独立,保证泛化估计干净。

分层(stratify)为什么必须:Covertype 极度不平衡。看一眼 test set 各类的真实样本数(即 classification_report 里的 support):

Class1234567合计
support1459195024619651201414000
占比36.5%48.8%6.2%0.5%1.6%3.0%3.5%100%

类 1+类 2 就占了 85%,而类 4 在整个测试集里只有 19 个样本。不分层随便切,某些 fold 里少数类可能直接为 0,指标就没法看了。

顺带一提:k-fold 是另一种玩法

我们用的是单次留出验证(single holdout):验证集固定一份。还有一种更稳的做法是 5-fold cross-validation——把 16,000 切成 5 份互不重叠的 fold,轮流让每一份当验证、其余四份当训练,每个超参配置得到 5 个分数取平均:

轮次1: fold 2,3,4,5 训练 → fold 1 验证
轮次2: fold 1,3,4,5 训练 → fold 2 验证
...
轮次5: fold 1,2,3,4 训练 → fold 5 验证

每个样本恰好当一次验证、四次训练。它更稳、还能估方差,代价是计算量 ~5 倍。在 20k 样本、SVM/MLP 每次 fit 都不便宜的情况下,单次 holdout 的 4,000 验证集已经够大够稳,所以我们选了前者。这本身就是一条可以写进报告 limitations 的取舍。


二、kNN 看着很猛,但那是"假强大"

最终 test 成绩出来,kNN 几乎和调了半天的 SVM 打平:

模型AccuracyMacro-F1Balanced Accfit 时间predict 时间
SVM0.8200.6940.6553.3s1.58s
kNN0.8110.6930.6810.005s0.065s
Decision Tree0.7680.6520.6220.10s0.001s

一个几乎不用训练(fit 只是把数据存起来)的模型,凭什么和 SVM 平起平坐?然后它为什么又不是工业界的主力?这两个问题的答案,藏在它的复杂度曲线和训练分数里。

信号一:训练 macro-F1 = 1.0(完美得可疑)

最优配置是 k=3, weights='distance',它在训练集上的 macro-F1 正好是 1.0。原因很简单:在自己的训练集上预测时,每个点的"最近邻"就是它自己(距离=0 → 距离加权下权重无穷大),所以必然预测成自己的真实标签。

这意味着:训练分数在 kNN 上几乎没有诊断价值(它永远满分)。能说明问题的是 "训练 1.0 vs 验证 0.67" 这个巨大的 gap——典型的过拟合 / 高方差信号。

信号二:复杂度曲线一路下滑

对 kNN 来说,k 越小 = 模型越复杂(只信极少数最近的点,决策边界很碎)。我们扫了 k ∈ [1,3,5,13,27,55,89](weights 固定在最优的 distance):

k13513275589
val macro-F10.6530.6720.6530.6090.5090.3920.349

峰值在 k=3 这种极小值,之后单调下滑。换句话说,模型只有在"非常局部"时才好,一旦让它平滑一点(k 变大)分数就掉。训练全对 + 必须用极小 k,合起来就是论文里那句话的含义:

"kNN remains competitive, but its perfect training Macro-F1 and declining complexity curve indicate a high-variance local rule." (kNN 依然有竞争力,但它完美的训练 macro-F1 和持续下滑的复杂度曲线表明,这是一个高方差的局部规则。)

顺带验证一下 weights:distance 全程压过 uniform

很多人复杂度图里只画了 n_neighbors,其实 weights 也扫了,只是结果在调参全表里:

k13513275589
distance0.6530.6720.6530.6090.5090.3920.349
uniform0.6530.6500.6240.5410.4400.3560.324

distance 在每个 k 上都 ≥ uniform,而且 k 越大优势越明显——因为 k 大时邻居里混进了很远的点,uniform 一视同仁会被拖累,distance 用 1/距离 加权自动压低远点。

那它为啥不是到处都用?

kNN 在这个作业里强,是因为环境对它太友好了:数据量不大、维度不高(54 维,核心是 10 个连续地理特征)、test 和 train 同分布、地理坐标天然"距离近就类别像"。换到真实场景,它的短板全暴露:

  1. 懒惰学习,成本全压在预测:它每次预测都要和全部训练样本算距离。这里 4k 测试点看着快,真要对千万级请求实时打分就爆炸。主流模型是"训练贵、预测极便宜",才符合"训练一次、预测无数次"的生产需求。
  2. 维度灾难:高维空间里所有点的距离趋于相等,"最近邻"失去意义。文本/图像那种几万维,纯 kNN 基本没法用。
  3. 模型 = 整个训练集:内存大、不可压缩、给不出可解释的规律、也无法外推。
  4. 对尺度和无关特征极敏感:必须仔细标准化(我们专门做了 scaling ablation)。

所以工业界更多把它当 baseline 或召回粗排。它的好成绩说明的是"这个问题对距离法友好",而不是"kNN 普遍最优"。


三、那个什么都不学的 Dummy,才是你最该先看的一行

最终指标表里有一行 Dummy most-frequent,来自 DummyClassifier(strategy="most_frequent")。它不学任何东西:不管输入是什么,永远预测训练集里最常见的那一类(这里是类 2,占 48.8%)。它走和真模型完全一样的 fit/predict/打分 流程,纯粹当地板线。

它的成绩很有教育意义:

指标Dummy含义
accuracy0.488就是类 2 的占比——蒙最大类的"白送分"
macro-F10.0947 类里只蒙对 1 类,其余 6 类 F1=0
balanced acc0.143≈ 1/7,等于瞎猜一类的水平

这一行干了两件大事:

  1. 戳破 accuracy 的假象。啥也不干就有 48.8% accuracy。所以 DT 的 0.768、SVM 的 0.82 看着高,真正的"增量"得减掉这条地板线才算数。
  2. 论证 macro-F1 才是这题的正确尺子。Dummy 的 macro-F1 只有 0.094,而真模型能到 0.45~0.69——这个差距才真实反映了对少数类的学习能力。accuracy 被 support 巨大的类 1、2 主导(占 85%),会把少数类的失败彻底掩盖。

Dummy 不是来比强弱的候选模型,而是一把对照标尺——确认问题不平凡、确认你的指标选对了。报告里如果不放它,你说"我的模型很强"就缺了参照系。