七、反向传播算法
反向传播Back Propagation 简称 BP 。 训练神经网络的核心算法之一,通过计算损失函数,相对于每个权重参数的梯度,来优化神经网络的权重
1. 前向传播
前向传播是把数据经过各层神经元的运算并逐层向前传输,知道输出层
1.1 数学表达
简单的三层神经网络(输入,隐藏,输出)前向传播的基本步骤分析
1.1.1 输入层到隐藏层
给定输入x和权重w1 以及偏置b1,隐藏层的输出
将z(1)通过激活函数激活
1.1.2 隐藏层到输出层
隐藏层的输出a(1)通过输出层的权重矩阵w2和偏置b2生成最终的输出:
输出层的激活函数a(2)最终预测结构
1.2 作用
输出的结果用于预测或计算损失
反向传播中,通过损失函数相对于每个参数的梯度来优化网络
2.BP基础梯度下降算法
梯度下降算法的目标是找到损失函数的最小参数
,核心是沿着损失函数梯度的负方向更新参数,逐步逼近局部最优或全局最优,从而模型更好地训练拟合训练数据
2.1 数学描述
2.1.1 数学公式
α是学习率
学习率太小:增加训练的时间和算力成本
学习率太大,大概率跳过最优解,进入无线的训练和震荡
所以学习率要随着训练的变化而进行
2.1.2 过程阐述
初始化参数 Θ,权重w和偏置b
计算梯度:损失函数 L(θ)对参数θ的梯度,表示损失函数在参数空间的变化率。
更新参数:按照梯度下降公式更新参数:,其中,\alpha 是学习率,用于控制更新步长。
迭代更新:重复【计算梯度和更新参数】步骤,直到某个终止条件(如梯度接近0、不再收敛、完成迭代次数等)。
2.2 传统下降方式
2.2.1 批量梯度下降BGD
BGD
特点:每次更新参数前,使用整个训练集来计算梯度
优点:收敛稳定,更准确地沿着损失函数的真实梯度方向下降
缺点:数据集大的时候 计算量很大,更新慢;需要大内存
公式:
其中,m 是训练集样本总数,是第 i 个样本及其标签
是第 i 个样本预测值。
例如,在训练集中有100个样本,迭代50轮。
那么在每一轮迭代中,都会一起使用这100个样本,计算整个训练集的梯度,并对模型更新。
所以总共会更新50次梯度。
import torch
from torch.utils.data import TensorDataset, DataLoader
from torch import nn, optim
# 生成随机输入数据和标签
x = torch.rand(1000, 10) # 1000 个样本,每个样本 10 个特征
y = torch.rand(1000, 1) # 1000 个样本对应的标签
# 将数据封装为 TensorDataset 格式
dataset = TensorDataset(x, y)
# 创建 DataLoader,用于批量加载数据
dataloader = DataLoader(dataset, batch_size=len(dataset)) # 这里设置 batch_size 为整个数据集大小,相当于一次性加载全部数据
# 定义线性回归模型,输入特征维度为 10,输出维度为 1
model = nn.Linear(10, 1)
# 定义均方误差损失函数
criterion = nn.MSELoss()
# 定义随机梯度下降优化器,学习率为 0.01
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 设置训练轮数为 100
epochs = 100
# 开始训练循环
for epoch in range(epochs):
# 遍历数据加载器中的每个批次数据
for b_x, b_y in dataloader:
# 清空梯度,避免梯度累积
optimizer.zero_grad()
# 前向传播,获取模型输出
output = model(b_x)
# 计算损失
loss = criterion(output, b_y)
# 反向传播,计算梯度
loss.backward()
# 更新模型参数
optimizer.step()
# 打印当前轮数和损失值
print("Epoch %d, Loss: %f" % (epoch, loss.item()))
2.2.2 随机梯度下降SGD
SGD
特点:每次更新参数时,仅使用一个样本来计算梯度
优点:更新频率高 计算快 适合大规模数据
能跳出局部最小值 有助于找到全局最优解
公式: 收敛不稳定,容易震荡 因为样本的梯度可能不完全代表整体方向
需要较小的学习率来缓解震荡
公式
其中,是当前随机抽取的样本及其标签。
如果训练集有100个样本,迭代50轮,那么每一轮迭代,会遍历这100个样本,每次会计算某一个样本的梯度,然后更新模型参数。
换句话说,100个样本,迭代50轮,那么就会更新100*50=5000次梯度。
因为每次只用一个样本训练,所以迭代速度会非常快。
但更新的方向会不稳定,这也导致随机梯度下降,可能永远都不会收敛。
不过也因为这种震荡属性,使得随机梯度下降,可以跳出局部最优解。
这在某些情况下,是非常有用的。
x = torch.rand(1000,10)
y = torch.rand(1000,1)
dataset = TensorDataset(x,y)
dataloader = DataLoader(dataset,batch_size =1)
model = nn.Linear(10,1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(),lr = 0.01)
epochs = 100
for epoch in range(epochs):
for b_x,b_y in dataloader:
optimizer.zero_grad()
output = model(b_x)
loss = criterion(output,b_y)
loss.backward()
optimizer.step()
print("Epoches %d , Loss: %f" %(epoch,loss.item()))
2.2.3 小批量梯度下降MGBD
MGBD
特点 :每次更新参数 使用一小部分的训练集
优点: 在效率和收敛稳定性之间取平衡
能利用向量化加速计算,如GPU
缺点 : 选择适当的批量比较困难,通常根据硬件的算力32/64/128/256等
公式
其中,b 是小批量的样本数量,也就是 batc_size。
例如,如果训练集中有100个样本,迭代50轮。
如果设置小批量的数量是20,那么在每一轮迭代中,会有5次小批量迭代。
换句话说,就是将100个样本分成5个小批量,每个小批量20个数据,每次迭代用一个小批量。
因此,按照这样的方式,会对梯度,进行50轮*5个小批量=250次更新。
x = torch.randn(1000,10)
y = torch.randn(1000,1)
dataset = TensorDataset(x,y)
dataloader = DataLoader(dataset, batch_size=100, shuffle=True)
model = nn.Linear(10,1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
epochs = 100
for epoch in range(epochs):
for b_x,b_y in dataloader:
optimizer.zero_grad()
output = model(b_x)
loss = criterion(output,b_y)
loss.backward()
optimizer.step()
print("Epoch %d , Loss : %f" %(epoch+1,loss.item()))
2.3 存在的问题
收敛速度慢:BGD和MBGD使用固定学习率,太大会导致震荡,太小又收敛缓慢。
局部最小值和鞍点问题:SGD在遇到局部最小值或鞍点时容易停滞,导致模型难以达到全局最优。
训练不稳定:SGD中的噪声容易导致训练过程中不稳定,使得训练陷入震荡或不收敛。
2.4 优化下降方式
用来提高收敛速度或稳定性
2.4.1 指数加权平均
EMA。是一种平滑时间序列。对过去赋值予不同的权重计算平均值。与简单移动平均不同,EMA赋值最近的数据给更高的权重,较远的数据权重较低,这样更敏感反映变化趋势。
比如今天股市的走势,和昨天发生的国际事件关系很大,和6个月前发生的事件关系相对肯定小一些。
给定时间序列\{x_t\},EMA在每个时刻 t 的值可以通过以下递推公式计算:
当t=1时:
当t>1时:
其中:
v_t 是第 t 时刻的EMA值;
x_t 是第 t 时刻的观测值;
\beta 是平滑系数,取值范围为 0\leq \beta < 1。\beta 越接近 1,表示对历史数据依赖性越高;越接近 0 则越依赖当前数据。
公式推导:
从上述公式可知:
当 β接近 1 时,β^k衰减较慢,因此历史数据的权重较高。
当 β接近 0 时,β^k衰减较快,因此历史数据的权重较低。
示例
假设我们有一组数据 x=[1,2,3,4,5],我们选择 β=0.1和β=0.9 来计算 EMA。
(1)β=0.1
初始化:
EMA_0=x_0=1
计算后续值:
最终,EMA 的值为 [1,1.9,2.89,3.889,4.8889]。
(2)β=0.9
初始化:
EMA_0=x_0=1
计算后续值:
最终,EMA 的值为 [1,1.1,1.29,1.561,1.9049]。
可以看到:
当 β=0.9 时,历史数据的权重较高,平滑效果较强。EMA值变化缓慢(新数据仅占10%权重),滞后明显。
当 β=0.1时,近期数据的权重较高,平滑效果较弱。EMA值快速逼近最新数据(每次新数据占90%权重)。
2.4.2 Momentunm
动量是梯度下降的优化方法,更好应对梯度变化和梯度消失问题,提高模型的效率和稳定性,它通过引入指数加权平均来积累历史梯度信息,形成“动量Momentum”,帮助算法更快越过局部最优或鞍点。
两步:
1.计算动量项 :
v(t-1)是之前的动量项;
β是动量系数(通常0.9
是当前的梯度
2.更新参数
用动量项更新参数
特点
惯性效应 :会加入之前梯度的累计,像是有惯性,不会因为遇到鞍点导致梯度逼近而停滞
减少震荡 : 平滑了梯度更新,减少在鞍点附近的震荡,帮助优化过程的稳步推进
加速收敛 : 持续在某个方向推进,能够快速穿过鞍点区域,避免在鞍点附近的长时间停留
在方向上的作用:
(1)梯度方向一致时
如果多个梯度在多个连续时刻方向一致(指向同一个方向)Monmentum会积累,更新速度加快
(2)梯度不一致时
梯度方向在不同时刻不一致(震荡) Monementum会通过积累历史的梯度信息部分抵消这些震荡
(3)局部最优或鞍点附近
在局部最优或者鞍点附近,梯度会变很小,导致标准梯度下降法停滞,Monmentum通过历史积累,可以帮忙更新参数越过这些区域
动量方向与梯度方向一致
(1)梯度方向一致时
多个时刻方向一致,动量会逐渐累计,动量方向与梯度方向一致。
(2)几何意义
如果损失函数形状时平滑单调的,Momentum会加速参数的更新,帮忙快速收敛
动量方向与梯度方向不一致时
(1)梯度方向不一致
梯度来回震荡,动量vt会平滑这些变化,使更新路径更加稳定
(2) 几何意义
如果损失函数使复杂切非凸的,Momentum会通过累计的梯度信息抵消部分震荡,让路径更加平滑
总结:
动量项更新:利用当前梯度和历史动量来计算新的动量项。
权重参数更新:利用更新后的动量项来调整权重参数。
梯度计算:在每个时间步计算当前的梯度,用于更新动量项和权重参数。
Monmentum算法使梯度值的平滑调整,并没有对梯度下降的学习率进行优化
import torch.nn as nn
import torch
model = nn.Linear(10, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01,momentum=0.9)
x = torch.randn(100, 10)
y = torch.randn(100, 1)
for epoch in range(100):
optimizer.zero_grad()
outputs = model(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
print(F"Epoch {epoch+1},Loss : {loss.item():.4f}")
2.4.5 AdaGrad
为每个参数引入独立的学习率,根据历史梯度的平方和来调整学习率,所以频繁更新参数,学习率会逐渐减小,反之亦反。AdaGrad避免了统一学习率的不足,更多用于处理稀疏数据和梯度变化大的问题
AdaGrad流程:
1.初始化:
设置参数θ0和学习率η。
梯度累计平方的向量G0初始化为零向量
2.梯度计算
在每个时间步t,计算损失函数J(θ)对参数θ的梯度
3. 累计梯度的平方
每个参数i累计梯度的平方:
Gt使累计的梯度平方和,gt使第i个参数在时间步t的梯v
4.参数更新
利用累计的梯度平方来更新参数
η使全局的初始学习率
ε使一个非常小的常数,用于避免除零操作通常10的-8
是自适应调整后的学习率
AdaGrad为每个参数分配不同的学习率
对于梯度较大的参数,Gt较大,学习率较小,从而避免更新过快
反之亦反
可以将AdaGrad类比为
梯度较大的参数,类似陡峭的山坡,需要较小的步长,避免跨度过大
梯度小,平缓的山坡,较大的步长来加速收敛
优点:
自适应学习率:由于每个参数的学习率是基于其梯度的累积平方和 G_{t,i} 来动态调整的,这意味着学习率会随着时间步的增加而减少,对梯度较大且变化频繁的方向非常有用,防止了梯度过大导致的震荡。
适合稀疏数据:AdaGrad 在处理稀疏数据时表现很好,因为它能够自适应地为那些较少更新的参数保持较大的学习率。
缺点:
学习率过度衰减:随着时间的推移,累积的时间步梯度平方值越来越大,导致学习率逐渐接近零,模型会停止学习。
不适合非稀疏数据:在非稀疏数据的情况下,学习率过快衰减可能导致优化过程早期停滞。
model = torch.nn.Linear(10,1)
criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)
x = torch.randn(100,10)
y = torch.randn(100,1)
for epoch in range(1000):
optimizer.zero_grad()
outputs = model(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
print(F"Epoch:{epoch+1},Loss:{loss.item():.4f}")
2.4.4 RMSProp
不是简单地累积所以梯度平方和。而是使用指数加权平均来逐步平均来逐步衰减时候的梯度信息。它引入指数加权平均来累积历史梯度的平方,动态调整学习率
是当前时刻的指数加权平均梯度平方
β是衰减因子,通常0.9
η是初始学习率
ϵ是一个小常数通常(10的-8)防止除0
gt是当前时刻梯度
优点
适应性强:RMSProp自适应调整每个参数的学习率,对于梯度变化较大的情况非常有效,使得优化过程更加平稳。
适合非稀疏数据:相比于AdaGrad,RMSProp更加适合处理非稀疏数据,因为它不会让学习率减小到几乎为零。
解决过度衰减问题:通过引入指数加权平均,RMSProp避免了AdaGrad中学习率过快衰减的问题,保持了学习率的稳定性
缺点
依赖于超参数的选择:RMSProp的效果对衰减率γ和学习率 η的选择比较敏感,需要一些调参工作。
model = nn.Linear(10, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.RMSprop(model.parameters()
, lr=0.01,alpha=0.9,eps = 1e-8)
x = torch.randn(100,10)
y = torch.randn(100,1)
for epoch in range(100):
optimizer.zero_grad()
outputs = model(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
print(F"Epoch : {epoch+1},Loss:{loss.item():.4f}")
2.4.5 Adam
将动量和RMSProp的优点结合
动量法:通过一阶动量(即梯度的指数加权平均)来加速收敛,尤其是在有噪声或梯度稀疏的情况下。
RMSProp:通过二阶动量(即梯度平方的指数加权平均)来调整学习率,使得每个参数的学习率适应其梯度的变化。
Adam过程
初始化:
初始化参数θ0和学习率η
初始化一阶动量估计m0 = 0 和二阶动量估计v0 =0
设定动量项的衰减率β和二阶动量的衰减率β2,通常β1 = 0.9 ; β2 = 0.999
设定一个小常数ε(10的-8次方)防止除零的错误
2 梯度计算
在每个时间步t,计算损失函数J(θ)对参数θ的梯度gt =
3 一阶动量估计(梯度的指数加权平均)
更新一阶动量估计
其中mt是当前时间步t的一阶动量估计,表示梯度单独指数加权平均
4 二阶动量估计(梯度平方的指数加权平均)
更新二阶动量估计
vt是当前时间步t的二阶动量估计,表示梯度平方的指数加权平均
5 偏差校正
由于一阶动量和二阶动量在初始阶段可能会有偏差,以二阶动量为例:
在计算指数加权平均时,初始化 v_{0}=0,那么,得到
,显然得到的 v_{1} 会小很多,导致估计的不准确,以此类推:
根据:,把 v_{1} 带入后, 得到:
,导致 v_{2} 远小于 g_{1} 和 g_{2},所以 v_{2} 并不能很好的估计出前两次训练的梯度。
所以这个估计是有偏差的。
使用以下公式进行偏差校正:
6 参数更新
使用校正后的动量估计更新参数
优点
高效稳健:Adam结合了动量法和RMSProp的优势,在处理非静态、稀疏梯度和噪声数据时表现出色,能够快速稳定地收敛。
自适应学习率:Adam通过一阶和二阶动量的估计,自适应调整每个参数的学习率,避免了全局学习率设定不合适的问题。
适用大多数问题:Adam几乎可以在不调整超参数的情况下应用于各种深度学习模型,表现良好。
缺点
超参数敏感:尽管Adam通常能很好地工作,但它对初始超参数(如 β1、β2)仍然较为敏感,有时需要仔细调参。
过拟合风险:由于Adam会在初始阶段快速收敛,可能导致模型陷入局部最优甚至过拟合。因此,有时会结合其他优化算法(如SGD)使用。
2.5 总结
梯度下降算法通过不断更新参数来最小化损失函数模式反向传播算法中计算权重调整的基础,根据数据规模和计算资源,选择合适的梯度下降方法可以显著提高模型训练效果
Adam是目前最为流行的优化算法之一,因其稳定性和高效性,广泛应用于各种深度学习模型的训练中。
model = nn.Linear(10, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01
,betas=(0.9, 0.999),eps = 1e-08)
x = torch.randn(100, 10)
y = torch.randn(100, 1)
for epoch in range(100):
optimizer.zero_grad()
outputs = model(x)
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
print(F"Epoch : {epoch+1},Loss : {loss.item():.4f}")