三个业务上毫不相干的任务:图像分类 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 秒实验
- 把 D002 里条纹的平移幅度从正负 4 像素改成正负 7 像素,图像分类的准确率会怎么动? 先猜方向,再跑。(提示:16 乘 16 一共就 16 行。)
- 把异常检测里的
k=5改成k=1和k=20,AUC 分别变多少? 想清楚 k 变大时,异常样本的分数为什么会被「拉低」。 - 把评分矩阵的隐因子数从 2 改成 5,但观测数仍然只有 25 个,RMSE 会升还是降? 这个结果说明了低秩假设的哪个前提?
常见误区
- 「准确率 100% 说明模型可以上线」。 准确率是数据集的性质,不是模型的性质。同一份代码换一批更难的图,可能掉到 60%。 报告里必须写清测试集是怎么来的。
- 「AUC 高就等于模型好用」。AUC 是排序指标,它掩盖了「阈值定在哪里」这个真正的工程问题。 在误报代价极高的场景(比如自动封号),AUC 0.95 的模型也可能不可用。
- 「RMSE 越小越好,不用管别的」。 RMSE 会被评分尺度的量纲带跑。 不给出「全填平均值」的基线,RMSE 这个数没有意义。
- 「推荐系统就是猜你喜欢的」。它猜的是「在已有观测上最自洽的」。 没被推荐过的东西,它永远没机会修正这个判断——这就是冷启动和茧房的根。
自测三问(点开对答案)
1. 为什么异常检测这里只能用 AUC,不能用准确率? —— **因为准确率要先有阈值,而「多可疑才算异常」这件事事先不知道。** AUC 把所有可能的阈值都扫了一遍,只反映排序能力。**代价是它不告诉你上线时该定在哪。** 2. 「评分补全的 RMSE 0.16」这个数,你还要问什么才能判断它好不好? —— **问基线。**最笨的做法是「全填平均值」,它的 RMSE 就等于评分的标准差 1.60。 **0.16 是它的十分之一,才算真的学到了东西。**另外要问观测比例: **每用户只观测 5 个(总共 6 个)已经非常宽松了。** 3. 如果把「三类条纹」换成「三类的真实照片」,同样的最近邻代码会怎样? —— **大幅退化,可能掉到五六成。**真实照片的变化在光照、角度、遮挡上, **像素距离对这些几乎不敏感**——**两张同一只猫的照片,像素距离可能比猫和狗之间还大。** **这正是深度模型替代手工特征的原因。**任务认全了,接下来要问: 同一个任务,为什么会有那么多不同的方法,它们的差别到底在哪一环?下一节把「方法」拆成模型、 准则、算法三件,并用六种方法在同一份数据上量出来。
- ☐ (本节完,下一节 1.3 1.3)