BP神经网络原理详解:前向传播、反向传播与链式求导实战

1次阅读
没有评论

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

image.webp

神经网络基础概念

BP 神经网络(Backpropagation Neural Network)是一种多层前馈网络,通过反向传播算法进行训练。它由输入层、隐藏层和输出层组成,每层包含多个神经元,神经元之间通过权重连接。

BP 神经网络原理详解:前向传播、反向传播与链式求导实战

前向传播的矩阵运算

前向传播是指输入数据从输入层经过隐藏层最终到达输出层的过程。具体步骤如下:

  1. 定义网络结构:假设有一个包含 1 个隐藏层的网络,输入层有 n 个神经元,隐藏层有 m 个神经元,输出层有 k 个神经元。
  2. 初始化权重:随机初始化权重矩阵 W1(n×m)和 W2(m×k),以及偏置向量 b1(m×1)和 b2(k×1)。
  3. 计算隐藏层输出:
    h = σ(W1.T * x + b1)

    其中 σ 是激活函数(如 Sigmoid 或 ReLU),x 是输入向量。

  4. 计算输出层输出:
    y_hat = σ(W2.T * h + b2)

反向传播与链式求导

反向传播是通过计算损失函数对各参数的梯度,利用梯度下降法更新参数的过程。核心是链式求导法则。

损失函数

以均方误差(MSE)为例:

L = 1/2 * (y - y_hat)^2

反向传播步骤

  1. 计算输出层梯度:
    ∂L/∂y_hat = -(y - y_hat)
    ∂y_hat/∂z2 = y_hat * (1 - y_hat)  # z2 = W2.T * h + b2
    ∂L/∂W2 = h * (∂L/∂y_hat * ∂y_hat/∂z2)
  2. 计算隐藏层梯度:
    ∂L/∂h = W2 * (∂L/∂y_hat * ∂y_hat/∂z2)
    ∂h/∂z1 = h * (1 - h)  # z1 = W1.T * x + b1
    ∂L/∂W1 = x * (∂L/∂h * ∂h/∂z1)
  3. 参数更新:
    W1 = W1 - η * ∂L/∂W1
    W2 = W2 - η * ∂L/∂W2

    其中 η 是学习率。

Python 实现

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        self.W1 = np.random.randn(input_size, hidden_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size)
        self.b2 = np.zeros((1, output_size))

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-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
        self.y_hat = self.sigmoid(self.z2)
        return self.y_hat

    def backward(self, x, y, learning_rate):
        m = x.shape[0]

        # Output layer gradient
        dL_dy_hat = -(y - self.y_hat)
        dy_hat_dz2 = self.y_hat * (1 - self.y_hat)
        dL_dz2 = dL_dy_hat * dy_hat_dz2
        dL_dW2 = np.dot(self.h.T, dL_dz2) / m
        dL_db2 = np.sum(dL_dz2, axis=0, keepdims=True) / m

        # Hidden layer gradient
        dL_dh = np.dot(dL_dz2, self.W2.T)
        dh_dz1 = self.h * (1 - self.h)
        dL_dz1 = dL_dh * dh_dz1
        dL_dW1 = np.dot(x.T, dL_dz1) / m
        dL_db1 = np.sum(dL_dz1, axis=0, keepdims=True) / m

        # Update parameters
        self.W2 -= learning_rate * dL_dW2
        self.b2 -= learning_rate * dL_db2
        self.W1 -= learning_rate * dL_dW1
        self.b1 -= learning_rate * dL_db1

    def train(self, x, y, epochs, learning_rate):
        for epoch in range(epochs):
            y_hat = self.forward(x)
            self.backward(x, y, learning_rate)
            loss = np.mean(np.square(y - y_hat))
            if epoch % 100 == 0:
                print(f'Epoch {epoch}, Loss: {loss}')

# Example usage
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.1)

工程实践

梯度消失 / 爆炸问题

  1. 使用 ReLU 激活函数替代 Sigmoid
  2. 采用权重初始化技巧(如 Xavier 初始化)
  3. 实施梯度裁剪(Gradient Clipping)

学习率设置

  1. 初始学习率通常设为 0.1 或 0.01
  2. 使用学习率衰减策略
  3. 考虑自适应优化器(如 Adam)

批量归一化

  1. 在每个隐藏层后添加批归一化层
  2. 计算 mini-batch 的均值和方差
  3. 使用可学习的缩放和平移参数

思考题

  1. 如何将本文代码扩展为支持 GPU 加速的版本?
  2. 使用 CuPy 替代 NumPy
  3. 或者迁移到 PyTorch/TensorFlow 框架

  4. 对比自动微分和手动实现的反向传播有何优劣?

  5. 自动微分:开发效率高,但灵活性低
  6. 手动实现:理解深入,但维护成本高
正文完
 0
评论(没有评论)