共计 3079 个字符,预计需要花费 8 分钟才能阅读完成。
神经网络基础概念
BP 神经网络是一种多层前馈神经网络,通过误差反向传播算法进行训练。我们先来看一下神经网络的基本结构:

- 输入层:接收原始数据
- 隐藏层:进行特征提取和转换
- 输出层:产生最终预测结果
前向传播数学原理
前向传播过程可以用以下公式表示:
- 输入层到第一隐藏层:
$$Z^{[1]} = W^{[1]}X + b^{[1]}$$
$$A^{[1]} = g^{[1]}(Z^{[1]})$$
- 隐藏层到输出层:
$$Z^{[2]} = W^{[2]}A^{[1]} + b^{[2]}$$
$$A^{[2]} = g^{[2]}(Z^{[2]})$$
其中,$W$ 是权重矩阵,$b$ 是偏置向量,$g$ 是激活函数。
损失函数计算
常用的损失函数是均方误差(MSE):
$$J(W,b) = \frac{1}{2m}\sum_{i=1}^m (y_i – a_i^{[L]})^2$$
BP 算法流程图解
flowchart TD
A[前向传播计算输出] --> B[计算输出层误差]
B --> C[反向传播误差]
C --> D[计算各层梯度]
D --> E[更新权重和偏置]
详细反向传播过程:
- 输出层误差:
$$\delta^{[L]} = (a^{[L]} – y) \odot g’^{[L]}(z^{[L]})$$
- 隐藏层误差:
$$\delta^{[l]} = (W^{[l+1]T}\delta^{[l+1]}) \odot g’^{[l]}(z^{[l]})$$
- 参数梯度:
$$\frac{\partial J}{\partial W^{[l]}} = \frac{1}{m}\delta^{[l]}a^{[l-1]T}$$
$$\frac{\partial J}{\partial b^{[l]}} = \frac{1}{m}\sum\delta^{[l]}$$
Python 实现代码
import numpy as np
class BPNN:
def __init__(self, layer_dims, learning_rate=0.01):
self.params = {}
self.layer_dims = layer_dims
self.learning_rate = learning_rate
# 初始化参数
for l in range(1, len(layer_dims)):
self.params['W'+str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * 0.01
self.params['b'+str(l)] = np.zeros((layer_dims[l], 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_propagation(self, X):
caches = []
A = X
L = len(self.params) // 2
for l in range(1, L+1):
A_prev = A
W = self.params['W'+str(l)]
b = self.params['b'+str(l)]
Z = np.dot(W, A_prev) + b
A = self.sigmoid(Z)
caches.append((A_prev, W, b, Z))
return A, caches
def compute_cost(self, AL, Y):
m = Y.shape[1]
cost = (1./m) * (-np.dot(Y, np.log(AL).T) - np.dot(1-Y, np.log(1-AL).T))
return np.squeeze(cost)
def backward_propagation(self, AL, Y, caches):
grads = {}
L = len(caches)
m = AL.shape[1]
Y = Y.reshape(AL.shape)
# 初始化反向传播
dAL = - (np.divide(Y, AL) - np.divide(1-Y, 1-AL))
# 第 L 层梯度
current_cache = caches[L-1]
A_prev, W, b, Z = current_cache
dZ = dAL * self.sigmoid_derivative(Z)
dW = (1./m) * np.dot(dZ, A_prev.T)
db = (1./m) * np.sum(dZ, axis=1, keepdims=True)
grads['dW'+str(L)] = dW
grads['db'+str(L)] = db
for l in reversed(range(L-1)):
current_cache = caches[l]
A_prev, W, b, Z = current_cache
dA_prev = np.dot(W.T, dZ)
dZ = dA_prev * self.sigmoid_derivative(Z)
dW = (1./m) * np.dot(dZ, A_prev.T)
db = (1./m) * np.sum(dZ, axis=1, keepdims=True)
grads['dW'+str(l+1)] = dW
grads['db'+str(l+1)] = db
return grads
def update_parameters(self, grads):
L = len(self.params) // 2
for l in range(1, L+1):
self.params['W'+str(l)] -= self.learning_rate * grads['dW'+str(l)]
self.params['b'+str(l)] -= self.learning_rate * grads['db'+str(l)]
def train(self, X, Y, iterations, print_cost=False):
costs = []
for i in range(iterations):
# 前向传播
AL, caches = self.forward_propagation(X)
# 计算损失
cost = self.compute_cost(AL, Y)
# 反向传播
grads = self.backward_propagation(AL, Y, caches)
# 更新参数
self.update_parameters(grads)
# 打印损失
if print_cost and i % 100 == 0:
print(f"Cost after iteration {i}: {cost}")
costs.append(cost)
return costs
避坑指南
激活函数选择
- Sigmoid:适合二分类问题,但容易出现梯度消失
- ReLU:常用在隐藏层,能缓解梯度消失问题
- Tanh:输出范围(-1,1),有时优于 sigmoid
梯度消失 / 爆炸解决方案
- 使用适当的权重初始化方法(如 Xavier 初始化)
- 使用 Batch Normalization
- 选择合适的激活函数
- 使用残差连接
学习率设置
- 初始学习率建议:0.01-0.1
- 可以使用学习率衰减策略
- 自适应优化器 (Adam) 通常能自动调整学习率
性能优化
Mini-batch vs 全量梯度下降
- Mini-batch:内存需求小,训练速度快,噪声有助于跳出局部最优
- 全量梯度下降:每次更新更准确,但计算开销大
向量化实现技巧
- 避免使用 for 循环处理样本
- 使用矩阵运算代替逐元素操作
- 合理利用广播机制
思考题
- 如何改进传统 BP 算法处理图像数据?
- 可以考虑使用卷积神经网络(CNN)
- 引入池化层减少参数数量
-
使用数据增强技术
-
比较 SGD、Adam 等优化器在本案例中的表现差异
- SGD:简单但需要手动调整学习率
- Adam:自适应学习率,通常收敛更快
- RMSprop:适合非平稳目标
正文完
