从‘女友判定机’到图像识别:用Python和NumPy从零实现一个真正的多层感知机(MLP)

张开发
2026/4/19 19:36:32 15 分钟阅读

分享文章

从‘女友判定机’到图像识别:用Python和NumPy从零实现一个真正的多层感知机(MLP)
从神经元到智能分类器用NumPy徒手实现多层感知机的数学之美在咖啡厅里我常看到初学者对着TensorFlow的model.fit()发呆——黑箱般的神经网络究竟如何运作当我们剥开深度学习框架的层层封装会发现最精妙的部分往往藏在矩阵乘法和导数计算中。本文将用纯NumPy构建一个能识别手写数字的多层感知机MLP就像用乐高积木搭建摩天大楼每一块砖都是可触摸的数学公式。1. 神经网络的乐高积木从生物神经元到数学公式1943年的某个深夜McCulloch和Pitts在纸上画出了第一个神经元数学模型。这个看似简单的结构如今支撑着整个深度学习宇宙class Neuron: def __init__(self, n_inputs): self.weights np.random.randn(n_inputs) self.bias np.random.randn() def forward(self, inputs): z np.dot(inputs, self.weights) self.bias return 1 / (1 np.exp(-z)) # Sigmoid激活权重矩阵的物理意义令人着迷当处理28×28的手写数字图像时展平为784维向量假设隐藏层有128个神经元这个全连接层的权重矩阵就是784×128的知识图谱。每个元素w_ij代表输入像素i与隐藏神经元j的连接强度。注意初始化权重时建议使用He初始化对于ReLU激活标准差设为√(2/n_inputs)常见的激活函数对比函数类型数学表达式梯度特性适用场景Sigmoid1/(1e^-x)0-0.25二分类输出层Tanh(e^x-e^-x)/(e^xe^-x)0-1隐藏层ReLUmax(0,x)0或1深层网络首选2. 构建网络骨架矩阵乘法背后的维度魔术实现全连接层时最精妙的是矩阵维度的舞蹈。假设输入X形状为(batch_size, input_dim)权重W形状为(input_dim, output_dim)前向传播就是def dense_forward(X, W, b): return np.dot(X, W) b # 广播机制自动处理偏置批量处理的优势在于GPU的并行计算。当batch_size32时我们实际上同时计算32个样本的预测。反向传播时梯度也是32个样本梯度的平均值这既稳定了训练又提升了效率。维度变化示例输入层32×784 (MNIST图像)隐藏层132×256 (经过W1:784×256)隐藏层232×128 (经过W2:256×128)输出层32×10 (经过W3:128×10)3. 损失函数模型的指南针与纠错机制交叉熵损失比MSE更适合分类任务它在概率空间衡量误差。对于多分类问题def cross_entropy(y_pred, y_true): m y_true.shape[0] log_likelihood -np.log(y_pred[range(m), y_true]) return np.sum(log_likelihood) / m反向传播时输出层的梯度计算异常简洁# softmax cross_entropy的联合梯度 grad_output y_pred.copy() grad_output[range(m), y_true] - 1 grad_output / m梯度流动的直观理解当预测概率p0.8而真实标签为1时梯度信号会温和地告诉网络已经不错但可以更好当p0.1时则发出强烈修正信号。4. 反向传播链式法则的工程奇迹实现反向传播时建议从输出层逐步回溯。以下是隐藏层的梯度计算def dense_backward(dZ, cache): X, W cache dW np.dot(X.T, dZ) db np.sum(dZ, axis0) dX np.dot(dZ, W.T) return dX, dW, db梯度检查技巧用数值梯度验证解析梯度这是调试的金标准def check_gradient(): eps 1e-7 f_theta loss(theta) f_theta_plus loss(theta eps) num_grad (f_theta_plus - f_theta) / eps print(f解析梯度:{analytic_grad}, 数值梯度:{num_grad})优化器选择对比优化器更新规则内存占用适用场景SGDθ θ - η∇θ低基础版本Momentumvγvη∇θ, θθ-v中逃离局部最优Adam自适应矩估计高默认首选5. 实战MNIST从数字识别看模型进化加载数据时的预处理至关重要def load_mnist(): (X_train, y_train), (X_test, y_test) mnist.load_data() X_train X_train.reshape(-1, 28*28) / 255.0 y_train np.eye(10)[y_train] # one-hot编码 return X_train, y_train超参数调优经验学习率从3e-4开始尝试批量大小32/64适合CPU256适合GPU层数2-3个隐藏层足以应对MNIST神经元数量每层128-512个训练循环的核心代码结构for epoch in range(epochs): for X_batch, y_batch in dataloader(): # 前向传播 probs model.forward(X_batch) # 计算损失 loss cross_entropy(probs, y_batch) # 反向传播 grad output_gradient(probs, y_batch) model.backward(grad) # 参数更新 optimizer.step(model.parameters)当我在第一次运行完整训练流程后看到测试准确率达到95%时那种理解每个计算环节带来的满足感远胜过调用model.fit()得到的99%准确率。这就像亲手组装引擎的机械师虽然性能可能不如工厂成品但对每个零件的理解深入骨髓。

更多文章