深度学习入门:从零理解bp误差反向传播算法

1次阅读
没有评论

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

image.webp

神经网络基础与反向传播的必要性

神经网络通过堆叠神经元层实现复杂函数拟合。前向传播计算预测值时,每个神经元的输出为:

深度学习入门:从零理解 bp 误差反向传播算法

$$
a_j^l = \sigma(\sum_k w_{jk}^l a_k^{l-1} + b_j^l)
$$

其中 $\sigma$ 为激活函数,$w$ 和 $b$ 分别代表权重和偏置。训练的核心目标是调整这些参数使得损失函数 $L$ 最小化,这正是反向传播算法的使命。

链式法则的数学本质

反向传播实质是复合函数求导的链式法则应用。以平方损失函数为例:

  1. 输出层误差项:
    $$\delta^L = \frac{\partial L}{\partial a^L} \odot \sigma'(z^L)$$

  2. 隐藏层误差传播:
    $$\delta^l = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^l)$$

  3. 参数梯度计算:
    $$\frac{\partial L}{\partial w^l} = \delta^l (a^{l-1})^T$$
    $$\frac{\partial L}{\partial b^l} = \delta^l$$

Python 实现详解

import numpy as np

class NeuralNetwork:
    def __init__(self, layers):
        # 初始化权重(He 初始化)self.weights = [np.random.randn(y, x)/np.sqrt(x) 
                        for x, y in zip(layers[:-1], layers[1:])]
        self.biases = [np.zeros((y, 1)) for y in layers[1:]]

    def forward(self, x):
        """前向传播计算"""
        for w, b in zip(self.weights, self.biases):
            x = sigmoid(np.dot(w, x) + b)
        return x

    def backprop(self, x, y):
        """反向传播计算梯度"""
        # 存储各层激活值和加权输入
        activations = [x]
        zs = []

        # 前向传播
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, activations[-1]) + b
            zs.append(z)
            activations.append(sigmoid(z))

        # 反向传播
        delta = (activations[-1] - y) * sigmoid_prime(zs[-1])
        nabla_w = [np.zeros_like(w) for w in self.weights]
        nabla_b = [np.zeros_like(b) for b in self.biases]
        nabla_w[-1] = np.dot(delta, activations[-2].T)
        nabla_b[-1] = delta

        for l in range(2, len(self.weights)+1):
            z = zs[-l]
            delta = np.dot(self.weights[-l+1].T, delta) * sigmoid_prime(z)
            nabla_w[-l] = np.dot(delta, activations[-l-1].T)
            nabla_b[-l] = delta

        return nabla_w, nabla_b

def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    return sigmoid(z)*(1-sigmoid(z))

梯度检查实现

数值梯度验证是调试的关键步骤:

def gradient_check(network, x, y, epsilon=1e-7):
    # 计算反向传播梯度
    nabla_w, nabla_b = network.backprop(x, y)

    # 数值梯度计算
    for layer in range(len(network.weights)):
        for i in range(network.weights[layer].shape[0]):
            for j in range(network.weights[layer].shape[1]):
                # 正向扰动
                network.weights[layer][i,j] += epsilon
                loss_plus = np.sum((network.forward(x) - y)**2)
                # 负向扰动
                network.weights[layer][i,j] -= 2*epsilon
                loss_minus = np.sum((network.forward(x) - y)**2)
                # 恢复原值
                network.weights[layer][i,j] += epsilon
                # 数值梯度
                numeric_grad = (loss_plus - loss_minus)/(2*epsilon)
                # 比较差异
                assert abs(nabla_w[layer][i,j] - numeric_grad) < 1e-5

常见问题深度解析

梯度消失 / 爆炸

当网络层数较深时,梯度可能呈现指数级衰减或增长。以 sigmoid 函数为例,其导数最大值为 0.25,经过 $n$ 层传播后梯度最多缩小 $0.25^n$。解决方案包括:

  • 使用 ReLU 及其变体激活函数
  • 采用残差连接结构
  • 合理的权重初始化(如 Xavier、He 初始化)

学习率选择

学习率 $\eta$ 直接影响收敛速度:

  1. 固定学习率:通常取 $10^{-3}$ 到 $10^{-5}$
  2. 自适应方法:Adam、RMSprop 等
  3. 学习率衰减:余弦退火、阶梯式衰减

激活函数对比

函数类型 优点 缺点
Sigmoid 输出平滑 容易饱和导致梯度消失
ReLU 计算高效 可能出现神经元死亡
LeakyReLU 缓解死亡问题 需要调参

实践避坑指南

  1. 梯度检查必做 :在复杂网络实现中,先用小样本验证梯度计算正确性
  2. 监控激活值分布 :使用 TensorBoard 等工具观察各层输出范围
  3. 谨慎选择初始化 :深层网络避免全零初始化,推荐 He 初始化配合 ReLU

思考与延伸

如何将反向传播扩展到卷积神经网络?关键在于理解:
1. 卷积核的局部连接特性
2. 池化层的梯度传播方式
3. 参数共享机制下的梯度计算

期待读者在掌握全连接网络的基础上,进一步探索 CNN 的反向传播实现。

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