5机器学习

神经网络

深度学习基础

神经元多层网络反向传播深度学习

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:神经网络(Neural Networks)

一、导读

1.1 本章学习目标

第5章介绍神经网络——深度学习的基石。神经网络通过多层非线性变换,能够学习数据中复杂的模式。从简单的感知机到现代的深度神经网络,这一章将带你理解神经网络的核心原理。

本章的核心学习目标包括:

  • 理解神经元模型:加权求和 + 激活函数的基本结构
  • 掌握多层前馈神经网络:网络结构、前向传播过程
  • 理解反向传播算法:梯度如何在网络中传播,参数如何更新
  • 掌握常见激活函数:sigmoid、ReLU、tanh 的特点和适用场景
  • 理解深度学习的含义:为什么"深度"如此重要
  • 了解常见的神经网络模型:CNN、RNN 的基本概念

1.2 为什么本章如此重要

神经网络是当前机器学习最成功的方法论。从图像识别到自然语言处理,从游戏 AI 到蛋白质结构预测,神经网络几乎在所有领域都取得了突破性进展。

理解神经网络的原理,不仅有助于使用深度学习工具,更重要的是理解其局限性。反向传播的梯度消失/爆炸问题、过拟合问题、训练不稳定性等,都需要深入理解才能有效解决。

1.3 核心问题引导

单个神经元能解决什么问题?为什么感知机无法解决 XOR 问题?

多层网络比单层网络强在哪里?

反向传播算法的数学原理是什么?链式法则如何应用?

为什么 ReLU 比 sigmoid 更常用?

"深度"学习的"深度"到底意味着什么?

神经网络为什么容易过拟合?如何防止?


二、核心概念详解

2.1 神经元模型

2.1.1 基本结构

神经元是神经网络的基本单元,模拟生物神经元的工作方式:

$$output = f(\sum_{i} w_i x_i + b) = f(w^T x + b)$$

其中 $f$ 是激活函数,$w$ 是权重,$b$ 是偏置。

python
import numpy as np

class Neuron:
    """单个神经元"""
    
    def __init__(self, n_inputs, activation='sigmoid'):
        self.weights = np.random.randn(n_inputs) * 0.01
        self.bias = 0
        self.activation = activation
    
    def sigmoid(self, z):
        return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
    
    def relu(self, z):
        return np.maximum(0, z)
    
    def forward(self, x):
        z = np.dot(x, self.weights) + self.bias
        if self.activation == 'sigmoid':
            return self.sigmoid(z)
        elif self.activation == 'relu':
            return self.relu(z)
        return z

# 示例
neuron = Neuron(n_inputs=3, activation='sigmoid')
x = np.array([1.0, 2.0, 3.0])
output = neuron.forward(x)
print(f"神经元输出: {output:.4f}")

2.1.2 感知机与 XOR 问题

感知机是最简单的神经元,使用阶跃函数作为激活函数。感知机只能解决线性可分问题,无法解决 XOR 问题。

python
def perceptron_xor_problem():
    """感知机无法解决XOR问题"""
    # XOR 数据
    X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
    y = np.array([0, 1, 1, 0])
    
    print("=== XOR 问题 ===")
    print("输入 → 输出")
    for i in range(4):
        print(f"[{X[i][0]}, {X[i][1]}] → {y[i]}")
    
    print("\nXOR 不是线性可分的:")
    print("无法用一条直线将 (0,0),(1,1) 与 (0,1),(1,0) 分开")
    print("因此单层感知机无法解决 XOR 问题")
    print("\n解决方案:使用多层网络")
    print("第一层:学习两个线性边界")
    print("第二层:组合两个边界,形成非线性决策")

perceptron_xor_problem()

2.2 多层前馈神经网络

2.2.1 网络结构

多层前馈神经网络由输入层、一个或多个隐藏层和输出层组成。每一层的神经元与下一层全连接。

python
class NeuralNetwork:
    """多层前馈神经网络"""
    
    def __init__(self, layer_sizes):
        """
        layer_sizes: [输入维度, 隐藏层1, 隐藏层2, ..., 输出维度]
        """
        self.layer_sizes = layer_sizes
        self.n_layers = len(layer_sizes) - 1
        self.weights = []
        self.biases = []
        
        # 初始化参数
        for i in range(self.n_layers):
            w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * 0.01
            b = np.zeros((1, layer_sizes[i+1]))
            self.weights.append(w)
            self.biases.append(b)
    
    def sigmoid(self, z):
        return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
    
    def sigmoid_derivative(self, a):
        return a * (1 - a)
    
    def relu(self, z):
        return np.maximum(0, z)
    
    def relu_derivative(self, z):
        return (z > 0).astype(float)
    
    def forward(self, X):
        """前向传播"""
        self.activations = [X]
        self.z_values = []
        
        a = X
        for i in range(self.n_layers):
            z = a @ self.weights[i] + self.biases[i]
            self.z_values.append(z)
            
            if i < self.n_layers - 1:
                a = self.relu(z)  # 隐藏层用ReLU
            else:
                a = self.sigmoid(z)  # 输出层用sigmoid
            
            self.activations.append(a)
        
        return a
    
    def backward(self, y, learning_rate=0.01):
        """反向传播"""
        m = y.shape[0]
        y = y.reshape(-1, 1)
        
        # 输出层误差
        delta = self.activations[-1] - y
        
        # 从输出层向隐藏层传播
        for i in range(self.n_layers - 1, -1, -1):
            # 计算梯度
            dw = self.activations[i].T @ delta / m
            db = np.sum(delta, axis=0, keepdims=True) / m
            
            # 传播误差到下一层
            if i > 0:
                delta = delta @ self.weights[i].T
                delta = delta * self.relu_derivative(self.z_values[i-1])
            
            # 更新参数
            self.weights[i] -= learning_rate * dw
            self.biases[i] -= learning_rate * db
    
    def train(self, X, y, epochs=1000, learning_rate=0.01):
        """训练网络"""
        losses = []
        for epoch in range(epochs):
            output = self.forward(X)
            loss = np.mean((output - y.reshape(-1, 1)) ** 2)
            losses.append(loss)
            self.backward(y, learning_rate)
            
            if epoch % 200 == 0:
                print(f"Epoch {epoch}: Loss = {loss:.4f}")
        
        return losses
    
    def predict(self, X, threshold=0.5):
        output = self.forward(X)
        return (output >= threshold).astype(int).flatten()

# 示例:解决XOR问题
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])

nn = NeuralNetwork([2, 4, 1])
nn.train(X, y, epochs=1000, learning_rate=0.5)

predictions = nn.predict(X)
print(f"\nXOR 预测结果: {predictions}")
print(f"真实标签:     {y}")

2.3 反向传播算法

2.3.1 数学推导

反向传播算法是训练神经网络的核心。它利用链式法则计算损失函数对每个参数的梯度。

对于第 $l$ 层的参数 $W^{[l]}$:

$$\frac{\partial L}{\partial W^{[l]}} = \delta^{[l]} \cdot (a^{[l-1]})^T$$

其中 $\delta^{[l]}$ 是第 $l$ 层的误差信号,通过链式法则从输出层逐层传播。

python
def backprop_demo():
    """反向传播的逐步推导"""
    print("=== 反向传播算法 ===")
    print("\n1. 前向传播:计算每一层的输出")
    print("   z[l] = W[l] @ a[l-1] + b[l]")
    print("   a[l] = activation(z[l])")
    print()
    print("2. 计算输出层误差:")
    print("   delta[L] = a[L] - y  (对于MSE损失+sigmoid)")
    print()
    print("3. 反向传播误差:")
    print("   delta[l] = (W[l+1].T @ delta[l+1]) * activation'(z[l])")
    print()
    print("4. 计算梯度:")
    print("   dW[l] = delta[l] @ a[l-1].T / m")
    print("   db[l] = sum(delta[l]) / m")
    print()
    print("5. 更新参数:")
    print("   W[l] = W[l] - lr * dW[l]")
    print("   b[l] = b[l] - lr * db[l]")

backprop_demo()

2.3.2 梯度消失与梯度爆炸

python
def gradient_problem_demo():
    """梯度消失和梯度爆炸演示"""
    print("=== 梯度消失与梯度爆炸 ===")
    
    # 梯度消失:sigmoid 激活函数
    z_values = np.array([-5, -2, 0, 2, 5])
    sigmoid_deriv = np.array([0.0067, 0.105, 0.25, 0.105, 0.0067])
    
    print("\nSigmoid 导数(梯度消失):")
    for z, d in zip(z_values, sigmoid_deriv):
        print(f"  z={z:>3}: sigmoid'(z)={d:.4f}")
    print("  → 导数最大只有0.25,多层连乘后梯度急剧缩小")
    
    # ReLU 缓解梯度消失
    print("\nReLU 导数(缓解梯度消失):")
    for z in z_values:
        d = 1.0 if z > 0 else 0.0
        print(f"  z={z:>3}: relu'(z)={d:.4f}")
    print("  → 正区间导数为1,梯度不会缩小")
    
    # 梯度爆炸
    print("\n梯度爆炸:当权重过大时")
    print("  如果 W 的绝对值 > 1,多层连乘后梯度指数增长")
    print("  解决方案:梯度裁剪、权重初始化、Batch Normalization")

gradient_problem_demo()

2.4 激活函数

python
def compare_activations():
    """对比不同激活函数"""
    z = np.linspace(-5, 5, 100)
    
    # Sigmoid
    sigmoid = 1 / (1 + np.exp(-z))
    
    # Tanh
    tanh = np.tanh(z)
    
    # ReLU
    relu = np.maximum(0, z)
    
    # Leaky ReLU
    leaky_relu = np.where(z > 0, z, 0.01 * z)
    
    print("=== 激活函数对比 ===")
    print(f"{'函数':<15} {'输出范围':<15} {'导数范围':<15} {'特点'}")
    print("-" * 65)
    print(f"{'Sigmoid':<15} {'(0, 1)':<15} {'(0, 0.25)':<15} {'概率输出,梯度消失'}")
    print(f"{'Tanh':<15} {'(-1, 1)':<15} {'(0, 1)':<15} {'零均值,梯度消失'}")
    print(f"{'ReLU':<15} {'[0, ∞)':<15} {'{0, 1}':<15} {'计算快,缓解消失,死亡ReLU'}")
    print(f"{'Leaky ReLU':<15} {'(-∞, ∞)':<15} '{0.01, 1}':<15} {'解决死亡ReLU'}")
    
    print("\n选择建议:")
    print("- 隐藏层:ReLU(默认选择)")
    print("- 二分类输出层:Sigmoid")
    print("- 多分类输出层:Softmax")
    print("- 回归输出层:线性")

compare_activations()

2.5 深度学习

2.5.1 为什么"深度"重要

深度网络比浅层网络有更强的表示能力。理论上,一个足够宽的单隐藏层网络可以近似任意连续函数(万能近似定理)。但深度网络用更少的参数达到同样的表示能力。

python
def depth_vs_width():
    """深度 vs 宽度"""
    print("=== 深度 vs 宽度 ===")
    print("\n万能近似定理:")
    print("单个隐藏层的前馈网络可以近似任意连续函数")
    print("但需要的隐藏层神经元数量可能指数级大")
    print()
    print("深度网络的优势:")
    print("- 用层次化的特征表示复杂函数")
    print("- 第1层:学习边缘、纹理等低级特征")
    print("- 第2层:组合低级特征为中级特征")
    print("- 第3层:组合中级特征为高级语义")
    print()
    print("示例:识别'猫'")
    print("第1层:边缘检测")
    print("第2层:纹理、颜色模式")
    print("第3层:眼睛、耳朵等部件")
    print("第4层:完整的猫脸")

depth_vs_width()

2.5.2 常见深度学习模型

python
def deep_learning_models():
    """常见深度学习模型概述"""
    models = {
        'CNN (卷积神经网络)': {
            '特点': '局部连接、权值共享、平移不变性',
            '应用': '图像分类、目标检测、图像分割',
            '关键层': '卷积层、池化层、全连接层',
        },
        'RNN (循环神经网络)': {
            '特点': '参数共享、隐藏状态、处理序列数据',
            '应用': '自然语言处理、语音识别、时间序列',
            '变体': 'LSTM、GRU(解决梯度消失)',
        },
        'Transformer': {
            '特点': '自注意力机制、并行计算、长距离依赖',
            '应用': 'NLP(BERT、GPT)、计算机视觉(ViT)',
            '关键组件': '多头注意力、位置编码、前馈网络',
        },
    }
    
    print("=== 常见深度学习模型 ===\n")
    for name, info in models.items():
        print(f"【{name}】")
        for key, value in info.items():
            print(f"  {key}: {value}")
        print()

deep_learning_models()

三、实战案例

3.1 案例一:从零实现神经网络

python
def neural_network_from_scratch():
    """从零实现完整的神经网络"""
    np.random.seed(42)
    
    # 生成螺旋数据集
    n = 200
    theta = np.random.rand(n, 1) * 2 * np.pi
    r = np.arange(n).reshape(-1, 1) / n
    X = np.hstack([r * np.cos(theta), r * np.sin(theta)])
    y = (np.arange(n) % 2).reshape(-1, 1)
    
    # 网络参数
    input_size = 2
    hidden_size = 16
    output_size = 1
    learning_rate = 0.1
    
    # 初始化
    W1 = np.random.randn(input_size, hidden_size) * 0.1
    b1 = np.zeros((1, hidden_size))
    W2 = np.random.randn(hidden_size, output_size) * 0.1
    b2 = np.zeros((1, output_size))
    
    # 训练
    for epoch in range(500):
        # 前向传播
        z1 = X @ W1 + b1
        a1 = np.maximum(0, z1)  # ReLU
        z2 = a1 @ W2 + b2
        a2 = 1 / (1 + np.exp(-z2))  # Sigmoid
        
        # 计算损失
        loss = -np.mean(y * np.log(a2 + 1e-10) + (1 - y) * np.log(1 - a2 + 1e-10))
        
        # 反向传播
        dz2 = a2 - y
        dW2 = a1.T @ dz2 / n
        db2 = np.sum(dz2, axis=0, keepdims=True) / n
        
        da1 = dz2 @ W2.T
        dz1 = da1 * (z1 > 0)
        dW1 = X.T @ dz1 / n
        db1 = np.sum(dz1, axis=0, keepdims=True) / n
        
        # 更新参数
        W2 -= learning_rate * dW2
        b2 -= learning_rate * db2
        W1 -= learning_rate * dW1
        b1 -= learning_rate * db1
        
        if epoch % 100 == 0:
            acc = np.mean((a2 >= 0.5) == y)
            print(f"Epoch {epoch}: Loss={loss:.4f}, Acc={acc:.2%}")
    
    print(f"\n最终准确率: {np.mean((a2 >= 0.5) == y):.2%}")

neural_network_from_scratch()

3.2 案例二:不同网络结构的对比

python
def compare_network_architectures():
    """对比不同网络结构"""
    np.random.seed(42)
    
    # 生成数据
    n = 300
    X = np.random.randn(n, 10)
    w_true = np.random.randn(10)
    y = (X @ w_true > 0).astype(int)
    
    # 划分数据集
    indices = np.random.permutation(n)
    train_idx, test_idx = indices[:240], indices[240:]
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    
    architectures = [
        ([10, 1], "单层网络"),
        ([10, 8, 1], "两层网络"),
        ([10, 16, 8, 1], "三层网络"),
        ([10, 32, 16, 8, 1], "四层网络"),
    ]
    
    print("=== 不同网络结构对比 ===")
    print(f"{'结构':<20} {'训练准确率':<12} {'测试准确率':<12}")
    print("-" * 48)
    
    for layers, name in architectures:
        nn = NeuralNetwork(layers)
        nn.train(X_train, y_train, epochs=500, learning_rate=0.05)
        train_acc = np.mean(nn.predict(X_train) == y_train)
        test_acc = np.mean(nn.predict(X_test) == y_test)
        print(f"{name:<20} {train_acc:<12.2%} {test_acc:<12.2%}")

compare_network_architectures()

3.3 案例三:激活函数对比实验

python
def activation_comparison():
    """对比不同激活函数的训练效果"""
    np.random.seed(42)
    
    # 生成数据
    n = 500
    X = np.random.randn(n, 20)
    w_true = np.random.randn(20)
    y = (X @ w_true > 0).astype(int)
    
    indices = np.random.permutation(n)
    X_train, y_train = X[indices[:400]], y[indices[:400]]
    X_test, y_test = X[indices[400:]], y[indices[400:]]
    
    activations = {
        'Sigmoid': lambda z: 1 / (1 + np.exp(-np.clip(z, -500, 500))),
        'Tanh': lambda z: np.tanh(z),
        'ReLU': lambda z: np.maximum(0, z),
    }
    
    print("=== 激活函数对比 ===")
    print(f"{'激活函数':<12} {'训练准确率':<12} {'测试准确率':<12}")
    print("-" * 40)
    
    for act_name, act_func in activations.items():
        nn = NeuralNetwork([20, 32, 16, 1])
        # 修改激活函数
        nn.relu = act_func
        nn.train(X_train, y_train, epochs=300, learning_rate=0.01)
        train_acc = np.mean(nn.predict(X_train) == y_train)
        test_acc = np.mean(nn.predict(X_test) == y_test)
        print(f"{act_name:<12} {train_acc:<12.2%} {test_acc:<12.2%}")

activation_comparison()

四、本章小结

第5章系统介绍了神经网络的核心内容:

神经元模型:加权求和 + 激活函数。感知机只能解决线性可分问题。

多层前馈网络:通过多层非线性变换学习复杂模式。反向传播算法通过链式法则计算梯度。

反向传播:从输出层到输入层逐层传播误差,计算每个参数的梯度。

激活函数:Sigmoid(概率输出)、ReLU(隐藏层首选)、Tanh(零均值)。

深度学习:深度网络通过层次化特征表示实现强大的学习能力。

常见模型:CNN(图像)、RNN(序列)、Transformer(注意力机制)。

神经网络的关键挑战包括梯度消失/爆炸、过拟合、训练不稳定性等。现代技术如 ReLU、Batch Normalization、Dropout、残差连接等有效缓解了这些问题。


五、思考与练习

思考题

为什么感知机不能解决 XOR 问题?添加一个隐藏层后为什么就可以了?

反向传播中,为什么需要保存前向传播的中间结果?

ReLU 的"死亡神经元"问题是什么?Leaky ReLU 如何解决?

为什么深层网络比浅层网络更难训练?

Dropout 为什么能防止过拟合?

编程练习

实现一个带有 Batch Normalization 的神经网络,观察训练速度的变化。

实现 Dropout,在不同 keep_prob 下比较训练和测试性能。

用 NumPy 实现一个简单的 CNN(卷积层 + 池化层 + 全连接层),在 MNIST 数据集上测试。

5/16神经网络
0%