🧠 第四章 数据标注:引导模型学习提升性能
📄 一句话概括

250 条训练数据,把 25 条标错(一成):训练准确率从 0.8960 掉到 0.8000, 测试从 0.8833 掉到 0.8367。错到四成,测试只剩 0.7267。 而且训练那一列掉得更多——训练准确率低,也可能是标注错了。

📝 本节和前面几节的关系

第三章把数据和知识库备好了。 这一节开始讲「怎么把原始材料变成训练数据」——也就是标注,它决定了模型的上限。

一、标注到底在做什么

模型的输入是一堆数字,输出也是一堆数字。 而你要它做的事是「认出图里有没有行人」「判断这句话该归哪一类」—— 这两套东西之间缺一座桥,标注就是那座桥。 所以标注的本质只有一句话: 把「人看得懂的东西」翻译成「模型算得动的数」。一张图加一句「这里有行人」, 变成「这个框的坐标是 60,170,100,230,类别编号是 1」。 翻译过程里有两处会失真:翻译得对不对(标注准不准), 翻译的规范统不统一(两个人标同一张图会不会一样)。 这两件事决定了数据质量,也决定了模型能学到什么。 很多人以为标注是「体力活、谁都能干」。 实际上它是定义问题: 「什么样的算行人」「半遮挡的算不算」「远处三像素的算不算」——这些没有标准答案,必须写进标注手册。

二、四种常见的标注类型

分类:给整张图一个标签。最便宜,也最粗——只回答「有没有」,不回答「在哪」。 检测:给每个目标画一个矩形框。回答「有没有」和「在哪」,成本按框的数量算。 分割:给每个像素归类。回答「形状是什么、面积多大」,成本最高,因为要描边。 关键点:标出特定部位(眼角、关节、车牌四角)。用于姿态估计、人脸对齐这类任务。 这四种不是「精度的四个档」,是「问题的四种问法」。业务只要判断有没有, 用分割就是纯浪费——下一节会把这笔账算出来。

三、标注噪声为什么比想象中致命

很自然会想:「标错一点,不就是多了一点噪声吗?模型能扛。」Demo 的结果否掉了这个想法。 250 条训练数据,错标 25 条(一成):测试准确率从 0.8833 掉到 0.8367, 掉 4.66 个百分点。错标 100 条(四成):掉到 0.7267,掉 15.66 个。 关键在于错标不是噪声,是错误的目标。 噪声是「输入抖了一下」,模型还能学到大致方向; 错标是「你告诉它往东,其实是西」,它学到的就是错的。 更麻烦的是它不会自己消失。 加数据只能稀释它,不能去掉它——100 条里有 10 条错的,加到 1000 条还是那 10 条,它们的影响被摊薄了, 但每一条还在往错的方向拉。 所以有一条底线:错标率超过一成,先别训,回去修标注。「先训一版看看」这个习惯, 在脏数据上纯属浪费算力。

四、训练准确率低,可能是标注的锅

Demo 里有个反直觉的方向: 训练准确率从 0.8960 掉到 0.5240,掉 37.2 个百分点;测试只掉 15.7 个百分点。 训练那一列掉得更多。 原因是错标让「拟合训练集」这件事本身变得不可能——同一批特征被要求给出两个矛盾的答案, 模型只能取平均,结果两边的样本都判不准。 这个观察很实用: 看到训练准确率上不去,常见的解释是「模型太小」「学习率不对」,但还有一个可能——标注是错的。 怎么区分?拿 20 条训练数据自己标一遍,跟数据集里的标签对一下。对不上三成以上,问题就不在模型。

五、动手 Demo:错标一成测试掉 4.66 个点,错标四成掉 15.66 个点

造 1500 条三类数据,固定 250 条作训练集、300 条作测试集(测试集始终干净)。 把训练集里 0%、5%、10%、20%、40% 的标签改成别的类,模拟标注错误, 看训练准确率和测试准确率各怎么变。

Xte, yte = F[-300:], y[-300:]      # 测试集始终干净
Xtr, clean = F[:250], y[:250]

for rate in (0.0, 0.05, 0.10, 0.20, 0.40):
    ytr = clean.copy()
    k = int(len(ytr) * rate)
    idx = rg.choice(len(ytr), k, replace=False)
    ytr[idx] = (ytr[idx] + rg.integers(1, 3, k)) % 3   # 错标成别的类
    W, b = train(Xtr, ytr)
    print(rate, acc(W, b, Xtr, ytr), acc(W, b, Xte, yte))

实跑输出:

--------------------------------------------------------------------
标注噪声:标错一成,模型准确率掉多少
--------------------------------------------------------------------
  标注错误率   错标条数   训练准确率   测试准确率
  ----------------------------------------------
          0%       0 条       0.8960       0.8833
          5%      12 条       0.8240       0.8633
         10%      25 条       0.8000       0.8367
         20%      50 条       0.6800       0.7700
         40%     100 条       0.5240       0.7267

  两列一起往下掉,但**训练那一列掉得更多**:
  训练从 0.8960 掉到 0.5240(37.2 个百分点),测试从 0.8833 掉到 0.7267(15.7 个百分点)。
  这个方向值得记一下:**错标让「拟合训练集」这件事本身变得不可能。**
  同一批特征被要求给出两个矛盾的答案,模型只能取平均。
  所以**训练准确率低不一定是欠拟合,也可能是标注是错的。**

  这里有个反直觉的地方要说清:**错标不是「多了一点噪声」,而是「给了错误的目标」。**
  随机噪声顶多让模型学不准;错标是让模型朝错的方向努力,所以破坏力比噪声大得多。
  而且**它不会自己消失——加数据只能把它稀释,不能把它去掉。**

  所以标注质量有一条底线:
  错标率 < 5%              可以接受,模型能扛住
  错标率 5% 到 10%         要抽查,找出系统性错误
  错标率 > 10%             先别训,回去修标注
  **「先训一版看看」这个习惯,在有噪声的数据上纯属浪费时间。**

表里五行,两列一起往下掉:测试从 0.8833(0%)到 0.8367(10%)到 0.7267(40%)。 错标一成掉 4.66 个百分点,错标四成掉 15.66 个。 注意这不是线性掉。 5% 到 10% 之间掉得最快,因为这一段正好是「模型还有救」和「模型开始学错」的分界。 第二列(训练准确率)掉得更狠:从 0.8960 一路到 0.5240,掉 37.2 个百分点。 这比测试掉得多,说明错标首先破坏的是「拟合训练集」这件事本身。 原因不难理解:同一片特征区域,一条标成 1 类、一条标成 2 类,模型没法同时满足, 只能给一个中间值——结果两条都判不准。 要说明的是:这里是「整条标错」这种最粗暴的错误,比例也设得偏高。 真实标注的错更多是「边界框偏了一点」「半遮挡的漏了」这类细微的,破坏力小些, 但方向是一样的:错标比噪声更伤。

30 秒实验

  1. 把错标方式从「改成别的类」换成「随机改成一类」,看曲线有没有变化。 系统性错标(比如把猫都标成狗)比随机错标更致命。
  2. 把训练集从 250 条加到 1000 条,其他不变,重跑一遍。 看同样的错误率下,掉得是不是少一些——这就是「稀释」的作用,但它去不掉错误。
  3. 把错标率固定在 10%,但只错在某一类上(比如只错 2 类),看测试准确率的变化。 局部错标会让模型对那一类完全失效。

常见误区

  1. 「标错一点无所谓,模型能扛」。错标是错误的目标,不是噪声。 一成错标就能让测试掉 4 个多点,四成直接掉 15 个点。
  2. 「训练准确率低就是模型太小」。也可能是标注错了——自相矛盾的标签会让训练准确率上不去。 先抽查 20 条标签再说。
  3. 「多加数据就能冲掉错误」。加数据只是稀释,那批错标还在里面。 修一条错标,比补五条新数据划算。
自测三问(点开对答案) 1. 标注的本质是什么? —— 把人能看懂的东西翻译成模型能算的数。 比如把「这里有行人」翻译成「框 60,170,100,230,类别 1」。翻译的准确性就是标注质量。 2. 为什么错标比随机噪声更致命? —— 噪声是输入抖动,模型还能学到大致方向;错标是给了错误的目标,模型朝错的方向努力。 而且错标不会自己消失,加数据只能稀释。 3. 训练准确率上不去,除了模型太小学不会,还有什么可能? —— 标注是错的。同一批特征被标成两个矛盾的类,模型无法同时满足,训练准确率就上不去。 抽查 20 条标签就能判断。

知道了标注为什么重要, 接下来看它在整条链路上到底起多大作用——下一节把「多标」和「修错」放在一起算一遍。

相关笔记