BP神经网络详解:从数学原理到Python实战

1次阅读
没有评论

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

image.webp

数学原理拆解

BP 神经网络的核心是链式求导。假设网络有 L 层,第 l 层的输出为:

BP 神经网络详解:从数学原理到 Python 实战

$$
a^{(l)} = f(z^{(l)}), \quad z^{(l)} = W^{(l)}a^{(l-1)} + b^{(l)}
$$

对于平方误差损失函数 $E = \frac{1}{2}|y – a^{(L)}|^2$,输出层的误差项为:

$$
\delta^{(L)} = (a^{(L)} – y) \odot f'(z^{(L)})
$$

反向传播时,第 l 层的误差项计算如下($\odot$ 表示逐元素乘):

$$
\delta^{(l)} = (W^{(l+1)T}\delta^{(l+1)}) \odot f'(z^{(l)})
$$

激活函数对比

  • Sigmoid
    $$
    \sigma(z) = \frac{1}{1+e^{-z}} \quad \sigma'(z) = \sigma(z)(1-\sigma(z))
    $$
    易导致梯度消失(导数最大仅 0.25)

  • Tanh
    $$
    \tanh(z) = \frac{e^z – e^{-z}}{e^z + e^{-z}} \quad \tanh'(z) = 1 – \tanh^2(z)
    $$
    梯度消失问题稍缓和(导数最大为 1)

  • ReLU
    $$
    ReLU(z) = max(0,z) \quad ReLU'(z) = \begin{cases}
    1 & z>0 \
    0 & z\leq0
    \end{cases}
    $$
    缓解梯度消失但可能导致神经元死亡

Python 实现核心代码

import numpy as np

class NeuralNetwork:
    def __init__(self, layer_dims, activation='relu'):
        self.params = {}
        # He 初始化(ReLU 适用)for l in range(1, len(layer_dims)):
            self.params['W'+str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * np.sqrt(2/layer_dims[l-1])
            self.params['b'+str(l)] = np.zeros((layer_dims[l], 1))

    def forward(self, X):
        cache = {'A0': X}
        for l in range(1, len(self.layer_dims)):
            Z = np.dot(self.params['W'+str(l)], cache['A'+str(l-1)]) + self.params['b'+str(l)]
            cache['Z'+str(l)] = Z
            cache['A'+str(l)] = self._activate(Z)
        return cache

    def backward(self, cache, y):
        grads = {}
        m = y.shape[1]
        # 输出层误差
        dZ = cache['A'+str(L)] - y  # 假设使用线性输出层
        grads['W'+str(L)] = np.dot(dZ, cache['A'+str(L-1)].T) / m
        grads['b'+str(L)] = np.sum(dZ, axis=1, keepdims=True) / m

        # 反向传播
        for l in reversed(range(1, L)):
            dA = np.dot(self.params['W'+str(l+1)].T, dZ)
            dZ = dA * self._activate_deriv(cache['Z'+str(l)])
            grads['W'+str(l)] = np.dot(dZ, cache['A'+str(l-1)].T) / m
            grads['b'+str(l)] = np.sum(dZ, axis=1, keepdims=True) / m
        return grads

关键参数设置

  1. 学习率选择
  2. 初始建议 0.001-0.1
  3. 观察损失曲线:持续震荡需调小,下降过慢可适当增大

  4. 批量大小

  5. 小批量(32-256)通常优于全批量
  6. 显存受限时可尝试梯度累计

  7. 权重初始化

  8. Xavier 初始化(tanh):$Var(W) = \frac{2}{n_{in} + n_{out}}$
  9. He 初始化(ReLU):$Var(W) = \frac{2}{n_{in}}$

典型问题解决方案

  • 梯度爆炸
    添加梯度裁剪

    max_norm = 5
    for grad in grads.values():
        norm = np.linalg.norm(grad)
        if norm > max_norm:
            grad *= max_norm / norm

  • 神经元死亡 (ReLU):
    使用 LeakyReLU:$\alpha=0.01$

  • 过拟合

  • 添加 L2 正则化:loss += 0.5 * lambda * np.sum(W**2)
  • 早停法(early stopping)

效果可视化

plt.plot(loss_history)
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.title(f'Final Accuracy: {accuracy:.2%}')
plt.grid()

进阶技巧

  1. 学习率衰减

    lr = initial_lr * (1. / (1. + decay_rate * epoch))

  2. 批归一化

  3. 在激活函数前插入 BN 层
  4. 需调整反向传播公式

  5. 模型保存

    np.savez('model.npz', **self.params)

通过这个实现,你应该能完整理解 BP 算法的运作机制。建议尝试用不同激活函数组合,观察训练过程的差异。

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