🧮 第1章 机器学习基础
📄 一句话概括

一次完整的机器学习是七步:造数(取数)、切分、标准化、训练、评估、画图看一眼、分组统计。 后面十七个算法换的只是第四步,其余六步一模一样。 这一节把七步走一遍,重点量两个数:量纲差 213 倍会让准确率从 0.9333 掉到 0.3556, 样本从 15 加到 45 又会把它拉回来。

📝 本节和前面几节的关系

1.1 讲了三种范式,这一节只走有监督那一条,因为它步骤最多、最容易踩坑。 第 2 章的九个算法都套这条流水线,只是第四步换成不同的拟合办法。 第 4 章会把第五步(评估)单独拆开细讲,那时会看到准确率之外还有一堆指标。

一、七步流水线:从一堆原始数据到一个能用的模型

教材把机器学习的步骤拆得很细,压成骨架就是七步:

步骤 做什么 这一节要量的数
1 取数 把要预测的东西变成一串数 150 个样本 × 4 个特征
2 切分 分成训练集和测试集,比例常见 7 比 3 105 与 45
3 标准化 每个特征减去均值除以标准差 标准差 116.5 变 1.0
4 训练 用训练集定下模型的参数 k 近邻不用训练,只存样本
5 评估 在测试集上算准确率、混淆矩阵 0.9333
6 画图 把数据落到平面上看一眼 三团分开还是有互串
7 统计 按类别分组算均值标准差 类 A 36 个、类 B 33 个

这七步里,第 2 步和第 3 步最容易被跳过,也最容易让分数虚高。跳过第 2 步,你在训练数据上评估, 等于拿练习题当考卷;跳过第 3 步,量纲大的特征会独吞整个距离。后面两节分别量这两件事。 还有一条贯穿全程的纪律:测试集只许看一次。反复用测试集调参数, 等于把答案漏进了训练——这时候测试分数就不再是「对未知数据的表现」,而是「对这份考卷的背诵程度」。 第 4 章会给出正规的做法(验证集加交叉验证)。

二、数据的重要性之一:量纲会骗人

先说一个反直觉的事实: 同一批数据、同一个算法、同一个 k,把特征缩一缩,准确率能从 0.3556 跳到 0.9333。 这次的数据是四个特征。前两个把三类分得开,标准差在 0.3 左右; 后两个是从同一个分布里抽的噪声,标准差 116.5——是前两个的 213 倍。 k 近邻算的是欧氏距离,四个特征平起平坐地相加。 距离的平方里,噪声维度贡献了 116.5 的平方量级,真信号维度只贡献 0.35 的平方量级。 结果就是:邻居是按噪声挑的。模型看起来很努力,实际在噪声里翻找。 标准化把每个维度压成均值 0、标准差 1,四个维度这才平权。 这不是「让数据变好看」,是把「谁更重要」这件事从量纲手里抢回来,交还给算法去学。 要注意的是,标准化用的均值和标准差必须从训练集算,再拿同一组数去变换测试集。 用全体数据算,测试集的分布信息就漏进来了,这是新手最常犯的隐蔽错误之一。

三、数据的重要性之二:样本量有天花板

第二个实验更朴素:算法一个字不改,只把训练样本从 15 加到 105。 结果是 0.7111、0.9333、 0.9333——15 个样本时明显不够,到 45 个就摸到这份数据的天花板,再加也不涨。 这三行数字合起来说明一件事: 样本量和算法是两个独立的旋钮,但它们的边际收益不一样。 样本太少时,换多高级的算法都白搭, 因为模型没有足够的信息去定参数; 样本足够之后,再堆样本也没有用,这时候瓶颈变成了特征本身够不够判别、模型容量够不够。 所以遇到效果不好的模型,先问三个问题:训练集有多大?测试分数和训练分数差多少? 数据本身有没有判别信息?先怀疑数据,再怀疑算法——这条经验在真实项目里救过很多人。

四、有监督的例子:分类与混淆矩阵

评估分类模型,只看一个准确率是不够的。教材在这一节引出了混淆矩阵,第 4 章会正式展开。 这里先用三行三列看个样子: 行是真实类别,列是预测类别,对角线上的数字是猜对的。 这次三类在测试集里分别有 14、17、14 个样本,错的那几个都落在相邻类上, 没有跨类乱飞(比如把类 A 判成类 C)。 「错在哪儿」比「错了几个」有用得多。 如果错误集中在相邻两类之间, 说明这两个类的特征本来就接近,要么补特征,要么接受这个上限; 如果错误散在四面八方,那多半是数据有问题——标签错了,或者特征根本没进来。

五、无监督的例子:聚类与纯度

把标签收走,只给坐标,k-means 分三簇,纯度 0.8952。 这个数比分类的 0.9333 低一点,是正常的:聚类没有标签可用,它只能按「离谁近」来分, 而真实的类别边界未必垂直于距离。 这里有个细节值得停一下:聚类用的也是训练集,不是全部数据。无监督虽然不需要标签, 但「测试集不能动」这条线一样要守。否则你调 k、调初始化,全都在偷看测试集。 k-means 还有一个坑:它只保证收敛到局部最优,初始中心选得不好,结果会差很多。 脚本里跑十次随机重启,取簇内平方和最小的那一组——这是工程上的常规做法,不是作弊。

六、可视化与表格:建模之前先看一眼

教材在 1.2 后半段花了很大篇幅讲 Matplotlib 和 pandas,很多读者会跳过,觉得「画图不是正经活」。 恰恰相反,这两个工具是发现问题最快的手段。 画图能看出来的东西,算指标未必看得出来:两团有没有重叠、有没有离群点、某一维是不是几乎没变化、 类别是不是严重不平衡。这些都直接影响后面选什么算法。 表格(分组统计)能看出另一件事:每个特征的判别力。如果某个特征在三类之间的均值几乎一样、 标准差却很大,那它基本是噪声; 如果某两类在一个特征上均值差得远、标准差又小,那这个特征就能把这两类分开。 这类判断花不了几分钟,却常常省掉一整轮调参。 沙箱里没有 pandas,所以 Demo 里用 numpy 手写了一遍分组统计。 教材那一行 df.groupby('target').describe() 背后做的事,就是「按类别取子集, 算均值标准差」——知道了原理,有没有 pandas 都能干。

七、动手 Demo:一条流水线走完,每一步都量一下

这一段把七步走完,每一步都留一个数。 数据是自造的:150 个样本、4 个特征、3 类, 结构照着鸢尾花来——沙箱里没有 sklearn 自带的数据集,所以自己造一份。 关键设计:前两个特征有判别力,后两个是纯噪声,但被放大了 100 倍。 这不是为了凑数,是为了让「量纲」这件事变得可见。

import numpy as np

mu, sd = Xtr.mean(axis=0), Xtr.std(axis=0)
Xtr_s = (Xtr - mu) / sd
Xte_s = (Xte - mu) / sd

print("第 3 个特征的标准差:原始 %.1f  标准化 %.1f" % (sd[2], Xtr_s[:, 2].std()))
print("原始数据准确率 %.4f" % accuracy(Xtr, Xte))
print("标准化后准确率 %.4f" % accuracy(Xtr_s, Xte_s))

# 混淆矩阵:行是真实类别,列是预测类别
cm = np.zeros((3, 3), int)
for t, p in zip(yte, pred):
    cm[t, p] += 1
print(cm)

实跑输出:

--------------------------------------------------------------------
机器学习的步骤:一条流水线走完,每一步都量一下
--------------------------------------------------------------------

  造一批替身数据:150 个样本、4 个特征、3 个类别。前两个特征把三类分得开,
  后两个是从同一个分布里抽的噪声,但**被放大了 100 倍**
  ——这一步是为了后面演示「量纲会骗人」。沙箱里没有 sklearn 自带的鸢尾花,
  所以自己造一份结构相同的。

  【一】分训练集和测试集(7 比 3)
  训练 105 个、测试 45 个。**先切再算任何统计量**,
  否则测试集的信息会漏进训练里,分数就虚高。

  【二】数据的重要性之一:量纲
  同样的 k 近邻、同样的 k = 5,只改了一件事——有没有标准化:
  输入          第 3 个特征的标准差       测试准确率
  --------------------------------------------------
  原始数据      116.5                     0.3556
  标准化之后    1.0                       0.9333
  原始数据里,第 3、4 个特征的标准差是 116.5,是前两个的 213 倍。距离一算,
  **噪声维度把真信号盖住了**,
  模型等于在噪声里找邻居。标准化之后每个维度都是 1,真信号才浮上来。

  【三】数据的重要性之二:样本量
  训练样本数    测试准确率
  ------------------------
  15            0.7111
  45            0.9333
  105           0.9333
  算法一个字没改,只把训练样本从 15 加到 105:15 个时只有 0.7111,到 45 个就满了,
  再加到 105 也不再涨。
  **数据不够时换算法救不了;数据够到这个任务的难度,最简单的算法也够用。**

  【四】有监督的例子:分类
  混淆矩阵(行 = 真实类别,列 = 预测类别):
     14    0    0
      0   15    2
      1    0   13
  对角线是猜对的。三类在测试集里分别有 14、17、14 个,错的那几个都落在相邻的类上,
  没有跨类乱飞——说明特征选得还行。

  【五】无监督的例子:聚类
  把训练集的标签全部收走,只给 105 个点的坐标,k-means 分三簇,纯度 0.8952。
  注意这次用的是**训练集**,不是全部数据——无监督也要守住「测试集不能动」这条线。

  【六】可视化:三类在前两个特征上的分布(A、B、C 是三类,· 是空位)
    ···························
    C·············C············
    ···········CC·C··CC········
    ········C·CC··C···C·C······
    ··········CC·C·C···········
    ···A····CC·C··CC·C·B··B····
    ·······A··C·C··············
    A···AA·····C·BC···B··B·····
    ···AAA·····BB······B·BB····
    ···A·AAA···A·A·B··BBB······
    A····A···B·········B··BBBB·
    ··A··A·AA····B·B··B···B·B··
    ·A···AA···A·B·····B···B·B··
    ·····A················B····
    A····AB····················
  这张字符画就是散点图的替身。教材里用 Matplotlib 画彩色散点,这里把点落到字符网格上,
  看的是同一件事:**三个簇分得开不开。**
   三团大致分得开,交界处还有零星互串——真实数据就是这样,前面那 0.9333 而不是 1.0000,
  就来自这些互串。

  【七】pandas 的活,用 numpy 干一遍:按类别分组统计前两个特征
  类别    个数  特征1 均值  特征1 标准差   特征2 均值  特征2 标准差
  ------------------------------------------------------------------
  类 A    36    0.096       0.258          0.007       0.387
  类 B    33    1.182       0.382          0.066       0.377
  类 C    36    0.710       0.313          1.068       0.272
  教材用 `df.groupby('target').describe()` 一行搞定。没有 pandas 就自己写循环加 mean、std——
  **分组统计这件事本身不难,难的是记住每一步在算什么。**

  一条流水线到这里走完了:
  造数(真实项目里是取数)→ 切分 → 标准化 → 训练 → 在测试集上评估 → 画图看一眼 → 分组统计找
  感觉。**后面十七个算法,换的只是「训练」那一步,其余六步一模一样。**

第一步的数字最扎眼:同样的 k 近邻、同样的 k = 5,原始数据 0.3556,标准化之后 0.9333。 差了将近三倍。注意这不是「标准化让模型变强了」,模型一点没变,是距离终于算对了。 样本量那三行是 0.7111、0.9333、0.9333。15 个样本时明显欠;到 45 就满了;105 和 45 一样。 这说明这份数据的信息量在 45 个样本左右就被榨干了,再加样本只是重复同样的信息。 「数据越多越好」是有条件的,条件是这个任务的复杂度还没被满足。 混淆矩阵里,错的那几个都在相邻类上。类 B 有 2 个被判成类 C,类 C 有 1 个被判成类 A。 这种「结构性的错」比随机错更值得看——它告诉你哪两类的边界本来就模糊。 聚类那一行是 0.8952。 比分类低,而且注意它用的是训练集。如果你把 k 改成 4,纯度一般会掉; 改成 2,会掉得更厉害。k 怎么选是 3.5 节的事,这里先记住:聚类没有唯一正确的答案。 字符画那一段是散点图的替身。 三团大致分得开,交界处有零星互串,前面那个 0.9333 而不是 1.0000, 来源就在这些互串上。教材用 Matplotlib 画彩色散点,这里把点落到字符网格——看的是同一件事。 最后一栏是 pandas 的活。 类 A 36 个、类 B 33 个、类 C 36 个,三个特征的均值标准差摆出来, 哪个特征有判别力一眼就看得出:特征 1 上类 B 是 1.182、类 A 是 0.096,差得远; 特征 2 上类 C 是 1.068、类 A 是 0.007,也差得远。这种「先看一眼」的习惯,比多调十轮参有用。

30 秒实验

  1. 把 D002 里噪声维度的放大倍数从 100 改成 1,原始数据的准确率会升到多少?先猜,再改数验证。
  2. 把切分比例从 7 比 3 改成 9 比 1,测试集只剩 15 个样本。准确率这个数会变得更可信还是更不可信?
  3. 标准化改成「除以最大值」而不是「减均值除标准差」,结果会变吗?想想两者的区别在哪。

常见误区

  1. 先看全量数据、再切训练测试。只要你在切分之前算过任何统计量(均值、标准差、最大最小), 测试集的信息就已经漏进训练了。顺序必须是:先切,再只用训练集算一切。
  2. 把标准化当成可选项。对 k 近邻、支持向量机、k-means、PCA 这些靠距离或方差干活的算法, 不标准化等于让量纲最大的那个特征一个人说了算。例外是决策树和随机森林,它们只看阈值, 对尺度不敏感。
  3. 用准确率给不平衡数据打分。99 个正常样本加 1 个异常样本,全判「正常」就有 0.99 的准确率。 这种分数毫无意义,要看的是召回率或 F1,第 4 章会讲。
自测三问(点开对答案) 1. 为什么标准化用的均值和标准差必须从训练集算,不能用全体数据? —— 用全体数据算,测试集的分布信息就参与了变换,等于提前偷看。 正确做法是:训练集算出的 mu 和 sd 存下来,测试集用同一组数变换。 2. 训练样本从 45 加到 105,准确率停在 0.9333 不动,这说明什么? —— 说明这份数据的瓶颈不在样本量,而在特征本身。 再加样本只是重复同样的信息,要提升得换特征或换模型。 3. 混淆矩阵里错的那几个都落在相邻类上,这个现象该怎么读? —— 说明这两类的特征本来就接近,边界模糊。如果是随机错,反而更该怀疑标签质量或特征缺失。

流水线跑通了,接下来进第 2 章:九个有监督算法,从最简单的线性回归开始, 一个一个看它们怎么在这条流水线的第四步上做文章。

相关笔记