神经网络就是把「加权和 + 挤压」这件事摞很多层。 一个隐层单元能折一道,几个拼起来就能围出一个圈。 实测同心圆数据: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 秒实验
- 把隐层单元加到 32 个,测试准确率还能更高吗?为什么不能超过 1.0?
- 把 tanh 换成 ReLU(
np.maximum(0, z)),训练会更快还是更慢?为什么会这样? - 如果去掉输出层的 sigmoid,直接拿线性输出做回归,还能训练吗?该换什么损失?
常见误区
- 以为「层数越多越好」。这一节量的是「宽度」。 加层不一定更好——没有残差连接时,深网络反而更难训。深度是手段,不是目标。
- 不做标准化就训练。权重初始化的尺度是按输入维度定的,输入尺度差十倍,第一层输出就会饱和, 梯度接近 0,学不动。这和 2.1 节梯度下降的坑是同一个。
- 把「万能近似定理」当成「一层网络能解决一切」。定理只说「存在一组权重能做到」, 没说「梯度下降能找得到」。存在性和可学性是两回事。
自测三问(点开对答案)
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。
- ☐ (本节完,下一节 2.9 算法 9:KNN)