用Python手搓神经网络:从输入层到输出层的完整实现(附代码)

1. 神经网络基础架构搭建

神经网络的本质是通过层级计算实现特征转换。我们先从最简单的三层结构开始——输入层负责接收数据,隐藏层进行特征提取,输出层生成预测结果。用NumPy实现时,关键在于理解权重矩阵的维度关系。

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((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros((1, output_size))

权重初始化技巧

  • 小随机数防止梯度爆炸
  • 偏置初始化为零
  • 输入/输出维度由数据特征决定

提示:隐藏层节点数通常取2的幂次方(如64、128),有利于GPU并行计算

2. 前向传播实现细节

前向传播是数据从输入到输出的流动过程,需要依次计算各层的线性变换和激活函数。

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
    return self.softmax(self.z2)

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

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

激活函数对比

函数类型 公式 优点 缺点
ReLU max(0, x) 计算简单,缓解梯度消失 负数区失效
Sigmoid 1/(1+e^-x) 输出范围(0,1) 容易梯度饱和
Tanh (e^x-e^-x)/(e^x+e^-x) 输出范围(-1,1) 计算量较大

3. 反向传播与参数更新

反向传播通过链式法则计算梯度,是神经网络学习的核心机制。我们采用交叉熵损失函数配合Softmax输出:

def backward(self, X, y, output):
    m = X.shape[0]  # 样本数量
    
    # 输出层梯度
    delta3 = output
    delta3[range(m), y] -= 1
    delta3 /= m
    
    # 隐藏层梯度
    dW2 = np.dot(self.a1.T, delta3)
    db2 = np.sum(delta3, axis=0, keepdims=True)
    
    delta2 = np.dot(delta3, self.W2.T) * (self.a1 > 0)
    dW1 = np.dot(X.T, delta2)
    db1 = np.sum(delta2, axis=0)
    
    return dW1, db1, dW2, db2

参数更新公式

W = W - learning_rate * dW
b = b - learning_rate * db

4. MNIST实战示例

下面展示如何在MNIST数据集上应用我们的神经网络:

from sklearn.datasets import fetch_openml
from sklearn.preprocessing import MinMaxScaler

# 数据加载与预处理
mnist = fetch_openml('mnist_784', version=1)
X = MinMaxScaler().fit_transform(mnist.data)
y = mnist.target.astype(int)

# 创建网络实例
nn = SimpleNN(input_size=784, hidden_size=128, output_size=10)

# 训练循环
for epoch in range(100):
    # 前向传播
    output = nn.forward(X)
    
    # 计算损失
    loss = -np.log(output[range(len(X)), y]).mean()
    
    # 反向传播
    dW1, db1, dW2, db2 = nn.backward(X, y, output)
    
    # 参数更新
    nn.W1 -= 0.1 * dW1
    nn.b1 -= 0.1 * db1
    nn.W2 -= 0.1 * dW2
    nn.b2 -= 0.1 * db2
    
    if epoch % 10 == 0:
        print(f"Epoch {epoch}, Loss: {loss:.4f}")

可视化调试技巧

  1. 损失曲线监控:确保损失值单调下降
  2. 权重直方图:观察参数分布是否合理
  3. 梯度检查:验证反向传播实现正确性
# 梯度检查示例
def gradient_check():
    epsilon = 1e-7
    perturb = np.zeros_like(nn.W1)
    perturb[0,0] = epsilon
    
    loss1 = compute_loss(X, y)
    nn.W1 += perturb
    loss2 = compute_loss(X, y)
    numerical_grad = (loss2 - loss1)/epsilon
    
    analytic_grad = dW1[0,0]
    diff = abs(numerical_grad - analytic_grad)
    print(f"Gradient check: {diff}")

5. 性能优化策略

基础实现跑通后,可以考虑以下优化手段:

批量归一化

def batchnorm_forward(x, gamma, beta, eps=1e-5):
    mu = np.mean(x, axis=0)
    var = np.var(x, axis=0)
    x_hat = (x - mu) / np.sqrt(var + eps)
    out = gamma * x_hat + beta
    return out

Dropout正则化

def dropout_forward(x, p_dropout):
    mask = (np.random.rand(*x.shape) > p_dropout) / (1 - p_dropout)
    return x * mask

优化器对比

优化器 更新规则 适用场景
SGD θ = θ - η∇θ 简单任务
Momentum v = γv + η∇θ; θ = θ - v 逃离局部最优
Adam 综合动量与自适应学习率 大多数深度学习任务

实际项目中,建议先用Adam优化器快速验证模型可行性,再根据需求切换更专业的优化策略。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐