共计 3270 个字符,预计需要花费 9 分钟才能阅读完成。
理解 BP 神经网络的核心:反向传播
反向传播(Backpropagation,BP)是神经网络训练的基石。对于初学者来说,理解其数学推导过程往往是第一个难关。本文将从最基础的单层感知机开始,逐步拆解 BP 算法的实现细节。

1. 从单层感知机开始
假设我们有一个最简单的单层神经网络(感知机),其输出为:
$$ y = \sigma(wx + b) $$
其中 $\sigma$ 是激活函数(如 Sigmoid),$w$ 是权重,$b$ 是偏置。
对于二分类问题,我们使用交叉熵损失函数:
$$ L = -[y_{true} \log(y) + (1-y_{true}) \log(1-y)] $$
计算损失对权重 $w$ 的梯度:
-
首先计算损失对输出 $y$ 的偏导:
$$ \frac{\partial L}{\partial y} = -\frac{y_{true}}{y} + \frac{1-y_{true}}{1-y} $$ -
然后计算 $y$ 对 $z=wx+b$ 的偏导(Sigmoid 函数的导数为 $\sigma'(z)=\sigma(z)(1-\sigma(z))$):
$$ \frac{\partial y}{\partial z} = y(1-y) $$ -
最后计算 $z$ 对 $w$ 的偏导:
$$ \frac{\partial z}{\partial w} = x $$
通过链式法则,三者相乘得到最终梯度:
$$ \frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial z} \cdot \frac{\partial z}{\partial w} = (y-y_{true})x $$
这个简单例子展示了链式法则在梯度计算中的核心作用。
2. 扩展到多层神经网络
对于多层网络,我们需要从输出层逐层反向计算梯度。以一个 3 层网络(输入层 - 隐藏层 - 输出层)为例:
-
前向传播过程:
$$ z^{[1]} = W^{[1]}x + b^{[1]} $$
$$ a^{[1]} = \sigma(z^{[1]}) $$
$$ z^{[2]} = W^{[2]}a^{[1]} + b^{[2]} $$
$$ a^{[2]} = \sigma(z^{[2]}) $$ -
反向传播梯度计算(以输出层为例):
$$ \frac{\partial L}{\partial W^{[2]}} = \frac{\partial L}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial W^{[2]}} $$
$$ = (a^{[2]} – y) \cdot a^{[1]T} $$
3. Python 完整实现
以下是用 NumPy 实现的完整代码:
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 权重初始化(使用 Xavier 初始化)self.W1 = np.random.randn(hidden_size, input_size) * np.sqrt(1./input_size)
self.b1 = np.zeros((hidden_size, 1))
self.W2 = np.random.randn(output_size, hidden_size) * np.sqrt(1./hidden_size)
self.b2 = np.zeros((output_size, 1))
def sigmoid(self, z):
return 1/(1+np.exp(-z))
def sigmoid_derivative(self, z):
s = self.sigmoid(z)
return s * (1-s)
def forward(self, X):
# 前向传播
self.Z1 = np.dot(self.W1, X) + self.b1
self.A1 = self.sigmoid(self.Z1)
self.Z2 = np.dot(self.W2, self.A1) + self.b2
self.A2 = self.sigmoid(self.Z2)
return self.A2
def backward(self, X, y, learning_rate=0.01):
m = X.shape[1] # 样本数量
# 输出层梯度
dZ2 = self.A2 - y
dW2 = (1/m) * np.dot(dZ2, self.A1.T)
db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True)
# 隐藏层梯度
dZ1 = np.dot(self.W2.T, dZ2) * self.sigmoid_derivative(self.Z1)
dW1 = (1/m) * np.dot(dZ1, X.T)
db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True)
# 参数更新
self.W2 -= learning_rate * dW2
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * dW1
self.b1 -= learning_rate * db1
4. 梯度检查
梯度检查是验证反向传播实现是否正确的重要手段:
def gradient_check(nn, X, y, epsilon=1e-7):
# 计算反向传播得到的梯度
nn.forward(X)
nn.backward(X, y)
# 对每个参数进行数值梯度近似
for param, grad_name in [(nn.W1, 'dW1'), (nn.b1, 'db1'), (nn.W2, 'dW2'), (nn.b2, 'db2')]:
param_shape = param.shape
grad = getattr(nn, grad_name)
# 对每个元素进行数值梯度计算
for i in range(min(10, param.size)): # 检查前 10 个元素
idx = np.unravel_index(i, param_shape)
original_value = param[idx]
# 计算 f(theta + epsilon)
param[idx] = original_value + epsilon
cost_plus = np.mean((nn.forward(X) - y)**2)
# 计算 f(theta - epsilon)
param[idx] = original_value - epsilon
cost_minus = np.mean((nn.forward(X) - y)**2)
# 恢复原值
param[idx] = original_value
# 数值梯度
numeric_grad = (cost_plus - cost_minus)/(2*epsilon)
# 比较
backprop_grad = grad[idx]
diff = abs(numeric_grad - backprop_grad)
print(f'{grad_name}[{idx}]: numeric {numeric_grad:.6f}, backprop {backprop_grad:.6f}, diff {diff:.6f}')
5. 训练注意事项
- 学习率选择 :
- 从较大的学习率(如 0.1)开始尝试,如果损失震荡则减小
-
可以尝试学习率衰减策略
-
梯度消失问题 :
- 深层网络中,Sigmoid 函数的导数最大为 0.25,多次连乘会导致梯度指数级减小
- 表现为浅层网络权重几乎不更新
-
解决方案:使用 ReLU 等激活函数、残差连接、批量归一化等
-
权重初始化 :
- 避免全零初始化,这会导致对称性问题
- Xavier 初始化适合 Sigmoid/Tanh
- He 初始化适合 ReLU
6. 思考与扩展
尝试修改代码实现 ReLU 激活函数及其导数:
1. ReLU 函数:$f(x) = \max(0, x)$
2. 其导数为:$f'(x) = 1$ if $x > 0$ else $0$
需要修改哪些部分?反向传播的计算会受到什么影响?这能解决梯度消失问题吗?
通过本文的学习,你应该已经掌握了 BP 神经网络的核心数学原理和实现方法。下一步可以尝试扩展网络深度、添加正则化项,或实现更复杂的网络结构。
