从零实现两个隐藏层的全连接神经网络:手写反向传播算法解析

1次阅读
没有评论

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

image.webp

神经网络基础概念回顾

在开始构建我们的神经网络之前,让我们先回顾一下几个核心概念:

从零实现两个隐藏层的全连接神经网络:手写反向传播算法解析

  • 全连接层 :神经网络中的基础层类型,每个神经元都与上一层的所有神经元相连
  • 激活函数 :引入非线性因素的关键组件,常见的有 ReLU、Sigmoid、Tanh 等
  • 损失函数 :衡量预测结果与真实值差异的函数,如交叉熵、均方误差

两个隐藏层网络架构设计

我们设计的网络结构如下:

  1. 输入层:接收原始数据
  2. 隐藏层 1:第一个全连接层,使用 ReLU 激活函数
  3. 隐藏层 2:第二个全连接层,继续使用 ReLU 激活函数
  4. 输出层:最终预测层,使用 Softmax 激活函数(分类任务)

这个结构可以表示为:Input → FC1 → ReLU → FC2 → ReLU → Output → Softmax

反向传播数学原理详解

反向传播的核心是链式法则。让我们分解梯度计算过程:

  1. 前向传播计算损失
  2. 从输出层开始反向计算各层梯度
  3. 更新权重参数

关键公式:

对于输出层:
$$\frac{\partial L}{\partial W_3} = \frac{\partial L}{\partial a_3} \cdot \frac{\partial a_3}{\partial z_3} \cdot \frac{\partial z_3}{\partial W_3}$$

对于隐藏层 2:
$$\frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial a_3} \cdot \frac{\partial a_3}{\partial z_3} \cdot \frac{\partial z_3}{\partial a_2} \cdot \frac{\partial a_2}{\partial z_2} \cdot \frac{\partial z_2}{\partial W_2}$$

对于隐藏层 1:
$$\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial a_3} \cdot … \cdot \frac{\partial a_1}{\partial z_1} \cdot \frac{\partial z_1}{\partial W_1}$$

完整的 Python 实现

以下是使用 NumPy 的实现代码:

import numpy as np

class TwoLayerNN:
    def __init__(self, input_size, hidden1, hidden2, output_size):
        # 初始化权重
        self.W1 = np.random.randn(input_size, hidden1) * 0.01
        self.b1 = np.zeros((1, hidden1))
        self.W2 = np.random.randn(hidden1, hidden2) * 0.01
        self.b2 = np.zeros((1, hidden2))
        self.W3 = np.random.randn(hidden2, output_size) * 0.01
        self.b3 = np.zeros((1, output_size))

    def relu(self, x):
        return np.maximum(0, x)

    def softmax(self, x):
        exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
        return exp_x / np.sum(exp_x, axis=1, keepdims=True)

    def forward(self, X):
        # 前向传播
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.relu(self.z1)
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.a2 = self.relu(self.z2)
        self.z3 = np.dot(self.a2, self.W3) + self.b3
        self.a3 = self.softmax(self.z3)
        return self.a3

    def backward(self, X, y, learning_rate):
        # 反向传播
        m = X.shape[0]

        # 输出层梯度
        dz3 = self.a3 - y
        dw3 = np.dot(self.a2.T, dz3) / m
        db3 = np.sum(dz3, axis=0, keepdims=True) / m

        # 隐藏层 2 梯度
        dz2 = np.dot(dz3, self.W3.T) * (self.z2 > 0)
        dw2 = np.dot(self.a1.T, dz2) / m
        db2 = np.sum(dz2, axis=0, keepdims=True) / m

        # 隐藏层 1 梯度
        dz1 = np.dot(dz2, self.W2.T) * (self.z1 > 0)
        dw1 = np.dot(X.T, dz1) / m
        db1 = np.sum(dz1, axis=0, keepdims=True) / m

        # 更新权重
        self.W3 -= learning_rate * dw3
        self.b3 -= learning_rate * db3
        self.W2 -= learning_rate * dw2
        self.b2 -= learning_rate * db2
        self.W1 -= learning_rate * dw1
        self.b1 -= learning_rate * db1

    def train(self, X, y, epochs, learning_rate):
        for epoch in range(epochs):
            # 前向传播
            output = self.forward(X)

            # 计算损失
            loss = -np.sum(y * np.log(output + 1e-8)) / X.shape[0]

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

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

训练过程可视化

训练过程中,我们可以记录并可视化以下指标:

  1. 训练损失曲线:观察损失是否在下降
  2. 验证准确率:评估模型泛化能力
  3. 权重分布:检查梯度是否合理
import matplotlib.pyplot as plt

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

常见问题与调试技巧

  1. 梯度消失
  2. 解决方法:使用 ReLU 激活函数、批归一化、残差连接
  3. 检查:打印各层梯度大小,确保它们不会指数级减小

  4. 学习率选择

  5. 开始建议使用 1e- 3 到 1e- 5 之间的值
  6. 可以采用学习率衰减策略

  7. 初始化问题

  8. 使用 Xavier 或 He 初始化
  9. 避免全零初始化

延伸阅读建议

  1. 更复杂的网络结构:
  2. 卷积神经网络 (CNN)
  3. 循环神经网络 (RNN)
  4. Transformer 结构

  5. 优化技巧:

  6. 批量归一化 (BatchNorm)
  7. Dropout 正则化
  8. 自适应优化器 (Adam, RMSprop)

  9. 高级主题:

  10. 注意力机制
  11. 自监督学习
  12. 元学习

总结

通过本文,我们从零实现了一个包含两个隐藏层的全连接神经网络,并详细讲解了反向传播算法的数学原理和实现细节。这个练习不仅帮助我们理解神经网络的底层工作原理,也为后续学习更复杂的模型打下了坚实基础。建议读者尝试修改网络结构或参数,观察对训练效果的影响,这是深入理解神经网络的最佳方式。

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