跳转至

神经网络基础

神经网络是深度学习的基石。理解了感知机、多层感知机、激活函数和反向传播,就掌握了深度学习的"底层语法"。


1. 感知机(Perceptron)

感知机是最简单的神经网络,由 Frank Rosenblatt 在 1957 年提出。它只有一个神经元,做的事情很简单:接收输入,加权求和,通过激活函数输出结果

\[ y = f\left(\sum_{i=1}^{n} w_i x_i + b\right) \]

其中 \(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))

感知机的学习规则

当预测错误时,按如下规则更新权重:

\[ w_i \leftarrow w_i + \eta \cdot (y_{\text{true}} - y_{\text{pred}}) \cdot x_i \]

其中 \(\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

每一层的计算:

\[ \mathbf{h} = \sigma(\mathbf{W}^{(1)}\mathbf{x} + \mathbf{b}^{(1)}), \quad \mathbf{y} = \mathbf{W}^{(2)}\mathbf{h} + \mathbf{b}^{(2)} \]

万能逼近定理(Universal Approximation Theorem)

一个含有足够多神经元的单隐藏层 MLP,可以以任意精度逼近任何连续函数。这意味着 MLP 理论上能学到任何模式——但"足够多"可能意味着天文数字的神经元,这就是为什么我们需要深度网络:用更多层来高效表示复杂函数。

为什么需要"深度"?

对比 宽而浅的网络 窄而深的网络
参数量 大量神经元,参数多 每层较少神经元,总参数可控
表达能力 理论上够,实际效率低 逐层抽象,表达更高效
类比 一个人做所有工作 流水线分工合作

直觉理解

识别一张人脸照片:浅层网络试图直接从像素跳到"这是张三";深层网络先学边缘 → 再学五官 → 再组合成人脸 → 最后识别身份。后者显然更合理。


3. 激活函数详解

激活函数是神经网络的"灵魂"。没有它,无论网络多少层,本质上都只是一个线性变换(多个线性变换的复合仍然是线性的)。

为什么需要非线性?

假设没有激活函数,两层网络的输出为:

\[ \mathbf{y} = \mathbf{W}^{(2)}(\mathbf{W}^{(1)}\mathbf{x}) = (\mathbf{W}^{(2)}\mathbf{W}^{(1)})\mathbf{x} = \mathbf{W}'\mathbf{x} \]

结果等价于一个单层线性网络!加入非线性激活函数后,每一层才能真正增加网络的表达能力。

常见激活函数

Sigmoid

\[ \sigma(z) = \frac{1}{1 + e^{-z}} \in (0, 1) \]
  • ✅ 输出可以解释为概率
  • 梯度消失:当 \(|z|\) 很大时,梯度趋近于 0,深层网络几乎无法训练
  • 非零中心:输出恒正,导致梯度更新时出现 zig-zag 现象

Tanh

\[ \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} \in (-1, 1) \]
  • ✅ 零中心化,比 Sigmoid 好
  • ❌ 仍有梯度消失问题(只是比 Sigmoid 轻一些)

ReLU(Rectified Linear Unit)

\[ \text{ReLU}(z) = \max(0, z) \]
  • ✅ 计算简单,收敛速度快
  • ✅ 有效缓解梯度消失
  • Dead ReLU:如果神经元输出一直为负,梯度永远是 0,这个神经元就"死了"

Leaky ReLU & ELU

为了解决 Dead ReLU 问题:

\[ \text{Leaky ReLU}(z) = \begin{cases} z & z > 0 \\ \alpha z & z \le 0 \end{cases}, \quad \alpha = 0.01 \]
\[ \text{ELU}(z) = \begin{cases} z & z > 0 \\ \alpha(e^z - 1) & z \le 0 \end{cases} \]

GELU & Swish

现代 Transformer 架构常用的激活函数:

\[ \text{GELU}(z) = z \cdot \Phi(z) \approx z \cdot \sigma(1.702z) \]
\[ \text{Swish}(z) = z \cdot \sigma(\beta z) \]

其中 \(\Phi(z)\) 是标准正态分布的累积分布函数。GELU 是 BERT、GPT 等模型的默认选择。

如何选择激活函数?

场景 推荐 理由
隐藏层(通用) ReLU 简单高效,默认首选
隐藏层(防死神经元) Leaky ReLU / ELU 负值区域有小梯度
Transformer 隐藏层 GELU 平滑近似,效果更好
二分类输出层 Sigmoid 输出概率 \(\in (0,1)\)
多分类输出层 Softmax 输出概率分布,和为 1
回归输出层 无(恒等函数) 直接输出实数值

4. 前向传播(Forward Propagation)

前向传播是神经网络"做预测"的过程。数据从输入层出发,逐层计算,直到输出层。

以一个两层 MLP 为例(输入 → 隐藏层 → 输出层):

第 1 步:隐藏层计算

\[ \mathbf{z}^{(1)} = \mathbf{W}^{(1)}\mathbf{x} + \mathbf{b}^{(1)}, \quad \mathbf{a}^{(1)} = \text{ReLU}(\mathbf{z}^{(1)}) \]

第 2 步:输出层计算

\[ \mathbf{z}^{(2)} = \mathbf{W}^{(2)}\mathbf{a}^{(1)} + \mathbf{b}^{(2)}, \quad \hat{\mathbf{y}} = \text{Softmax}(\mathbf{z}^{(2)}) \]

第 3 步:计算损失

\[ L = -\sum_{c} y_c \log \hat{y}_c \quad (\text{交叉熵损失}) \]

5. 反向传播(Backpropagation)

反向传播是深度学习最核心的算法,由 Rumelhart 等人在 1986 年提出。它用链式法则高效计算损失函数对每个参数的梯度。

核心思想

知道最终的误差是多少后,把这个误差按贡献比例分摊给每一层的每一个参数。

以单个神经元为例

假设有一个简单的计算图:\(x \xrightarrow{w} z = wx + b \xrightarrow{\sigma} a = \sigma(z) \xrightarrow{} L\)

根据链式法则:

\[ \frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} \cdot \frac{\partial z}{\partial w} \]

其中:

  • \(\frac{\partial L}{\partial a}\):损失对输出的敏感度(从后面传来的)
  • \(\frac{\partial a}{\partial z} = \sigma'(z)\):激活函数的导数(这就是为什么激活函数的梯度很重要!
  • \(\frac{\partial z}{\partial w} = x\):输入值

多层网络的反向传播

设网络有 \(L\) 层,反向传播从最后一层开始向前计算:

步骤 1:计算输出层的梯度

\[ \boldsymbol{\delta}^{(L)} = \frac{\partial L}{\partial \mathbf{z}^{(L)}} = \hat{\mathbf{y}} - \mathbf{y} \quad \text{(交叉熵 + Softmax 的简洁结果)} \]

步骤 2:逐层向前传递误差

\[ \boldsymbol{\delta}^{(l)} = (\mathbf{W}^{(l+1)})^T \boldsymbol{\delta}^{(l+1)} \odot \sigma'(\mathbf{z}^{(l)}) \]

步骤 3:计算参数梯度

\[ \frac{\partial L}{\partial \mathbf{W}^{(l)}} = \boldsymbol{\delta}^{(l)} (\mathbf{a}^{(l-1)})^T, \quad \frac{\partial L}{\partial \mathbf{b}^{(l)}} = \boldsymbol{\delta}^{(l)} \]

步骤 4:更新参数

\[ \mathbf{W}^{(l)} \leftarrow \mathbf{W}^{(l)} - \eta \frac{\partial L}{\partial \mathbf{W}^{(l)}} \]

梯度消失与梯度爆炸

反向传播的每一层都要乘以激活函数的导数 \(\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 函数

将一组实数转化为概率分布,常用于多分类任务的输出层:

\[ \text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} \]

性质:所有输出 \(\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)。