Python+Numpy实现通用神经网络反向传播算法的问题排查
问题诊断
你当前的反向传播实现存在两个核心错误,导致多层网络、带非线性激活时无法正常工作:
- 梯度传递逻辑完全错误:你用
la.inv(weights[i].T @ weights[i]) @ weights[i].T @ (y - biases[i])传递梯度的写法,本质是用伪逆求解线性方程组反推上一层输出,仅在单层线性无激活网络的特殊场景下碰巧生效,完全不符合反向传播的链式求导规则。矩阵求逆计算成本高、数值稳定性差,本就不应该出现在标准反向传播流程中。 - 缺失激活函数导数项:反向传播跨层传递梯度时,必须乘以对应层激活函数的导数,这是网络拟合非线性关系的核心支撑,你的代码完全没有这部分计算,引入非线性激活后梯度方向必然错误。
另外你当前用三层嵌套for循环更新权重的写法效率极低,完全可以用numpy矩阵运算替代,速度会提升数个量级。
反向传播的正确实现逻辑
首先明确标准前向传播的计算规则,你需要在保存各层激活输出的基础上,额外保存每一层的线性计算结果:
- 对第i层,先计算线性输出:
z_i = W_i @ a_{i-1} + b_i,其中a_{i-1}是上一层的激活输出,a_0为网络输入 - 再经过激活函数得到当前层输出:
a_i = activation(z_i) - 最后一层输出
a_L为网络预测值,若使用MSE损失,损失为L = np.mean((y - a_L)**2)
反向传播的核心是从输出层开始,逐层向前传递损失对当前层线性输出z的梯度(记为delta),全程不需要矩阵求逆:
- 输出层delta:
delta_L = (a_L - y) * activation_derivative(z_L),若输出层用线性激活,导数为1,delta可直接简化为预测值减真实值 - 隐藏层delta递推:
delta_i = (W_i.T @ delta_{i+1}) * activation_derivative(z_i),其中*为逐元素乘法 - 权重梯度:
dW_i = delta_i @ a_{i-1}.T / batch_size,单样本训练时无需除以batch size - 偏置梯度:
db_i = np.mean(delta_i, axis=batch_axis),单样本训练时db_i等于delta_i
修正后的代码参考
import numpy as np def back_prop(y, layers, zs, weights, biases, activation_deriv, lr=1e-3): """ 参数说明 ---------- y: 样本真实标签 layers: 各层激活输出列表,layers[0]为输入,layers[-1]为最终预测值 zs: 各层线性输出z的列表,和layers一一对应 weights/biases: 网络的权重、偏置参数列表 activation_deriv: 激活函数的导数计算函数 lr: 学习率 """ batch_size = y.shape[0] # 初始化输出层梯度 delta = (layers[-1] - y) * activation_deriv(zs[-1]) # 从最后一层向前逐层更新参数、传递梯度 for i in range(len(weights)-1, -1, -1): a_prev = layers[i] # 计算梯度 dW = delta @ a_prev.T / batch_size db = np.mean(delta, axis=0).reshape(biases[i].shape) # 参数更新 weights[i] -= lr * dW biases[i] -= lr * db # 非输入层时继续向上传递梯度 if i > 0: delta = (weights[i].T @ delta) * activation_deriv(zs[i-1]) return weights, biases
调优注意事项
- 你原来设置的学习率
10e-8(即1e-7)过小,正常训练MSE损失的网络时,学习率通常在1e-2~1e-4区间,需要根据任务实际收敛情况调整。 - 如果使用交叉熵损失配合Softmax输出层,输出层delta可直接简化为
a_L - y,无需额外乘Softmax导数,数值稳定性更好。 - 前向传播时必须同步保存每一层的线性输出z和激活输出a,否则无法正确计算激活函数的导数项。
内容的提问来源于stack exchange,提问作者Moiz Khan
相关产品推荐
相关产品推荐

