本文迁移自 mindcarver/91ai · 原始位置
docs/machine-learning/stage2-deep-learning/01-neural-network-intro.md· 由 @阿新聊ai 整理。
神经网络入门:从感知机到多层网络,通用近似定理,以及为什么需要深层
TL;DR: 感知机是神经网络的最小单元——一个线性函数加一个阈值判断。单层感知机只能解决线性可分问题(连 XOR 都搞不定),但把多层感知机堆叠起来就能逼近任何连续函数——这就是通用近似定理。然而浅层网络要逼近复杂函数可能需要指数级的神经元,而深层网络通过层级组合可以用少得多的参数达到同样的效果。现实世界的数据(图像、语言、声音)天然具有层级结构,这就是深度学习有效的原因。
为什么这很重要
1957 年,Frank Rosenblatt 发明了感知机(Perceptron),被认为是人工智能的起点之一。1969 年,Minsky 和 Papert 在《Perceptrons》一书中证明了单层感知机无法解决 XOR(异或)问题。这个结论导致了对神经网络的第一次寒冬——很多人认为神经网络是死胡同。
问题的本质是:单层感知机只能画一条直线来分开数据。如果数据不能用一条直线分开(像 XOR 那样),单层感知机就无能为力。
但解决方案其实很简单:再加一层。多层感知机(MLP)通过组合多个线性决策边界,可以逼近任意复杂的决策边界。1989 年,Cybenko 在数学上证明了这个直觉:带一个隐层的前馈网络可以逼近任意连续函数——通用近似定理(Universal Approximation Theorem,UAT)。
但通用近似定理有一个重要的限制:它只说"存在"这样一个网络,没说这个网络有多大。实际上,对于某些复杂函数,浅层网络可能需要指数级数量的神经元,而深层网络只需要多项式级的参数就能做到同样好。这就是为什么我们需要深层网络。
核心概念
感知机:最简单的神经元
感知机是最简单的神经网络,它做的事情可以用一句话概括:计算输入的加权和,如果超过阈值就输出 1,否则输出 0。
$$ \hat{y} = \begin{cases} 1 & \text{if } \sum_{i} w_i x_i + b > 0 \ 0 & \text{otherwise} \end{cases} $$
其中 $w_i$ 是权重(每个输入的重要性),$b$ 是偏置(阈值),$x_i$ 是输入。
日常类比:感知机就像一个做决定的人。他听取多个意见(输入),每个意见有不同的可信度(权重),然后做一个综合判断。如果综合得分超过某个标准(阈值),他就做出肯定的决定;否则拒绝。
感知机能做什么:
- AND 运算(x1 AND x2)
- OR 运算(x1 OR x2)
- 任何线性可分的分类问题
感知机不能做什么:
- XOR 运算(x1 XOR x2)——不能用一条直线分开
- 任何非线性可分的问题
AND 运算(线性可分) XOR 运算(非线性可分)
x2 x2
↑ ↑
1 | × ● 1 | × ● ← 无法画一条直线
| | 把 × 和 ● 分开
0 | × × 0 | ● ×
+——→ x1 +——→ x1
0 1 0 1
● = 输出 1 ● = 输出 1
× = 输出 0 × = 输出 0
多层感知机(MLP)
多层感知机的解决方案是:让多个感知机先各自画一条线,然后用另一层感知机来组合这些线的结果。
对于 XOR 问题:
- 第一层的感知机 A 学到:x1 OR x2(上面的线)
- 第一层的感知机 B 学到:NOT (x1 AND x2)(下面的线)
- 第二层的感知机 C 学到:A AND B
结果:C 的输出就是 x1 XOR x2。
多层感知机解决 XOR:
输入层 隐藏层 输出层
x1 ──→ (感知机 A: OR) ──┐
\ ├──→ (感知机 C: AND) ──→ 输出
x2 ──→ (感知机 B: NAND) ─┘
真值表:
x1 x2 | A(OR) B(NAND) | C(A AND B)
0 0 | 0 1 | 0 ✓
0 1 | 1 1 | 1 ✓
1 0 | 1 1 | 1 ✓
1 1 | 1 0 | 0 ✓
这就是多层网络的核心思想:每一层学习一个更高级别的特征,层与层之间通过组合产生更复杂的功能。
通用近似定理
1989 年,Cybenko 证明了一个惊人的结果:
定理(简化版):给定任意连续函数 $f: [0,1]^n \to [0,1]$ 和任意精度 $\epsilon > 0$,存在一个带单个隐藏层的前馈网络,使得对于所有 $x \in [0,1]^n$:
$$ |f(x) - \hat{f}(x)| < \epsilon $$
换句话说:一个隐藏层足够了。 只要隐藏层足够宽(有足够多的神经元),你可以逼近任何连续函数到任意精度。
但这里有一个巨大的"但是":定理说"存在"这样一个网络,但没告诉你:
- 需要多少个神经元(可能是指数级的)
- 能不能通过梯度下降学到这些权重
- 实际需要多少训练数据
为什么需要深层网络
既然一个隐藏层就够了,为什么我们要用几十层甚至几百层的深度网络?
关键洞察来自 Telgarsky (2016) 的结果:存在一类函数,深度为 $O(k)$ 的网络可以用 $O(k)$ 个神经元表示,但深度为 $O(1)$ 的网络需要 $O(2^k)$ 个神经元。
换句话说,对于某些函数,深层网络比浅层网络指数级更高效。
直觉解释:
考虑一个反复应用同一个简单操作的任务。比如判断一个 10 位二进制数的奇偶性。
浅层网络的方法:直接从 10 个输入映射到输出。需要学习所有 $2^{10} = 1024$ 种输入模式。
深层网络的方法:
- 第 1 层:判断前 2 位的奇偶性 → 5 个中间结果
- 第 2 层:合并相邻的结果 → 3 个中间结果
- 第 3 层:继续合并 → 2 个结果
- 第 4 层:最终合并 → 输出
每层只需要 $O(n)$ 个神经元,总共 $O(n \log n)$ 个参数。而浅层网络可能需要 $O(2^n)$ 个参数。
现实世界的类比:
想象你要识别一张图片里是不是有猫。
浅层方法:直接从像素映射到"猫/非猫"。这需要记住所有可能的猫的像素组合——不同的姿势、颜色、大小、角度、光照……组合爆炸。
深层方法:
- 第 1 层:检测边缘(水平线、垂直线、对角线)
- 第 2 层:组合边缘检测简单形状(角、圆弧、交叉线)
- 第 3 层:组合形状检测部件(眼睛、耳朵、爪子的轮廓)
- 第 4 层:组合部件检测完整对象(猫的脸、猫的身体)
每一层都在前一层的输出上构建更高级的抽象。这种方式更高效,因为它重用了低层的计算。
现实世界数据的层级结构:
很多自然数据天然具有层级/组合结构:
| 数据类型 | 层级结构 |
|---|---|
| 图像 | 像素 → 边缘 → 纹理 → 部件 → 对象 → 场景 |
| 语言 | 字符 → 词 → 短语 → 句子 → 段落 → 文章 |
| 语音 | 声波 → 频谱 → 音素 → 音节 → 词 → 句子 |
| 音乐 | 音符 → 和弦 → 乐句 → 乐段 → 乐章 |
深度学习的成功,本质上是因为网络的结构匹配了数据的层级结构。
工作原理(简化的心智模型)
神经网络是乐高积木
把神经网络想象成乐高积木。
单个神经元:一块最简单的乐高积木——一个小方块。它能做的事情很有限。
单层网络:把很多小方块排成一排。你可以用它们拼出一条直线(线性决策边界)。
多层网络:把一层积木叠在另一层上。每一层在上一层的输出上构建更复杂的结构。第一层拼出简单形状,第二层组合简单形状成复杂形状,第三层组合复杂形状成更复杂的结构。
关键洞察:深度比宽度更重要。100 块积木叠 10 层,比 1000 块积木铺 1 层能拼出更复杂的结构。因为每一层都在前面层的成果上构建,实现了计算的层级重用。
前向传播:从输入到输出
前向传播(Forward Propagation)是信息从输入层经过隐藏层传到输出层的过程。
对于一个 3 层网络(1 个输入层、1 个隐藏层、1 个输出层):
$$ \begin{aligned} \mathbf{h} &= \sigma(W_1 \mathbf{x} + \mathbf{b}_1) \quad \text{(隐藏层)}\ \hat{\mathbf{y}} &= \sigma(W_2 \mathbf{h} + \mathbf{b}_2) \quad \text{(输出层)} \end{aligned} $$
其中:
- $\mathbf{x}$ 是输入向量
- $W_1, \mathbf{b}_1$ 是第一层的权重和偏置
- $\sigma$ 是激活函数(如 ReLU、Sigmoid)
- $\mathbf{h}$ 是隐藏层的输出
- $W_2, \mathbf{b}_2$ 是第二层的权重和偏置
- $\hat{\mathbf{y}}$ 是网络的输出
矩阵视角:
假设输入是 784 维(28×28 像素的图片),隐藏层有 128 个神经元,输出是 10 维(数字 0-9 的概率)。
输入 x: [784 × 1]
权重 W1: [128 × 784] → 隐藏层 h: [128 × 1]
权重 W2: [10 × 128] → 输出 y: [10 × 1]
总参数量: 784×128 + 128 + 10×128 + 10 = 101,770
网络的几何理解
一个理解神经网络的强大方式是几何视角:
- 每一层的线性变换($W\mathbf{x} + \mathbf{b}$)做了两件事:旋转空间(通过 $W$)和平移空间(通过 $\mathbf{b}$)
- 激活函数(如 ReLU)对空间做了非线性扭曲(折叠、弯曲)
- 整个网络就是一系列旋转、平移和非线性扭曲的组合
原始数据空间 第 1 层变换后 第 2 层变换后
× × × × × × ×
× × × → 弯曲空间 → × × × → 再弯曲 → ● ● ●
● ● ● ● ● ● × 和 ● 分开了!
这就是为什么神经网络能处理非线性问题:通过层层的非线性变换,网络把原始空间"扭曲"成一个新的空间,在这个新空间里,数据变成线性可分的了。
工作原理(详细机制)
感知机的数学定义
感知机的数学模型:
$$ \hat{y} = \text{step}\left(\sum_{i=1}^{n} w_i x_i + b\right) = \text{step}(\mathbf{w}^T \mathbf{x} + b) $$
其中 step 函数是:
$$ \text{step}(z) = \begin{cases} 1 & z > 0 \ 0 & z \leq 0 \end{cases} $$
感知机的几何意义:$\mathbf{w}^T \mathbf{x} + b = 0$ 定义了输入空间中的一个超平面(在 2D 中是一条直线,在 3D 中是一个平面)。感知机的任务就是找到正确的超平面来分开数据。
多层感知机的完整定义
一个 $L$ 层的前馈网络定义为:
$$ \begin{aligned} \mathbf{h}^{(0)} &= \mathbf{x} \ \mathbf{h}^{(l)} &= \sigma\left(W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)}\right), \quad l = 1, \ldots, L-1 \ \hat{\mathbf{y}} &= \sigma_{\text{out}}\left(W^{(L)} \mathbf{h}^{(L-1)} + \mathbf{b}^{(L)}\right) \end{aligned} $$
其中:
- $\mathbf{h}^{(l)}$ 是第 $l$ 层的输出
- $W^{(l)}$ 是第 $l$ 层的权重矩阵
- $\mathbf{b}^{(l)}$ 是第 $l$ 层的偏置向量
- $\sigma$ 是隐藏层激活函数(通常是 ReLU)
- $\sigma_{\text{out}}$ 是输出层激活函数(回归用恒等函数,二分类用 Sigmoid,多分类用 Softmax)
通用近似定理的形式化
定理(Cybenko, 1989; Hornik, 1991):
设 $\sigma$ 是一个非常数、有界、连续的激活函数。则对于任意连续函数 $f: [0,1]^n \to \mathbb{R}$ 和任意 $\epsilon > 0$,存在 $N \in \mathbb{N}$,权重 $\mathbf{w}_i \in \mathbb{R}^n$,$b_i, \alpha_i \in \mathbb{R}$($i = 1, \ldots, N$),使得:
$$ \left| f(\mathbf{x}) - \sum_{i=1}^{N} \alpha_i \sigma(\mathbf{w}_i^T \mathbf{x} + b_i) \right| < \epsilon, \quad \forall \mathbf{x} \in [0,1]^n $$
定理的含义:
- 右边就是单隐藏层网络的输出
- 定理说:只要隐藏层足够宽($N$ 足够大),你可以逼近任何连续函数
- 这是存在性定理:它说"存在"这样的网络,但不告诉你怎么找到它
UAT 的常见误解:
误解 1:"UAT 说明我们只需要一个隐藏层就行了。" → 理论上是的,但实践中 $N$ 可能大到无法计算。深度网络用更少的参数达到同样的效果。
误解 2:"UAT 说明神经网络可以学到任何函数。" → UAT 只说"存在"一组权重可以逼近目标函数。能不能通过梯度下降学到这组权重,是另一个问题。
误解 3:"UAT 说明网络越大越好。" → 虽然理论上更大的网络可以逼近得更精确,但更大的网络也更容易过拟合,更难训练。
深度的指数优势
Telgarsky (2016) 的结果(简化版):
对于任意的 $k$,存在一个函数 $f_k: \mathbb{R} \to \mathbb{R}$,满足:
- $f_k$ 可以被深度为 $O(k)$、每层宽度为 $O(1)$ 的网络精确计算
- 但任何深度为 $O(\log k)$ 的网络至少需要 $O(2^k / k)$ 个神经元才能近似 $f_k$
具体的构造:考虑函数 $f(x) = \text{triangle}^{(k)}(x)$,即三角波函数反复嵌套 $k$ 次。
- 这个函数有一个自然的层级结构:每一层做一次"折叠"
- 深度为 $k$ 的网络可以通过逐层计算来精确表示这个函数
- 浅层网络需要指数级的神经元,因为它必须"一次性"学会所有折叠
现实世界的对应:
很多实际函数也有这种层级结构。比如图像识别:
- 判断"两个像素是否构成一条边缘"→ 简单函数
- 判断"两条边缘是否构成一个角"→ 组合上面的函数
- 判断"几个角是否构成一只眼睛"→ 组合上面的函数
- 判断"眼睛、耳朵、鼻子是否构成猫脸"→ 组合上面的函数
每一层都在做简单操作,但通过层级组合产生了非常复杂的功能。深层网络天然匹配这种结构。
为什么没有激活函数就不行
如果所有激活函数都是线性的($\sigma(x) = x$),那么不管你堆多少层,整个网络等价于一个单层线性变换。
证明:对于两层线性网络:
$$ \begin{aligned} \mathbf{h} &= W_1 \mathbf{x} + \mathbf{b}_1 \ \hat{\mathbf{y}} &= W_2 \mathbf{h} + \mathbf{b}_2 \ &= W_2(W_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}2 \ &= \underbrace{W_2 W_1}{W'} \mathbf{x} + \underbrace{W_2 \mathbf{b}_1 + \mathbf{b}2}{\mathbf{b}'} \end{aligned} $$
结果还是 $\hat{\mathbf{y}} = W' \mathbf{x} + \mathbf{b}'$,一个线性变换。不管堆多少层都是如此。
这就是为什么激活函数必须是非线性的。非线性激活函数让每一层都能对空间做"弯曲"和"折叠",从而产生复杂的决策边界。没有非线性,深层网络就退化成了单层线性模型。
容量与泛化
网络的容量(capacity)是它能表示的函数族的复杂程度。容量由两个因素决定:
- 宽度:每层神经元的数量
- 深度:层数
容量和泛化之间存在权衡:
- 容量太小 → 欠拟合(模型无法表达目标函数)
- 容量太大 → 过拟合(模型记住了训练数据的噪声)
但深度学习的一个反直觉现象是:有时候更大的模型反而泛化更好。 这和我们之前讨论的 Double Descent 现象一致——在越过插值阈值后,更大的模型反而测试误差更低。
代码示例
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons, make_circles
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')
np.random.seed(42)
# ============================================================
# 1. 感知机实现
# ============================================================
print("=" * 70)
print("1. 感知机(Perceptron)")
print("=" * 70)
class Perceptron:
"""简单的感知机实现"""
def __init__(self, lr=0.01, n_epochs=100):
self.lr = lr
self.n_epochs = n_epochs
def fit(self, X, y):
n_samples, n_features = X.shape
self.w = np.zeros(n_features)
self.b = 0
for epoch in range(self.n_epochs):
errors = 0
for i in range(n_samples):
y_pred = 1 if (np.dot(self.w, X[i]) + self.b) > 0 else 0
error = y[i] - y_pred
if error != 0:
self.w += self.lr * error * X[i]
self.b += self.lr * error
errors += 1
if errors == 0:
break
return self
def predict(self, X):
return (np.dot(X, self.w) + self.b > 0).astype(int)
# AND 门
X_and = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_and = np.array([0, 0, 0, 1])
perceptron = Perceptron(lr=0.1, n_epochs=100)
perceptron.fit(X_and, y_and)
print(f"AND 门: {perceptron.predict(X_and)} (期望 [0, 0, 0, 1])")
# OR 门
y_or = np.array([0, 1, 1, 1])
perceptron.fit(X_and, y_or)
print(f"OR 门: {perceptron.predict(X_and)} (期望 [0, 1, 1, 1])")
# XOR 门
y_xor = np.array([0, 1, 1, 0])
perceptron.fit(X_and, y_xor)
xor_pred = perceptron.predict(X_and)
print(f"XOR 门: {xor_pred} (期望 [0, 1, 1, 0])")
print(f"XOR 正确率: {accuracy_score(y_xor, xor_pred):.2f} ← 感知机无法解决 XOR!")
print(f"\n原因: XOR 不是线性可分的。感知机只能画一条直线,无法分开 XOR 的两类。")
# ============================================================
# 2. 多层感知机实现
# ============================================================
print("\n" + "=" * 70)
print("2. 多层感知机(MLP)— 用 NumPy 从零实现")
print("=" * 70)
class MLP:
"""从零实现的多层感知机"""
def __init__(self, layer_sizes, lr=0.1, activation='relu'):
self.layer_sizes = layer_sizes
self.lr = lr
self.activation = activation
self.n_layers = len(layer_sizes) - 1
# 初始化权重(Xavier 初始化)
self.weights = []
self.biases = []
for i in range(self.n_layers):
scale = np.sqrt(2.0 / (layer_sizes[i] + layer_sizes[i+1]))
W = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * scale
b = np.zeros((1, layer_sizes[i+1]))
self.weights.append(W)
self.biases.append(b)
def _activate(self, z):
if self.activation == 'relu':
return np.maximum(0, z)
elif self.activation == 'sigmoid':
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def _activate_derivative(self, z):
if self.activation == 'relu':
return (z > 0).astype(float)
elif self.activation == 'sigmoid':
s = self._activate(z)
return s * (1 - s)
def _softmax(self, z):
exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))
return exp_z / np.sum(exp_z, axis=1, keepdims=True)
def forward(self, X):
"""前向传播"""
self.activations = [X]
self.z_values = []
h = X
for i in range(self.n_layers - 1):
z = h @ self.weights[i] + self.biases[i]
self.z_values.append(z)
h = self._activate(z)
self.activations.append(h)
# 输出层
z = h @ self.weights[-1] + self.biases[-1]
self.z_values.append(z)
if self.weights[-1].shape[1] == 1:
output = self._activate(z) # 二分类用 sigmoid
else:
output = self._softmax(z) # 多分类用 softmax
self.activations.append(output)
return output
def backward(self, y):
"""反向传播"""
m = y.shape[0]
gradients_w = [None] * self.n_layers
gradients_b = [None] * self.n_layers
# 输出层梯度
delta = self.activations[-1] - y
for i in range(self.n_layers - 1, -1, -1):
gradients_w[i] = self.activations[i].T @ delta / m
gradients_b[i] = np.mean(delta, axis=0, keepdims=True)
if i > 0:
delta = (delta @ self.weights[i].T) * self._activate_derivative(self.z_values[i-1])
return gradients_w, gradients_b
def fit(self, X, y, epochs=1000, verbose=True):
"""训练"""
for epoch in range(epochs):
output = self.forward(X)
grad_w, grad_b = self.backward(y)
for i in range(self.n_layers):
self.weights[i] -= self.lr * grad_w[i]
self.biases[i] -= self.lr * grad_b[i]
if verbose and (epoch + 1) % 200 == 0:
loss = -np.mean(y * np.log(output + 1e-8) + (1 - y) * np.log(1 - output + 1e-8))
pred = (output > 0.5).astype(int)
acc = accuracy_score(y.flatten(), pred.flatten())
print(f" Epoch {epoch+1}: loss={loss:.4f}, accuracy={acc:.4f}")
def predict(self, X):
output = self.forward(X)
return (output > 0.5).astype(int)
# 用 MLP 解决 XOR
print("\n训练 MLP 解决 XOR 问题:")
mlp = MLP(layer_sizes=[2, 4, 1], lr=0.5, activation='sigmoid')
y_xor_2d = y_xor.reshape(-1, 1).astype(float)
mlp.fit(X_and.astype(float), y_xor_2d, epochs=2000)
xor_pred_mlp = mlp.predict(X_and.astype(float))
print(f"\nXOR 预测: {xor_pred_mlp.flatten()}")
print(f"XOR 正确率: {accuracy_score(y_xor, xor_pred_mlp.flatten()):.2f}")
print("→ MLP 成功解决了 XOR 问题!")
# ============================================================
# 3. 决策边界可视化
# ============================================================
print("\n" + "=" * 70)
print("3. 决策边界可视化")
print("=" * 70)
# 生成非线性数据集
datasets = {
'Moons': make_moons(n_samples=500, noise=0.15, random_state=42),
'Circles': make_circles(n_samples=500, noise=0.1, factor=0.5, random_state=42),
'XOR-like': (np.random.randn(500, 2), (np.logical_xor(
np.random.randn(500) > 0, np.random.randn(500) > 0)).astype(int))
}
fig, axes = plt.subplots(2, 3, figsize=(18, 10))
for col, (name, (X_data, y_data)) in enumerate(datasets.items()):
# 感知机
perc = Perceptron(lr=0.1, n_epochs=100)
perc.fit(X_data, y_data)
perc_acc = accuracy_score(y_data, perc.predict(X_data))
# MLP
y_2d = y_data.reshape(-1, 1).astype(float)
mlp = MLP(layer_sizes=[2, 16, 8, 1], lr=0.1, activation='relu')
mlp.fit(X_data, y_2d, epochs=2000, verbose=False)
mlp_acc = accuracy_score(y_data, mlp.predict(X_data).flatten())
# 可视化
h = 0.05
x_min, x_max = X_data[:, 0].min() - 1, X_data[:, 0].max() + 1
y_min, y_max = X_data[:, 1].min() - 1, X_data[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
for row, (model, title, acc) in enumerate([
(perc, f'感知机 (acc={perc_acc:.2f})', perc_acc),
(mlp, f'MLP (acc={mlp_acc:.2f})', mlp_acc)
]):
ax = axes[row, col]
if row == 0:
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
else:
Z = model.predict(np.c_[xx.ravel(), yy.ravel()].astype(float)).reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
ax.scatter(X_data[:, 0], X_data[:, 1], c=y_data, cmap='coolwarm', edgecolors='k', s=20)
ax.set_title(f'{name} - {title}')
if row == 0:
ax.set_xlabel('(单层感知机只能画直线)')
else:
ax.set_xlabel('(MLP 可以画复杂边界)')
axes[0, 0].set_ylabel('感知机')
axes[1, 0].set_ylabel('MLP (3层)')
plt.tight_layout()
plt.savefig('perceptron_vs_mlp.png', dpi=150)
plt.show()
print("感知机 vs MLP 决策边界可视化已保存")
# ============================================================
# 4. 深度 vs 宽度实验
# ============================================================
print("\n" + "=" * 70)
print("4. 深度 vs 宽度实验")
print("=" * 70)
from sklearn.neural_network import MLPClassifier
X_deep, y_deep = make_moons(n_samples=2000, noise=0.2, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X_deep, y_deep, test_size=0.2, random_state=42)
print(f"{'架构':<35} {'参数量':<10} {'测试准确率':<12}")
print("-" * 60)
configs = [
# 浅层 + 宽
([8], '浅层 8神经元'),
([32], '浅层 32神经元'),
([128], '浅层 128神经元'),
([512], '浅层 512神经元'),
([2048], '浅层 2048神经元'),
# 深层 + 窄
([8]*2, '2层 × 8神经元'),
([8]*4, '4层 × 8神经元'),
([8]*8, '8层 × 8神经元'),
([16]*2, '2层 × 16神经元'),
([16]*4, '4层 × 16神经元'),
([32]*2, '2层 × 32神经元'),
([64]*2, '2层 × 64神经元'),
]
for layers, name in configs:
clf = MLPClassifier(hidden_layer_sizes=layers, max_iter=2000,
random_state=42, early_stopping=True)
clf.fit(X_tr, y_tr)
acc = accuracy_score(y_te, clf.predict(X_te))
# 计算参数量
n_params = 0
prev = 2
for l in (layers if isinstance(layers, tuple) else [layers]):
n_params += prev * l + l
prev = l
n_params += prev * 1 + 1 # 输出层
print(f" {name:<33} {n_params:<10d} {acc:<12.4f}")
# 预期输出类似:
# 架构 参数量 测试准确率
# ------------------------------------------------------------
# 浅层 8神经元 33 0.8350
# 浅层 32神经元 129 0.8600
# 浅层 128神经元 513 0.9150
# 浅层 512神经元 2049 0.9200
# 浅层 2048神经元 8193 0.9250
# 2层 × 8神经元 105 0.8600
# 4层 × 8神经元 241 0.9100
# 8层 × 8神经元 441 0.9300
# 2层 × 16神经元 337 0.9150
# 4层 × 16神经元 753 0.9350
# 2层 × 32神经元 1249 0.9250
# 2层 × 64神经元 4737 0.9300
print("\n观察:")
print(" 1. 深层网络在相同参数量下通常比浅层网络表现更好")
print(" 2. 参数量增加带来的边际收益递减")
print(" 3. 太深的网络(8层×8)可能因为梯度消失而训练困难")
# ============================================================
# 5. 通用近似定理演示
# ============================================================
print("\n" + "=" * 70)
print("5. 通用近似定理演示:用网络逼近各种函数")
print("=" * 70)
# 要逼近的目标函数
target_functions = {
'sin(2πx)': lambda x: np.sin(2 * np.pi * x),
'|x - 0.5|': lambda x: np.abs(x - 0.5),
'阶梯函数': lambda x: (x > 0.3).astype(float) + (x > 0.7).astype(float) * 0.5,
'锯齿波': lambda x: 2 * np.abs(2 * (x - np.floor(x + 0.5)))
}
x_fit = np.linspace(0, 1, 200).reshape(-1, 1)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes = axes.flatten()
for idx, (name, func) in enumerate(target_functions.items()):
y_fit = func(x_fit).ravel()
# 用不同宽度的网络逼近
for n_neurons in [4, 16, 64, 256]:
from sklearn.neural_network import MLPRegressor
mlp = MLPRegressor(hidden_layer_sizes=[n_neurons], activation='relu',
max_iter=5000, random_state=42, solver='adam',
learning_rate_init=0.01)
mlp.fit(x_fit, y_fit)
y_pred = mlp.predict(x_fit)
mse = np.mean((y_fit - y_pred) ** 2)
axes[idx].plot(x_fit, y_pred, '--', alpha=0.7,
label=f'{n_neurons} neurons (MSE={mse:.4f})')
axes[idx].plot(x_fit, y_fit, 'k-', linewidth=2, label='目标函数')
axes[idx].set_title(f'逼近 {name}')
axes[idx].legend(fontsize=8)
axes[idx].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('universal_approximation.png', dpi=150)
plt.show()
print("通用近似定理演示已保存")
print("→ 随着神经元数量增加,网络越来越精确地逼近目标函数")
print("→ 但注意:某些函数(如阶梯函数)需要更多神经元才能精确逼近")
# ============================================================
# 6. 网络架构选择指南
# ============================================================
print("\n" + "=" * 70)
print("6. 网络架构选择指南")
print("=" * 70)
guide = """
╔════════════════════════════════════════════════════════════════════════╗
║ 神经网络架构选择指南 ║
╠════════════════════════════════════════════════════════════════════════╣
║ ║
║ 隐藏层数量(深度)选择: ║
║ ├── 0 层:线性模型(逻辑回归、线性回归) ║
║ ├── 1 层:简单非线性问题(XOR、浅层模式) ║
║ ├── 2-3 层:中等复杂度问题(结构化数据、简单图像) ║
║ └── 4+ 层:复杂问题(需要层级特征提取) ║
║ ║
║ 每层神经元数量(宽度)选择: ║
║ ├── 经验法则:每层 2/3 × (输入维度 + 输出维度) 到 2 × 输入维度 ║
║ ├── 逐层递减:如 [512, 256, 128, 64](常见模式) ║
║ └── 不要太少(欠拟合)也不要太多(过拟合+计算浪费) ║
║ ║
║ 实用建议: ║
║ 1. 从小网络开始,逐步增大直到验证集性能不再提升 ║
║ 2. 先加宽度,再加深度 ║
║ 3. 使用 dropout / batch normalization 防止过拟合 ║
║ 4. 深度 > 宽度(大多数情况下) ║
║ 5. 残差连接可以帮助训练更深的网络 ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
"""
print(guide)
真实案例
案例 1:ImageNet 竞赛中的网络深度演进
ImageNet 图像分类竞赛的历史是网络深度不断增长的历史:
| 年份 | 模型 | 层数 | Top-5 错误率 | 关键创新 |
|---|---|---|---|---|
| 2012 | AlexNet | 8 | 15.3% | ReLU, Dropout, GPU 训练 |
| 2014 | VGGNet | 19 | 7.3% | 小卷积核 (3×3),更深 |
| 2014 | GoogLeNet | 22 | 6.7% | Inception 模块,多尺度 |
| 2015 | ResNet | 152 | 3.6% | 残差连接,解决梯度消失 |
| 2017 | SENet | 152+ | 2.3% | 注意力机制 |
从 8 层到 152 层,错误率从 15.3% 降到 3.6%(人类大约 5%)。深度是性能提升的主要驱动力。
但直接把网络做深是不行的。VGGNet 之后的很多尝试表明,简单地堆叠更多层反而让性能变差(退化问题,不是过拟合而是训练不收敛)。直到何恺明发明了残差连接(ResNet),才能有效地训练 100+ 层的网络。
案例 2:AlphaGo 中的网络架构
AlphaGo 使用两个深度网络:
-
策略网络(Policy Network):13 层 CNN,输入是棋盘状态,输出是每个位置的落子概率。负责"下一步该下在哪里"。
-
价值网络(Value Network):类似的架构,但输出一个标量——当前局面的胜率估计。负责"当前局面谁更有优势"。
为什么需要深层?因为围棋的策略是高度组合的:
- 第 1-4 层:检测基本的棋形(如"眼"、"虎口")
- 第 5-8 层:组合棋形检测局部战术
- 第 9-13 层:组合战术理解全局战略
浅层网络无法有效地表示这种层级策略。
案例 3:NLP 中的深度——从 Word2Vec 到 Transformer
自然语言处理中的网络深度也经历了显著的增长:
- Word2Vec (2013):单层,学习词的向量表示
- LSTM (1997, 广泛应用 2014-2017):通常 2-4 层
- ELMo (2018):2 层双向 LSTM
- BERT-Base (2018):12 层 Transformer,1.1 亿参数
- GPT-3 (2020):96 层 Transformer,1750 亿参数
- GPT-4 (2023):估计 100+ 层,参数量未公开
语言的理解天然是层级的:字符 → 词 → 短语 → 句子 → 段落。深层 Transformer 通过自注意力机制在每一层建立词与词之间的关系,逐层构建更高级的语言理解。
权衡取舍以及何时不该使用
深层网络的代价
训练困难:梯度消失/爆炸使得深层网络难以训练。虽然 ResNet、BatchNorm 等技术缓解了这个问题,但训练 100+ 层的网络仍然需要大量经验和技巧。
计算资源:深层网络需要更多的计算和内存。训练 GPT-3 需要几千块 GPU 运行几周。
数据需求:深层网络有更多参数需要学习,通常需要更多训练数据。在小数据集上,简单的模型可能更好。
可解释性差:深层网络的决策过程很难解释。对于需要可解释性的场景(如医疗诊断),浅层模型或决策树可能更合适。
何时不该用深度学习
-
数据量小(< 1000 样本)。深度学习需要大量数据来学习参数。小数据集上,线性模型或树模型通常更好。
-
结构化/表格数据。对于特征工程做得好的结构化数据,XGBoost 等梯度提升方法通常比神经网络表现更好且更快。
-
需要可解释性。如果你的业务场景需要解释"为什么模型做了这个预测",深度学习不是好选择。
-
推理延迟要求高。在嵌入式设备或实时系统中,深层网络的推理延迟可能不可接受。
-
数据没有层级结构。如果你的数据是低维的、简单的关系,线性模型就够了。不需要杀鸡用牛刀。
常见的错误做法
错误 1:上来就用深度学习
看到问题就用神经网络,而不先尝试简单模型。正确做法是:先建立线性模型基线,再尝试树模型,只有在这两者都不够好时才考虑深度学习。
错误 2:盲目增加深度
网络不收敛就加层,结果更不收敛。应该先检查学习率、数据质量、梯度流等问题。有时候问题不是深度不够,而是训练方法不对。
错误 3:忽视网络规模和数据量的匹配
100 万参数的网络用 1000 条数据训练——严重过拟合。参数量应该和数据量匹配。经验法则:训练数据量至少应该是参数量的 5-10 倍。
关键要点
-
感知机是神经网络的基本单元,它计算输入的加权和并通过激活函数输出。单层感知机只能解决线性可分问题。
-
多层感知机通过组合多个感知机,可以逼近任意复杂的决策边界。通用近似定理保证了这一点——但需要的神经元数量可能很大。
-
深度比宽度更重要。对于具有层级结构的函数(包括大多数现实世界的数据),深层网络可以用指数级更少的参数来表示相同的函数。
-
非线性激活函数是多层网络有效的关键。没有非线性激活,不管堆多少层都等价于一个线性变换。
-
现实世界的数据天然具有层级结构(像素→边缘→形状→部件→对象),这解释了为什么深度学习有效——网络的结构匹配了数据的结构。
-
通用近似定理是理论保证,不是实践指南。它告诉你"存在"一个网络可以逼近目标函数,但不告诉你这个网络有多大,也不告诉你怎么找到它。
-
深度学习的适用场景:数据量大、数据有层级结构(图像、文本、语音)、性能比可解释性更重要。对于小数据或结构化数据,传统方法可能更好。
延伸阅读
-
Cybenko, G. (1989). "Approximation by superpositions of a sigmoidal function." Mathematics of Control, Signals and Systems, 2(4), 303-314. — 通用近似定理的原始论文
-
Hornik, K. (1991). "Approximation capabilities of multilayer feedforward networks." Neural Networks, 4(2), 251-257. — UAT 的更一般化证明
-
Telgarsky, M. (2016). "Benefits of depth in neural networks." COLT. — 证明了深层网络相对于浅层网络的指数优势
-
Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning, Chapter 6. — 深度前馈网络的系统介绍
-
Nielsen, M. (2015). Neural Networks and Deep Learning. 免费在线教材,从直觉到数学的完整路径
-
He, K., Zhang, X., Ren, S., & Sun, J. (2016). "Deep Residual Learning for Image Recognition." CVPR. — ResNet,解决了深层网络的训练问题
评论
还没有评论
欢迎留下第一条评论,帮助这篇内容更快形成讨论。