神经网络基础¶
神经网络是深度学习的基石。理解了感知机、多层感知机、激活函数和反向传播,就掌握了深度学习的"底层语法"。
1. 感知机(Perceptron)¶
感知机是最简单的神经网络,由 Frank Rosenblatt 在 1957 年提出。它只有一个神经元,做的事情很简单:接收输入,加权求和,通过激活函数输出结果。
其中 \(f\) 是阶跃函数(输出 0 或 1)。
感知机能做什么?
感知机可以实现线性可分的逻辑运算,比如 AND、OR。但它无法处理 XOR(异或) 问题——这是感知机的致命局限,因为 XOR 不是线性可分的。
graph LR
X1((x₁)) -->|w₁| N[Σ + b]
X2((x₂)) -->|w₂| N
X3((x₃)) -->|w₃| N
N -->|f| Y((y))
感知机的学习规则¶
当预测错误时,按如下规则更新权重:
其中 \(\eta\) 是学习率。本质是:错了就调,调的方向让结果更接近正确答案。
2. 多层感知机(MLP)¶
为了解决感知机无法处理非线性问题的缺陷,研究者在输入层和输出层之间加入了隐藏层(Hidden Layer),构成多层感知机。
graph LR
subgraph 输入层
I1((x₁))
I2((x₂))
I3((x₃))
end
subgraph 隐藏层
H1((h₁))
H2((h₂))
H3((h₃))
H4((h₄))
end
subgraph 输出层
O1((y₁))
O2((y₂))
end
I1 --> H1; I1 --> H2; I1 --> H3; I1 --> H4
I2 --> H1; I2 --> H2; I2 --> H3; I2 --> H4
I3 --> H1; I3 --> H2; I3 --> H3; I3 --> H4
H1 --> O1; H1 --> O2
H2 --> O1; H2 --> O2
H3 --> O1; H3 --> O2
H4 --> O1; H4 --> O2
每一层的计算:
万能逼近定理(Universal Approximation Theorem)
一个含有足够多神经元的单隐藏层 MLP,可以以任意精度逼近任何连续函数。这意味着 MLP 理论上能学到任何模式——但"足够多"可能意味着天文数字的神经元,这就是为什么我们需要深度网络:用更多层来高效表示复杂函数。
为什么需要"深度"?¶
| 对比 | 宽而浅的网络 | 窄而深的网络 |
|---|---|---|
| 参数量 | 大量神经元,参数多 | 每层较少神经元,总参数可控 |
| 表达能力 | 理论上够,实际效率低 | 逐层抽象,表达更高效 |
| 类比 | 一个人做所有工作 | 流水线分工合作 |
直觉理解
识别一张人脸照片:浅层网络试图直接从像素跳到"这是张三";深层网络先学边缘 → 再学五官 → 再组合成人脸 → 最后识别身份。后者显然更合理。
3. 激活函数详解¶
激活函数是神经网络的"灵魂"。没有它,无论网络多少层,本质上都只是一个线性变换(多个线性变换的复合仍然是线性的)。
为什么需要非线性?¶
假设没有激活函数,两层网络的输出为:
结果等价于一个单层线性网络!加入非线性激活函数后,每一层才能真正增加网络的表达能力。
常见激活函数¶
Sigmoid¶
- ✅ 输出可以解释为概率
- ❌ 梯度消失:当 \(|z|\) 很大时,梯度趋近于 0,深层网络几乎无法训练
- ❌ 非零中心:输出恒正,导致梯度更新时出现 zig-zag 现象
Tanh¶
- ✅ 零中心化,比 Sigmoid 好
- ❌ 仍有梯度消失问题(只是比 Sigmoid 轻一些)
ReLU(Rectified Linear Unit)¶
- ✅ 计算简单,收敛速度快
- ✅ 有效缓解梯度消失
- ❌ Dead ReLU:如果神经元输出一直为负,梯度永远是 0,这个神经元就"死了"
Leaky ReLU & ELU¶
为了解决 Dead ReLU 问题:
GELU & Swish¶
现代 Transformer 架构常用的激活函数:
其中 \(\Phi(z)\) 是标准正态分布的累积分布函数。GELU 是 BERT、GPT 等模型的默认选择。
如何选择激活函数?¶
| 场景 | 推荐 | 理由 |
|---|---|---|
| 隐藏层(通用) | ReLU | 简单高效,默认首选 |
| 隐藏层(防死神经元) | Leaky ReLU / ELU | 负值区域有小梯度 |
| Transformer 隐藏层 | GELU | 平滑近似,效果更好 |
| 二分类输出层 | Sigmoid | 输出概率 \(\in (0,1)\) |
| 多分类输出层 | Softmax | 输出概率分布,和为 1 |
| 回归输出层 | 无(恒等函数) | 直接输出实数值 |
4. 前向传播(Forward Propagation)¶
前向传播是神经网络"做预测"的过程。数据从输入层出发,逐层计算,直到输出层。
以一个两层 MLP 为例(输入 → 隐藏层 → 输出层):
第 1 步:隐藏层计算
第 2 步:输出层计算
第 3 步:计算损失
5. 反向传播(Backpropagation)¶
反向传播是深度学习最核心的算法,由 Rumelhart 等人在 1986 年提出。它用链式法则高效计算损失函数对每个参数的梯度。
核心思想¶
知道最终的误差是多少后,把这个误差按贡献比例分摊给每一层的每一个参数。
以单个神经元为例¶
假设有一个简单的计算图:\(x \xrightarrow{w} z = wx + b \xrightarrow{\sigma} a = \sigma(z) \xrightarrow{} L\)
根据链式法则:
其中:
- \(\frac{\partial L}{\partial a}\):损失对输出的敏感度(从后面传来的)
- \(\frac{\partial a}{\partial z} = \sigma'(z)\):激活函数的导数(这就是为什么激活函数的梯度很重要!)
- \(\frac{\partial z}{\partial w} = x\):输入值
多层网络的反向传播¶
设网络有 \(L\) 层,反向传播从最后一层开始向前计算:
步骤 1:计算输出层的梯度
步骤 2:逐层向前传递误差
步骤 3:计算参数梯度
步骤 4:更新参数
梯度消失与梯度爆炸
反向传播的每一层都要乘以激活函数的导数 \(\sigma'(z)\)。如果这个值一直 < 1(如 Sigmoid),梯度会指数级衰减(梯度消失);如果 > 1,梯度会指数级增长(梯度爆炸)。这是深度网络训练困难的根本原因,也是 ReLU、残差连接、Batch Norm 等技术被发明的动机。
6. 权重初始化¶
好的初始化策略对训练至关重要。如果权重都初始化为 0,所有神经元的输出和梯度都一样,网络永远无法学到不同的特征(对称性问题)。
常用初始化方法¶
| 方法 | 初始化公式 | 适用激活函数 |
|---|---|---|
| Xavier(Glorot) | \(w \sim \mathcal{N}(0, \frac{2}{n_{\text{in}} + n_{\text{out}}})\) | Sigmoid、Tanh |
| He(Kaiming) | \(w \sim \mathcal{N}(0, \frac{2}{n_{\text{in}}})\) | ReLU 系列 |
原理
核心目标是让每一层输出的方差保持稳定,既不爆炸也不消失。Xavier 假设激活函数是线性的,He 初始化则考虑了 ReLU 会把一半值置零的特性。
7. Softmax 与输出层设计¶
Softmax 函数¶
将一组实数转化为概率分布,常用于多分类任务的输出层:
性质:所有输出 \(\in (0, 1)\) 且和为 1。
数值稳定性
实际实现中,为了防止指数溢出,通常会先减去最大值:\(\text{Softmax}(z_i) = \frac{e^{z_i - \max(z)}}{\sum_j e^{z_j - \max(z)}}\)
输出层设计总结¶
| 任务类型 | 输出层激活 | 损失函数 | 输出含义 |
|---|---|---|---|
| 二分类 | Sigmoid | 二元交叉熵(BCE) | 正类概率 |
| 多分类(互斥) | Softmax | 交叉熵(CE) | 各类概率分布 |
| 多标签分类 | Sigmoid(每个输出) | 多个 BCE | 各标签独立概率 |
| 回归 | 无(恒等) | MSE / MAE | 连续实数值 |
8. 计算图与自动微分¶
现代深度学习框架(PyTorch、TensorFlow)的核心能力是自动微分(Autograd):你只需定义前向计算过程,框架自动帮你完成反向传播。
计算图¶
每一步前向计算都会被记录为一个有向无环图(DAG):
x ──┐
├── z = wx + b ──→ a = ReLU(z) ──→ L = loss(a, y)
w ──┘ ↑
b ──────────────────────────────────────────┘
反向传播时,框架沿着这个图从 \(L\) 反向遍历,自动用链式法则计算每个节点的梯度。
PyTorch 示例¶
import torch
import torch.nn as nn
# 定义一个简单的两层网络
model = nn.Sequential(
nn.Linear(784, 128), # 输入层 → 隐藏层
nn.ReLU(),
nn.Linear(128, 10) # 隐藏层 → 输出层(10 类)
)
# 前向传播
x = torch.randn(32, 784) # 一批 32 个样本
logits = model(x) # 前向传播
loss = nn.CrossEntropyLoss()(logits, labels)
# 反向传播(一行搞定!)
loss.backward() # 自动计算所有参数的梯度
# 梯度存储在参数的 .grad 属性中
for name, param in model.named_parameters():
print(f"{name}: grad shape = {param.grad.shape}")
PyTorch 的动态计算图
PyTorch 使用动态计算图(Define-by-Run),每次前向传播都重新构建计算图,调试方便、灵活性高。TensorFlow 1.x 使用静态图(Define-and-Run),需要先定义图再执行,2.x 之后默认也切换到了动态图(Eager Mode)。