准确术语:Multi-Layer Perceptron (MLP),即多层感知机。也叫 Feedforward Neural Network(前馈神经网络)。 "深度学习"只是"层数多的 MLP/CNN/RNN 等"的统称,不是一个独立的概念。
| 年份 | 事件 | 关键人物 |
|---|---|---|
| 1958 | 感知机 (Perceptron) — 单层线性分类器 | Rosenblatt |
| 1969 | 《Perceptrons》 — 证明单层感知机无法解决 XOR | Minsky & Papert |
| 1969-1986 | 神经网络寒冬 — 学术界几乎放弃 | — |
| 1986 | 反向传播 — 多层网络的训练算法 | Rumelhart, Hinton, Williams |
| 2006 | 深度信念网络 — 深层网络预训练 | Hinton |
| 2012 | AlexNet — CNN + ReLU + GPU → ImageNet 冠军 | Krizhevsky, Hinton |
链式法则(Chain Rule)是微积分的基础定理,1700 年代就有了。反向传播在数学上就是链式法则的应用。那为什么从 1958 到 1986 花了近 30 年?
Bengio 在 Quora 上的回答:
很多看似显而易见的想法只有在事后才变得显而易见。
在控制论中,很早就开始应用链式法则来解决多层非线性系统。 但在 80 年代早期,神经网络的输出是离散的,这样就无法用基于梯度的方法来优化了。
这时 Rumelhart 和 Hinton 想到,只要把输出做成平滑的 (sigmoid),就可以用链式法则来训练多层神经网络了。 刚好英伟达的显卡提供了 CUDA 的计算接口,也有了算力支撑。
所以这不仅仅是链式法则的问题,而是要跳出离散输出的框架,这种理念上的变革并不容易。
具体来说:
1958 感知机用的激活函数 — 阶跃函数 (Step Function):
f(z) = 1 if z > 0
0 if z ≤ 0
导数 = 0(几乎处处)
→ 梯度无法传播 → 多层网络无法训练
1986 Rumelhart & Hinton 的关键突破 — 换成 Sigmoid:
f(z) = 1 / (1 + e^(-z))
导数 = f(z) * (1 - f(z))
→ 处处非零 → 链式法则能一路传到底层
→ 多层网络终于能训练了!
输入编码 隐藏层 1 隐藏层 2 输出层
a=7 → [0,0,...,1,0,0]
┌─────────┐ ┌─────────┐ ┌──────────┐
concat → [20 维] ──→│W₁[20×64]│──→│W₂[64×64]│──→│W₃[64×82] │──→ Softmax → 预测
│+ b₁[64] │ │+ b₂[64] │ │+ b₃[82] │
b=8 → [0,...,0,1,0] │ ReLU │ │ ReLU │ │ │
└─────────┘ └─────────┘ └──────────┘
参数量:20×64 + 64 + 64×64 + 64 + 64×82 + 82 = 10,834
对比 Ch0 Transformer: 18,304 参数(多了 69%)
99 乘法表只有 100 个输入组合。MLP 的工作方式:
输入 (7, 8) → one-hot 拼接 [20维]
↓
W₁ 的某些行被强烈激活(检测到 "a=7" 且 "b=8" 的模式)
↓
ReLU 过滤掉不相关的神经元
↓
W₂ 进一步组合特征
↓
W₃ 的第 56 列被最强激活 → 输出 56
这就是一个可微分的查找表。不需要 token 之间的信息路由(Attention),因为所有信息已经在输入中拼接好了。
z₁ = X × W₁ + b₁ [N×20] × [20×64] = [N×64]
a₁ = ReLU(z₁) [N×64]
z₂ = a₁ × W₂ + b₂ [N×64] × [64×64] = [N×64]
a₂ = ReLU(z₂) [N×64]
z₃ = a₂ × W₃ + b₃ [N×64] × [64×82] = [N×82]
ŷ = Softmax(z₃) [N×82] — 预测概率
L = -(1/N) × Σᵢ log(ŷᵢ[targetᵢ]) — 交叉熵
Step 1: 输出层梯度
∂L/∂z₃ = ŷ - one_hot(target) [N×82]
为什么这么简洁?
L = -log(ŷ_target)
∂L/∂z₃ᵢ = ŷᵢ - 𝟙{i=target}
交叉熵 + Softmax 的导数恰好简化成这个形式。
Step 2: W₃, b₃ 的梯度
∂L/∂W₃ = a₂ᵀ × ∂L/∂z₃ [64×N]ᵀ × [N×82] = [64×82] ✓
∂L/∂b₃ = Σ ∂L/∂z₃ [82] ✓
Step 3: 梯度传到 a₂
∂L/∂a₂ = ∂L/∂z₃ × W₃ᵀ [N×82] × [82×64] = [N×64] ✓
Step 4: 穿过激活函数(关键!)
∂L/∂z₂ = ∂L/∂a₂ ⊙ ReLU'(z₂) [N×64] ⊙ [N×64] = [N×64]
ReLU'(z) = 1 if z > 0
0 if z ≤ 0
如果这里用阶跃函数:
Step'(z) = 0 几乎处处 → ∂L/∂z₂ = 0 → 梯度断裂!
这就是 Rosenblatt 的感知机无法训练多层的根本原因。
Step 5-6: 递推到 Layer 1(完全同构)
∂L/∂W₂ = a₁ᵀ × ∂L/∂z₂
∂L/∂b₂ = Σ ∂L/∂z₂
∂L/∂a₁ = ∂L/∂z₂ × W₂ᵀ
∂L/∂z₁ = ∂L/∂a₁ ⊙ ReLU'(z₁)
∂L/∂W₁ = Xᵀ × ∂L/∂z₁
∂L/∂b₁ = Σ ∂L/∂z₁
参数更新(SGD)
W ← W - lr × ∂L/∂W
b ← b - lr × ∂L/∂b
| 激活函数 | 公式 | 导数 | 收敛 epoch | 提出时间 |
|---|---|---|---|---|
| Step (阶跃) | 1 if z>0 else 0 | 0 (几乎处处) | ∞ (无法训练) | 1958 |
| Sigmoid | 1/(1+e⁻ᶻ) | σ(1-σ) | 1203 | 1986 |
| ReLU | max(0,z) | 1 or 0 | 294 | 2012 |
| GELU | z·Φ(z) | 复杂 | 588 | 2016 |
ReLU 最快的原因:正区间导数恒为 1,梯度无损传递,没有 sigmoid 的梯度消失问题。
=== 梯度检查 ===
W1 (20, 64) max_rel_err = 0.00e+00 ✅
b1 (64,) max_rel_err = 1.72e-09 ✅
W2 (64, 64) max_rel_err = 1.14e-08 ✅
b2 (64,) max_rel_err = 8.58e-08 ✅
W3 (64, 82) max_rel_err = 1.92e-07 ✅
b3 (82,) max_rel_err = 2.69e-09 ✅
数值梯度 vs 解析梯度的相对误差 < 1e-4,验证反向传播实现正确。
=== ReLU 训练 ===
Epoch 0: Loss 4.505, Acc 1%
Epoch 100: Loss 2.250, Acc 47%
Epoch 200: Loss 0.992, Acc 93%
Epoch 294: Loss 0.387, Acc 100% 🎉
=== 示例预测 ===
7×8=56 预测:56 (70.9%) ✅
3×4=12 预测:12 (60.1%) ✅
9×9=81 预测:81 (57.3%) ✅
0×5= 0 预测: 0 (99.1%) ✅
6×7=42 预测:42 (88.0%) ✅
| MLP (本章) | Transformer (Ch0) | |
|---|---|---|
| 参数量 | 10,834 | 18,304 |
| 收敛速度 | 294 epochs | ~500 steps |
| 能力 | 死记硬背 100 个答案 | 学到 token 之间的关系 |
| 输入方式 | 拼接 one-hot(位置无关) | 序列输入(位置有关) |
| 泛化性 | ❌ 只能做 1 位数 | ✅ 可扩展到多位数(Ch1-6) |
| 本质 | 查找表 | 查找表 + 路由器 |
一个自然的问题:输入必须 one-hot 吗?输出必须分类吗?
| 方案 | 输入 | 输出 | 思路 |
|---|---|---|---|
| A | one-hot [20维] | 分类 82类 (softmax+交叉熵) | 模式匹配 → 查表 |
| B | one-hot [20维] | 回归 1个数字 (MSE) | 模式匹配 → 算数 |
| C | 原始 [a,b] 2维 | 分类 82类 (softmax+交叉熵) | 曲面拟合 → 查表 |
| D | 原始 [a,b] 2维 | 回归 1个数字 (MSE) | 曲面拟合 → 算数 |
| 方案 | 能否 100% | 收敛 epoch | 参数量 | 速度对比 |
|---|---|---|---|---|
| A one-hot + 分类 | ✅ 100% | 294 | 10,834 | 基准 |
| B one-hot + 回归 | ✅ 100% | 14,123 | 8,385 | 慢 48× |
| C 2维 + 分类 | ✅ 100% | 1,155 | 27,090 | 慢 4× |
| D 2维 + 回归 | ❌ 63% | >20,000 | 16,769 | 不收敛 |
7×8=56,模型输出 54:
分类 (交叉熵):P(54) 最高 → 完全错误,惩罚 = -log(P(56)) ≈ 巨大
回归 (MSE): 输出 54 → 误差 = (54-56)² = 4 → "差一点",温和惩罚
核心区别:
分类:54 和 100 都是"错误答案",惩罚一样重
回归:54 比 100 "更接近正确",惩罚更轻
乘法要精确答案,不要"差不多" → 分类更合适
输入 a=7, b=8:
原始 2维:网络看到 [7, 8],必须学会 f(7,8)=56 这个非线性曲面
7 和 8 之间有大小关系,但乘法不是简单的线性关系
网络需要用 ReLU 去逼近一个二元乘法函数
one-hot 20维:网络看到 [0,0,0,0,0,0,0,1,0,0, 0,0,0,0,0,0,0,0,1,0]
第 7 位和第 18 位亮了
W₁ 的对应行被激活 → 直接匹配模式 → 查表输出
类比:
原始输入 = 给你两个数字,让你算乘法
one-hot = 给你 100 张卡片,每张写好答案,你只需要找对卡片
2 维输入 + 回归是最难的组合:
- 输入只有 2 个数字,信息密度最低
- 输出只有 1 个神经元,必须精确到整数
- MSE 损失对"差一点"不敏感
- 20000 epochs 后还有 37 个错误(包括 0×0=0 预测成 1)
结论:100% 硬指标下,分类(softmax + 交叉熵)是最可靠的路径。one-hot 输入进一步加速收敛。这也是为什么 Transformer 的输出层用的也是 softmax + 交叉熵。
cd courses/ch8_mlp_from_scratch
# 完整流程(训练 + 评估 + 激活函数对比 + 可视化)
python3 run.py
# 只运行核心训练
python3 mlp_multiply.py
# 只生成激活函数对比图
python3 visualize.py
| 文件 | 说明 |
|---|---|
mlp_multiply.py |
核心代码:纯 numpy MLP,每行对应公式 |
run.py |
一键入口:训练 + 评估 + 对比 + 可视化 |
visualize.py |
matplotlib 绘图:训练曲线、激活函数、权重热力图 |
如果要做 2 位数乘法(10-99 × 10-99):
| 1 位数 (本章) | 2 位数 | 3 位数 | 4 位数 | |
|---|---|---|---|---|
| 输入组合 | 100 | 8,100 | 810,000 | 81,000,000 |
| 输出类别 | 82 | 9,802 | 998,002 | 99,980,002 |
| MLP 参数 | 10K | 2.6M | ~260M | ~26B |
| 训练数据 | 100 条全部 | 8,100 条全部 | 81 万条全部 | 8100 万条全部 |
每增加一位数,参数和数据量都增长 ~100 倍。4 位数需要 260 亿参数——比 GPT-3 还大,只为了背乘法表。
MLP 的输出层大小 = 所有可能答案的数量(指数增长)。 Transformer 的输出层大小 = 字符表大小(恒定 14)。
MLP 做不了多位数,那 Transformer (2017) 发明之前呢?其实走了不少弯路:
| 年份 | 方法 | 做了什么 | 乘法结果 |
|---|---|---|---|
| 2014 | LSTM (Zaremba & Sutskever) | "Learning to Execute":LSTM 学执行简单程序 | 加法能做,乘法很差 |
| 2014 | Neural Turing Machine (Graves) | 给 RNN 加外部记忆条 | 能学 copy/sort,乘法不行 |
| 2015 | Neural GPU (Kaiser & Sutskever) | 专门为算术设计的并行架构 | 多位数乘法部分成功,但不鲁棒 |
| 2015 | Stack RNN (Joulin & Mikolov) | 给 RNN 加栈结构 | 简单模式可以,复杂算术不行 |
| 2017 | Transformer (Vaswani et al.) | 自注意力,并行处理全序列 | 架构突破 |
| 2022 | Transformer + CoT | 分步推理 | 多位数 100% |
LSTM 处理 45×67= 的方式:
时刻1: 读入 "4" → 更新隐藏状态 h₁
时刻2: 读入 "5" → 更新 h₂ (h₁ 信息开始衰减)
时刻3: 读入 "×" → 更新 h₃
时刻4: 读入 "6" → 更新 h₄ (h₁ 信息进一步衰减)
时刻5: 读入 "7" → 更新 h₅
时刻6: 读入 "=" → 开始输出
问题:到输出时,h₅ 要同时记住 4、5、6、7 和它们的位置
全靠一个固定大小的向量 h → 信息瓶颈
Transformer 处理同样的输入:
"=" 的 Attention 直接回看 "4"、"5"、"6"、"7"
不需要经过中间状态 → 没有信息衰减
所有 token 距离都是 1 → 并行直达
Transformer 单纯做乘法也不太行——直接预测 45×67=3015 仍然需要一步到位。
真正的突破是 2022 年的 Chain-of-Thought(就是 Ch1 做的):
不是让模型一步算出答案,而是让它展示推理过程:
45×67=S1:45×60=2700;S2:45×7=315;A1:2700+315=3015;Z3015
每一步只需要 1 位数运算 → MLP 级别的查表就够了
Attention 负责路由中间结果 → 把各步串起来
MLP (1986): 能查表,不能推理 ← 本章
↓
RNN/LSTM (2014): 能按顺序推理,但信息衰减
↓
Transformer (2017): 能并行访问全部历史,但单步推理有限 ← Ch0
↓
Transformer + CoT: 把复杂推理拆成简单步骤 ← Ch1
↓ 每步用 Attention 路由 + FFN 查表
Ch2-Ch6: RoPE / KV Cache / FlashAttention / MLA+MoE
优化速度、压缩内存、扩展规模