从零实现神经网络:前向传播、反向传播与训练全流程解析

1次阅读
没有评论

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

image.webp

神经网络基础与实现意义

神经网络作为深度学习的核心架构,其通过多层非线性变换实现复杂函数的逼近能力。理解前向传播、反向传播的数学原理和代码实现,是掌握深度学习的关键基础。本文将从零开始实现一个双层神经网络,涵盖矩阵运算、梯度计算和参数更新全过程,帮助读者建立对神经网络工作机制的直观认知。

从零实现神经网络:前向传播、反向传播与训练全流程解析

技术实现详解

1. 前向传播实现

前向传播本质是逐层的矩阵乘法与激活函数变换。以下是用 NumPy 实现的全连接层示例:

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # 参数初始化(后续会讨论初始化策略)self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        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.a1 = self.sigmoid(self.z1)
        # 第二层计算
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.a2 = self.sigmoid(self.z2)
        return self.a2

关键点说明:

  • 矩阵乘法使用 np.dot 实现维度变换
  • Sigmoid 激活函数将线性变换转为非线性
  • 每层输出保留为类变量,供反向传播使用

2. 反向传播推导

反向传播通过链式法则计算损失函数对参数的梯度。以均方误差损失为例:

  1. 输出层梯度计算:
    $$\frac{\partial L}{\partial z_2} = (a_2 – y) \odot \sigma'(z_2)$$

  2. 隐藏层梯度计算:
    $$\frac{\partial L}{\partial W_2} = a_1^T \frac{\partial L}{\partial z_2}$$
    $$\frac{\partial L}{\partial a_1} = \frac{\partial L}{\partial z_2} W_2^T$$
    $$\frac{\partial L}{\partial z_1} = \frac{\partial L}{\partial a_1} \odot \sigma'(z_1)$$

代码实现:

def backward(self, X, y, output):
    m = X.shape[0]  # 样本数量

    # 输出层梯度
    dZ2 = (output - y) * self.sigmoid(self.z2) * (1 - self.sigmoid(self.z2))
    dW2 = np.dot(self.a1.T, dZ2) / m
    db2 = np.sum(dZ2, axis=0, keepdims=True) / m

    # 隐藏层梯度
    dA1 = np.dot(dZ2, self.W2.T)
    dZ1 = dA1 * self.sigmoid(self.z1) * (1 - self.sigmoid(self.z1))
    dW1 = np.dot(X.T, dZ1) / m
    db1 = np.sum(dZ1, axis=0, keepdims=True) / m

    return dW1, db1, dW2, db2

3. 参数更新与训练

使用随机梯度下降 (SGD) 更新参数:

def update_params(self, grads, learning_rate=0.1):
    dW1, db1, dW2, db2 = grads
    self.W1 -= learning_rate * dW1
    self.b1 -= learning_rate * db1
    self.W2 -= learning_rate * dW2
    self.b2 -= learning_rate * db2

完整训练流程:

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

        # 计算损失(均方误差)loss = np.mean((output - y) ** 2)

        # 反向传播
        grads = self.backward(X, y, output)

        # 参数更新
        self.update_params(grads, lr)

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

实践陷阱与解决方案

1. 梯度消失 / 爆炸

现象:深层网络训练时梯度指数级减小或增大

解决方案:

  • 使用 ReLU 等修正激活函数
  • 采用 Xavier/Glorot 初始化
  • 添加梯度裁剪(gradient clipping)

2. 参数初始化

错误示例:全零初始化会导致对称性问题

推荐方法:

# Xavier 初始化(适合 Sigmoid/Tanh)self.W1 = np.random.randn(input_size, hidden_size) / np.sqrt(input_size)
# He 初始化(适合 ReLU)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2/input_size)

3. 学习率调整

静态学习率的改进方案:

# 指数衰减
learning_rate = 0.1 * (0.95 ** epoch)
# 阶梯衰减
if epoch % 100 == 0:
    learning_rate *= 0.5

思考与扩展

  1. 批量训练实现:修改前向 / 反向传播中的矩阵运算维度,支持 mini-batch 输入
  2. 激活函数对比:ReLU 的梯度计算为dZ = dA * (Z > 0),避免 Sigmoid 的梯度饱和
  3. L2 正则化:在损失函数中添加 0.5 * reg * (np.sum(W1**2) + np.sum(W2**2)),梯度计算相应增加reg * W1
正文完
 0
评论(没有评论)