当前位置: 首页 > news >正文

深度学习:反向传播算法

前言

反向传播(Backpropagation, BP)算法作为深度学习的核心算法,运用链式法则高效计算神经网络中各参数的梯度,为模型优化指明方向。本文将详细讲解该算法的原理、实现及优化技巧,并附上完整的代码实例。

1. 前向传播

1.1 数学表达

前向传播是神经网络进行预测的过程,数据从输入层经过隐藏层最终到达输出层。

1.1.1 输入层到隐藏层

假设我们有一个简单的三层神经网络(输入层、隐藏层、输出层),输入层到隐藏层的计算可以表示为:

其中:

  • x是输入向量

  • W1​ 是输入层到隐藏层的权重矩阵

  • b1是偏置向量

  • σ 是激活函数(如Sigmoid、ReLU等)

1.1.2 隐藏层到输出层

隐藏层到输出层的计算类似:

1.2 作用

前向传播的主要作用是根据当前参数计算网络的输出,为后续的损失计算和反向传播做准备。

代码实现

import numpy as npdef sigmoid(x):"""Sigmoid激活函数"""return 1 / (1 + np.exp(-x))def forward_propagation(x, W1, b1, W2, b2):"""前向传播实现参数:x -- 输入数据 (n_x, 1)W1 -- 第一层权重矩阵 (n_h, n_x)b1 -- 第一层偏置向量 (n_h, 1)W2 -- 第二层权重矩阵 (n_y, n_h)b2 -- 第二层偏置向量 (n_y, 1)返回:h -- 隐藏层输出y -- 输出层输出"""# 输入层到隐藏层z1 = np.dot(W1, x) + b1h = sigmoid(z1)# 隐藏层到输出层z2 = np.dot(W2, h) + b2y = sigmoid(z2)return h, y# 示例数据
n_x = 3  # 输入层维度
n_h = 4  # 隐藏层维度
n_y = 1  # 输出层维度# 随机初始化参数
np.random.seed(1)
W1 = np.random.randn(n_h, n_x) * 0.01
b1 = np.zeros((n_h, 1))
W2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros((n_y, 1))# 输入数据
x = np.array([[0.1], [0.2], [0.3]])# 前向传播
h, y = forward_propagation(x, W1, b1, W2, b2)
print("隐藏层输出:", h)
print("网络输出:", y)

2. BP基础之梯度下降算法

2.1 数学描述

2.1.1 数学公式

梯度下降算法的核心是通过损失函数对参数的梯度来更新参数:

其中:

  • θ 是模型参数

  • α 是学习率

  • J(θ) 是损失函数

2.1.2 过程阐述
  1. 计算损失函数关于参数的梯度

  2. 按照负梯度方向更新参数:反向传播算法

  3. 重复上述过程直到收敛

2.2 传统下降方式

2.2.1 批量梯度下降(Batch Gradient Descent)

使用全部训练数据计算梯度:

def batch_gradient_descent(X, Y, W1, b1, W2, b2, learning_rate, iterations):"""批量梯度下降实现参数:X -- 输入数据 (n_x, m)Y -- 真实标签 (n_y, m)W1, b1, W2, b2 -- 网络参数learning_rate -- 学习率iterations -- 迭代次数返回:parameters -- 更新后的参数"""m = X.shape[1]  # 样本数量for i in range(iterations):# 前向传播Z1 = np.dot(W1, X) + b1A1 = sigmoid(Z1)Z2 = np.dot(W2, A1) + b2A2 = sigmoid(Z2)# 计算损失loss = -np.mean(Y * np.log(A2) + (1-Y) * np.log(1-A2))# 反向传播dZ2 = A2 - YdW2 = np.dot(dZ2, A1.T) / mdb2 = np.sum(dZ2, axis=1, keepdims=True) / mdA1 = np.dot(W2.T, dZ2)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(dZ1, X.T) / mdb1 = np.sum(dZ1, axis=1, keepdims=True) / m# 更新参数W1 = W1 - learning_rate * dW1b1 = b1 - learning_rate * db1W2 = W2 - learning_rate * dW2b2 = b2 - learning_rate * db2if i % 100 == 0:print(f"Iteration {i}, Loss: {loss}")return W1, b1, W2, b2# 示例数据
X = np.random.randn(3, 100)  # 100个样本,每个样本3个特征
Y = np.random.randint(0, 2, (1, 100))  # 二分类标签# 训练网络
W1, b1, W2, b2 = batch_gradient_descent(X, Y, W1, b1, W2, b2, 0.1, 1000)
2.2.2 随机梯度下降(Stochastic Gradient Descent)

每次使用单个样本更新参数:

def stochastic_gradient_descent(X, Y, W1, b1, W2, b2, learning_rate, iterations):"""随机梯度下降实现参数:X -- 输入数据 (n_x, m)Y -- 真实标签 (n_y, m)W1, b1, W2, b2 -- 网络参数learning_rate -- 学习率iterations -- 迭代次数返回:parameters -- 更新后的参数"""m = X.shape[1]  # 样本数量for i in range(iterations):for j in range(m):# 获取单个样本x = X[:, j].reshape(-1, 1)y = Y[:, j].reshape(-1, 1)# 前向传播Z1 = np.dot(W1, x) + b1A1 = sigmoid(Z1)Z2 = np.dot(W2, A1) + b2A2 = sigmoid(Z2)# 反向传播dZ2 = A2 - ydW2 = np.dot(dZ2, A1.T)db2 = dZ2dA1 = np.dot(W2.T, dZ2)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(dZ1, x.T)db1 = dZ1# 更新参数W1 = W1 - learning_rate * dW1b1 = b1 - learning_rate * db1W2 = W2 - learning_rate * dW2b2 = b2 - learning_rate * db2# 计算整体损失_, A2 = forward_propagation(X, W1, b1, W2, b2)loss = -np.mean(Y * np.log(A2) + (1-Y) * np.log(1-A2))if i % 10 == 0:print(f"Iteration {i}, Loss: {loss}")return W1, b1, W2, b2# 使用同样的数据训练
W1, b1, W2, b2 = stochastic_gradient_descent(X, Y, W1, b1, W2, b2, 0.01, 100)
2.2.3 小批量梯度下降(Mini-batch Gradient Descent)

折中方案,使用小批量数据更新参数:

def mini_batch_gradient_descent(X, Y, W1, b1, W2, b2, learning_rate, iterations, batch_size=32):"""小批量梯度下降实现参数:X -- 输入数据 (n_x, m)Y -- 真实标签 (n_y, m)W1, b1, W2, b2 -- 网络参数learning_rate -- 学习率iterations -- 迭代次数batch_size -- 批量大小返回:parameters -- 更新后的参数"""m = X.shape[1]  # 样本数量batches = m // batch_sizefor i in range(iterations):# 打乱数据permutation = np.random.permutation(m)X_shuffled = X[:, permutation]Y_shuffled = Y[:, permutation]for j in range(batches):# 获取小批量数据start = j * batch_sizeend = start + batch_sizex = X_shuffled[:, start:end]y = Y_shuffled[:, start:end]# 前向传播Z1 = np.dot(W1, x) + b1A1 = sigmoid(Z1)Z2 = np.dot(W2, A1) + b2A2 = sigmoid(Z2)# 反向传播dZ2 = A2 - ydW2 = np.dot(dZ2, A1.T) / batch_sizedb2 = np.sum(dZ2, axis=1, keepdims=True) / batch_sizedA1 = np.dot(W2.T, dZ2)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(dZ1, x.T) / batch_sizedb1 = np.sum(dZ1, axis=1, keepdims=True) / batch_size# 更新参数W1 = W1 - learning_rate * dW1b1 = b1 - learning_rate * db1W2 = W2 - learning_rate * dW2b2 = b2 - learning_rate * db2# 计算整体损失_, A2 = forward_propagation(X, W1, b1, W2, b2)loss = -np.mean(Y * np.log(A2) + (1-Y) * np.log(1-A2))if i % 10 == 0:print(f"Iteration {i}, Loss: {loss}")return W1, b1, W2, b2# 使用同样的数据训练
W1, b1, W2, b2 = mini_batch_gradient_descent(X, Y, W1, b1, W2, b2, 0.1, 100, 32)

2.3 存在的问题

传统梯度下降方法存在以下问题:

  1. 学习率选择困难

  2. 容易陷入局部最优

  3. 对于不同参数可能需要不同的学习率

  4. 对于稀疏数据表现不佳

2.4 优化下降方式

2.4.1 指数加权平均

指数加权平均(Exponentially Weighted Moving Average)是一种处理序列数据的平滑方法:

其中β是衰减率。

2.4.2 Momentum

动量法(Momentum)通过引入速度变量来加速学习:

def momentum_gradient_descent(X, Y, W1, b1, W2, b2, learning_rate, iterations, beta=0.9):"""带动量的梯度下降参数:X, Y -- 训练数据和标签W1, b1, W2, b2 -- 初始参数learning_rate -- 学习率iterations -- 迭代次数beta -- 动量参数返回:更新后的参数"""m = X.shape[1]# 初始化动量vW1 = np.zeros_like(W1)vb1 = np.zeros_like(b1)vW2 = np.zeros_like(W2)vb2 = np.zeros_like(b2)for i in range(iterations):# 前向传播Z1 = np.dot(W1, X) + b1A1 = sigmoid(Z1)Z2 = np.dot(W2, A1) + b2A2 = sigmoid(Z2)# 计算损失loss = -np.mean(Y * np.log(A2) + (1-Y) * np.log(1-A2))# 反向传播dZ2 = A2 - YdW2 = np.dot(dZ2, A1.T) / mdb2 = np.sum(dZ2, axis=1, keepdims=True) / mdA1 = np.dot(W2.T, dZ2)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(dZ1, X.T) / mdb1 = np.sum(dZ1, axis=1, keepdims=True) / m# 更新动量vW1 = beta * vW1 + (1 - beta) * dW1vb1 = beta * vb1 + (1 - beta) * db1vW2 = beta * vW2 + (1 - beta) * dW2vb2 = beta * vb2 + (1 - beta) * db2# 更新参数W1 = W1 - learning_rate * vW1b1 = b1 - learning_rate * vb1W2 = W2 - learning_rate * vW2b2 = b2 - learning_rate * vb2if i % 100 == 0:print(f"Iteration {i}, Loss: {loss}")return W1, b1, W2, b2# 使用动量法训练
W1, b1, W2, b2 = momentum_gradient_descent(X, Y, W1, b1, W2, b2, 0.01, 1000)
2.4.3 AdaGrad

AdaGrad算法自适应地为每个参数分配不同的学习率:

 

 

def adagrad(X, Y, W1, b1, W2, b2, learning_rate, iterations, epsilon=1e-8):"""AdaGrad优化算法参数:X, Y -- 训练数据和标签W1, b1, W2, b2 -- 初始参数learning_rate -- 初始学习率iterations -- 迭代次数epsilon -- 防止除零的小常数返回:更新后的参数"""m = X.shape[1]# 初始化累积平方梯度GW1 = np.zeros_like(W1)Gb1 = np.zeros_like(b1)GW2 = np.zeros_like(W2)Gb2 = np.zeros_like(b2)for i in range(iterations):# 前向传播Z1 = np.dot(W1, X) + b1A1 = sigmoid(Z1)Z2 = np.dot(W2, A1) + b2A2 = sigmoid(Z2)# 计算损失loss = -np.mean(Y * np.log(A2) + (1-Y) * np.log(1-A2))# 反向传播dZ2 = A2 - YdW2 = np.dot(dZ2, A1.T) / mdb2 = np.sum(dZ2, axis=1, keepdims=True) / mdA1 = np.dot(W2.T, dZ2)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(dZ1, X.T) / mdb1 = np.sum(dZ1, axis=1, keepdims=True) / m# 更新累积平方梯度GW1 += dW1 ** 2Gb1 += db1 ** 2GW2 += dW2 ** 2Gb2 += db2 ** 2# 更新参数W1 = W1 - learning_rate * dW1 / (np.sqrt(GW1) + epsilon)b1 = b1 - learning_rate * db1 / (np.sqrt(Gb1) + epsilon)W2 = W2 - learning_rate * dW2 / (np.sqrt(GW2) + epsilon)b2 = b2 - learning_rate * db2 / (np.sqrt(Gb2) + epsilon)if i % 100 == 0:print(f"Iteration {i}, Loss: {loss}")return W1, b1, W2, b2# 使用AdaGrad训练
W1, b1, W2, b2 = adagrad(X, Y, W1, b1, W2, b2, 0.1, 1000)
2.4.4 RMSProp

RMSProp改进了AdaGrad的累积梯度方式:

 

 

 

def rmsprop(X, Y, W1, b1, W2, b2, learning_rate, iterations, beta=0.9, epsilon=1e-8):"""RMSProp优化算法参数:X, Y -- 训练数据和标签W1, b1, W2, b2 -- 初始参数learning_rate -- 初始学习率iterations -- 迭代次数beta -- 衰减率epsilon -- 防止除零的小常数返回:更新后的参数"""m = X.shape[1]# 初始化累积平方梯度GW1 = np.zeros_like(W1)Gb1 = np.zeros_like(b1)GW2 = np.zeros_like(W2)Gb2 = np.zeros_like(b2)for i in range(iterations):# 前向传播Z1 = np.dot(W1, X) + b1A1 = sigmoid(Z1)Z2 = np.dot(W2, A1) + b2A2 = sigmoid(Z2)# 计算损失loss = -np.mean(Y * np.log(A2) + (1-Y) * np.log(1-A2))# 反向传播dZ2 = A2 - YdW2 = np.dot(dZ2, A1.T) / mdb2 = np.sum(dZ2, axis=1, keepdims=True) / mdA1 = np.dot(W2.T, dZ2)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(dZ1, X.T) / mdb1 = np.sum(dZ1, axis=1, keepdims=True) / m# 更新累积平方梯度GW1 = beta * GW1 + (1 - beta) * dW1 ** 2Gb1 = beta * Gb1 + (1 - beta) * db1 ** 2GW2 = beta * GW2 + (1 - beta) * dW2 ** 2Gb2 = beta * Gb2 + (1 - beta) * db2 ** 2# 更新参数W1 = W1 - learning_rate * dW1 / (np.sqrt(GW1) + epsilon)b1 = b1 - learning_rate * db1 / (np.sqrt(Gb1) + epsilon)W2 = W2 - learning_rate * dW2 / (np.sqrt(GW2) + epsilon)b2 = b2 - learning_rate * db2 / (np.sqrt(Gb2) + epsilon)if i % 100 == 0:print(f"Iteration {i}, Loss: {loss}")return W1, b1, W2, b2# 使用RMSProp训练
W1, b1, W2, b2 = rmsprop(X, Y, W1, b1, W2, b2, 0.01, 1000)
2.4.5 Adam

Adam(Adaptive Moment Estimation)结合了Momentum和RMSProp的优点:

 

 

 

 

 

2.5 总结

不同优化算法的比较:

算法优点缺点适用场景
批量GD稳定收敛计算开销大,内存要求高小数据集
随机GD计算快,可在线学习收敛不稳定大数据集,在线学习
小批量GD平衡计算效率和稳定性需要调batch size大多数场景
Momentum加速收敛,减少震荡需要调动量参数深网络,高维参数
AdaGrad自适应学习率累积梯度导致学习率过早减小稀疏数据
RMSProp解决AdaGrad学习率衰减问题需要调衰减率非平稳目标
Adam结合Momentum和RMSProp优点超参数较多大多数深度学习任务

3. 使用PyTorch实现反向传播

现代深度学习框架如PyTorch已经内置了自动微分和优化算法,可以大大简化实现:

import torch
import torch.nn as nn
import torch.optim as optim# 定义网络结构
class SimpleNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(SimpleNN, self).__init__()self.fc1 = nn.Linear(input_size, hidden_size)self.sigmoid1 = nn.Sigmoid()self.fc2 = nn.Linear(hidden_size, output_size)self.sigmoid2 = nn.Sigmoid()def forward(self, x):out = self.fc1(x)out = self.sigmoid1(out)out = self.fc2(out)out = self.sigmoid2(out)return out# 参数设置
input_size = 3
hidden_size = 4
output_size = 1
learning_rate = 0.1
num_epochs = 1000# 创建模型
model = SimpleNN(input_size, hidden_size, output_size)# 定义损失函数和优化器
criterion = nn.BCELoss()  # 二分类交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=learning_rate)  # 使用Adam优化器# 准备数据 (转换为PyTorch张量)
X_tensor = torch.from_numpy(X.T).float()
Y_tensor = torch.from_numpy(Y.T).float()# 训练循环
for epoch in range(num_epochs):# 前向传播outputs = model(X_tensor)loss = criterion(outputs, Y_tensor)# 反向传播和优化optimizer.zero_grad()  # 清空梯度loss.backward()  # 反向传播计算梯度optimizer.step()  # 更新参数if (epoch+1) % 100 == 0:print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')# 测试模型
with torch.no_grad():test_input = torch.tensor([[0.1, 0.2, 0.3]], dtype=torch.float)predicted = model(test_input)print(f'测试输入: {test_input}, 预测输出: {predicted.item()}')

4. 反向传播的数学推导

为了更深入理解反向传播,让我们推导一下简单的两层神经网络的梯度计算。

4.1 定义网络和损失函数

网络结构:

  1. 输入层到隐藏层:$z_1 = W_1 x + b_1$, $a_1 = \sigma(z_1)$

  2. 隐藏层到输出层:$z_2 = W_2 a_1 + b_2$, $a_2 = \sigma(z_2)$

损失函数(二分类交叉熵):

J=−1m∑i=1m[y(i)log⁡(a2(i))+(1−y(i))log⁡(1−a2(i))]J=−m1​i=1∑m​[y(i)log(a2(i)​)+(1−y(i))log(1−a2(i)​)]

4.2 梯度计算

  1. 输出层梯度:

∂J∂z2=a2−y∂z2​∂J​=a2​−y∂J∂W2=∂J∂z2⋅a1T∂W2​∂J​=∂z2​∂J​⋅a1T​∂J∂b2=∂J∂z2∂b2​∂J​=∂z2​∂J​

  1. 隐藏层梯度:

∂J∂a1=W2T⋅∂J∂z2∂a1​∂J​=W2T​⋅∂z2​∂J​∂J∂z1=∂J∂a1⊙σ′(z1)=∂J∂a1⊙a1⊙(1−a1)∂z1​∂J​=∂a1​∂J​⊙σ′(z1​)=∂a1​∂J​⊙a1​⊙(1−a1​)∂J∂W1=∂J∂z1⋅xT∂W1​∂J​=∂z1​∂J​⋅xT∂J∂b1=∂J∂z1∂b1​∂J​=∂z1​∂J​

5. 总结

反向传播算法是深度学习训练的核心,通过链式法则高效计算梯度。本文从基础的前向传播开始,详细介绍了各种梯度下降算法及其优化方法,并提供了完整的实现代码。现代深度学习框架虽然自动实现了反向传播,但理解其原理对于调试模型和解决实际问题至关重要。

实际应用中,Adam通常是首选优化器,但在特定场景下其他优化器可能表现更好。理解不同优化算法的特性有助于我们根据具体问题选择合适的训练策略。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

http://www.dtcms.com/a/275132.html

相关文章:

  • Google Test 介绍和使用指南
  • 《QtPy:Python与Qt的完美桥梁》
  • STM32 IIC通信(寄存器与hal库实现)
  • 组件杠杠结构
  • 干眼症的预防与治疗
  • 域名锁是什么?有必要安装域名锁吗?
  • 拼数(字符串排序)
  • TransUnet医学图像分割模型
  • PrimeTime (PT Shell) report_timing 报告全字段完整解析
  • 深度对比扣子(Coze) vs n8n
  • halcon 求一个tuple的极值点
  • 上位机知识篇---高效下载安装方法
  • Auto-GPT 简易教程
  • Ant Design ProTable重置函数全解析
  • 【Ubuntu 22.04 ROS2 Humble】没有数字签名。 N: 无法安全地用该源进行更新
  • 47-RK3588 用瑞芯微官方提供recovery进行OTA升级
  • VR协作海外云:跨国企业沉浸式办公解决方案
  • ATAM与效用树:架构评估的核心方法论
  • 喷涂机器人cad【1张】+三维图+设计说明书+降重
  • 【SpringAI】6.向量检索(redis)
  • 【JAVA】面向对象三大特性之继承
  • PICO4 MR开发之外部存储读写
  • 论迹不论心
  • Vue和Element的使用
  • 【跟着PMP学习项目管理】每日一练 - 6
  • 深度学习归一化方法维度参数详解(C/H/W/D完全解析)
  • Linux学习笔记
  • ParaCAD 笔记 png 图纸标注数据集
  • 智能Agent场景实战指南 Day 10:产品经理Agent工作流
  • 【Zephyr开发实践系列】07_SPI-Flash数据操作的非对齐与边界处理分析