BP神经网络介绍:从数学原理到Python实战

1次阅读
没有评论

共计 2538 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

从单层感知机到多层神经网络

初学机器学习时,我们常从单层感知机(Perceptron)开始。这种简单模型能解决线性可分问题,比如基本的 AND/OR 逻辑运算。但遇到非线性问题(如异或 XOR)时,单层结构就暴露了致命缺陷——它无法学习非线性关系。这就引出了多层神经网络的必要性:通过叠加隐藏层和非线性激活函数,网络可以拟合任意复杂函数。

BP 神经网络介绍:从数学原理到 Python 实战

核心原理三步走

1. 前向传播的矩阵化表示

前向传播是数据从输入层流向输出层的过程。假设我们有一个单隐藏层网络:

  • 输入层维度:$n_{input}$
  • 隐藏层维度:$n_{hidden}$
  • 输出层维度:$n_{output}$

权重矩阵和偏置的维度分别为:

$$
W_1 \in \mathbb{R}^{n_{input} \times n_{hidden}}, \quad
b_1 \in \mathbb{R}^{n_{hidden}}
$$

$$
W_2 \in \mathbb{R}^{n_{hidden} \times n_{output}}, \quad
b_2 \in \mathbb{R}^{n_{output}}
$$

前向传播公式:

$$
\begin{aligned}
z_1 &= X W_1 + b_1 \
a_1 &= \sigma(z_1) \
z_2 &= a_1 W_2 + b_2 \
\hat{y} &= \sigma(z_2)
\end{aligned}
$$

其中 $\sigma$ 是 Sigmoid 激活函数。矩阵化实现让计算更高效,尤其适合 GPU 并行。

2. 损失函数与反向传播

采用均方误差损失函数:

$$
J = \frac{1}{2m}\sum_{i=1}^m (\hat{y}_i – y_i)^2
$$

关键是通过链式法则计算梯度。以输出层权重 $W_2$ 为例:

$$
\frac{\partial J}{\partial W_2} =
\underbrace{\frac{\partial J}{\partial \hat{y}}}{\delta_2} \cdot
\underbrace{\frac{\partial \hat{y}}{\partial z_2}}
\cdot
\underbrace{\frac{\partial z_2}{\partial W_2}}_{a_1}
$$

实际实现时,我们会先计算输出层误差 $\delta_2$,再反向传播到隐藏层:

$$
\delta_1 = (\delta_2 W_2^T) \odot \sigma'(z_1)
$$

3. 学习率与批量大小

  • 学习率 :太大导致震荡,太小收敛慢。建议从 0.01 开始尝试
  • 批量大小 :小批量(如 32/64)比全批量更高效,兼具随机性和稳定性

二者需要协同调整:增大批量时,可适当增加学习率。经验公式:

$$
\text{新学习率} = \text{原学习率} \times \frac{\text{ 新批量大小}}{\text{ 原批量大小}}
$$

Python 实战代码

Sigmoid 及其导数实现

import numpy as np

def sigmoid(x):
    """向量化的 Sigmoid 函数"""
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    """Sigmoid 的导数,输入可以是原始值或激活值"""
    s = sigmoid(x) if np.abs(x).max() > 10 else x  # 避免重复计算
    return s * (1 - s)

单隐藏层网络训练

# 数据标准化(重要!)X = (X - np.mean(X, axis=0)) / np.std(X, axis=0)

# 初始化参数(Xavier 方法)W1 = np.random.randn(input_size, hidden_size) * np.sqrt(1/input_size)
b1 = np.zeros(hidden_size)
W2 = np.random.randn(hidden_size, output_size) * np.sqrt(1/hidden_size)
b2 = np.zeros(output_size)

for epoch in range(epochs):
    # Mini-Batch 划分
    for i in range(0, len(X), batch_size):
        X_batch = X[i:i+batch_size]
        y_batch = y[i:i+batch_size]

        # 前向传播
        z1 = np.dot(X_batch, W1) + b1
        a1 = sigmoid(z1)
        z2 = np.dot(a1, W2) + b2
        y_pred = sigmoid(z2)

        # 反向传播
        delta2 = (y_pred - y_batch) * sigmoid_derivative(z2)
        dW2 = np.dot(a1.T, delta2) / batch_size
        db2 = np.sum(delta2, axis=0) / batch_size

        delta1 = np.dot(delta2, W2.T) * sigmoid_derivative(z1)
        dW1 = np.dot(X_batch.T, delta1) / batch_size
        db1 = np.sum(delta1, axis=0) / batch_size

        # 参数更新
        W2 -= learning_rate * dW2
        b2 -= learning_rate * db2
        W1 -= learning_rate * dW1
        b1 -= learning_rate * db1

避坑指南

梯度消失问题

当使用 Sigmoid 激活时,深层网络容易出现梯度消失。解决方案:

  • 改用 ReLU 激活函数:$\text{ReLU}(x) = \max(0, x)$
  • 残差连接(ResNet 思路)

权重初始化

避免全零初始化!推荐方法:

  • Xavier 初始化:$W \sim N(0, \sqrt{\frac{2}{n_{in}+n_{out}}})$
  • He 初始化(ReLU 专用):$W \sim N(0, \sqrt{\frac{2}{n_{in}}})$

延伸思考

  1. 如何用 TensorBoard 可视化训练过程?
  2. 记录损失、准确率、权重分布等
  3. 可视化计算图和高维嵌入

  4. Adam vs SGD:

  5. Adam 适合非平稳目标,自动调整学习率
  6. SGD 更稳定,可能找到更优解但需精细调参

通过这次实践,我深刻体会到:理论推导是基础,但只有亲手实现代码,才能真正掌握神经网络的精妙之处。建议读者尝试修改网络结构,观察不同超参数对训练的影响,这是提升理解的最佳途径。

正文完
 0
评论(没有评论)