从零开始:用Python手把手实现一个前馈神经网络(FNN)完整代码示例

张开发
2026/4/12 16:27:28 15 分钟阅读

分享文章

从零开始:用Python手把手实现一个前馈神经网络(FNN)完整代码示例
从零开始用Python手把手实现一个前馈神经网络FNN完整代码示例在人工智能领域前馈神经网络Feedforward Neural Network, FNN是最基础也最经典的模型之一。它不仅是深度学习入门的必经之路更是理解更复杂神经网络结构的基石。本文将带你从零开始用Python一步步实现一个完整的FNN包括网络构建、训练过程和性能评估。不同于理论讲解我们将聚焦于实际编码中的每个细节让你真正掌握FNN的实现精髓。1. 环境准备与基础概念在开始编码之前我们需要确保开发环境配置正确并理解一些核心概念。Python 3.7版本是必须的同时需要安装以下关键库pip install numpy matplotlib tensorflow为什么选择这些库NumPy提供了高效的数值计算能力Matplotlib用于可视化训练过程而TensorFlow虽然是一个深度学习框架但我们将仅使用其基础功能来验证我们手写实现的正确性。前馈神经网络的核心特点包括单向信息流数据从输入层流向输出层没有反馈连接全连接结构相邻层的每个神经元都相互连接非线性激活通过激活函数引入非线性变换能力提示虽然现代深度学习框架已经高度优化但从零实现能帮助你深入理解神经网络的工作原理这对调试复杂模型和解决实际问题至关重要。2. 网络结构设计与实现2.1 初始化网络参数让我们首先定义网络的结构。假设我们要构建一个具有以下结构的FNN输入层784个神经元对应28x28图像隐藏层1128个神经元使用ReLU激活隐藏层264个神经元使用ReLU激活输出层10个神经元使用Softmax激活用于多分类import numpy as np class FNN: def __init__(self, input_size, hidden_sizes, output_size): self.input_size input_size self.hidden_sizes hidden_sizes self.output_size output_size # 初始化权重和偏置 self.params {} layer_sizes [input_size] hidden_sizes [output_size] for i in range(1, len(layer_sizes)): # Xavier/Glorot初始化 scale np.sqrt(2.0 / (layer_sizes[i-1] layer_sizes[i])) self.params[fW{i}] np.random.randn(layer_sizes[i-1], layer_sizes[i]) * scale self.params[fb{i}] np.zeros((1, layer_sizes[i]))2.2 实现前向传播前向传播是神经网络的核心计算过程我们需要为每一层实现正确的计算逻辑def relu(self, x): return np.maximum(0, x) def softmax(self, x): exp_x np.exp(x - np.max(x, axis1, keepdimsTrue)) return exp_x / np.sum(exp_x, axis1, keepdimsTrue) def forward(self, X): self.cache {A0: X} A_prev X # 隐藏层前向传播 for i in range(1, len(self.hidden_sizes)1): Z np.dot(A_prev, self.params[fW{i}]) self.params[fb{i}] A self.relu(Z) self.cache[fZ{i}] Z self.cache[fA{i}] A A_prev A # 输出层前向传播 output_Z np.dot(A_prev, self.params[fW{i1}]) self.params[fb{i1}] output_A self.softmax(output_Z) self.cache[fZ{i1}] output_Z self.cache[fA{i1}] output_A return output_A3. 损失函数与反向传播3.1 交叉熵损失实现对于多分类问题交叉熵损失是最常用的选择def cross_entropy_loss(self, y_pred, y_true): m y_true.shape[0] log_likelihood -np.log(y_pred[range(m), y_true]) loss np.sum(log_likelihood) / m return loss3.2 反向传播算法反向传播是训练神经网络的关键需要仔细计算每一层的梯度def backward(self, X, y): m X.shape[0] grads {} L len(self.hidden_sizes) 1 # 总层数 # 输出层梯度 dZ self.cache[fA{L}].copy() dZ[range(m), y] - 1 dZ / m grads[fdW{L}] np.dot(self.cache[fA{L-1}].T, dZ) grads[fdb{L}] np.sum(dZ, axis0, keepdimsTrue) dA_prev np.dot(dZ, self.params[fW{L}].T) # 隐藏层梯度从后向前 for l in reversed(range(1, L)): dZ dA_prev * (self.cache[fZ{l}] 0).astype(float) grads[fdW{l}] np.dot(self.cache[fA{l-1}].T, dZ) grads[fdb{l}] np.sum(dZ, axis0, keepdimsTrue) dA_prev np.dot(dZ, self.params[fW{l}].T) return grads4. 训练过程与优化技巧4.1 实现小批量梯度下降完整的训练流程需要实现数据分批处理和参数更新def train(self, X, y, epochs100, batch_size32, learning_rate0.01): n_samples X.shape[0] losses [] for epoch in range(epochs): # 打乱数据 permutation np.random.permutation(n_samples) X_shuffled X[permutation] y_shuffled y[permutation] epoch_loss 0 for i in range(0, n_samples, batch_size): # 获取当前批次 X_batch X_shuffled[i:ibatch_size] y_batch y_shuffled[i:ibatch_size] # 前向传播 y_pred self.forward(X_batch) # 计算损失 loss self.cross_entropy_loss(y_pred, y_batch) epoch_loss loss * X_batch.shape[0] # 反向传播 grads self.backward(X_batch, y_batch) # 参数更新 for param in self.params: self.params[param] - learning_rate * grads[fd{param}] # 计算平均epoch loss epoch_loss / n_samples losses.append(epoch_loss) if epoch % 10 0: print(fEpoch {epoch}, Loss: {epoch_loss:.4f}) return losses4.2 实用优化技巧在实际训练中我们可以引入几种常见的优化技术学习率衰减learning_rate initial_lr * (1.0 / (1.0 decay_rate * epoch))动量加速velocity momentum * velocity - learning_rate * dw w velocityL2正则化loss 0.5 * lambda_ * np.sum(w**2 for w in self.params.values())注意当实现这些优化技术时务必在反向传播步骤中正确计算相应的梯度调整。5. 模型评估与调试5.1 评估指标实现除了损失函数我们还需要其他指标来评估模型性能def accuracy(self, X, y): y_pred self.forward(X) predictions np.argmax(y_pred, axis1) return np.mean(predictions y) def confusion_matrix(self, X, y): y_pred self.forward(X) predictions np.argmax(y_pred, axis1) cm np.zeros((self.output_size, self.output_size), dtypeint) for true, pred in zip(y, predictions): cm[true, pred] 1 return cm5.2 常见问题与调试在实现过程中你可能会遇到以下典型问题问题现象可能原因解决方案损失不下降学习率太小增大学习率或检查梯度计算损失为NaN学习率太大减小学习率或使用梯度裁剪准确率随机权重初始化不当使用Xavier/Glorot初始化训练慢批量大小不合适调整批量大小或使用优化器调试神经网络的关键步骤检查前向传播的输出范围是否合理验证反向传播的梯度计算是否正确监控训练过程中各层的激活值和梯度分布使用小数据集先过拟合确保模型能力足够6. 完整示例与MNIST实战让我们用MNIST数据集测试我们的实现from tensorflow.keras.datasets import mnist from tensorflow.keras.utils import to_categorical # 加载数据 (X_train, y_train), (X_test, y_test) mnist.load_data() # 预处理 X_train X_train.reshape(-1, 784).astype(float32) / 255.0 X_test X_test.reshape(-1, 784).astype(float32) / 255.0 # 创建模型 model FNN(input_size784, hidden_sizes[128, 64], output_size10) # 训练 losses model.train(X_train, y_train, epochs50, batch_size64, learning_rate0.01) # 评估 train_acc model.accuracy(X_train, y_train) test_acc model.accuracy(X_test, y_test) print(fTrain Accuracy: {train_acc:.4f}, Test Accuracy: {test_acc:.4f})在实际项目中你可能需要调整以下超参数以获得更好性能隐藏层数量和大小学习率和批量大小激活函数选择如尝试LeakyReLU正则化强度通过这个完整实现你应该已经掌握了FNN的核心原理和实现细节。虽然现代深度学习框架已经高度优化但理解底层实现原理能让你在遇到问题时更快定位原因在需要自定义层或特殊结构时更加得心应手。

更多文章