深度学习入门:从零理解bp反向传播计算的数学原理与实现

1次阅读
没有评论

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

image.webp

前言

作为深度学习的基础,反向传播算法(Backpropagation)是每个初学者必须掌握的技能。刚开始接触时,可能会觉得数学推导复杂难懂。本文将从最基础的数学原理出发,结合 Python 代码实现,带你一步步理解这个核心算法。

深度学习入门:从零理解 bp 反向传播计算的数学原理与实现

核心概念

前向传播与反向传播

神经网络的学习过程可以看作是两个阶段的循环:

  1. 前向传播 :输入数据通过网络层层传递,最终得到预测值
  2. 反向传播 :计算预测值与真实值的误差,并将这个误差反向传播回网络,用于调整各层参数

梯度下降与链式法则

  • 梯度下降 :通过计算损失函数对参数的梯度,沿着梯度反方向调整参数,使损失函数值减小
  • 链式法则 :反向传播的核心数学工具,用于计算复合函数的导数

数学推导

以一个简单的单隐藏层神经网络为例,推导权重更新的过程。

网络结构

  1. 输入层:$x$
  2. 隐藏层:$h = \sigma(W_1x + b_1)$
  3. 输出层:$y = W_2h + b_2$
  4. 损失函数:$L = \frac{1}{2}(y – t)^2$,其中 $t$ 是真实值

反向传播推导

  1. 计算输出层梯度:
    $$\frac{\partial L}{\partial y} = y – t$$
    $$\frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial W_2} = (y-t)h^T$$

  2. 计算隐藏层梯度:
    $$\frac{\partial L}{\partial h} = W_2^T(y-t)$$
    $$\frac{\partial L}{\partial z} = \frac{\partial L}{\partial h} \cdot \sigma'(z)$$
    $$\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial z}x^T$$

  3. 参数更新:
    $$W_1 = W_1 – \eta \frac{\partial L}{\partial W_1}$$
    $$W_2 = W_2 – \eta \frac{\partial L}{\partial W_2}$$

Python 实现

下面是一个简单的神经网络实现,包含完整的前向传播和反向传播过程。

import numpy as np

class SimpleNN:
    def __init__(self, input_size, hidden_size, output_size):
        # 初始化权重
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros(output_size)

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def sigmoid_derivative(self, x):
        return self.sigmoid(x) * (1 - self.sigmoid(x))

    def forward(self, x):
        # 前向传播
        self.z1 = np.dot(x, self.W1) + self.b1
        self.h = self.sigmoid(self.z1)
        self.z2 = np.dot(self.h, self.W2) + self.b2
        return self.z2

    def backward(self, x, y, t, learning_rate=0.01):
        # 反向传播
        m = x.shape[0]  # 样本数量

        # 输出层梯度
        dL_dy = (y - t) / m
        dL_dW2 = np.dot(self.h.T, dL_dy)
        dL_db2 = np.sum(dL_dy, axis=0)

        # 隐藏层梯度
        dL_dh = np.dot(dL_dy, self.W2.T)
        dL_dz1 = dL_dh * self.sigmoid_derivative(self.z1)
        dL_dW1 = np.dot(x.T, dL_dz1)
        dL_db1 = np.sum(dL_dz1, axis=0)

        # 参数更新
        self.W1 -= learning_rate * dL_dW1
        self.b1 -= learning_rate * dL_db1
        self.W2 -= learning_rate * dL_dW2
        self.b2 -= learning_rate * dL_db2

    def train(self, X, Y, epochs=1000, learning_rate=0.01):
        losses = []
        for epoch in range(epochs):
            # 前向传播
            y_pred = self.forward(X)

            # 计算损失
            loss = np.mean(0.5 * (y_pred - Y) ** 2)
            losses.append(loss)

            # 反向传播
            self.backward(X, y_pred, Y, learning_rate)

            if epoch % 100 == 0:
                print(f'Epoch {epoch}, Loss: {loss}')

        return losses

可视化展示

使用 matplotlib 绘制训练过程中的损失函数变化曲线:

import matplotlib.pyplot as plt

# 创建并训练网络
input_size = 2
hidden_size = 4
output_size = 1
nn = SimpleNN(input_size, hidden_size, output_size)

# 生成简单数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
Y = np.array([[0], [1], [1], [0]])  # XOR 问题

# 训练并获取损失历史
losses = nn.train(X, Y, epochs=1000, learning_rate=0.1)

# 绘制损失曲线
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()

避坑指南

  1. 梯度消失问题
  2. 现象:深层网络中,梯度在反向传播时变得越来越小
  3. 解决方案:使用 ReLU 等激活函数替代 sigmoid;尝试残差连接

  4. 学习率设置不当

  5. 现象:损失函数震荡不收敛或下降过慢
  6. 解决方案:尝试不同的学习率(如 0.1, 0.01, 0.001);使用学习率衰减策略

  7. 权重初始化不当

  8. 现象:网络无法正常学习
  9. 解决方案:使用 Xavier 或 He 初始化方法替代随机初始化

扩展思考

  1. 尝试实现不同激活函数的反向传播:
  2. ReLU: $f(x) = max(0, x)$
  3. LeakyReLU: $f(x) = max(0.01x, x)$
  4. Tanh: $f(x) = \frac{e^x – e^{-x}}{e^x + e^{-x}}$

  5. 扩展网络结构:

  6. 增加隐藏层数量
  7. 尝试不同的损失函数(交叉熵损失等)

  8. 实现 mini-batch 梯度下降:

  9. 将数据集分成小批次进行训练
  10. 比较与全批次训练的差异

结语

通过本文的学习,你应该已经掌握了反向传播的基本原理和实现方法。建议动手实践代码,尝试修改网络结构和参数,观察训练效果的变化。深度学习的学习曲线虽然陡峭,但只要理解了这些基础概念,后面的路会越走越宽。

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