🧮 第2章 有监督学习
📄 一句话概括

神经网络就是把「加权和 + 挤压」这件事摞很多层。 一个隐层单元能折一道,几个拼起来就能围出一个圈。 实测同心圆数据:1 个隐层单元 0.6750,2 个 0.8250, 4 个就 1.0000—— 只用 13 个参数,画出了支持向量机用核方法才掰弯的那条边界。 交叉熵从 0.7031 一路降到 0.0062。

📝 本节和前面几节的关系

到 2.7 节为止,所有模型的边界形状都是事先定好的:逻辑回归和 SVM 是直线, 核方法靠人挑核函数,树是横平竖直的方块。 神经网络换了个做法:形状由层数和单元数决定,权重自己学。 这一节只讲最朴素的两层, 深度学习那一整套在 43 课 里逐层拆过。

一、从一个神经元说起

一个神经元做的事和逻辑回归一模一样:把输入加权求和,再过一道挤压函数,输出一个数。 所以单个神经元等价于逻辑回归。 教材在这里强调了一个历史细节:最早的感知机连挤压函数都没有,直接拿加权和的符号当输出。 它只能画直线,1969 年被证明连异或都学不会,直接导致了神经网络第一次寒冬。 破解的办法不是换更好的单个神经元,而是把多个神经元摞起来:第一层若干个神经元各算一个数, 这些数再作为第二层的输入。 这里有个关键点容易忽略: 如果层与层之间没有挤压函数,摞多少层都等于一层。 因为线性函数的复合还是线性函数。 是非线性让「深」有意义。 教材里用的挤压函数是 sigmoid(2.3 节见过)或 tanh。 现在主流用 ReLU,但原理一样:给线性变换之间插一个弯的东西。

二、前向:一层一层算过去

前向传播就是「算」。 输入 x 进来,第一层算 H = tanh(x·W1 + b1),第二层算 p = sigmoid(H·W2 + b2),输出一个概率。 参数量的算法要会背:输入 d 维、隐层 h 个、输出 1 个,第一层有 d·h 个权重加 h 个偏置, 第二层有 h 个权重加 1 个偏置,合计 h(d + 1) + h + 1。d = 2 时就是 3h + 1。 所以 h = 4 只有 13 个参数,h = 8 是 25 个。 这个数量比很多人想的少得多——支持向量机配上 RBF 核时,要存的支持向量就有几十上百个。 参数少却能表达复杂形状,这是神经网络最反直觉的地方。 原因在于它的参数是「共享」的:每个隐层单元用同一套输入,但权重不同, 相当于从不同角度看同一份数据。视角的组合数随单元数指数增长,参数量只随单元数线性增长。

三、反向:从误差倒着推责任

训练的目标还是老一套:定义损失,求梯度,往下走。这一节用的是交叉熵(2.3 节),梯度就是预测减真实。 难点在于隐层的参数没有「正确答案」。输出层的误差好算,但第一层的权重该改多少? 它离输出隔了一层,误差得传回去。 传的办法是链式法则。输出层的梯度乘上「这一层的输出对上一层输出的导数」,就得到上一层的梯度。 代码里那一行 (g @ W2.T) * (1 - H * H) 就是这个意思:前一半是把误差按权重传回去, 后一半是 tanh 的导数。 tanh 的导数恰好是 1 − tanh²,所以代码里可以直接用输出算,不用真的求导。 这是选 tanh 的一个实际好处。 这个过程叫反向传播。它本身不神秘——就是把链式法则按层写了一遍,只是层数多的时候手推容易错。 43 课第 4 章有一节专门讲它怎么一步步推。

四、为什么多一层就能弯

这是这一节最该想明白的问题:一个隐层单元只能做「加权和 + 挤压」,它凭什么能拼出一个圈? 直观理解:一个隐层单元的输出在空间里是一条过渡带——一边接近 +1、一边接近 −1,中间平滑过渡。 这条带子是一条直线(或曲面)。 多个这样的带子加权求和,就能拼出各种形状。 要围一个圈,至少需要几条带子从不同方向切过去,把它们加起来,中间区域的值就和其他区域不同了。 Demo 里量了这个直觉:1 个单元 0.6750(还是直线)、2 个 0.8250、4 个 1.0000。 从 2 到 4 那一步是质变,不是量变。 因为 2 条带子只能切出一个角,4 条才能围出封闭区域。 教材在这里提了一句「万能近似定理」:足够宽的一层网络能逼近任何连续函数。 这句话常被误读成「一层就够,不用深」。 实际上它只说「存在」, 没说「容易学」——宽度足够但学不出来,和没有是一样的。深度有用的真正原因是它更省参数、 更容易优化,这是后话。

五、动手 Demo:把直线摞起来,就弯成了圈

同心圆数据,内圈半径 0.8、外圈 2.2,各 200 个,圆心在 (2, 2)。训练 280 个、测试 120 个。 先做标准化——神经网络的权重初始化是按输入尺度给的,不标准化会让第一层直接饱和。 网络结构是最小的:输入 2 维、隐层 h 个 tanh、输出 1 个 sigmoid、损失用交叉熵、全批量梯度下降。 没有动量、没有自适应学习率。

import numpy as np

W1 = rg.normal(0, 1.0 / np.sqrt(2), (2, h))   # 输入 2 维,隐层 h 个
b1 = np.zeros(h)
W2 = rg.normal(0, 1.0 / np.sqrt(h), (h, 1))

for it in range(4000):
    H = np.tanh(Xtr @ W1 + b1)                # 前向:第一层
    p = 1.0 / (1.0 + np.exp(-(H @ W2 + b2)))  # 前向:输出层
    g = (p - ytr.reshape(-1, 1)) / len(Xtr)   # 交叉熵的梯度
    gW2 = H.T @ g                             # 反向:输出层
    gH = (g @ W2.T) * (1.0 - H * H)           # 反向:穿过 tanh
    gW1 = Xtr.T @ gH                          # 反向:第一层
    W1 -= 0.6 * gW1
    W2 -= 0.6 * gW2

实跑输出:

--------------------------------------------------------------------
神经网络:把直线摞起来,就弯成了圈
--------------------------------------------------------------------

  同心圆,内圈半径 0.8、外圈 2.2。训练 280 个、测试 120 个。
  先做标准化——神经网络的权重初始化是按输入尺度给的,不标准化的话,
  半径 2 的坐标会让第一层的输出直接饱和,学得很慢。

  【一】没有隐层:还是那条直线
  隐层单元数  参数量    测试准确率
  ----------------------------------
  1           4         0.6750
  2           7         0.8250
  4           13        1.0000
  8           25        1.0000
  16          49        1.0000
  只有 1 个隐层单元时是 0.6750——
  **一个单元只能折一道,决策边界还是直线,和 2.3 节逻辑回归是同一个东西。**
   2 个单元到 0.8250,4 个就到 1.0000。原因是每个隐层单元能把空间折一次,
  几条折线拼起来就够围一个圈;**单元太少,折线不够围。**

  【二】训练过程:损失一路往下
  轮数      训练交叉熵
  ------------------------
  0         0.7031
  800       0.0249
  1600      0.0121
  2400      0.0081
  3200      0.0062
  头 800 轮掉得最快,后面越来越慢。这是梯度下降的通病:**越接近谷底,梯度越小。**
  如果嫌慢,办法是加动量、换自适应学习率,或者把数据标准化——本书不展开,知道有这几招就行。

  【三】参数量是怎么算的
  输入 2 维、隐层 h 个、输出 1 个:第一层有 2 乘 h 个权重加 h 个偏置,
  第二层有 h 个权重加 1 个偏置,合起来 3h + 1。h = 8 时是 25 个参数——
  **比支持向量机还少,可它画得出曲线。**
  这就是神经网络厉害的地方:参数不多,却能拼出复杂的形状。

  最后提醒一句:这一节用的是最朴素的「一层隐层 + tanh + 交叉熵」。深度学习那一整套东西,
  在 [[43. 📕 深度学习入门教室/00-索引|43 课深度学习入门教室]] 里一步步拆过,这里只要记住:
  **它和前面八个算法是同一个框架,只是把「拟合什么形状」这件事交给了层数。**

四个数字:1 个单元 0.6750、2 个 0.8250、4 个 1.0000、8 个 1.0000。 1 个单元时它还是逻辑回归(决策边界是直线),4 个单元就够围一个圈了,而且只用了 13 个参数。 对比一下 2.5 节:同样的「画个圈」任务,核方法要算 200×200 的核矩阵、 解一个 200 元的线性方程组;神经网络只用 13 个参数、4000 轮矩阵乘法。两条路都行,代价不同。 训练曲线那张表:0.7031 → 0.0249 → 0.0121 → 0.0081 → 0.0062。头 800 轮掉了 96%, 后面 3000 多轮只掉了 0.02。这是梯度下降的共性:越接近谷底越慢。 想快就得上动量或自适应方法。 参数量 3h + 1 这个式子值得记住。h = 4 时 13 个, 而 2.1 节的线性回归在 2 个特征上只有 3 个参数。多了 10 个参数,就多了「弯」的能力。 最后那句提醒很重要:这一节讲的是最朴素的版本。 真实网络里有卷积、池化、dropout、批归一化、残差连接——那些东西解决的是「更深更难训」的问题, 不是「能不能弯」的问题。

30 秒实验

  1. 把隐层单元加到 32 个,测试准确率还能更高吗?为什么不能超过 1.0?
  2. 把 tanh 换成 ReLU(np.maximum(0, z)),训练会更快还是更慢?为什么会这样?
  3. 如果去掉输出层的 sigmoid,直接拿线性输出做回归,还能训练吗?该换什么损失?

常见误区

  1. 以为「层数越多越好」。这一节量的是「宽度」。 加层不一定更好——没有残差连接时,深网络反而更难训。深度是手段,不是目标。
  2. 不做标准化就训练。权重初始化的尺度是按输入维度定的,输入尺度差十倍,第一层输出就会饱和, 梯度接近 0,学不动。这和 2.1 节梯度下降的坑是同一个。
  3. 把「万能近似定理」当成「一层网络能解决一切」。定理只说「存在一组权重能做到」, 没说「梯度下降能找得到」。存在性和可学性是两回事。
自测三问(点开对答案) 1. 为什么层与层之间一定要有非线性函数? —— 因为线性函数的复合还是线性函数。没有非线性,摞一百层也等价于一层,决策边界还是直线。 是非线性让「深」有了意义。 2. 输入 2 维、隐层 8 个、输出 1 个的两层网络,一共多少个参数? —— 25 个。第一层 2×8 个权重加 8 个偏置,第二层 8 个权重加 1 个偏置,合计 3×8 + 1 = 25。 3. 1 个隐层单元的网络和逻辑回归有什么区别? —— 本质上没有区别——一个单元做的是加权和加挤压,决策边界还是一条直线。 Demo 里 1 个单元得 0.6750,跟逻辑回归在同一水平。

第 2 章还剩最后一个算法。它连训练都没有:把数据存起来,来一个点就看它的邻居——2.9 节的 KNN。

相关笔记