📗 第1章 绪论
📄 一句话概括

一个只有四个样本的问题——异或——把神经网络卡了十几年。 没有隐层时,八个随机种子最好的那个也只到 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 秒实验

  1. 把 init_mlp 的初始化标准差从 sqrt(2 / fan_in) 改成 0.01,重跑。 隐层 8 个那一行也会掉下来。 初始化太小,信号一层层缩水,梯度传不回去——这就是后来「初始化方法」成为一门学问的原因。
  2. 把 train 的 lr 从 0.5 改成 5.0,重跑。会出现准确率乱跳甚至变成 0.50 的情况。 学习率太大,更新一步就跨过了谷底。 这个旋钮在第 2.9 节会专门讲。
  3. 把隐层个数扫到 16、32,重跑。八个种子的成功率不会一直涨,会在某个点饱和。 异或只有四个样本,容量过剩之后没有额外好处。

常见误区

  1. 「1969 年明斯基证明了神经网络不行」。 他证明的是「单层感知器表达不了异或」,这个结论完全正确。 被误传成「神经网络不行」,是后人的过度推广。 多层网络从来都能表达异或,只是当时训练不出来。
  2. 「理论上能表达,就一定能训练出来」。 实测隐层 2 个(理论够用)八次只成功一次。 表达力是「存在一组参数能做到」,可训练性是「梯度下降能不能找到那组参数」。 两件事之间隔着一整个优化问题。
  3. 「深度学习是 2012 年突然出现的」。 反向传播 1986 年就普及了,卷积网络的雏形在 1980 年代末就有。 2012 年爆发是因为数据(ImageNet)、算力(GPU)、方法(ReLU、Dropout)三样同时到位。
  4. 「历史只是故事,对动手没帮助」。 这段历史里的每个卡点,今天都还在:梯度消失、初始化、学习率、过拟合。 知道它们曾经怎么卡住人,比记住年份有用得多。
自测三问(点开对答案) 1. 为什么单层感知器做不了异或? —— 因为异或的两类在平面上是对角交叉的,任何一条直线都切不开。 单层感知器的决策边界就是一条直线,所以结构上就做不到。 实测八个随机种子最好的也只到 0.50,等于瞎猜。 2. 两个隐单元理论上就够表达异或,为什么实测八次只成功一次? —— 因为「能表达」和「能训练出来」是两件事。 参数少时损失曲面坑洼,梯度下降容易停在不好的地方,结果强烈依赖初始值。 隐单元加到 8 个后八次全对,不是模型更聪明,是可走的路径更多了。 3. 三次浪潮分别卡在什么地方? —— 第一次卡在「多层网络不知道怎么训练」,反向传播解决了它。 第二次卡在「层一深梯度就消失」,2006 年的逐层预训练缓解了它。 第三次没有低谷,因为数据、算力、方法三样同时到位了。

历史讲完了,回到一个更实际的问题: 既然传统机器学习也能做分类,为什么非要这么多层? 答案不在「精度高一点」,而在「谁来设计特征」这件事上。 下一节用同一份图像数据把两种做法摆在一起比。

相关笔记