🤖 第1章 什么是机器学习
📄 一句话概括

三个业务上毫不相干的任务:图像分类 100%、异常检测 AUC 0.84、评分补全 RMSE 0.16。 那个 100% 要当心读: 三类条纹的走向几乎正交,256 维空间里根本混不起来——不是算法厉害,是任务简单。 评分补全的 RMSE 0.16,而真实评分的标准差是 1.60。 25 个观测还原 30 个格子,靠的是「低秩假设」这个前提。

📝 本节和前面几节的关系

上一节把五种问法摆了出来,这一节给它们配上具体的业务名词。 教材这一节列了一长串任务,用意不是让你记住,是让你遇到新问题时能对上号。 对上号之后,第 3 章往后的每一个算法就都有了归属: 这个算法解决哪一类问法,用在哪一类任务上。

一、例子是用来对号入座的

教材在这一节列了一串任务:手写数字识别、人脸识别、垃圾邮件过滤、语音识别、图像去噪、推荐、 异常检测。列这么长的用意不是让你记住清单,是让你拿到新问题时能对上号。 因为一旦对上号,后面 19 章的算法就能直接搬。反过来,对不上号的时候,你会在几个算法之间反复试, 说不出为什么。 这一节我挑三个做,一个比一个「不那么明显是有监督」: 图像分类(标准的有监督)、异常检测(只有正常样本)、评分补全(大部分格子没有值)。

二、图像分类:这一张是哪一类

我造了 3 类条纹图,横、竖、斜各一种,每类 100 张,每张随机平移正负 4 个像素、再加噪声。 输入是 16 乘 16 的灰度图,也就是 256 维向量。 分类器是最朴素的最近邻:算测试图和每张训练图的欧氏距离,取最近的 3 张投票。 测试准确率 100%。 满分要怎么读?三类条纹的走向几乎正交, 在 256 维空间里它们几乎不可能混到一起——不是算法厉害,是任务简单。 换成真实手写数字就难得多:同一个「7」,有人加横杠、有人斜着写,类内的变化可以比类间还大。 这就是为什么 MNIST 上刷到 99% 不能直接搬到你自己的业务图上。

三、异常检测:这张不像正常

这个任务只给正常样本:200 张正常条纹,另加 20 张三倍噪声的当异常。训练时一张异常都没见过。 打分方式很土: 算这张图到最近 5 张正常图(k 等于 5)的平均平方距离,距离越大越可疑。然后算 AUC,得 0.84。 AUC 的意思是:随便抽一张正常、一张异常,异常那张的分数更高的概率。0.84 就是 84% 的配对能排对。 它和准确率不是一回事——准确率要先定阈值,AUC 不用。 这是它能用在异常检测上的原因:你往往不知道阈值该定在哪。 异常检测的难点从来不是算法,是「正常」这个词的定义。这里的异常是「同结构、 三倍噪声」——差别在有多脏,不在结构不对。 真实场景里的异常往往长得很正常,只是某一处不对:焊缝上一条两毫米的裂纹、发票上少了一个税号。

四、评分补全:他没看过的会给几分

5 个用户乘 6 个物品的评分矩阵,每个用户只评过 5 个, 剩下的 5 个格子空着。真实的评分由 2 个隐因子生成。 用交替最小二乘去填:固定用户的向量解物品,再固定物品解用户,来回 80 轮。 补全的 RMSE 是 0.16,而真实评分的标准差是 1.60。 RMSE 只有标准差的十分之一——25 个观测把 30 个格子基本还原了。 靠的是「低秩假设」:用户的喜好只由少数几个隐含因素决定,所以少量观测就能定出这些因素。 这个假设错了,补全就会给出很自信的错答案。 推荐系统的「信息茧房」就是这么来的:模型以为你不喜欢,其实只是没给你看过。 这类任务的正式名字叫矩阵分解,推荐、基因表达补全、传感器缺失数据,都是同一件事。

五、把三个任务翻成同一张表

三个业务名词拆开,其实填的是同一张表。

任务 输入 x 输出 y 损失 属于
图像分类 256 个像素 3 个类别之一 0/1 损失 有监督分类
异常检测 256 个像素 「像正常」的分数 距离,无标签 无监督
评分补全 用户号与物品号 1 到 5 的分 平方损失 半个监督
这张表的价值:三个看起来毫无关系的业务,填进同一张表之后,差别只剩「输出是什么」。
而「输出是什么」决定了损失函数,损失函数决定了算法——这就是第 3 章往后要展开的东西。
补一句教材没强调的:真正难的往往是「输入 x 选什么」。同一张图,用像素当 x 是 256 维;
用边缘方向直方图当 x 可能只有 8 维,而且更抗平移。
这个选择在书里叫「特征设计」,在项目里它经常决定成败。
还有一个容易忽略的问题:输出的维度未必是 1。评分补全要填整张表,
本质是一次输出 30 个数;第 19 章的多任务学习就是专门处理这类「一次问多个答案」的。

六、动手 Demo:三个任务:图像分类 100%、异常检测 AUC 0.84、评分补全 RMSE 0.16

三段互不相干的代码,共用一个随机流。 第一段造 3 类条纹图(横、竖、斜),每张随机平移正负 4 像素加噪声,用最近邻(k 等于 3)分类; 第二段只用正常条纹做参照,算每张图到最近 5 张正常图的平均平方距离, 拿它给正常样本和「三倍噪声」异常样本排序算 AUC; 第三段造 5 用户乘 6 物品、2 个隐因子的评分矩阵,每用户只留 5 个观测,用交替最小二乘补全。

def base(kind):                      # 三类条纹的正弦底图
    if kind == 0:
        return np.sin((yy + 2.0) * 1.6)
    if kind == 1:
        return np.sin((xx + 2.0) * 1.6)
    return np.sin((yy + xx + 2.0) * 1.1)

def sample(kind, noise):             # 平移加噪声
    im = base(kind)
    im = np.roll(im, int(rg.integers(-4, 5)), axis=0)
    im = np.roll(im, int(rg.integers(-4, 5)), axis=1)
    return im + rg.normal(0.0, noise, im.shape)

dd = ((Xte[:, None, :] - Xtr[None, :, :]) ** 2).sum(2)   # 全对距离
idx = np.argsort(dd, 1)[:, :3]                            # 最近 3 张
pred = [np.bincount(ytr[idx[i]]).argmax() for i in range(len(Xte))]

def score(Q, Xa, k=5):               # 到最近 k 个正常样本的平均距离
    d2 = ((Q[:, None, :] - Xa[None, :, :]) ** 2).sum(2)
    return np.sort(d2, 1)[:, :k].mean(1)

for _ in range(80):                  # 交替最小二乘
    for u in range(U5):
        Am = Qi[mask[u]]
        Pu[u] = np.linalg.solve(Am.T @ Am + 0.01 * np.eye(K2),
                                Am.T @ R[u, mask[u]])
    for i in range(I6):
        Am = Pu[mask[:, i]]
        Qi[i] = np.linalg.solve(Am.T @ Am + 0.01 * np.eye(K2),
                                Am.T @ R[mask[:, i], i])

实跑输出:

--------------------------------------------------------------------
图像分类 100%、异常检测 AUC 0.84、评分补全 RMSE 0.16
--------------------------------------------------------------------
  图像                     16 乘 16 灰度图,3 类条纹,每类 100 张
  分类器                   最近邻(欧氏距离,k 等于 3)
  异常集                   200 张正常条纹,20 张三倍噪声
  评分矩阵                 5 用户乘 6 物品,2 个隐因子,每人观测 5 个

  任务       问法                             指标   数值
  ------------------------------------------------------
  图像分类   这是三类的哪一类           测试准确率   100%
  异常检测   这张图是不是「不像正常」          AUC   0.84
  评分补全   这个人会给没看过的打几分         RMSE   0.16

  **满分说明三类条纹完全分得开——横、竖、斜三种走向几乎没有交集。**
  **这不是算法厉害,是任务简单。**
  **换成真实手写数字更难:同一个「7」,有人加横杠、有人斜着写,类内的变化可以比类间还大。**
  **异常检测的 AUC 是 0.84。**
  这里的异常是**同一类条纹图把噪声加到三倍**——**它和正常样本的差别只在「有多脏」,不在结构。**
  **所以它比「纯噪声」版本难,但仍比真实场景简单:真实异常往往长得很正常,只是某一处不对。**
  评分补全的 RMSE 是 0.16,而真实评分的标准差是 1.60。
  **它只用了 25 个观测去还原 30 个格子——靠的是「低秩假设」:**
  **假设用户的喜好只由少数几个隐含因素决定,那么少量观测就能定出这些因素。**
  **这个假设错了,补全就会给出很自信的错答案——推荐系统的「信息茧房」就是这么来的。**
  要说明:三类条纹是我用正弦函数**造的**,噪声和平移我加的;
  **异常是「同结构、三倍噪声」,比真实的划痕、偏移还差一层;**
  评分矩阵由两个隐因子生成,是「教科书式」的低秩。
  **但三件事的问法是真的:分类、异常、补全,分别对应有监督、无监督、「半个监督」三类问题。**

100% 这个数几乎肯定有诈,不管它出现在谁的报告里。 这里的诈在于「三类条纹在 256 维空间里彼此正交」—— 任务本身没有难度,换成正脸补光、残缺字符、低分辨率车牌,立刻掉下来。 AUC 0.84 和准确率不能比。0.84 说的是「排序能力」,它不管你把阈值定在 0.5 还是 0.9。 而工程上你要交付的恰恰是一个阈值——所以 AUC 好看的模型,上线时可能照样一堆误报。 RMSE 0.16 要连着 1.60 一起读。单看 0.16 你不知道好不好; 和「什么都不做、全填平均值」的基线比才看得出价值——全填平均值的 RMSE 就等于标准差 1.60, 0.16 是它的十分之一。 三个任务里只有第一个是「标准的有监督」。第二个没有负样本,第三个大部分格子没值。 这两类问题占了真实项目的多数,而教材的篇幅主要在讲第一个。 这个错位值得警惕:书本上的标准问题,往往是最容易的那部分。 要说明:三类条纹是我用正弦函数造的,噪声和平移也是我加的; 异常是「同结构、三倍噪声」,比真实的划痕、偏移还差一层;评分矩阵由两个隐因子生成, 是「教科书式」的低秩。 但三件事的问法是真的:分类、异常、补全,正好对应有监督、无监督、半个监督。

30 秒实验

  1. 把 D002 里条纹的平移幅度从正负 4 像素改成正负 7 像素,图像分类的准确率会怎么动? 先猜方向,再跑。(提示:16 乘 16 一共就 16 行。)
  2. 把异常检测里的 k=5 改成 k=1 和 k=20,AUC 分别变多少? 想清楚 k 变大时,异常样本的分数为什么会被「拉低」。
  3. 把评分矩阵的隐因子数从 2 改成 5,但观测数仍然只有 25 个,RMSE 会升还是降? 这个结果说明了低秩假设的哪个前提?

常见误区

  1. 「准确率 100% 说明模型可以上线」。 准确率是数据集的性质,不是模型的性质。同一份代码换一批更难的图,可能掉到 60%。 报告里必须写清测试集是怎么来的。
  2. 「AUC 高就等于模型好用」。AUC 是排序指标,它掩盖了「阈值定在哪里」这个真正的工程问题。 在误报代价极高的场景(比如自动封号),AUC 0.95 的模型也可能不可用。
  3. 「RMSE 越小越好,不用管别的」。 RMSE 会被评分尺度的量纲带跑。 不给出「全填平均值」的基线,RMSE 这个数没有意义。
  4. 「推荐系统就是猜你喜欢的」。它猜的是「在已有观测上最自洽的」。 没被推荐过的东西,它永远没机会修正这个判断——这就是冷启动和茧房的根。
自测三问(点开对答案) 1. 为什么异常检测这里只能用 AUC,不能用准确率? —— **因为准确率要先有阈值,而「多可疑才算异常」这件事事先不知道。** AUC 把所有可能的阈值都扫了一遍,只反映排序能力。**代价是它不告诉你上线时该定在哪。** 2. 「评分补全的 RMSE 0.16」这个数,你还要问什么才能判断它好不好? —— **问基线。**最笨的做法是「全填平均值」,它的 RMSE 就等于评分的标准差 1.60。 **0.16 是它的十分之一,才算真的学到了东西。**另外要问观测比例: **每用户只观测 5 个(总共 6 个)已经非常宽松了。** 3. 如果把「三类条纹」换成「三类的真实照片」,同样的最近邻代码会怎样? —— **大幅退化,可能掉到五六成。**真实照片的变化在光照、角度、遮挡上, **像素距离对这些几乎不敏感**——**两张同一只猫的照片,像素距离可能比猫和狗之间还大。** **这正是深度模型替代手工特征的原因。**

任务认全了,接下来要问: 同一个任务,为什么会有那么多不同的方法,它们的差别到底在哪一环?下一节把「方法」拆成模型、 准则、算法三件,并用六种方法在同一份数据上量出来。

相关笔记