做监督学习作业时,大家的注意力很容易全压在"调参调到 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):
| Class | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 合计 |
|---|---|---|---|---|---|---|---|---|
| support | 1459 | 1950 | 246 | 19 | 65 | 120 | 141 | 4000 |
| 占比 | 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 打平:
| 模型 | Accuracy | Macro-F1 | Balanced Acc | fit 时间 | predict 时间 |
|---|---|---|---|---|---|
| SVM | 0.820 | 0.694 | 0.655 | 3.3s | 1.58s |
| kNN | 0.811 | 0.693 | 0.681 | 0.005s | 0.065s |
| Decision Tree | 0.768 | 0.652 | 0.622 | 0.10s | 0.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):
| k | 1 | 3 | 5 | 13 | 27 | 55 | 89 |
|---|---|---|---|---|---|---|---|
| val macro-F1 | 0.653 | 0.672 | 0.653 | 0.609 | 0.509 | 0.392 | 0.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 也扫了,只是结果在调参全表里:
| k | 1 | 3 | 5 | 13 | 27 | 55 | 89 |
|---|---|---|---|---|---|---|---|
| distance | 0.653 | 0.672 | 0.653 | 0.609 | 0.509 | 0.392 | 0.349 |
| uniform | 0.653 | 0.650 | 0.624 | 0.541 | 0.440 | 0.356 | 0.324 |
distance 在每个 k 上都 ≥ uniform,而且 k 越大优势越明显——因为 k 大时邻居里混进了很远的点,uniform 一视同仁会被拖累,distance 用 1/距离 加权自动压低远点。
那它为啥不是到处都用?
kNN 在这个作业里强,是因为环境对它太友好了:数据量不大、维度不高(54 维,核心是 10 个连续地理特征)、test 和 train 同分布、地理坐标天然"距离近就类别像"。换到真实场景,它的短板全暴露:
- 懒惰学习,成本全压在预测:它每次预测都要和全部训练样本算距离。这里 4k 测试点看着快,真要对千万级请求实时打分就爆炸。主流模型是"训练贵、预测极便宜",才符合"训练一次、预测无数次"的生产需求。
- 维度灾难:高维空间里所有点的距离趋于相等,"最近邻"失去意义。文本/图像那种几万维,纯 kNN 基本没法用。
- 模型 = 整个训练集:内存大、不可压缩、给不出可解释的规律、也无法外推。
- 对尺度和无关特征极敏感:必须仔细标准化(我们专门做了 scaling ablation)。
所以工业界更多把它当 baseline 或召回粗排。它的好成绩说明的是"这个问题对距离法友好",而不是"kNN 普遍最优"。
三、那个什么都不学的 Dummy,才是你最该先看的一行
最终指标表里有一行 Dummy most-frequent,来自 DummyClassifier(strategy="most_frequent")。它不学任何东西:不管输入是什么,永远预测训练集里最常见的那一类(这里是类 2,占 48.8%)。它走和真模型完全一样的 fit/predict/打分 流程,纯粹当地板线。
它的成绩很有教育意义:
| 指标 | Dummy | 含义 |
|---|---|---|
| accuracy | 0.488 | 就是类 2 的占比——蒙最大类的"白送分" |
| macro-F1 | 0.094 | 7 类里只蒙对 1 类,其余 6 类 F1=0 |
| balanced acc | 0.143 | ≈ 1/7,等于瞎猜一类的水平 |
这一行干了两件大事:
- 戳破 accuracy 的假象。啥也不干就有 48.8% accuracy。所以 DT 的 0.768、SVM 的 0.82 看着高,真正的"增量"得减掉这条地板线才算数。
- 论证 macro-F1 才是这题的正确尺子。Dummy 的 macro-F1 只有 0.094,而真模型能到 0.45~0.69——这个差距才真实反映了对少数类的学习能力。accuracy 被 support 巨大的类 1、2 主导(占 85%),会把少数类的失败彻底掩盖。
Dummy 不是来比强弱的候选模型,而是一把对照标尺——确认问题不平凡、确认你的指标选对了。报告里如果不放它,你说"我的模型很强"就缺了参照系。