共计 4221 个字符,预计需要花费 11 分钟才能阅读完成。
神经网络基础概念
在开始之前,我们先简单回顾下神经网络的基本结构。神经网络由多个层组成,包括输入层、隐藏层和输出层。每一层包含多个神经元,神经元之间通过权重连接。前向传播就是输入数据从输入层经过隐藏层最终到达输出层的过程。

前向传播
前向传播的计算过程可以用以下步骤描述:
- 输入数据通过输入层进入网络
- 每个隐藏层神经元计算加权和:z = w·x + b
- 对加权和应用激活函数:a = σ(z)
- 前一层输出作为下一层输入,直到输出层
用 Python 代码实现前向传播:
def forward_propagation(X, parameters):
"""
实现前向传播
参数:
X -- 输入数据
parameters -- 包含权重的字典 {'W1':..., 'b1':..., 'W2':..., 'b2':...}
返回:
AL -- 最后一层的激活值
caches -- 包含各层的 Z 和 A 的元组列表
"""
caches = []
A = X
L = len(parameters) // 2 # 网络层数
for l in range(1, L):
A_prev = A
W = parameters['W' + str(l)]
b = parameters['b' + str(l)]
Z = np.dot(W, A_prev) + b
A = sigmoid(Z)
caches.append((Z, A))
# 输出层
WL = parameters['W' + str(L)]
bL = parameters['b' + str(L)]
ZL = np.dot(WL, A) + bL
AL = sigmoid(ZL)
caches.append((ZL, AL))
return AL, caches
反向传播原理
反向传播是 BP 神经网络训练的核心,它通过计算损失函数对各个参数的梯度来更新权重。整个过程基于链式法则,从输出层向输入层反向传递误差。
数学推导的关键步骤:
- 计算输出层的误差:dZL = AL – Y (假设使用平方误差损失)
- 计算输出层权重梯度:dWL = (1/m) * dZL · A_prev.T
- 反向传播误差:dA_prev = WL.T · dZ
- 计算前一层的误差:dZ = dA * σ'(Z)
- 重复上述过程直到第一层
伪代码表示反向传播过程:
反向传播算法伪代码:
输入: 网络参数, 前向传播缓存, 真实标签 Y
输出: 各层参数的梯度
1. 计算输出层误差 dZL = AL - Y
2. 初始化梯度字典 grads = {}
3. 计算输出层梯度:
grads['dW'+L] = (1/m) * dZL · A_prev.T
grads['db'+L] = (1/m) * np.sum(dZL, axis=1, keepdims=True)
4. 从 L - 1 层到第 1 层:
a. 计算当前层的 dA = W_next.T · dZ_next
b. 计算当前层的 dZ = dA * σ'(Z_current)
c. 计算当前层梯度:
grads['dW'+l] = (1/m) * dZ · A_prev.T
grads['db'+l] = (1/m) * np.sum(dZ, axis=1, keepdims=True)
5. 返回 grads
Python 完整实现
下面是一个完整的 BP 神经网络实现,包含前向传播、反向传播和参数更新:
import numpy as np
import matplotlib.pyplot as plt
# 激活函数及其导数
def sigmoid(x):
return 1/(1+np.exp(-x))
def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1-s)
# 初始化参数
def initialize_parameters(layer_dims):
parameters = {}
L = len(layer_dims)
for l in range(1, L):
parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * 0.01
parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
return parameters
# 前向传播 (前面已给出完整代码)
# 计算成本函数
def compute_cost(AL, Y):
m = Y.shape[1]
cost = (-1/m) * np.sum(Y*np.log(AL) + (1-Y)*np.log(1-AL))
return np.squeeze(cost)
# 反向传播
def backward_propagation(AL, Y, caches, parameters):
grads = {}
L = len(parameters) // 2
m = AL.shape[1]
Y = Y.reshape(AL.shape)
# 初始化反向传播
dZL = AL - Y
# 输出层梯度
A_prev = caches[L-2][1] # 倒数第二层的 A
grads['dW' + str(L)] = (1/m) * np.dot(dZL, A_prev.T)
grads['db' + str(L)] = (1/m) * np.sum(dZL, axis=1, keepdims=True)
# 从 L - 1 层到 1 层的反向传播
for l in reversed(range(1, L)):
current_cache = caches[l-1]
Z, A = current_cache
if l > 1:
A_prev = caches[l-2][1]
else:
A_prev = caches[0][0] # 输入层
dA = np.dot(parameters['W'+str(l+1)].T, dZL)
dZ = dA * sigmoid_derivative(Z)
grads['dW' + str(l)] = (1/m) * np.dot(dZ, A_prev.T)
grads['db' + str(l)] = (1/m) * np.sum(dZ, axis=1, keepdims=True)
dZL = dZ # 为下一层准备
return grads
# 参数更新
def update_parameters(parameters, grads, learning_rate):
L = len(parameters) // 2
for l in range(1, L+1):
parameters['W' + str(l)] -= learning_rate * grads['dW' + str(l)]
parameters['b' + str(l)] -= learning_rate * grads['db' + str(l)]
return parameters
可视化权重更新过程
我们可以用 matplotlib 制作权重更新的动画,直观展示训练过程:
import matplotlib.animation as animation
from IPython.display import HTML
# 初始化图形
fig, ax = plt.subplots(figsize=(8,6))
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.grid(True)
# 初始化数据
X = np.random.randn(2, 100)
Y = (X[0,:] > X[1,:]).astype(int).reshape(1,-1)
parameters = initialize_parameters([2,4,1])
# 动画更新函数
def animate(i):
global parameters
# 前向传播
AL, caches = forward_propagation(X, parameters)
# 计算成本
cost = compute_cost(AL, Y)
# 反向传播
grads = backward_propagation(AL, Y, caches, parameters)
# 更新参数
parameters = update_parameters(parameters, grads, 0.1)
# 绘制决策边界
ax.clear()
ax.set_title(f"Iteration: {i}, Cost: {cost:.4f}")
# 绘制数据点
ax.scatter(X[0, Y==0], X[1, Y==0], color='red', label='Class 0')
ax.scatter(X[0, Y==1], X[1, Y==1], color='blue', label='Class 1')
# 绘制决策边界
h = 0.01
x_min, x_max = X[0,:].min()-1, X[0,:].max()+1
y_min, y_max = X[1,:].min()-1, X[1,:].max()+1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# 预测网格点的类别
Z, _ = forward_propagation(np.c_[xx.ravel(), yy.ravel()].T, parameters)
Z = Z.reshape(xx.shape)
# 绘制决策边界和区域
ax.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdBu)
ax.legend()
return ax
# 创建动画
ani = animation.FuncAnimation(fig, animate, frames=100, interval=200, blit=False)
# 显示动画
HTML(ani.to_jshtml())
避坑指南
在实现 BP 神经网络时,有几个常见问题需要注意:
- 学习率选择
- 学习率太大可能导致震荡甚至发散
- 学习率太小会导致收敛过慢
-
建议从 0.01 开始尝试,根据训练情况调整
-
梯度消失问题
- 深层网络中,sigmoid 函数的导数最大值为 0.25
- 误差反向传播时,梯度会逐层缩小
-
解决方案:使用 ReLU 等激活函数,或采用残差连接
-
参数初始化
- 全零初始化会导致所有神经元学习相同的内容
- 随机初始化时,值太大可能导致神经元饱和
-
建议使用 Xavier 或 He 初始化方法
-
批量大小选择
- 批量太小会导致训练不稳定
- 批量太大会降低内存效率
- 一般选择 32、64、128 等 2 的幂次
总结与扩展
通过本文,我们详细讲解了 BP 神经网络的前向传播和反向传播原理,并提供了完整的 Python 实现。你可以尝试以下扩展实验:
- 修改网络结构(增加 / 减少隐藏层或神经元数量),观察对收敛速度的影响
- 尝试不同的激活函数(如 ReLU、tanh),比较效果
- 可视化不同层的权重更新过程,理解信息如何在网络中流动
- 实现动量法或 Adam 等优化算法,加速训练过程
BP 神经网络是深度学习的基础,理解其工作原理对后续学习更复杂的模型有很大帮助。希望这篇文章能帮助你掌握 BP 算法的核心思想。
正文完
