共计 1870 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
很多机器学习初学者在学习神经网络时,往往直接调用 TensorFlow 或 PyTorch 等框架,虽然能快速搭建模型,但容易陷入 ” 黑箱 ” 困境。具体表现为:

- 不理解权重更新的数学本质,只会机械调用
model.fit() - 遇到梯度消失 / 爆炸等问题时无从下手调试
- 无法针对特定任务修改网络结构细节
本文将通过最基础的 numpy 实现,带你彻底理解 BP 神经网络的核心原理。
数学原理
前向传播计算图
以一个 3 层网络(输入层 2 节点,隐藏层 3 节点,输出层 1 节点)为例:
输入 X → 隐藏层 Z =sigmoid(W1*X+b1) → 输出 Y_pred=W2*Z+b2
损失函数(MSE)
$$ L = \frac{1}{2}(Y_{pred} – Y_{true})^2 $$
反向传播关键步骤
-
输出层梯度:
$$ \frac{\partial L}{\partial W_2} = (Y_{pred}-Y_{true}) \cdot Z^T $$ -
隐藏层梯度(含 sigmoid 求导):
$$ \sigma'(z) = \sigma(z)(1-\sigma(z)) $$
$$ \frac{\partial L}{\partial W_1} = [(Y_{pred}-Y_{true}) \cdot W_2^T \odot \sigma'(Z)] \cdot X^T $$
Python 实现
网络初始化
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 随机初始化权重(注意:小数值防止梯度爆炸)self.W1 = np.random.randn(hidden_size, input_size) * 0.01
self.b1 = np.zeros((hidden_size, 1))
self.W2 = np.random.randn(output_size, hidden_size) * 0.01
self.b2 = np.zeros((output_size, 1))
前向传播
def forward(self, X):
# 隐藏层计算(注意转置处理维度)self.Z = 1 / (1 + np.exp(-(np.dot(self.W1, X.T) + self.b1)))
# 输出层计算
self.Y_pred = np.dot(self.W2, self.Z) + self.b2
return self.Y_pred.T # 转回原始维度
反向传播
def backward(self, X, Y, lr=0.01):
m = X.shape[0] # 样本数量
# 输出层误差
dY = self.Y_pred - Y.T
dW2 = np.dot(dY, self.Z.T) / m
db2 = np.sum(dY, axis=1, keepdims=True) / m
# 隐藏层误差(含 sigmoid 导数)dZ = np.dot(self.W2.T, dY) * self.Z * (1 - self.Z)
dW1 = np.dot(dZ, X) / m
db1 = np.sum(dZ, axis=1, keepdims=True) / m
# 更新权重
self.W2 -= lr * dW2
self.b2 -= lr * db2
self.W1 -= lr * dW1
self.b1 -= lr * db1
避坑指南
- 梯度爆炸:初始化权重过大会导致梯度指数级增长
-
解决方案:使用
* 0.01缩小初始值范围 -
学习率选择:过大震荡,过小收敛慢
-
解决方案:先用 0.01 尝试,观察损失曲线调整
-
激活函数饱和:sigmoid 在两端梯度接近 0
- 解决方案:输入数据做归一化(X/255 等)
延伸思考
-
计算效率:全连接层的矩阵乘法时间复杂度是 O(n^3),如何通过改变网络结构降低计算量?
-
梯度消失:当网络层数加深时,梯度会逐层衰减,有哪些现代神经网络结构能解决这个问题?
完整代码示例
# 数据准备
X = np.array([[0,0], [0,1], [1,0], [1,1]]) # 异或问题
Y = np.array([[0], [1], [1], [0]])
# 训练循环
nn = NeuralNetwork(2, 3, 1)
for epoch in range(10000):
Y_pred = nn.forward(X)
nn.backward(X, Y)
if epoch % 1000 == 0:
loss = np.mean((Y_pred - Y)**2)
print(f"Epoch {epoch}, Loss: {loss:.4f}")
最终在异或问题上,损失可以降到 0.001 以下。通过这个实践,你应该已经掌握了 BP 网络最核心的 ” 前向计算损失,反向传播梯度 ” 的运作机制。建议尝试修改隐藏层节点数,观察对结果的影响,这是理解神经网络能力的关键一步。
