一个只有四个样本的问题——异或——把神经网络卡了十几年。 没有隐层时,八个随机种子最好的那个也只到 0.50,等于瞎猜。 加上隐层就能做,但不是每次都成功:隐层 2 个时八次只对 1 次,3 个对 6 次,8 个才八次全对。 三次浪潮、两次低谷,卡住的都是同一件事:能不能再多一层。
这一节讲历史,但不是为了记年份。 历史里最有价值的部分是「当时到底卡在哪」,因为那个卡点今天还在——只是换了个形式。 神经网络的第一次低谷和一次四行的真值表有关。 1969 年,明斯基和佩珀特在《感知器》一书里指出:单层感知器连异或都做不了。 这句话在当时被理解成「神经网络这条路走不通」,研究经费随之断掉。 这个判断在当时是对的,在后来是错的。 对,是因为单层确实做不到;错,是因为「加一层」就能做到——而当时没人知道怎么训练多层。
一、卡住它的那四行
异或问题的全部内容就是四行:
| x1 | x2 | 输出 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
| 把四个点画在平面上:(0,0) 和 (1,1) 一类,(0,1) 和 (1,0) 另一类。 | ||
| 两类的连线在对角线上交叉——这就是「线性不可分」的最小例子。 | ||
| 单层感知器的决策规则是「输入的加权和超过阈值就输出 1」,在平面上就是一条直线。 | ||
| 一条直线切不开这个叉形,所以它做不到——不是训练得不好,是结构上做不到。 | ||
| 实测八个随机种子,最好的那个 0.50,正好等于瞎猜。 | ||
| 换任何学习率、任何训练轮数都一样,因为问题出在模型族不在优化。 |
二、加一层就能做,但要碰运气
加上隐层之后:
| 隐层个数 | 八个种子里全对的次数 | 八次的准确率 |
|---|---|---|
| 2 | 1 / 8 | 0.50 到 1.00 |
| 3 | 6 / 8 | 0.75 到 1.00 |
| 4 | 5 / 8 | 0.75 到 1.00 |
| 8 | 8 / 8 | 全部 1.00 |
| 注意第一行:理论上两个隐单元就够表达异或(两个半平面一交就是那个叉),但实测八次只成功一次。 | ||
| 这就是那个年代真正的困难:不是「能不能表达」,是「能不能训练出来」。 | ||
| 参数一多,损失曲面就变得坑坑洼洼,梯度下降很容易停在一个不怎么样的地方。 | ||
| 隐单元加到 8 个,八次全对。 | ||
| 多的那 6 个单元并没有让模型「更聪明」,只是让损失曲面上多了几条出路——总有一条能走到低处。 | ||
| 这个现象在第 2 章讲学习率、第 6 章讲初始化时会再遇到。 | ||
| 它说明一件事:深度学习的很多「技巧」,本质都是在跟优化难度打交道。 |
三、三次浪潮
把时间线摊开:
| 时间 | 发生了什么 | 技术上卡在哪 |
|---|---|---|
| 1943 | M-P 模型提出,第一个神经元的数学抽象 | 权重还得人手定 |
| 1958 | 感知器问世,能自动学权重 | 只有一层 |
| 1969 | 《感知器》指出单层做不了异或 | 多层训不了 |
| 1986 | 反向传播普及,多层网络可以训练 | 层一深就训不动 |
| 2006 | 深层信念网络用逐层预训练破局 | 算力仍然不够 |
| 2012 | AlexNet 在图像识别比赛上大幅领先 | 此后成为主流 |
| 三次浪潮,两次低谷,卡点各不相同但都指向同一处。 | ||
| 第一次低谷(1969 之后):多层网络不知道怎么训练。 | ||
| 反向传播解决了这个,但它带来了第二个问题——层一深,梯度就消失或爆炸。 | ||
| 第二次低谷(1990 年代中后期):支持向量机等方法的兴起,加上深层网络确实训不动, | ||
| 神经网络又被冷落。 | ||
| 2006 年的逐层预训练(第 4、5 章会讲)让深层网络重新可训,算力的增长则让它变得实用。 | ||
| 2012 年之后就没有低谷了,因为条件齐了:数据够多、算力够强、方法够稳。 | ||
| 换句话说,深度学习不是某一天被「发明」出来的,是三个条件同时到位的产物。 |
四、这段历史该记住什么
第一,表达力和可训练性是两件事。 两个隐单元就能表达异或,但训练不出来;八个隐单元能训练出来,虽然用不上那么多。 一个模型「理论上能做」和「实际上能做」之间隔着一整个优化问题。 第二,被否定的往往是当时的训练手段,不是想法本身。 1969 年的结论「单层感知器做不了异或」没错,但被推广成「神经网络没前途」就错了。 这类误推在技术史上反复发生。 第三,很多「新技术」其实是老想法等到了配套条件。 反向传播的数学早在 1970 年代就有,卷积网络的雏形在 1980 年代末就出现了, 真正爆发是在数据和算力跟上之后。
五、动手 Demo:发展历程:一个异或问题,困了神经网络十几年
这段代码先证明没有隐层时异或怎么做都不行,再逐个数隐层个数、每个数跑八个随机种子, 看有几次能训练成功。 重点看两处。第二段「八个种子最好的也只到 0.50」;第三段那张表——隐层 2 个只有 1/8,8 个才 8/8。
# -*- coding: utf-8 -*-
import numpy as np
def init_mlp(sizes, seed=0):
"""He 初始化:每层权重的标准差取 sqrt(2 / 输入个数)。"""
rg = np.random.default_rng(seed)
W, b = [], []
for i in range(len(sizes) - 1):
W.append(rg.normal(0.0, np.sqrt(2.0 / sizes[i]), (sizes[i], sizes[i + 1])))
b.append(np.zeros(sizes[i + 1]))
return {"W": W, "b": b}
def forward(p, X, keep=False):
Z, A = [], [X]
h = X
n = len(p["W"])
for i in range(n):
z = h @ p["W"][i] + p["b"][i]
Z.append(z)
h = z if i == n - 1 else np.maximum(z, 0.0)
A.append(h)
return (A, Z) if keep else h
def softmax(S):
S = S - S.max(axis=1, keepdims=True)
E = np.exp(S)
return E / E.sum(axis=1, keepdims=True)
def backward(p, X, T):
A, Z = forward(p, X, keep=True)
n = len(p["W"])
d = (softmax(A[-1]) - T) / len(X)
gW = [None] * n
gb = [None] * n
for i in range(n - 1, -1, -1):
gW[i] = A[i].T @ d
gb[i] = d.sum(axis=0)
if i > 0:
d = (d @ p["W"][i].T) * (Z[i - 1] > 0.0)
return {"W": gW, "b": gb}
def train(p, X, T, lr=0.5, epochs=3000, seed=0):
"""四个样本,全批量,所以不需要打乱。"""
for _ in range(epochs):
g = backward(p, X, T)
for i in range(len(p["W"])):
p["W"][i] -= lr * g["W"][i]
p["b"][i] -= lr * g["b"][i]
return p
def acc(p, X, y):
return float(np.mean(np.argmax(forward(p, X), axis=1) == y))
# 异或:两个输入相同时输出 0,不同时输出 1
X = np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
y = np.array([0, 1, 1, 0])
T = np.zeros((4, 2))
T[np.arange(4), y] = 1.0
print('[1] 一个只有四个样本的问题,却困了神经网络十多年')
print(' 输入两个 0 或 1,相同输出 0,不同输出 1')
print(' 这四个点画在平面上,两类的对角线交叉,一条直线切不开')
print('')
print('[2] 没有隐层:无论怎么训练都不行')
best = 0.0
for s in range(8):
p = train(init_mlp([2, 2], seed=s), X, T, lr=0.5, epochs=3000)
best = max(best, acc(p, X, y))
print(' 八个随机种子,最好的那个也只到 %.2f' % best)
print(' 原因不是优化没做好,是它压根没有能表达异或的结构')
print('')
print('[3] 加上隐层:能做了,但不是每次都成功')
print(' 隐层个数 八个种子里全对的次数 八次的准确率')
for h in (2, 3, 4, 8):
vals = []
for s in range(8):
p = train(init_mlp([2, h, 2], seed=s), X, T, lr=0.5, epochs=3000)
vals.append(acc(p, X, y))
print(' %4d %d / 8 %s'
% (h, sum(1 for v in vals if v == 1.0), ' '.join('%.2f' % v for v in vals)))
print('')
print('[4] 这就是历史上那段低谷的技术原因')
print(' 1969 年明斯基指出单层感知器连异或都做不了,研究冷了十几年')
print(' 1986 年反向传播让多层网络可以训练,才重新热起来')
print(' 2006 年深层网络找到可行的初始化与预训练办法,深度学习才算立住')
print(' 三次起伏,卡的都是同一件事:能不能又多一层')
实跑输出:
--------------------------------------------------------------------
发展历程:一个异或问题,困了神经网络十几年
--------------------------------------------------------------------
[1] 一个只有四个样本的问题,却困了神经网络十多年
输入两个 0 或 1,相同输出 0,不同输出 1
这四个点画在平面上,两类的对角线交叉,一条直线切不开
[2] 没有隐层:无论怎么训练都不行
八个随机种子,最好的那个也只到 0.50
原因不是优化没做好,是它压根没有能表达异或的结构
[3] 加上隐层:能做了,但不是每次都成功
隐层个数 八个种子里全对的次数 八次的准确率
2 1 / 8 0.50 0.75 0.50 0.50 0.75 0.75 1.00 0.75
3 6 / 8 1.00 1.00 1.00 1.00 1.00 0.75 1.00 0.75
4 5 / 8 1.00 1.00 0.75 0.75 1.00 0.75 1.00 1.00
8 8 / 8 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00
[4] 这就是历史上那段低谷的技术原因
1969 年明斯基指出单层感知器连异或都做不了,研究冷了十几年
1986 年反向传播让多层网络可以训练,才重新热起来
2006 年深层网络找到可行的初始化与预训练办法,深度学习才算立住
三次起伏,卡的都是同一件事:能不能又多一层
第一段那句「一条直线切不开」是整个故事的起点。
异或是线性不可分的最小例子,只有四个点,却足够说明问题。
第二段的 0.50 要强调它是「上限」而不是「没调好」。
换学习率、换轮数、换种子,都突破不了——因为模型只会画直线。
这是「模型族不够」和「优化不够」的区别,两者要分清。
第三段那张表最有信息量的是第一行。
隐层 2 个:理论上够,实际上八次只成一次。
这个反差就是 1970 年代的真实处境——不是不知道要加层,是加层之后训不出来。
第三段最后一行 8/8 也不该读成「8 个隐单元比 2 个好」。
它说的是:参数多了,损失曲面上通往低处的路也多了,随机初始化的运气成分被平均掉了。
这在今天叫「过参数化有利于优化」,是个仍在研究的话题。
代码里 train 用的是全批量(四个样本一次算完),没有打乱。
因为样本只有四个,批量大小和学习率的搭配要单独特调——这也是小数据集的麻烦之处。
30 秒实验
- 把
init_mlp的初始化标准差从sqrt(2 / fan_in)改成 0.01,重跑。 隐层 8 个那一行也会掉下来。 初始化太小,信号一层层缩水,梯度传不回去——这就是后来「初始化方法」成为一门学问的原因。 - 把
train的lr从 0.5 改成 5.0,重跑。会出现准确率乱跳甚至变成 0.50 的情况。 学习率太大,更新一步就跨过了谷底。 这个旋钮在第 2.9 节会专门讲。 - 把隐层个数扫到 16、32,重跑。八个种子的成功率不会一直涨,会在某个点饱和。 异或只有四个样本,容量过剩之后没有额外好处。
常见误区
- 「1969 年明斯基证明了神经网络不行」。 他证明的是「单层感知器表达不了异或」,这个结论完全正确。 被误传成「神经网络不行」,是后人的过度推广。 多层网络从来都能表达异或,只是当时训练不出来。
- 「理论上能表达,就一定能训练出来」。 实测隐层 2 个(理论够用)八次只成功一次。 表达力是「存在一组参数能做到」,可训练性是「梯度下降能不能找到那组参数」。 两件事之间隔着一整个优化问题。
- 「深度学习是 2012 年突然出现的」。 反向传播 1986 年就普及了,卷积网络的雏形在 1980 年代末就有。 2012 年爆发是因为数据(ImageNet)、算力(GPU)、方法(ReLU、Dropout)三样同时到位。
- 「历史只是故事,对动手没帮助」。 这段历史里的每个卡点,今天都还在:梯度消失、初始化、学习率、过拟合。 知道它们曾经怎么卡住人,比记住年份有用得多。
自测三问(点开对答案)
1. 为什么单层感知器做不了异或? —— 因为异或的两类在平面上是对角交叉的,任何一条直线都切不开。 单层感知器的决策边界就是一条直线,所以结构上就做不到。 实测八个随机种子最好的也只到 0.50,等于瞎猜。 2. 两个隐单元理论上就够表达异或,为什么实测八次只成功一次? —— 因为「能表达」和「能训练出来」是两件事。 参数少时损失曲面坑洼,梯度下降容易停在不好的地方,结果强烈依赖初始值。 隐单元加到 8 个后八次全对,不是模型更聪明,是可走的路径更多了。 3. 三次浪潮分别卡在什么地方? —— 第一次卡在「多层网络不知道怎么训练」,反向传播解决了它。 第二次卡在「层一深梯度就消失」,2006 年的逐层预训练缓解了它。 第三次没有低谷,因为数据、算力、方法三样同时到位了。历史讲完了,回到一个更实际的问题: 既然传统机器学习也能做分类,为什么非要这么多层? 答案不在「精度高一点」,而在「谁来设计特征」这件事上。 下一节用同一份图像数据把两种做法摆在一起比。
- ☐ (本节完,下一节 1.3 为什么是深度学习)