Error BackPropagation(误差逆传播)

server/2024/10/19 6:41:46/

误差逆传播(反向传播,BP算法)

引言E

误差逆传播算法(Error BackPropagation,BP)是神经网络中常用的传播算法(又叫做反向传播)。BP算法可以应用于多层前馈神经网络(FFN)以及其他类型的网络,如训练递归神经网络。通常所说的“BP网络”一般是指用BP算法训练的多层前馈神经网络
在这里插入图片描述
反向传播从字面意思理解与前向传播互相对应。在简单的神经网络中,反向传播可以理解为最优化损失函数过程,求解每个参与运算的梯度的方法。前向传播可以理解为,从输入层到隐藏层再到输出层的过程,前向传播过程往往是设定模型的过程,也可以理解为是在设计方程进行求解。
那么其实BP算法的过程就可以分为:前向传播(求误差),反向传播(误差回传)。
前向传播为反向传播准备好要用的数值,反向传播本质上是一种高效求梯度的方法。

原理

前向传播

y = w ∗ x + b y = w*x+b y=wx+b可以用下面的图来表示,从左边开始依次代入数据,最终计算出y的值,就是前向传播的结果。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

例如,赋值x=-2,y=5,b=-4,那么从左侧开始进行数据流动,最后计算可以得到y=-14,而其中x * y = -10。
前向传播的作用是为反向传播准备好要用的数值。

反向传播

反向传播算法,是根据损失函数,求出损失函数关于每一层的权值及偏置项的偏导数,也称为梯度,用该值更新初始的权值和偏置项,一直更新到损失函数取得最小值或是设置的迭代次数完成为止。以此来计算神经网络中的最佳的参数。
反向传播会用到链式法则(Chain Rule),通过链式法则来求出每一层的梯度。
在这里插入图片描述

设输入层数据为X,输入层到隐藏层的参数为w、 b 1 b_1 b1,隐藏层到输出层的参数为 v , b 2 v,b_2 v,b2,激活函数使用 g 1 g_1 g1, g 2 g_2 g2,那么就有神经网络模型:
输入层到隐藏层:
n e t 1 = w T x + b 1 , h = g 1 ( n e t 1 ) net_1 = w^Tx+b_1,h=g_1(net_1) net1=wTx+b1,h=g1(net1)
隐藏层到输出层:
n e t 2 = v T h + b 2 , y ^ = g 2 ( n e t 2 ) net_2 = v^Th+b_2,\hat y=g_2(net_2) net2=vTh+b2,y^=g2(net2)
模型:
y ^ = g 2 ( n e t 2 ) = g 2 ( v T g 1 ( n e t 1 ) + b 2 ) = g 2 ( v T g 1 ( w T x + b 1 ) + b 2 ) \hat y = g_2(net_2) = g_2(v^Tg_1(net_1)+b_2) = g_2(v^Tg_1(w^Tx+b_1)+b_2) y^=g2(net2)=g2(vTg1(net1)+b2)=g2(vTg1(wTx+b1)+b2)
损失函数为:
E ( θ ) = 1 2 ∑ i = 1 2 ( y i − y ^ i ) 2 E(\theta)=\frac{1}{2}\sum_{i=1}^2(y_i-\hat y_i)^2 E(θ)=21i=12(yiy^i)2
算法步骤:

  1. 初始化网络中的权值和偏置,分别记为
    w ( 0 ) , b 1 ( 0 ) , v ( 0 ) , b 2 ( 0 ) w^{(0)},b_1^{(0)},v^{(0)},b_2^{(0)} w(0),b1(0),v(0),b2(0)

  2. 激活函数前向传播,得到各层输出和损失函数的期望:
    E ( θ ) = 1 2 ∑ i = 1 2 ( y i − y ^ i ) 2 E(\theta)=\frac{1}{2}\sum_{i=1}^2(y_i-\hat y_i)^2 E(θ)=21i=12(yiy^i)2
    此时本模型设定中,输出值为2维的列数数据(两个样本),所以是 1 2 \frac{1}{2} 21,一般情况下,损失函数表示为
    E ( θ ) = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 E(\theta)=\frac{1}{n}\sum_{i=1}^n(y_i-\hat y_i)^2 E(θ)=n1i=1n(yiy^i)2

  3. 根据损失函数,计算出输出单元的误差项和隐藏单元的误差项
    输出单元的误差项,就是计算损失函数关于输出单元的梯度或偏导,根据链式法则得到:
    ▽ ( k ) V = ∂ E ∂ v = ∂ n e t 2 ∂ v ∂ y ^ ∂ n e t 2 ∂ E ∂ y ^ \bigtriangledown (k)^V = \frac{\partial E}{\partial v} = \frac{\partial net_2}{\partial v}\frac{\partial \hat y}{\partial net_2}\frac {\partial E}{\partial \hat y} (k)V=vE=vnet2net2y^y^E
    ▽ ( k ) b 2 = ∂ E ∂ v = ∂ n e t 2 ∂ b 2 ∂ y ^ ∂ n e t 2 ∂ E ∂ y ^ \bigtriangledown (k)^{b_2} = \frac{\partial E}{\partial v} = \frac{\partial net_2}{\partial b_2}\frac{\partial \hat y}{\partial net_2}\frac {\partial E}{\partial \hat y} (k)b2=vE=b2net2net2y^y^E

  4. 更新神经网络中的权值和偏置
    v ( k ) = v ( k − 1 ) − η ▽ ( k ) v = v ( k − 1 ) − η ∂ E ∂ v , b 2 ( k ) = b 2 ( k − 1 ) − η ∂ E ∂ b 2 v^{(k)} = v^{(k-1)} - \eta \bigtriangledown(k)^v = v^{(k-1)} - \eta \frac{\partial E}{\partial v}, b_2^{(k)} = b_2^{(k-1)} - \eta \frac{\partial E}{\partial b_2} v(k)=v(k1)η(k)v=v(k1)ηvE,b2(k)=b2(k1)ηb2E
    w ( k ) = w ( k − 1 ) − η ▽ ( k ) w = w ( k − 1 ) − η ∂ E ∂ v , b 1 ( k ) = b 1 ( k − 1 ) − η ∂ E ∂ b 1 w^{(k)} = w^{(k-1)} - \eta \bigtriangledown(k)^w = w^{(k-1)} - \eta \frac{\partial E}{\partial v}, b_1^{(k)} = b_1^{(k-1)} - \eta \frac{\partial E}{\partial b_1} w(k)=w(k1)η(k)w=w(k1)ηvE,b1(k)=b1(k1)ηb1E
    其中 η \eta η是学习率, k = 1 , 2 , . . . n k=1,2,...n k=1,2,...n是表示更新次数或迭代次数。

  5. 重复2-4,直至损失函数小于阈值或迭代次数用完。’

下面我们根据李宏毅的ppt来具体讨论一下backforward。
在这里插入图片描述

我们假设神经网络的损失函数定义为
L ( θ ) = ∑ n = 1 N C n ( θ ) L(\theta) = \sum_{n=1}^N C^n(\theta) L(θ)=n=1NCn(θ)
那么就有损失函数对权重的偏导数
∂ L ( θ ) ∂ w = ∑ n = 1 N ∂ C n ( θ ) ∂ w \frac{\partial L(\theta)}{\partial w} = \sum_{n=1}^N \frac{\partial C^n(\theta)}{\partial w} wL(θ)=n=1NwCn(θ)
我们只需要能够计算单个样本对参数的偏导值再进行求和,就可以得到总的损失函数对权重的偏导。
在这里插入图片描述
在这里插入图片描述

这是一个不断递归的过程,只需要知道后面的z的偏导,就可以求出前面的偏导,因此也叫做反向传播。
前向传播使得我们可以获得 ∂ α ∂ z \frac{\partial \alpha}{\partial z} zα的值,反向传播是用来计算 ∂ C ∂ α \frac{\partial C}{\partial \alpha} αC的值。
我们对于计算 ∂ C ∂ α \frac{\partial C}{\partial \alpha} αC可以分为两类:

  1. 直接到达输出层,后续没有隐藏层。
    在这里插入图片描述

此时我们通过最后输出层的 y 1 y_1 y1 y 2 y_2 y2,通过链式法则,可以直接获得。
2. 不是输出层,后续还有隐藏层。
在这里插入图片描述

那么我们可以通过不断的反向传播,首先计算出由输出层到倒数第一个隐藏层的偏导,然后再向前计算,一直可以计算到第一层的偏导,这也就是为什么反向传播用来求梯度很快,因为如果正向进行求梯度,有很多值未知,但是反向传播,从最后开始向前求梯度,就能够一直做乘法,求出。
总结BP算法就能够分为两步:
在这里插入图片描述

在前面的正向可以得到链式法则前部分值,反向获得链式法则后部分值,结合起来就可以运用反向传播进行训练神经网络

缺陷

  1. 局部最小(local minima)
    对于多层网络,误差曲面可能含有多个不同的局部极小值,梯度下降可能导致陷入局部极小值。
  2. 权值过多
    当隐藏节点过多,层数越多时,权值成倍增长。权值的增长意味着对应的空间维数的增加,过高的维数易导致训练后期的过拟合。
  3. 过拟合
    训练的次数过多、空间维数过高均容易过拟合。

改进

  1. Momentum
    动量法权值调整算法的具体做法是:将上一次权值调整量的一部分迭加到按本次误差计算所得的权值调整量上,作为本次的实际权值调整量,即:
    w ( k ) = β w ( k − 1 ) − η ▽ k w w^{(k)} = \beta w^{(k-1)} - \eta \bigtriangledown k^w w(k)=βw(k1)ηkw
    其中 β \beta β是动量系数, η \eta η 是学习率。
    通过每次沿着原先方向和梯度下降的方向来移动,从而避免陷入局部最小。
    在这里插入图片描述

在这里插入图片描述

最后可以发现动量是前几次所有梯度下降的总和。
在这里插入图片描述

如图中所示,当走到局部最优时,由于考虑了动量(即先前的移动方向)那么就会跳出局部最优,继续向前。当走到顶峰时,如果动量的影响比梯度的影响要大,那么就可能会返回原来最优点。
2. Adaptive Learning Rate
在学习收敛的情况下,增大 η \eta η以缩短学习时间;当 η \eta η偏大致使不能收敛时,要及时减小它的值,直到收敛为止。此方法适用于设置阈值的情况下。
(1)Adagrad
在这里插入图片描述

坡度比较大时,lr减小,坡度比较小时,lr变大。
在某些深度学习模型上能获得很不错的效果,但并不能适用于所有模型。因为算法在训练开始时就对梯度平方进行累积,在一些实验中,容易导致学习率过早和过量地减小。
在这里插入图片描述

(2)RMSProp
在这里插入图片描述

在这里插入图片描述

对每个参数使用不同的学习率,这些学习率是根据参数的最近梯度大小自适应调整的。具体来说,RMSProp使用平方梯度的指数加权移动平均来调整学习率,从而使得学习率的调整更加平滑。旨在解决 Adagrad 算法在深度学习训练过程中学习率逐渐减小直至无法进一步学习的问题。
(3)Adam:RMSProp+Momentum
在这里插入图片描述

  1. Momentum-ALR
    采用动量法,BP算法可以找到更优的解;采用自适应学习速率法时,BP算法可以缩短训练时间。将以上两种方法结合起来,就得到动量-自适应学习率调整算法。

在这里插入图片描述

当使用Adagrad后,可以缓解梯度上下浮动无法收敛的问题,但是最后会存在,无法进一步学习,还是会继续上下浮动,因此可以引入Learning Rate Scheduling,来控制学习率,使得参数更新接近最优。

  1. Learning Rate Decay
    在这里插入图片描述

越接近最优值时,控制学习率衰减。
2. Warm Up
控制学习率先上升,然后在下降。
在这里插入图片描述

代码

BP算法

import math
import numpy as np
from numpy import *# 激活函数
def sigmoid(z):a = []for each in z:b = 1/(1+math.exp(-each[0]))# sigmoid函数a.append(b)return a
def maxout1(z):s = []for each in z:for m in each:s.append(max(m,0))return s# 前向传播,返回预测值
def forwordmd(X,W,V,B1,B2):net1 = W.T*X+B1 #输入 -> 隐藏H = matrix(sigmoid(np.array(net1))).T # 隐藏层单元,此时由于是转化为了np.array计算,所以需要进行转置。net2 = V.T*H+B2 #隐藏 -> 输出pred_y = matrix(sigmoid(np.array(net2))).T # 预测值return pred_y,H# 反向传播,更新权重
def Bpaugorith(Y,pred_y,H,V,aph,W):Errorterm = 0.5*(Y-pred_y).T*(Y-pred_y)# 给出误差公式# 计算输出单元的误差项a1 = multiply(pred_y-Y,pred_y) # 矩阵对应元素相乘a2 = multiply(a1,1-pred_y)Verror = H*a2.T # 计算隐藏单元的误差项Werror = X*(multiply(multiply(H,1-H),(V*a2))).T# 更新权重Vupdate = V - aph*VerrorWupdate = W - aph*Werrorreturn Vupdate,Wupdate,Errortermif __name__ =='__main__':X = matrix([0.05,0.10]).TY = matrix([0,1]).T# 给出初始权重W = matrix([[0.15,0.20],[0.25,0.30]])B1 = matrix([0.1,0.1]).TV = matrix([[0.40,0.45],[0.50,0.55]])B2 = matrix([0.2,0.2]).T# ***********初始权重亦可随机生成***********# 随机生成参数# np.random.seed(0)# W = matrix(np.random.normal(0,1,[2,2]))# B1 = matrix(np.random.normal(0, 1, [1, 2]))# V = matrix(np.random.normal(0, 1, [2, 2]))# B2 = matrix(np.random.normal(0, 1, [1, 2]))# ***********随机生成参数部分,若有自己设定,将此部分注释*********aph = 0.5 # 学习率# *********从此处为迭代次数设置部分***********# 迭代10次Vn = 1# 迭代次数0for i in range(n):# 激活前向算法pred_y, H= forwordmd(X,W,V,B1,B2)  # 得到预测值和隐藏层值# 更新权重Vupdate, Wupdate,Errorvalue = Bpaugorith(Y,pred_y,H,V,aph,W)  # 得到更新的权重W,V = Wupdate,Vupdateprint('迭代次数:%d' % n)print('预测值:')print(pred_y)print('更新的权重V:')print(Vupdate)print('更新的权重W:')print(Wupdate)print('损失值:')print(Errorvalue)# *********迭代次数部分结束 **********# *********从此处为阈值设置部分***********# 阈值e,可根据需要自行更改,若需要运行此部分,请将迭代次数部分注释后运行# e,m = 0.19,1# pred_y, H, net1, net2 = forwordmd(X,W,V,B1,B2)  # 得到预测值和隐藏层值# 更新权重# Vupdate, Wupdate, Errorvalue = Bpaugorith(Y,pred_y,H,V,net1,net2,aph,W)  # 得到更新的权重# W,V = Wupdate,Vupdate# while Errorvalue>e:#     # 激活前向算法#     pred_y, H, net1, net2 = forwordmd(X,W,V,B1,B2)  # 得到预测值和隐藏层值#     # 更新权重#     Vupdate, Wupdate, Errorvalue = Bpaugorith(Y,pred_y,H,V,net1,net2,aph,W)  # 得到更新的权重#     W, V = Wupdate, Vupdate#     m = m+1# print('阈值e:%.2f' % e)# print('更新权重:%d次' % m)# print('预测值:')# print(pred_y)# print('更新的权重V:')# print(Vupdate)# print('更新的权重W:')# print(Wupdate)# print('损失值:')# print(Errorvalue)# *********阈值设置部分结束***********

迭代次数:1
预测值:
[[0.66389539]
[0.67570502]]
更新的权重V:
[[0.36051488 0.46894075]
[0.46037665 0.56900707]]
更新的权重W:
[[0.14983026 0.1997824 ]
[0.24966051 0.29956481]]
损失值:
[[0.27296216]]

动量法

import numpy as np
import torch
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
import torch.nn as nn
import torch.nn.functional as Fimport time
import matplotlib.pyplot as plt
%matplotlib inlinedef sgd_momentum(parameters, vs, lr, gamma):for param, v in zip(parameters, vs):v[:] = gamma * v + lr * param.grad.dataparam.data = param.data - vdef data_tf(x):x = np.array(x, dtype='float32') / 255x = (x - 0.5) / 0.5 # 标准化,这个技巧之后会讲到x = x.reshape((-1,)) # 拉平x = torch.from_numpy(x)return x# 使用MNIST数据集,没有的话需要下载
train_set = MNIST('./data', train=True, transform=data_tf) # 载入数据集,申明定义的数据变换
test_set = MNIST('./data', train=False, transform=data_tf)# 定义 loss 函数
criterion = nn.CrossEntropyLoss()# DataLoader 对数据进行加载打包
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
# 使用 Sequential 定义 3 层神经网络
net = nn.Sequential(nn.Linear(784, 200),nn.ReLU(),nn.Linear(200, 10),
)# 将速度初始化为和参数形状相同的零张量
vs = []
for param in net.parameters():vs.append(torch.zeros_like(param.data))# 开始训练
losses = []start = time.time() # 记时开始
for e in range(5):train_loss = 0for im, label in train_data:# 前向传播out = net(im)loss = criterion(out, label)# 反向传播net.zero_grad()loss.backward()sgd_momentum(net.parameters(), vs, 1e-2, 0.9) # 使用的动量参数为 0.9,学习率 0.01# 记录误差train_loss += loss.item()losses.append(loss.item())print('epoch: {}, Train Loss: {:.6f}'.format(e, train_loss / len(train_data)))
end = time.time() # 计时结束
print('使用时间: {:.5f} s'.format(end - start))
x_axis = np.linspace(0, 5, len(losses), endpoint=True)
plt.semilogy(x_axis, losses, label='momentum: 0.9')
plt.legend(loc='best')

epoch: 0, Train Loss: 0.365234
epoch: 1, Train Loss: 0.171782
epoch: 2, Train Loss: 0.126027
epoch: 3, Train Loss: 0.100056
epoch: 4, Train Loss: 0.085386
使用时间: 32.14786 s

自适应学习率(Adagrad)

import torch
import torch.nn as nn# 生成训练数据
torch.manual_seed(1234)
num_samples = 1000
num_features = 10
X = torch.randn(num_samples, num_features)
w_true = torch.randn(num_features, 1)
y = X @ w_true + torch.randn(num_samples, 1) * 0.1# 定义网络模型
model = nn.Linear(num_features, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)# 训练过程
num_epochs = 100
for epoch in range(num_epochs):# 前向传播和计算损失outputs = model(X)loss = criterion(outputs, y)# 反向传播和参数更新optimizer.zero_grad()loss.backward()optimizer.step()# 打印训练过程中的损失if (epoch+1) % 10 == 0 or epoch == 0:print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

Epoch [1/100], Loss: 24.7046
Epoch [10/100], Loss: 23.4447
Epoch [20/100], Loss: 22.7591
Epoch [30/100], Loss: 22.2500
Epoch [40/100], Loss: 21.8304
Epoch [50/100], Loss: 21.4676
Epoch [60/100], Loss: 21.1447
Epoch [70/100], Loss: 20.8520
Epoch [80/100], Loss: 20.5830
Epoch [90/100], Loss: 20.3333
Epoch [100/100], Loss: 20.0998

参考

CSDN博客

知乎

博客园

知乎

机器学习(Hung-yi Lee)

知乎

代码


http://www.ppmy.cn/server/132972.html

相关文章

Scala的sortedWith

sortedWith:基于函数的排序,通过一个comparator函数,实现自定义排序的逻辑。 sortWith方法: 它使用传入的比较函数对集合进行排序。 在排序过程中,根据比较函数的返回值来决定元素的顺序。 如果比较函数返回true&…

如何通过Chrome设置保护你的在线隐私

在当今数字时代,保护个人隐私和在线安全变得尤为重要。谷歌浏览器作为全球最受欢迎的网络浏览器之一,提供了多种功能来帮助用户保护自己的在线隐私。本教程将指导你如何通过谷歌浏览器设置来提高你的在线隐私保护水平。(本文由https://www.li…

穿越沙漠问题

题目:一辆吉普车穿越1000km的沙漠。吉普车的总装油量为500L,耗油率为1L/km。由于沙漠中没有油库,必须先用这辆车在沙漠中建立临时油库。若吉普车用最少的耗油量穿越沙漠,应在哪些地方建立油库,以及各处存储的油量是多少…

PyQt入门指南二十二 QSlider滑块组件应用实例

在PyQt中,QSlider 是一个非常实用的组件,它允许用户通过拖动滑块来选择一个值的范围内的特定值。下面是一个简单的实例,展示了如何使用 QSlider 组件。 首先,确保你已经安装了 PyQt5。如果没有安装,可以使用 pip 进行…

基于深度学习的进化神经网络设计

基于深度学习的进化神经网络设计(Evolutionary Neural Networks, ENNs)结合了进化算法(EA)和神经网络(NN)的优点,用于自动化神经网络架构的设计和优化。通过模拟自然进化的选择、变异、交叉等过…

复习:JavaScript 中的原型

在 JavaScript 中,原型(Prototype)是一个非常核心且强大的概念,它主要用在对象继承的实现上。理解原型和原型链,对于深入理解 JavaScript 的面向对象编程至关重要。 1. 什么是原型? 每个 JavaScript 对象…

从开发板传送文件回本地

1.从开发板传送文件 rsync -avz --progress -e “ssh -p 134” --exclude ‘*.mp4’ nvidialab.mlboy.site:~/from30t/flask/recorder ~/zz_xiangmmu/uav_result

【刷题】东方博宜OJ 1136 - 输出m和n范围内的完全数(完美数)

1136 - 输出m和n范围内的完全数(完美数) 东方博宜OJ 输入 2 10输出 6题解 这题时间范围要注意&#xff0c;因数自定义函数不够优化会超时。 #include <bits/stdc.h> #define long long ll; #define unsigned long long ull; using namespace std;int f(int n) {int…