BP神经网络Python实现:从零构建源码到避坑指南

1次阅读
没有评论

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

image.webp

BP 神经网络 Python 实现:从零构建源码到避坑指南

背景痛点

BP 神经网络是深度学习的基础,但在实现过程中新手常会遇到以下问题:

BP 神经网络 Python 实现:从零构建源码到避坑指南

  1. 梯度消失 :深层网络中梯度逐渐变小,导致权重更新缓慢甚至停滞。
  2. 过拟合 :模型在训练集表现优异但泛化能力差。
  3. 训练震荡 :损失函数波动大,难以收敛。

与框架(如 PyTorch)相比,Numpy 原生实现有以下特点:

  • 优点:更透明,能深入理解算法细节;轻量级,适合教学和调试。
  • 缺点:需要手动实现所有功能,效率较低。

核心实现

网络结构

我们实现一个三层网络:输入层、隐藏层和输出层。

关键代码

正向传播

import numpy as np

# 激活函数
class Sigmoid:
    @staticmethod
    def forward(z):
        return 1 / (1 + np.exp(-z))

    @staticmethod
    def derivative(a):
        return a * (1 - a)

# 正向传播
class ForwardPropagation:
    @staticmethod
    def run(X, W1, b1, W2, b2):
        # 第一层
        z1 = np.dot(X, W1) + b1
        a1 = Sigmoid.forward(z1)

        # 第二层
        z2 = np.dot(a1, W2) + b2
        a2 = Sigmoid.forward(z2)

        return a2, (z1, a1, z2, a2)

反向传播

class BackPropagation:
    @staticmethod
    def run(X, y, cache, W1, W2):
        z1, a1, z2, a2 = cache
        m = X.shape[0]

        # 输出层误差
        dZ2 = a2 - y
        dW2 = (1/m) * np.dot(a1.T, dZ2)
        db2 = (1/m) * np.sum(dZ2, axis=0, keepdims=True)

        # 隐藏层误差
        dZ1 = np.dot(dZ2, W2.T) * Sigmoid.derivative(a1)
        dW1 = (1/m) * np.dot(X.T, dZ1)
        db1 = (1/m) * np.sum(dZ1, axis=0, keepdims=True)

        return dW1, db1, dW2, db2

优化实践

权重初始化

def initialize_parameters(n_input, n_hidden, n_output):
    # Xavier/Glorot 初始化
    W1 = np.random.randn(n_input, n_hidden) * np.sqrt(1/n_input)
    b1 = np.zeros((1, n_hidden))
    W2 = np.random.randn(n_hidden, n_output) * np.sqrt(1/n_hidden)
    b2 = np.zeros((1, n_output))

    return W1, b1, W2, b2

学习率衰减

class LearningRateScheduler:
    @staticmethod
    def fixed(epoch, lr=0.01):
        return lr

    @staticmethod
    def adagrad(grad, cache, eps=1e-8):
        cache += grad**2
        return grad / (np.sqrt(cache) + eps)

L2 正则化

def compute_cost(y_hat, y, W1, W2, lambda_=0.1):
    m = y.shape[0]
    cross_entropy = -np.mean(y*np.log(y_hat) + (1-y)*np.log(1-y_hat))
    l2_penalty = (lambda_/(2*m)) * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    return cross_entropy + l2_penalty

避坑指南

梯度检查

def gradient_check(X, y, W1, b1, W2, b2, epsilon=1e-7):
    # 计算数值梯度
    parameters = [W1, b1, W2, b2]
    grad_approx = []

    for param in parameters:
        param_approx = np.zeros_like(param)

        for i in range(param.shape[0]):
            for j in range(param.shape[1]):
                param_plus = param.copy()
                param_plus[i,j] += epsilon

                param_minus = param.copy()
                param_minus[i,j] -= epsilon

                # 计算损失差
                # 这里需要根据实际情况调整
                loss_plus = compute_loss(X, y, W1, b1, W2, b2)
                loss_minus = compute_loss(X, y, W1, b1, W2, b2)

                param_approx[i,j] = (loss_plus - loss_minus)/(2*epsilon)

        grad_approx.append(param_approx)

    return grad_approx

早停实现

class EarlyStopping:
    def __init__(self, patience=5, min_delta=0.001):
        self.patience = patience
        self.min_delta = min_delta
        self.counter = 0
        self.best_loss = float('inf')

    def __call__(self, current_loss):
        if self.best_loss - current_loss > self.min_delta:
            self.best_loss = current_loss
            self.counter = 0
        else:
            self.counter += 1
            if self.counter >= self.patience:
                return True
        return False

总结与思考

通过本文,我们实现了 BP 神经网络的核心逻辑,并针对常见问题提供了解决方案。完整的实现需要考虑更多细节,比如:

  1. 如何改进代码以支持 Mini-batch 训练?
  2. 如何添加更多的激活函数(如 ReLU)?
  3. 如何可视化训练过程以更好地调试模型?

这些改进方向留给读者作为练习。希望本文能帮助你理解 BP 神经网络的实现细节,在机器学习的学习之路上更进一步。

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