跳到主要内容

从任意实数到「是 / 否」— 两条直线做分类

这一章讲三件事: 怎么把一个任意实数变成「属于这一类的可能性」; 判断类别为什么不能用「差的平方」当尺子;以及一个从全零起步的模型,损失为什么恰好是 0.6931。

它在全书链条里的位置: 第 04 章那条直线只会估一个数。 这一章给它接上最后一道工序,它就能回答「是」或「否」了。

需要的基础: 第 04 章的五个零件、训练误差与泛化。

1. 先看现象:直线吐出的是任意实数,而问题问的是「是不是」

第 04 章那条直线,给一个街区吐出一个数:1.95。这个数就是答案。

可换成「这封邮件是不是垃圾邮件」,答案只有两种。 你还是可以让直线吐一个数,但吐出来的可能是 −7.3,也可能是 214。 这两个数怎么变成「是」或「否」?

书里的做法是在直线后面加一道挤压: 把线性输出从整条实数轴挤到 0 和 1 之间, 挤完那个数就可以当成「属于第 1 类的可能性」来读1

「可能性」这件事有个正式说法:概率——一个 0 到 1 之间的数, 越接近 1 表示越可能,越接近 0 表示越不可能。

全章主走查:1 000 条弯月形数据的两类判定
═══════════════════════════════════════════════════════════════
数据 两条带噪声的弯月各一类,每条样本 2 个描述,共 1 000 条
按 640 / 160 / 200 切成训练、验证、测试三份
─────────────────────────────────────────────────────────────
§4 ① 参数全设成 0 → 线性输出恒为 0
§4 ② 过那道挤压 → 预测概率全是 0.5000
§4 ③ 算损失 → 0.6931(这个数不是巧合,见 §4)
§4 ④ 训 500 轮,每步挪 0.1
§4 ⑤ 测试集上 → 成绩 0.8450,损失 0.3245
═══════════════════════════════════════════════════════════════

2. 那条两端压平、中间近似直线的 S 形曲线

这一节只讲一个函数,但这个函数会在后面十几章里反复出现。

它长这样:

1.0 ┤ ╭────────────
│ ╭──╯
0.5 ┤ ╭────╯ ← 0 附近近似一条直线
│ ╭───╯
0.0 ┤────────╯
└────┬────┬────┬────┬────┬────
−10 −5 0 5 10

图说:输入趋向负无穷时输出趋近 0,趋向正无穷时输出趋近 1;
两端都压得很平,中间那一段近似线性。

书里对它的描述是:在 0 附近近似线性,两端则快速饱和; 输出恒落在 0 到 1 之间,正好可以当概率读2

它有三个名字,而且你三个都会撞见,所以这里一次挂全: 框架里的函数名是 torch.sigmoid;书里和数学教材里写作 Logistic 函数; 论文和大多数代码注释里直接叫 Sigmoid这三个说的是同一条曲线。

顺带说清一件容易混的事: 严格地说,「Sigmoid 型」是一类 S 形函数的统称, 还包括后面第 06 章要讲的 Tanh 等; 但在框架和多数论文里,「Sigmoid」默认特指这里这一条3

还有一条工程提醒,书里用笔记框标了出来: 照公式直接实现会出事——当输入取很大的负数时,那个指数运算会数值溢出; 工程实战中应直接调用框架的版本,它内部按输入的正负做了分段稳定实现,永远不会上溢4

「溢出」这个词后面还会再出现一次,而且是同一个病: 指数函数长得太快,exp(1000) 这种数超出了浮点数能表示的范围,结果变成「无穷大」。 第 5 节讲多分类时会看到它的另一种形态。

3. 尺子换一把:只盯着「给正确答案打了多少分」

结论先行:判断类别不用「差的平方」,用一把专门量两个分布远近的尺子。

先拆一个字:熵指的是「一件事有多不确定」——一件铁定会发生的事熵为 0, 一件五五开的事熵最高。

书里的说法是:记真实标签为一个分布、模型预测为另一个分布,目标是让后者尽量贴近前者; 把这两个分布放在一起量出来的那个数就叫交叉熵——两个分布越接近,交叉熵越小5

只有两类时(这种任务就叫二分类)可以写得很直白。 模型只输出一个数 ŷ(属于第 1 类的概率), 那么属于第 0 类的概率就是 1 − ŷ。于是5:

单条样本的损失
────────────────────────────────────────────────
真实答案是 1 → 损失 = −log(ŷ) ŷ 越接近 1,损失越小
真实答案是 0 → 损失 = −log(1 − ŷ) ŷ 越接近 0,损失越小
────────────────────────────────────────────────
一整批的损失 = 每条算完再取平均

注意这把尺子的形状:它只看模型给正确答案打了多少分,不看别的。 给对了 0.99,损失是 −log(0.99) ≈ 0.01; 给对了 0.5,损失是 −log(0.5) ≈ 0.693; 给成了 0.01,损失是 −log(0.01) ≈ 4.6。答错得越离谱,罚得越狠。

这就解释了主走查里那个 0.6931 是怎么来的——下一节揭晓。

4. 主走查:从全零起步走到 0.8450

数据先交代: 1 000 条样本由两条带噪声的弯月形分布生成,每条弯月对应一类, 每条样本含 2 个描述;按 640 / 160 / 200 切成训练、验证、测试6噪声的标准差取 0.2,所以两条弯月的交界处会有一些混在一起的点。

① 参数全设成 0
书里说明:单层模型不存在对称性问题,所以可以放心全设成 0[^7]
→ 线性输出 = 0 × x₁ + 0 × x₂ + 0 = 0,不管输入是什么

② 过那道挤压
σ(0) = 1 / (1 + e⁰) = 1 / 2 = 0.5
→ 书里实际打印:三条随机样本的预测概率全是 0.5000[^7]

③ 算损失
每条样本的损失都是 −log(0.5) = log 2 = 0.6931…
→ 书里实际打印:tensor(0.6931)[^8]
⚠ 这个数不是巧合:一个「完全不知道」的模型必然给出这个损失

④ 训练
每步挪 0.1,训 500 轮,每 50 轮打印一次[^9]
期间盯着验证集:成绩一变好就把当前这份存下来(这叫早停的思路)

⑤ 测试
→ 书里实际打印:成绩 0.8450,损失 0.3245[^10]

第 ③ 步那个 0.6931 值得单独记住,因为它是一把随身的尺子: 二分类任务里,一个完全随机猜的模型,损失就是 0.6931。 你训练时看到损失卡在 0.69 附近不动,基本可以断定模型什么都没学到。

第 ⑤ 步那个 0.8450 怎么读? 两条弯月本来就有交叠区(噪声 0.2), 一条直线不可能把它们完全分开——85% 已经接近一条直线在这份数据上的上限。 第 06 章会给同一份数据换一个能画弯线的模型,看看能到多少。

成绩这把尺子这里也交代一句: 书里用的是准确率——预测正确的样本占总样本的比例7。 它的算法很朴素:二分类时把概率跟 0.5 比,大于等于就判成 17

还有一个零件在这一章登场:书里的 Runner 升到了第二版, 它加入了训练循环,并在每轮之后到验证集上评一次、把最好的那份存下来8这就是第 03 章说的「一版比一版能干」里的第二版。

5. 多分类:Softmax,以及那个每份实现都要做的减法

结论先行:类别多于两个时,不是跑多个二分类,而是让 C 个分数一起归一化。

书里的做法是:给每个类别配一条线性判别,得到 C 个分数, 再用 Softmax 把这 C 个分数变成一个总和为 1 的概率分布——所谓概率分布,就是一组加起来正好等于 1 的可能性9

Softmax 在做什么
───────────────────────────────────────────────
输入 4 个分数 [0.1, 0.2, 0.3, 0.4]
① 各自取指数 [e^0.1, e^0.2, e^0.3, e^0.4]
② 除以它们的和 让四个数加起来等于 1
───────────────────────────────────────────────
书里实际输出 [0.2138, 0.2363, 0.2612, 0.2887]

读法:分数最高的那个拿到最大的概率,但其他三个也不是 0
—— 它是「软」的排序,不是「非此即彼」

这里是这一章第二个必须记住的工程细节,而且书里专门用一小节讲它。 照公式直接算会面临两类数值问题10:

  • 上溢出:输入里有很大的正数时,取指数的结果可能超过浮点数能表示的范围;
  • 下溢出:输入普遍是很大的负数时,取指数的结果全被舍入成 0,分母变成 0。

稳态化的做法是先整体减去最大值。 这样指数里的最大项降为 0(而 e⁰ = 1),避免上溢; 同时分母里至少含有一个 1,避免下溢10

这一步为什么重要? 因为减去一个常数不改变 Softmax 的结果—— 分子分母同乘一个数会约掉。所以这是一次零代价的稳定化, 你打开任何一份 Softmax 实现,都会看到这行减法。

这个「先减一个数再取指数」的动作后面还会以另一种身份出现: 第 20 章讲怎么从模型输出里取词时,会在取指数之前先除以一个旋钮, 用它来控制输出的保守还是大胆——那个旋钮和这里的减法作用在同一个位置上。

多分类的交叉熵也简化得很干净: 真实答案写成一个只有一位是 1 的向量, 所以交叉熵其实只关心模型对真实类别赋的那一个概率11。 代码上因此不用真的去构造那个向量,按下标取出来就行。

6. 三簇三分类:1.0986 起步,0.7850 收场

书里造了第二个数据集:1 000 条样本,每条 2 个描述, 标签来自 3 个空间分布不同的簇;为了既能看出三个区域、又保留交叠, 三个簇中心放在一个等边三角形的三个顶点上12

同样从全零起步,同样跑 500 轮、每步挪 0.1,得到13:

起步 参数全零 → 三个类别的概率都是 0.3333 = 1/3
损失 −log(1/3) = log 3 = 1.0986
测试 成绩 0.7850,损失 0.5066

又一次:1.0986 也不是巧合。 和第 4 节那个 0.6931 是同一回事—— C 个类别时,完全随机猜的损失就是 log C。

随身尺子:随机猜的损失是多少
──────────────────────────
2 类 log 2 = 0.6931
3 类 log 3 = 1.0986
10 类 log 10 = 2.3026
65 类 log 65 = 4.1744 ← 第 20 章那个字符级模型的起点
──────────────────────────

书里对 0.7850 给了解释:数据集故意保留了一部分交叠区域和特征噪声, 模型不会把所有样本都完美分开,这比完全分离的玩具数据更接近真实分类任务14

7. 案例:鸢尾花,以及那个 0.9333

这一节是这一章的案例实践,走的是第 04 章那条完整流水线。

数据: 150 条样本、3 种鸢尾花各 50 条,每条由花萼长宽、花瓣长宽 4 项描述组成15。 按 120 / 15 / 15 切成训练、验证、测试,同时对特征做最简单的缩放, 把 4 项描述压到同一个量纲16

训练:每步挪 1.0(注意这个数比前面大了十倍)、训 300 轮, 并自动保留验证集上表现最好的那份17

结果:测试成绩 0.9333、损失 0.314918。 再对单条样本做一次预测:真实类别 2,预测类别 219

但这个 0.9333 不能全信,而这句话是书里自己说的。 书里的动手练习原话是:测试集只有 15 条样本,单次随机划分得到的准确率方差(同一件事重复做多次、结果彼此散得有多开)很大; 建议把种子换成多个不同的取值、多次重新划分并训练,统计准确率的平均值与波动范围, 据此说明为什么这种小数据集上的单点准确率(如本节报告的 0.9333)不能全信20

15 条测试样本意味着什么? 每答错一条,准确率就掉 6.67 个百分点。 0.9333 就是「错了一条」(14/15 = 0.9333)。 换一个随机划分,它可能是 1.0000,也可能是 0.8667——而这三个数看起来差得很远。

8. 作者的判断与证据

书里给了证据的:

  • 全零起步 → 概率 0.5 / 0.3333——两次都是实际打印输出,而且给出了原因(线性输出恒为 0)21;
  • 0.6931 与 1.0986——两次实际打印,分别是 log 2 与 log 3;
  • 三组测试成绩 0.8450 / 0.7850 / 0.9333——都是实际打印221318

属于作者自己给的边界:

  • 0.9333 不能全信——书里在动手练习里明写了这一点,并给出了缓解办法(多次划分、交叉验证)20;
  • 0.7850 不是模型不行——书里说明数据集故意保留了交叠区14

书里给了做法但没做实验的:

  • 拆成 C 个「这一类 vs 其他」的二分类——书里把它留成了动手练习,正文没有对照数据23;
  • 另外两种画直线的老办法在同一份数据上的表现——一种叫感知器, 另一种名字听着唬人、干的也是「找一条把两堆分开的界线」这件事,叫支持向量机; 书里同样把它们留成了练习,没有给出成绩24

判断(我们的,不是书里的):这一章最值钱的不是模型,是那两个「随机猜的损失」。 0.6931 和 1.0986 这两个数,把「损失」这个抽象的量变成了有参照物的量。 没有它们,你看到损失 0.7 不知道是好是坏;有了它们,你一眼看出模型什么都没学到。 如果错,会错在: 这条只对交叉熵成立,对回归任务的均方误差不适用 ——那里的参照物得另找(比如全部预测成平均值时的误差)。 判据是:去第 20 章看那个 log 65 ≈ 4.17,书里正是拿它当基线(一个「什么都不做时是多少」的参照值)在读损失。

9. 边界与局限

这一章没覆盖的:

没讲什么依据 / 为什么值得知道
感知器与那台「找分界线」的老机器书里的知识点图里列了,但只在动手练习里出现24
类别不平衡怎么办全书的分类数据集都是各类样本数相当的
交叉验证只在动手练习里被提了一句20

另外两处没覆盖的单独摊开说,因为里面有四个出门会撞见的名字:

  • 另外三把量分类成绩的尺子。 书里只在第 04 章列举了一次、直到第 19 章才真的用上。 第一把叫精确率:判成这一类的里面,有多少是判对的。

  • 第二把叫召回率:真正属于这一类的里面,有多少被找了出来。

  • 第三把叫 F1:把前两把取一种平均,兼顾两头。

  • 那条判定的分界线该划在哪。 书里一律划在 0.5,没讨论过挪一挪会怎样; 这条线的正式名字叫阈值

出门会撞见的名字:

  • torch.sigmoid / Logistic 函数 / Sigmoid —— 第 2 节那同一条曲线的三个名字;

  • F.binary_cross_entropy —— 第 3 节那把尺子在框架里的名字;

  • nn.CrossEntropyLoss —— 多分类那一把;它内部已经含了 Softmax, 所以模型最后一层不要再叠一次(这件事第 07 章会再强调一遍);

  • 未归一化分数(logits) —— 指的是过 Softmax 之前的那 C 个分数; 你在别人的代码里看到 logits 这个名字,它就是这个东西;

  • 早停(early stopping) —— 第 4 节 ④ 那个「盯着验证集、留最好的那份」的做法8

10. 可带走的

  1. 分类比回归多一道挤压:把任意实数挤到 0 和 1 之间,挤完当概率读;
  2. 那条 S 形曲线有三个名字:torch.sigmoid、Logistic 函数、Sigmoid;
  3. 别照公式手写它,框架的版本做了分段稳定实现,不会上溢;
  4. 分类用交叉熵,它只盯着「给正确答案打了多少分」;
  5. 二分类随机猜的损失是 0.6931,C 类是 log C——这是判断「学没学到」的随身尺子;
  6. 参数全零起步时,单层模型不会出对称问题,概率恰好均分;
  7. Softmax 的实现必须先整体减去最大值,这是零代价的稳定化,减一个常数不改变结果;
  8. 多分类交叉熵只关心真实类别那一个概率,所以不用真的构造那个只有一位是 1 的向量;
  9. 弯月上 0.8450 已接近一条直线的上限,想更高得换能画弯线的模型;
  10. 15 条测试样本上的 0.9333 = 错了一条,这种单点成绩不能全信,书里自己也这么说。

11. 原文地图

主题原书章原文位置
挤压成概率第3章 线性模型text/04-ch03.txt:38(搜「二分类问题中最常用的线性模型」) · text/04-ch03.txt:49(搜「两端则快速饱和」)
三个名字与数值稳定第3章 线性模型text/04-ch03.txt:53(搜「直接套用公式实现」) · text/05-ch04.txt:105(搜「默认特指」)
交叉熵第3章 线性模型text/04-ch03.txt:217(搜「训练阶段需要一把」)
弯月数据集与切分第3章 线性模型text/04-ch03.txt:66(搜「两条带噪声的弯月形函数生成」) · text/04-ch03.txt:131(搜「切分训练集、验证集和测试集」)
全零起步与 0.6931第3章 线性模型text/04-ch03.txt:212(搜「所有样本的预测概率都是」) · text/04-ch03.txt:265(搜「0.6931」)
二分类的测试成绩第3章 线性模型text/04-ch03.txt:533(搜「0.8450」)
Softmax 与减最大值第3章 线性模型text/04-ch03.txt:645(搜「两类数值问题」) · text/04-ch03.txt:669(搜「0.2138」)
三分类的两个数第3章 线性模型text/04-ch03.txt:719(搜「原因仍然是参数全零」) · text/04-ch03.txt:882(搜「0.7850」)
鸢尾花第3章 线性模型text/04-ch03.txt:904(搜「共有 150 条样本」) · text/04-ch03.txt:1014(搜「0.9333」)
0.9333 不能全信第3章 线性模型text/04-ch03.txt:1040(搜「单次随机划分得到的准确率方差很大」)

Footnotes

  1. 出处:「第3章 线性模型」第 38 段(text/04-ch03.txt:38,搜「二分类问题中最常用的线性模型」)。原文:Logistic 回归和线性回归一样,把特征经权重线性叠加;不同之处在于额外套了一个非线性的「挤压」函数,把线性输出转换为后验概率,从而把连续的线性值映射到合理的概率区间。

  2. 出处:「第3章 线性模型」第 49 段(text/04-ch03.txt:49,搜「两端则快速饱和」)。原话:Logistic 函数在 0 附近近似线性,两端则快速饱和——输入趋向负无穷时输出趋近 0,趋向正无穷时输出趋近 1;它的输出恒落在 (0, 1) 区间,正好可以作为后验概率。

  3. 出处:「第4章 前馈神经网络」第 105 段(text/05-ch04.txt:105,搜「默认特指」)。这是书里的一条提醒:在 PyTorch 及多数论文里,「Sigmoid」默认特指 Logistic 函数;但严格地说,Sigmoid 型是一类 S 形函数的统称,还包括 Tanh、ArcTan、平滑阶跃函数等。

  4. 出处:「第3章 线性模型」第 53 段(text/04-ch03.txt:53,搜「直接套用公式实现」)。原话:直接套用公式实现时,当 x 取很大的负数,torch.exp(-x) 会数值溢出;工程实战中应直接调用 torch.sigmoid(x)——它内部按 x 的正负做了分段稳定实现,永远不会上溢,这也是本书后续代码统一使用的版本。

  5. 出处:「第3章 线性模型」第 217 段(text/04-ch03.txt:217,搜「训练阶段需要一把」)。原文:记 y 为样本的真实标签分布、ŷ 为模型的预测分布,目标是让 ŷ 尽量贴近 y;常用的工具是交叉熵损失函数——两分布越接近,交叉熵越小,偏离越远,损失越大。二分类的展开式见同段之后的公式 (3.4)。 2

  6. 出处:「第3章 线性模型」第 66 段(text/04-ch03.txt:66,搜「两条带噪声的弯月形函数生成」)。切分比例见第 131 段(text/04-ch03.txt:131,搜「切分训练集、验证集和测试集」);噪声设定见第 107 段(text/04-ch03.txt:107,搜「采样 1 000 条数据」)。

  7. 出处:「第3章 线性模型」第 385 段(text/04-ch03.txt:385,搜「预测正确的样本占总样本的比例」)。判定方式见代码:二分类时把概率与 0.5 比较,多分类时取最大值所在的位置。 2

  8. 出处:「第3章 线性模型」第 421 段(text/04-ch03.txt:421,搜「把训练循环加进」)。原文说明这一版 Runner 每轮迭代算损失、反向求梯度、调用优化器更新参数,同时引入提前停止思想,在验证集上跟踪指标并保留最优模型。 2

  9. 出处:「第3章 线性模型」第 542 段(text/04-ch03.txt:542,搜「需要把它推广到」)。原文:面对类别数大于 2 的多分类问题,需要把它推广到 C 条线性判别同时存在的形式——这就是 Softmax 回归。

  10. 出处:「第3章 线性模型」第 645 段(text/04-ch03.txt:645,搜「两类数值问题」)。原文列了上溢出与下溢出两种,并给出稳态化技巧:先减去最大值,这样指数中的最大值降为 0 避免上溢,同时分母至少包含 exp(0) = 1 避免下溢。 2

  11. 出处:「第3章 线性模型」第 741 段(text/04-ch03.txt:741,搜「交叉熵其实只关心」)。原文:由于真实标签向量只在真实类别上为 1、其余位置都是 0,交叉熵其实只关心模型对真实类别赋的那一个概率;代码实现因此按索引取出真实类别上的概率,避免显式构造那个向量。

  12. 出处:「第3章 线性模型」第 556 段(text/04-ch03.txt:556,搜「标签来自」)。原文说明把 3 个簇中心放在等边三角形的三个顶点上,再用类内扰动和特征噪声控制任务难度。

  13. 出处:「第3章 线性模型」第 719 段(text/04-ch03.txt:719,搜「原因仍然是参数全零」)与第 774 段(text/04-ch03.txt:774,搜「1.0986」)。测试成绩见第 882 段(text/04-ch03.txt:882,搜「0.7850」),实际输出为 [Test] score/loss: 0.7850/0.5066 2

  14. 出处:「第3章 线性模型」第 849 段(text/04-ch03.txt:849,搜「由于数据集故意保留了一部分交叠区域」)。原话:模型不会把所有样本都完美分开,这比完全分离的玩具数据更接近真实分类任务。 2

  15. 出处:「第3章 线性模型」第 904 段(text/04-ch03.txt:904,搜「共有 150 条样本」)。原文:覆盖 3 种鸢尾花类别,每类 50 条;每条样本由花萼长度、花萼宽度、花瓣长度、花瓣宽度 4 个特征组成。

  16. 出处:「第3章 线性模型」第 928 段(text/04-ch03.txt:928,搜「切分成训练、验证和测试三部分」)。代码里的实际切分数是 120 / 15 / 15。

  17. 出处:「第3章 线性模型」第 971 段(text/04-ch03.txt:971,搜「训练 300 个回合」)。学习率 1.0 见同段之后的代码。

  18. 出处:「第3章 线性模型」第 1014 段(text/04-ch03.txt:1014,搜「0.9333」)。实际输出为 [Test] score/loss: 0.9333/0.3149 2

  19. 出处:「第3章 线性模型」第 1031 段(text/04-ch03.txt:1031,搜「The true category is 2」)。

  20. 出处:「第3章 线性模型」第 1040 段(text/04-ch03.txt:1040,搜「单次随机划分得到的准确率方差很大」)。这是书里的动手练习 3.7,原文明写「据此说明为什么这种小数据集上的单点准确率(如本节报告的 0.9333)不能全信,并尝试用交叉验证来缓解这种不确定性」。 2 3

  21. 出处:「第3章 线性模型」第 212 段(text/04-ch03.txt:212,搜「所有样本的预测概率都是」)。原话:所有样本的预测概率都是 0.5;原因很简单——权重和偏置全部为 0,线性输出恒为 0,再经 Logistic 得到 σ(0) = 0.5。参数初始化为 0 的理由见第 156 段(text/04-ch03.txt:156,搜「单层模型不存在对称性问题」)。

  22. 出处:「第3章 线性模型」第 533 段(text/04-ch03.txt:533,搜「0.8450」)。实际输出为 [Test] score/loss: 0.8450/0.3245

  23. 出处:「第3章 线性模型」第 871 段(text/04-ch03.txt:871,搜「多分类还有另一种思路」)。这是动手练习 3.5,书里只提出对比要求,正文没有给出对照数据。

  24. 出处:「第3章 线性模型」第 1035 段(text/04-ch03.txt:1035,搜「尝试实现」)。这是动手练习 3.6,涉及感知器与支持向量机,正文没有实现。 2