# 神经网络入门:从感知机到多层网络,通用近似定理,以及为什么需要深层 **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 ``` ### 网络的几何理解 一个理解神经网络的强大方式是几何视角: 1. **每一层的线性变换**($W\mathbf{x} + \mathbf{b}$)做了两件事:旋转空间(通过 $W$)和平移空间(通过 $\mathbf{b}$) 2. **激活函数**(如 ReLU)对空间做了非线性扭曲(折叠、弯曲) 3. 整个网络就是一系列旋转、平移和非线性扭曲的组合 ``` 原始数据空间 第 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)是它能表示的函数族的复杂程度。容量由两个因素决定: 1. **宽度**:每层神经元的数量 2. **深度**:层数 容量和泛化之间存在权衡: - 容量太小 → 欠拟合(模型无法表达目标函数) - 容量太大 → 过拟合(模型记住了训练数据的噪声) 但深度学习的一个反直觉现象是:**有时候更大的模型反而泛化更好。** 这和我们之前讨论的 Double Descent 现象一致——在越过插值阈值后,更大的模型反而测试误差更低。 ## 代码示例 ```python 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 使用两个深度网络: 1. **策略网络(Policy Network)**:13 层 CNN,输入是棋盘状态,输出是每个位置的落子概率。负责"下一步该下在哪里"。 2. **价值网络(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 运行几周。 **数据需求**:深层网络有更多参数需要学习,通常需要更多训练数据。在小数据集上,简单的模型可能更好。 **可解释性差**:深层网络的决策过程很难解释。对于需要可解释性的场景(如医疗诊断),浅层模型或决策树可能更合适。 ### 何时不该用深度学习 1. **数据量小(< 1000 样本)**。深度学习需要大量数据来学习参数。小数据集上,线性模型或树模型通常更好。 2. **结构化/表格数据**。对于特征工程做得好的结构化数据,XGBoost 等梯度提升方法通常比神经网络表现更好且更快。 3. **需要可解释性**。如果你的业务场景需要解释"为什么模型做了这个预测",深度学习不是好选择。 4. **推理延迟要求高**。在嵌入式设备或实时系统中,深层网络的推理延迟可能不可接受。 5. **数据没有层级结构**。如果你的数据是低维的、简单的关系,线性模型就够了。不需要杀鸡用牛刀。 ### 常见的错误做法 **错误 1:上来就用深度学习** 看到问题就用神经网络,而不先尝试简单模型。正确做法是:先建立线性模型基线,再尝试树模型,只有在这两者都不够好时才考虑深度学习。 **错误 2:盲目增加深度** 网络不收敛就加层,结果更不收敛。应该先检查学习率、数据质量、梯度流等问题。有时候问题不是深度不够,而是训练方法不对。 **错误 3:忽视网络规模和数据量的匹配** 100 万参数的网络用 1000 条数据训练——严重过拟合。参数量应该和数据量匹配。经验法则:训练数据量至少应该是参数量的 5-10 倍。 ## 关键要点 1. **感知机是神经网络的基本单元**,它计算输入的加权和并通过激活函数输出。单层感知机只能解决线性可分问题。 2. **多层感知机通过组合多个感知机,可以逼近任意复杂的决策边界**。通用近似定理保证了这一点——但需要的神经元数量可能很大。 3. **深度比宽度更重要**。对于具有层级结构的函数(包括大多数现实世界的数据),深层网络可以用指数级更少的参数来表示相同的函数。 4. **非线性激活函数是多层网络有效的关键**。没有非线性激活,不管堆多少层都等价于一个线性变换。 5. **现实世界的数据天然具有层级结构**(像素→边缘→形状→部件→对象),这解释了为什么深度学习有效——网络的结构匹配了数据的结构。 6. **通用近似定理是理论保证,不是实践指南**。它告诉你"存在"一个网络可以逼近目标函数,但不告诉你这个网络有多大,也不告诉你怎么找到它。 7. **深度学习的适用场景**:数据量大、数据有层级结构(图像、文本、语音)、性能比可解释性更重要。对于小数据或结构化数据,传统方法可能更好。 ## 延伸阅读 1. Cybenko, G. (1989). "Approximation by superpositions of a sigmoidal function." *Mathematics of Control, Signals and Systems*, 2(4), 303-314. — 通用近似定理的原始论文 2. Hornik, K. (1991). "Approximation capabilities of multilayer feedforward networks." *Neural Networks*, 4(2), 251-257. — UAT 的更一般化证明 3. Telgarsky, M. (2016). "Benefits of depth in neural networks." *COLT*. — 证明了深层网络相对于浅层网络的指数优势 4. Goodfellow, I., Bengio, Y., & Courville, A. (2016). *Deep Learning*, Chapter 6. — 深度前馈网络的系统介绍 5. Nielsen, M. (2015). *Neural Networks and Deep Learning*. 免费在线教材,从直觉到数学的完整路径 6. He, K., Zhang, X., Ren, S., & Sun, J. (2016). "Deep Residual Learning for Image Recognition." *CVPR*. — ResNet,解决了深层网络的训练问题