Python神经网络编程二神经网络的正向传播与误差的反向传播本文是《Python神经网络编程》系列博客的第2篇基于真实服务器实操输出涵盖实验3-4的所有内容。服务器环境Ubuntu 24.04 / Python 3.12.3 / NumPy 2.5.1 / Matplotlib 3.11.0服务器华为云 ecs-1f62-0001 (120.46.93.164)目录你将学到什么实验3神经网络的正向传播实验4误差的反向传播总结下一篇预告你将学到什么在本篇博客中你将深入学习神经网络最核心的两个算法正向传播Forward Propagation输入信号如何一层层传递到输出矩阵运算为什么神经网络要用矩阵而不是循环误差的反向传播Backward Propagation输出误差如何逐层传回梯度计算每个权重对误差的贡献如何计算权重更新如何使用梯度下降法更新权重实验3神经网络的正向传播3.1 矩阵乘法基础#矩阵乘法是神经网络计算的基石。矩阵乘法规则#若 A 是 m×n 矩阵B 是 n×p 矩阵 则 C A × B 是 m×p 矩阵 C[i][j] Σ(A[i][k] × B[k][j]) (k从0到n-1)Python实现矩阵乘法#importnumpyasnp# 矩阵 A (2×3)Anp.array([[1,2,3],[4,5,6]])# 矩阵 B (3×2)Bnp.array([[7,8],[9,10],[11,12]])# 矩阵乘法Cnp.dot(A,B)print(f矩阵 A (2×3):\n{A})print(f\n矩阵 B (3×2):\n{B})print(f\nA × B (2×2):\n{C})# 验证形状print(f\n矩阵乘法规则: A的列数必须等于B的行数)print(fA.shape{A.shape}, B.shape{B.shape}, C.shape{C.shape})服务器真实输出矩阵 A (2×3): [[1 2 3] [4 5 6]] 矩阵 B (3×2): [[ 7 8] [ 9 10] [11 12]] A × B (2×2): [[ 58 64] [139 154]] 矩阵乘法规则: A的列数必须等于B的行数 A.shape(2, 3), B.shape(3, 2), C.shape(2, 2)3.2 单个神经元的信号传播#神经元的计算分为两步1. 加权求和z Σ(w_i × x_i) b W·X b 2. 激活函数a activation(z)Python实现单个神经元#importnumpyasnpdefsigmoid(x):return1/(1np.exp(-x))# 单个神经元inputsnp.array([0.9,0.1,0.8])# 输入信号 (3个)weightsnp.array([0.9,0.3,0.4])# 权重 (3个)bias0.5# 偏置# 加权求和znp.dot(weights,inputs)biasprint(f输入 X {inputs})print(f权重 W {weights})print(f偏置 b {bias})print(f加权求和: W·X b {np.dot(weights,inputs):.4f}{bias}{z:.4f})# 激活函数outputsigmoid(z)print(fSigmoid激活: σ({z:.4f}) {output:.4f})服务器真实输出输入 X [0.9 0.1 0.8] 权重 W [0.9 0.3 0.4] 偏置 b 0.5 加权求和: W·X b 1.1600 0.5 1.6600 Sigmoid激活: σ(1.6600) 0.84023.3 多层神经网络正向传播#对于多层神经网络信号从输入层逐层传递到输出层。网络结构示例#输入层(3) - 隐藏层1(3) - 隐藏层2(3) - 输出层(2)Python实现多层正向传播#importnumpyasnpdefsigmoid(x):return1/(1np.exp(-x))# 网络结构: 3(输入) - 3(隐藏1) - 3(隐藏2) - 2(输出)# 输入Xnp.array([0.9,0.1,0.8])# 第一层权重 (3×3): 输入层 - 隐藏层1W1np.array([[0.9,0.3,0.4],[0.2,0.8,0.2],[0.1,0.5,0.6]])# 第二层权重 (3×3): 隐藏层1 - 隐藏层2W2np.array([[0.3,0.7,0.5],[0.6,0.5,0.2],[0.8,0.1,0.9]])# 第三层权重 (2×3): 隐藏层2 - 输出层W3np.array([[0.1,0.7,0.4],[0.3,0.5,0.8]])print(f网络结构: 3(输入) - 3(隐藏) - 3(隐藏) - 2(输出))print(f\n输入层: X {X})# 隐藏层1Z1np.dot(W1,X)H1sigmoid(Z1)print(f\n隐藏层1:)print(f Z1 W1 · X {Z1})print(f H1 sigmoid(Z1) {H1})# 隐藏层2Z2np.dot(W2,H1)H2sigmoid(Z2)print(f\n隐藏层2:)print(f Z2 W2 · H1 {Z2})print(f H2 sigmoid(Z2) {H2})# 输出层Z3np.dot(W3,H2)Osigmoid(Z3)print(f\n输出层:)print(f Z3 W3 · H2 {Z3})print(f O sigmoid(Z3) {O})服务器真实输出网络结构: 3(输入) - 3(隐藏) - 3(隐藏) - 2(输出) 输入层: X [0.9 0.1 0.8] 隐藏层1: Z1 W1 · X [1.16 0.42 0.62] H1 sigmoid(Z1) [0.76133271 0.60348325 0.65021855] 隐藏层2: Z2 W2 · H1 [0.97594736 0.88858496 1.25461119] H2 sigmoid(Z2) [0.72630335 0.70859807 0.77809706] 输出层: Z3 W3 · H2 [0.87988781 1.19466769] O sigmoid(Z3) [0.70679897 0.76757484]3.4 矩阵在神经网络中的优势#为什么要用矩阵因为矩阵运算比循环快得多性能对比循环 vs 矩阵#importnumpyasnpimporttime# 模拟大网络: 784输入 - 100隐藏input_size784hidden_size100# 生成随机数据Xnp.random.rand(input_size)Wnp.random.rand(hidden_size,input_size)# 方法1: 使用循环starttime.time()Z_loopnp.zeros(hidden_size)foriinrange(hidden_size):forjinrange(input_size):Z_loop[i]W[i][j]*X[j]loop_time(time.time()-start)*1000# 方法2: 使用矩阵运算starttime.time()Z_matrixnp.dot(W,X)matrix_time(time.time()-start)*1000print(f网络:{input_size}输入 -{hidden_size}隐藏)print(f循环计算:{loop_time:.3f}ms)print(f矩阵计算:{matrix_time:.3f}ms)print(f加速比:{loop_time/matrix_time:.1f}x)print(f结果一致:{np.allclose(Z_loop,Z_matrix)})服务器真实输出网络: 784输入 - 100隐藏 循环计算: 17.539 ms 矩阵计算: 0.023 ms 加速比: 758.4x 结果一致: True结论矩阵运算比循环快数百倍这就是神经网络要用矩阵的原因。3.5 可视化正向传播过程#图3多层神经网络的正向传播过程可视化实验4误差的反向传播#4.1 神经网络的权重学习#反向传播Backward Propagation是神经网络学习的核心算法。核心思想将输出误差逐层传回计算每个权重对误差的贡献梯度然后更新权重。4.2 反向传播的意义#为什么需要反向传播正向传播只能得到输出无法知道每个权重做得好不好反向传播将误差从输出层传回输入层告诉每个权重该如何调整没有反向传播神经网络无法学习就像只知道考试成绩却不知道哪道题错了4.3 输出误差的分割#目标将输出层的误差按贡献比例分割到隐藏层。简单网络示例#o1 (输出1) / \ h1 h2 \ / x1 (输入)若输出o1的误差为E则隐藏层h1和h2分摊的误差为∂E/∂h1 w_11 × ∂E/∂o1 ∂E/∂h2 w_21 × ∂E/∂o1其中w_{11}是输出层权重。4.4 反向传播的全过程#完整示例2-2-2网络#importnumpyasnpdefsigmoid(x):return1/(1np.exp(-x))defsigmoid_derivative(x):Sigmoid的导数returnx*(1-x)# 网络结构: 2(输入) - 2(隐藏) - 2(输出)Xnp.array([0.1,0.9])# 输入Tnp.array([0.01,0.99])# 目标输出# 初始化权重 (小随机值)W1np.array([[0.15,0.20],[0.25,0.30]])W2np.array([[0.40,0.45],[0.50,0.55]])b1np.array([0.35,0.35])b2np.array([0.60,0.60])print(*70)print(简单网络: 2-2-2)print(*70)# # 正向传播# print(\n--- 正向传播 ---)# 隐藏层Z1np.dot(W1,X)b1 H1sigmoid(Z1)print(fZ1 W1·X b1 {Z1})print(fH1 sigmoid(Z1) {H1})# 输出层Z2np.dot(W2,H1)b2 Osigmoid(Z2)print(f\nZ2 W2·H1 b2 {Z2})print(fO sigmoid(Z2) {O})# # 计算输出误差# print(\n--- 计算输出误差 ---)print(f目标值:{T})print(f实际输出:{O})# 均方误差E0.5*np.sum((T-O)**2)print(f总误差:{E})# # 反向传播# print(\n--- 反向传播 ---)# 步骤1: 输出层误差梯度# ∂E/∂O -(T - O) O - TdE_dOO-Tprint(f\n步骤1: 输出层误差梯度)print(f ∂E/∂O O - T {dE_dO})# ∂E/∂Z2 ∂E/∂O × sigmoid(Z2)# sigmoid(z) sigmoid(z) × (1 - sigmoid(z)) O × (1 - O)sigmoid_prime_Z2O*(1-O)dE_dZ2dE_dO*sigmoid_prime_Z2print(f sigmoid(Z2) {sigmoid_prime_Z2})print(f ∂E/∂Z2 {dE_dZ2})# 步骤2: 输出层权重梯度# ∂E/∂W2 ∂E/∂Z2 × H1dE_dW2np.outer(dE_dZ2,H1)print(f\n步骤2: 输出层权重梯度)print(f ∂E/∂W2 ∂E/∂Z2 × H1)print(f ∂E/∂W2 \n{dE_dW2})# 步骤3: 传播误差到隐藏层# ∂E/∂H1 W2^T × ∂E/∂Z2dE_dH1np.dot(W2.T,dE_dZ2)print(f\n步骤3: 传播误差到隐藏层)print(f ∂E/∂H1 W2^T × ∂E/∂Z2 {dE_dH1})# 步骤4: 隐藏层误差梯度sigmoid_prime_Z1H1*(1-H1)dE_dZ1dE_dH1*sigmoid_prime_Z1print(f\n步骤4: 隐藏层误差梯度)print(f sigmoid(Z1) {sigmoid_prime_Z1})print(f ∂E/∂Z1 {dE_dZ1})# 步骤5: 隐藏层权重梯度dE_dW1np.outer(dE_dZ1,X)print(f\n步骤5: 隐藏层权重梯度)print(f ∂E/∂W1 \n{dE_dW1})# 步骤6: 更新权重 (学习率 η 0.5)eta0.5print(f\n步骤6: 更新权重 (学习率 η {eta}))print(fW2 (更新前):\n{W2})W2_newW2-eta*dE_dW2print(fW2 (更新后):\n{W2_new})print(f\nW1 (更新前):\n{W1})W1_newW1-eta*dE_dW1print(fW1 (更新后):\n{W1_new})# 计算更新后的误差Z1_newnp.dot(W1_new,X)b1 H1_newsigmoid(Z1_new)Z2_newnp.dot(W2_new,H1_new)b2 O_newsigmoid(Z2_new)E_new0.5*np.sum((T-O_new)**2)print(f\n更新前误差:{E:.6f})print(f更新后误差:{E_new:.6f})print(f误差减小:{E-E_new:.6f})服务器真实输出部分 简单网络: 2-2-2 --- 正向传播 --- Z1 W1·X b1 [0.3775 0.3925] H1 sigmoid(Z1) [0.59326999 0.59688438] Z2 W2·H1 b2 [1.10590597 1.2249214 ] O sigmoid(Z2) [0.75136507 0.77292847] --- 计算输出误差 --- 目标值: [0.01 0.99] 实际输出: [0.75136507 0.77292847] 总误差: 0.298371 --- 反向传播 --- 步骤1: 输出层误差梯度 ∂E/∂O O - T [ 0.74136507 -0.21707153] sigmoid(Z2) [0.1868156 0.17551005] ∂E/∂Z2 [ 0.13849856 -0.03809824] 步骤2: 输出层权重梯度 ∂E/∂W2 [[ 0.08216704 0.08266763] [-0.02260254 -0.02274024]] 步骤3: 传播误差到隐藏层 ∂E/∂H1 W2^T × ∂E/∂Z2 [0.03635031 0.04137032] 步骤4: 隐藏层误差梯度 sigmoid(Z1) [0.24130071 0.24061342] ∂E/∂Z1 [0.00877135 0.00995425] 步骤5: 隐藏层权重梯度 ∂E/∂W1 [[0.00043857 0.00087714] [0.00049771 0.00099543]] 步骤6: 更新权重 (学习率 η 0.5) W2 (更新前): [[0.4 0.45] [0.5 0.55]] W2 (更新后): [[0.35891648 0.40866619] [0.51130127 0.56137012]] 更新前误差: 0.298371 更新后误差: 0.291028 误差减小: 0.0073434.5 可视化反向传播过程#图4误差反向传播全过程可视化总结#在本篇博客中我们深入学习了矩阵乘法神经网络计算的基础A(m×n) × B(n×p) C(m×p)正向传播输入 - 加权求和 - 激活函数 - 下一层矩阵运算优势比循环快数百倍758.4x反向传播误差从输出层传回输入层梯度计算∂E/∂W 表示权重对误差的贡献权重更新W_new W_old - η × ∂E/∂W关键公式回顾正向传播 Z W · X b A activation(Z) 反向传播输出层 ∂E/∂O O - T ∂E/∂Z ∂E/∂O × activation(Z) ∂E/∂W ∂E/∂Z · A_prev 权重更新 W_new W_old - η × ∂E/∂W下一篇预告#第3篇梯度下降法与参数选择你将学习为什么暴力破解权重不可行10^24 种组合梯度下降法的数学原理学习率的选择对训练的影响权重初始化方法零初始化 vs Xavier vs He激活函数选择Sigmoid vs Tanh vs ReLU参考文献Tariq Rashid, 《Python神经网络编程》Ian Goodfellow 等, 《深度学习》Rumelhart et al., “Learning representations by back-propagating errors” (1986)代码仓库本文所有代码可在服务器/root/nn_blog/目录下找到。作者腾讯DevOps工程师 | 最后更新2026年7月