理论学习:从一条直线到多层网络
前置篇结尾留了一个问题:图里的箭头在前向时传数据,反向时传梯度,那些梯度到底怎么算?这一篇先不回答反向,先把前向的结构讲透。因为如果连一层里发生了什么都没弄清,后面手推反向传播只会变成符号游戏。
先看一个一定会失败的场景。你写了一个单层分类器,输入两个数,输出一个类别,决策边界是一条直线。在二维平面上,它能把左上和右下分开,也能把上下分开。现在换成 XOR 数据:(0,0) 和 (1,1) 是负类,(0,1) 和 (1,0) 是正类。你把直线转了一圈,怎么放都有一个点站错边。

<!-- 配图:784→256→128→10 的 MLP 流水线,标出每层参数量 -->
这不是调参能解决的问题。单层网络的表达能力有一个硬上限,XOR 正好卡在上限外面。这一章要做的,就是把这个上限拆开:为什么单层不够、隐藏层补了什么、补到什么程度才算够、以及补的代价是多少。
一、XOR:一条直线为什么不够
先把 XOR 的四个点画出来。

<!-- 配图:四个 XOR 点,三条候选直线都至少错一个点 -->
单层网络对输入 (x₁, x₂) 做的事只有一件:
z = w₁x₁ + w₂x₂ + b
ŷ = step(z) # z > 0 判为正类,否则负类决策边界就是 z = 0 这条直线。现在把四个点代进去,要求输出满足 XOR:
(0,0) → 负类:b < 0
(1,0) → 正类:w₁ + b > 0
(0,1) → 正类:w₂ + b > 0
(1,1) → 负类:w₁ + w₂ + b < 0前两个正类约束相加,得到 w₁ + w₂ + 2b > 0。从 (1,1) 的负类约束又得到 w₁ + w₂ + b < 0,两边再加一个 b,就是 w₁ + w₂ + 2b < b。而 b < 0,所以 w₁ + w₂ + 2b < 0。同一个量既大于 0 又小于 0,矛盾。
结论:不存在任何 (w₁, w₂, b) 能让单层网络拟合 XOR。这不是初始化不好,也不是学习率没调对,是假设空间里根本没有解。
这也解释了单层网络的适用边界:它只能处理线性可分的问题。线性可分听起来是个技术词,翻译成人话就是「存在一条直线(或高维的超平面)能把两类分开」。很多真实问题不满足这个条件,图像、文本、语音都不满足。
二、隐藏层:先换坐标,再画线
单层不够,加一层。两个隐藏单元就够了。

<!-- 配图:左图原空间两条边界,右图隐藏空间一条边界 -->
给两个隐藏单元安排两组权重:
h₁ = step(x₁ + x₂ − 0.5) # x₁+x₂ > 0.5 时为 1
h₂ = step(x₁ + x₂ − 1.5) # x₁+x₂ > 1.5 时为 1
ŷ = step(h₁ − h₂ − 0.5) # h₁=1 且 h₂=0 时判为正类代四个点:
| 点 | h₁ | h₂ | h₁−h₂−0.5 | ŷ |
|---|---|---|---|---|
| (0,0) | 0 | 0 | −0.5 | 0 |
| (1,0) | 1 | 0 | 0.5 | 1 |
| (0,1) | 1 | 0 | 0.5 | 1 |
| (1,1) | 1 | 1 | −0.5 | 0 |
四个点全对。原来在二维空间里找不到一条线,现在把每个点映射成 (h₁, h₂),在新空间里一条 h₁ − h₂ = 0.5 的线就把它们分开了。(1,0) 和 (0,1) 映射到同一个点 (1,0),两类点在隐藏空间里合并成了一个线性可分的问题。
这就是「表示学习」四个字的实际含义:网络不是直接在原始输入上画边界,而是先用隐藏层换一组坐标,让问题在新坐标里变简单,再由最后一层画线。隐藏层学的不是分类规则,是坐标变换。
一个隐藏单元画一条边界,两个就能围出一个带子。再加宽,边界能拐更多弯:

<!-- 配图:1 个、2 个、8 个隐藏单元的三联决策边界 -->
第三张图的边界已经接近曲线。它不是真的曲线,是很多段直线拼出来的。每个 ReLU 单元贡献一个折角,折角多了,折线就看起来像曲线。宽度买到的就是折角的数量。
偏置在这里干什么
上面的 step 里,−0.5 和 −1.5 就是偏置。没有偏置,两个隐藏单元的拐点都卡在 0,两条边界会重合,XOR 还是解不开。

<!-- 配图:b=0 与 b=1 两条 ReLU 曲线,拐点位置不同 -->
偏置不是可有可无的截距,它是每个单元的「触发阈值」。权重决定边界的方向,偏置决定边界离原点多远。一个网络要拼出复杂的边界,方向和距离都得能调。
激活函数:没有它,加了层也白加
隐藏层之后必须接非线性激活。如果只做 W₂(W₁x + b₁) + b₂,展开还是 (W₂W₁)x + (W₂b₁ + b₂),两个线性变换的复合还是一个线性变换。加多少层都等价于一层,XOR 依然解不开。

<!-- 配图:ReLU、Sigmoid、Tanh 三条曲线 -->
三个常用选择:
| 激活 | 形状 | 导数 | 主要问题 |
|---|---|---|---|
| ReLU | max(0, z) | 0 或 1 | 负半轴梯度为 0,单元可能「死掉」 |
| Sigmoid | 1/(1+e⁻ᶻ) | 最大 0.25 | 两端饱和,深层梯度消失 |
| Tanh | (eᶻ−e⁻ᶻ)/(eᶻ+e⁻ᶻ) | 最大 1 | 仍然两端饱和,但零中心 |
这一章的实战用 ReLU。理由很实际:计算便宜,正半轴导数恒为 1,不会像 Sigmoid 那样每层都把梯度乘掉四分之三。代价是负半轴的单元一旦彻底死掉,就再也活不过来——第 3 章会专门处理。
三、万能近似定理:保证存在,不保证学会
隐藏层能拼折线,折线多了能逼近曲线。这个直觉有没有上限?有,而且数学上已经说清楚了。

<!-- 配图:目标曲线与 12 段折线近似,底部画 ReLU 折角基 -->
万能近似定理(Universal Approximation Theorem)说:只要一个隐藏层足够宽,使用单调递增、有界的激活函数,就能在紧集上以任意精度逼近任意连续函数。
这句话要拆成三个关键词。
「存在」:存在一组权重能做到,不等于梯度下降能找到。定理不提供施工图,只保证楼能盖。
「足够宽」:宽度可能非常大。某些函数用浅层网络逼近,需要的单元数随精度指数增长;同样的函数用深层网络,单元数可能只是多项式增长。定理没说「一个隐藏层就够了,所以深网络没必要」。
「连续函数」:定理管的是连续函数。如果目标是一个阶跃、一个集合指示函数,或者数据里全是噪声,定理不负责。它也不管泛化:在训练集上逼近得好,不代表在测试集上表现好。
所以万能近似定理的正确用法,是把它当「表达能力的存在性保证」,而不是「随便加宽就能训好」的承诺。真正决定效果的还有三件事:优化能不能找到那组权重、样本够不够、归纳偏置对不对。
深与宽:同样的参数,不同的折法
「一个隐藏层就够」在表达力上成立,在参数效率上不成立。

<!-- 配图:宽而浅的 16 单元与窄而深的 4×4 结构对比 -->
考虑一个来回折了很多次的函数。浅层网络要把每个折角独立画出来,宽度随折角数量线性增长。深层网络可以让前一层的折角成为后一层的输入,后一层在前一层的结果上继续折,折角数量随深度组合增长。同样的参数预算,深网络能表达的折点更多。
代价也在这里:层数越深,反向传播的链越长,梯度越容易消失。第 2 章会用数值实验把这个现象复现出来。
四、参数账:每加一个宽度,钱包薄一层
结构讲完,算钱。
一个 784 → w → 10 的两层 MLP,参数量是:
第一层:784×w + w
第二层:w×10 + 10
合计:795w + 10
<!-- 配图:参数量随 w 从 64 到 1024 的直线,标三个点 -->
代几个常见的宽度:
| 隐藏宽度 w | 第一层参数 | 第二层参数 | 合计 |
|---|---|---|---|
| 64 | 50,240 | 650 | 50,890 |
| 128 | 100,480 | 1,290 | 101,770 |
| 256 | 200,960 | 2,570 | 203,530 |
| 512 | 401,920 | 5,130 | 407,050 |
| 1024 | 802,816 | 10,250 | 813,066 |
宽度翻倍,参数翻倍。这条直线就是 DNN 的账本。
再往上加一层 256 → 128:
784→256:200,960
256→128:32,896
128→10:1,290
合计:235,146比两层的 203,530 只多了 31,616 个参数,但多了一次非线性变换。这就是「加深」在参数效率上的吸引力:多花 15% 的参数,多一层折角组合。
计算量和显存
参数量不等于计算量。一次前向的乘加次数(MAC):
784×256 = 200,704
256×10 = 2,560
合计 203,264 MAC/样本batch 64 时,一次前向约 1,300 万次乘加。CPU 上跑一个 epoch(10,000 个样本)就是 20 亿次乘加,听起来吓人,但现代 CPU 的 SIMD 和矩阵库能把它压到几秒到几十秒。这也是为什么这一章敢用 CPU 训练 MNIST 子集。
显存(或内存)这边,float32 下 203,530 个参数占 814KB。训练时还要存梯度、Adam 的一阶矩和二阶矩,每样一份,总共约 3.2MB。真正的大头是激活值:batch 64、隐藏层 256 维,一层激活就是 64×256×4 = 64KB,再加上输入层的 64×784×4 = 200KB。小网络在 CPU 上跑,内存完全不是瓶颈。
把规模放大到前置篇那张 224×224×3 的图,接 1000 个隐藏单元:一层就 1.5 亿参数,float32 600MB,训练时三份状态就是 1.8GB。参数账在图像上会迅速失控,这就是第 4 章 CNN 要解决的问题。
五、训练在改什么:一张损失曲面
结构只决定「能表达什么」,训练决定「实际表达成什么」。
前向走完,网络输出 10 个 logits,softmax 变成概率,交叉熵算出损失。损失是一个关于所有参数的函数,参数几万个,曲面就是几万维的。人脑想象不了几万维,但可以把它压成二维看一眼。

<!-- 配图:椭圆等高线与一条来回横跳的 SGD 轨迹 -->
横轴和纵轴是两个参数方向,等高线是损失相同的点。最中间是损失最低的地方。训练做的事,就是从一个随机起点出发,沿着梯度的反方向一步步往下走。
步子的大小就是学习率。步子太小,走半天还在半山腰;步子太大,会在窄谷的两侧来回横跳,甚至直接跳出去,损失变成 NaN。图里那条蓝色轨迹就是步子太大的样子:每次都想往中间走,每次都冲过头。
这一章只走到「知道有这张曲面」为止。曲面为什么长这样、梯度怎么算出来、学习率怎么选,是第 2 章和第 3 章的事。
为什么全零初始化一定失败
训练开始前要初始化权重。一个看起来很自然的想法是「全都设成 0」,干净、对称。实际结果是网络完全学不动。
原因不难推:同一层的两个隐藏单元如果权重完全相同,它们收到的输入相同、算出的输出相同、反向传播收到的梯度也相同。梯度下降给它们的更新量一模一样,它们永远保持相同。一层 256 个单元,实际只有一个单元在工作,其余 255 个是它的副本。这个现象叫「对称性无法打破」。
正确的做法是随机初始化,让每个单元从不同的起点出发。初始化的方差还要配合激活函数和层数选,第 3 章会展开。这一章的实战会亲手复现一次全零初始化,看到准确率停在 10% 左右。
一个容易踩的坑:输入没归一化
MNIST 的像素是 0–255。直接把原始像素喂进 784→256 的第一层,z = Σ wᵢxᵢ 里每个 xᵢ 都乘了 255 倍,z 的绝对值会轻松到几千。ReLU 不会饱和,但梯度会跟着放大,学习率稍微大一点就 NaN。
现象:第一个 epoch 的 loss 直接变成 nan,或者从 2.3 跳到几千。
原因:输入尺度太大,第一层的预激活值落在很陡的区域,梯度爆炸。
修复:把像素除以 255 拉到 0–1,再按 MNIST 的均值 0.1307、标准差 0.3081 做标准化。归一化之后,同样的学习率就能稳定训练。
这不是 MNIST 特有的问题。任何输入尺度差了几个数量级的任务,都要先做归一化,否则第一层的权重更新会被最大的那个特征主导。
六、落到 MNIST:这一章的网络长什么样
把上面的零件拼起来,就是一个能跑的网络:
输入 784(28×28 像素拉平)
→ Linear(784, 256) + ReLU
→ Linear(256, 10)
→ softmax + 交叉熵
<!-- 配图:5 张 28×28 手写数字,标注拉平为 784 维 -->
MNIST 有 60,000 张训练图、10,000 张测试图。这一章不跑全量,抽 10,000 张训练、2,000 张测试,把一个 epoch 压到几十秒,让读者能在十分钟内看到结果。
输出层 10 个 logits 对应 0–9。softmax 把 logits 变成概率:
pᵢ = exp(zᵢ) / Σⱼ exp(zⱼ)交叉熵衡量预测分布和真实标签的差距:
L = −log p_y预测对的时候,p_y 接近 1,L 接近 0;预测错的时候,p_y 很小,L 很大。梯度下降要做的,就是让正确类别的概率变大。

<!-- 配图:logits [2.0, 1.0, 0.1] 经 softmax 得到三个概率 -->
验收线是测试集 95%。这个数字不高,但足够说明一件事:一个只有 20 万参数的两层网络,在十分钟内就能把 10 类手写数字分对九成以上。后面 CNN 要做的,是在同样的数据上把剩下那几个百分点抠出来。
七、这一章没解决的问题
网络能跑,曲线能降,但有一行代码始终是个黑箱:
loss.backward()它一行就把所有参数的梯度算完了。链式法则怎么在几百个节点上展开、为什么全零初始化会让梯度恒为零、为什么层数一深梯度就消失——这些问题的答案都在 backward() 里面。
为什么层数一深梯度就消失?因为反向传播的链式法则是一串雅可比矩阵的连乘。如果每层把梯度缩小到 0.25,五层之后只剩约千分之一:

<!-- 配图:五个 0.25 连乘,结果 0.0009765625 -->
参数更新量正比于这个梯度,所以前面的层几乎不动。这正是 Sigmoid 深层网络难训的原因,也是第 2 章要正面处理的问题。
下一篇先把一个神经元的前向拆到每个乘加,第 2 章再把箭头反过来,一层层手推梯度。
自检
合上笔记,试着回答:
- 用反证法说明单层网络为什么解不开 XOR。
- 两个隐藏单元是如何把 XOR 变成线性可分的?
- 万能近似定理保证了什么,没保证什么?
784→256→10的参数量是多少?宽度翻倍后呢?- 全零初始化为什么会让一层 256 个单元退化成一个?
- 输入不归一化时,第一个 epoch 可能看到什么现象?
答不上来的那一条,回到对应小节,把公式和数字重新推一遍。下一章从最小的神经元开始,把前向的每个乘加算清楚。