深度学习基础02_损失函数BP算法(上)
目录
一、损失函数
1、线性回归损失函数
1.MAE损失
2.MSE损失
3.SmoothL1Loss
2、多分类损失函数--CrossEntropyLoss
3、二分类损失函数--BCELoss
4、总结
二、BP算法
1、前向传播
1.输入层(Input Layer)到隐藏层(Hidden Layer)
2.隐藏层(Hidden Layer)到输出层(Output Layer)
2、反向传播
1.原理
2.链式法则
3.案例
神经元计算
损失计算
参数更新
反向传播写法01
反向传播写法02(更常用)
一、损失函数
1、线性回归损失函数
1.MAE损失
MAE(Mean Absolute Error,平均绝对误差)通常也被称为 L1-Loss,通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。
公式
特点
-
鲁棒性:与均方误差(MSE)相比,MAE对异常值(outliers)更为鲁棒,因为它不会像MSE那样对较大误差平方敏感。
-
物理意义直观:MAE以与原始数据相同的单位度量误差,使其易于解释。
应用场景
MAE通常用于需要对误差进行线性度量的情况,尤其是当数据中可能存在异常值时,MAE可以避免对异常值的过度惩罚。
import torch
import torch.nn as nn
def test01():
"""
MAE 平均绝对误差 通常也被称为 L1-Loss
预测值和真实值之间的绝对差取平均值
"""
l1_loss_fn = nn.L1Loss()
y_pred = torch.tensor([1,2,3],dtype=torch.float32)
y_true = torch.tensor([1.5,2.5,3.5],dtype=torch.float32)
loss = l1_loss_fn(y_pred,y_true)
print(loss) # tensor(0.5000)
2.MSE损失
MSE(Mean Squared Error,均方误差),平均评分误差通过对预测值和真实值之间的误差平方取平均值,来衡量预测值与真实值之间的差异。
公式
特点
-
平方惩罚:因为误差平方,MSE 对较大误差施加更大惩罚,所以 MSE 对异常值更为敏感。
-
凸性:MSE 是一个凸函数,这意味着它具有一个唯一的全局最小值,有助于优化问题的求解。
应用场景
MSE被广泛应用在神经网络中。
def test02():
"""
MSE 平均平方误差 通常也被称为 L2-Loss
测值和真实值之间的误差平方取平均值
"""
l2_loss_fn = nn.MSELoss()
y_pred = torch.tensor([1,2,3],dtype=torch.float32)
y_true = torch.tensor([1.5,2.5,3.5],dtype=torch.float32)
loss = l2_loss_fn(y_pred,y_true)
print(loss) # tensor(0.2500)
3.SmoothL1Loss
SmoothL1Loss可以做到在损失较小时表现为 L2 损失,而在损失较大时表现为 L1 损失。
公式
所有样本的平均损失:
特点
-
平滑过渡:当误差较小时,损失函数表现为 L2 Loss(平方惩罚);当误差较大时,损失函数逐渐向 L1 Loss过渡。这种平滑过渡既能对大误差有所控制,又不会对异常值过度敏感。
-
稳健性:对于异常值更加稳健,同时在小误差范围内提供了较好的优化效果。
应用场景
SmoothL1Loss常用于需要对大误差进行一定控制但又不希望完全忽略小误差的回归任务。特别适用于目标检测任务中的边界框回归,如 Faster R-CNN 等算法中。
def test03():
"""
SmoothL1Loss 平滑损失函数
在损失较小时表现为 L2 损失,而在损失较大时表现为 L1 损失
"""
# 方式一
loss_fn = nn.SmoothL1Loss()
# 方式二
loss_fn = nn.funtional.smooth_l1_loss
y_pred = torch.tensor([1,2,3,6],dtype=torch.float32)
y_true = torch.tensor([1.5,2.5,3.5,4.5],dtype=torch.float32)
loss = loss_fn(y_pred,y_true)
print(loss)
2、多分类损失函数--CrossEntropyLoss
交叉熵损失函数,使用在输出层使用softmax激活函数进行多分类时,一般都采用交叉熵损失函数。
公式
其中,样本的实际标签 y 和模型预测的概率分布, y 是one-hot编码形式,表示真实类别 , 表示第 i 类的概率估计值(经过 softmax 后的概率分布) 。
*one-hot 编码
假设我们有一个三分类问题,三个可能的类别分别是0、1和2。如果某个样本的真实标签是1,那么它的one-hot编码就是 [0,1,0]。这意味着:
- 第一个位置(索引0)是0,表示该样本不属于类别0。
- 第二个位置(索引1)是1,表示该样本属于类别1。
- 第三个位置(索引2)是0,表示该样本不属于类别2。
特点
Softmax 直白来说就是将网络输出的 logits 通过 softmax 函数,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们将它理解成概率,选取概率最大(也就是值对应最大的)节点,作为我们的预测目标类别。
def test04():
"""
CrossEntropyLoss 交叉熵损失函数
使用在输出层使用softmax激活函数进行多分类
"""
# 初始化
criterion = nn.CrossEntropyLoss()
# 设有三个类别,模型输出是未经softmax的logits
one_hot = torch.tensor([[1.5, 2.0, 0.5], [0.5, 1.0, 1.5]]) # 两个输出
loss = criterion(one_hot, labels) # (L1+L2)/2
print(loss.item()) # 0.6422001123428345
3、二分类损失函数--BCELoss
二分类交叉熵损失函数,使用在输出层使用sigmoid激活函数进行二分类时。对于二分类问题,CrossEntropyLoss 的简化版本称为二元交叉熵(Binary Cross-Entropy Loss)
公式
log的底数一般默认为e,y是真实类别目标,根据公式可知L是一个分段函数 :
- y=1时,L = -log(sigmoid 激活值)
- y=0时,L = -log(1-sigmoid 激活值)
def test05():
"""
BCELoss 二分类交叉熵损失函数
使用在输出层使用sigmoid激活函数进行二分类时
"""
# y 是模型的输出,已经被sigmoid处理过,确保其值域在(0,1)
y = torch.tensor([[0.7], [0.2], [0.9], [0.7]])
# targets 是真实的标签,0或1
t = torch.tensor([[1], [0], [1], [0]], dtype=torch.float)
# 方式一
bce_loss_fn = nn.BCELoss()
loss = bce_loss_fn(y,t)
# 方式二
loss = nn.functional.binary_cross_entropy(y,t)
print(loss)
4、总结
以上损失函数是一个样本的损失值,总样本的损失值是求损失均值即可。
-
当输出层使用softmax多分类时,使用交叉熵损失函数;
-
当输出层使用sigmoid二分类时,使用二分类交叉熵损失函数, 比如在逻辑回归中使用;
-
当功能为线性回归时,使用smooth L1损失函数或均方差损失-L2 loss;
二、BP算法
多层神经网络的学习能力比单层网络强得多。想要训练多层网络,需要更强大的学习算法。误差反向传播算法(Back Propagation)是其中最杰出的代表,它是目前最成功的神经网络学习算法。
现实任务使用神经网络时,大多是在使用 BP 算法进行训练,值得指出的是 BP 算法不仅可用于多层前馈神经网络,还可以用于其他类型的神经网络。
通常说 BP 网络时,一般是指用 BP 算法训练的多层前馈神经网络。
误差反向传播算法(BP)的基本步骤
-
前向传播:正向计算得到预测值。
-
计算损失:通过损失函数 计算预测值和真实值的差距。
-
梯度计算:反向传播的核心是计算损失函数 L 对每个权重和偏置的梯度。
-
更新参数:一旦得到每层梯度,就可以使用梯度下降算法来更新每层的权重和偏置,使得损失逐渐减小。
-
迭代训练:将前向传播、梯度计算、参数更新的步骤重复多次,直到损失函数收敛或达到预定的停止条件。
1、前向传播
前向传播(Forward Propagation)把输入数据经过各层神经元的运算并逐层向前传输,一直到输出层为止。
一个简单的三层神经网络(输入层、隐藏层、输出层)前向传播的基本步骤分析:
1.输入层(Input Layer)到隐藏层(Hidden Layer)
给定输入 x 和权重矩阵 W 及偏置向量 b,隐藏层的输出(激活值)计算如下:
将z通过激活函数激活:
2.隐藏层(Hidden Layer)到输出层(Output Layer)
隐藏层的输出经过输出层的权重矩阵 W 和偏置 b 生成最终输出:
z通过输出层的激活函数激活后就是最终预测结果:
前向传播的主要作用
计算神经网络的输出结果,用于预测或计算损失。
在反向传播中使用,通过计算损失函数相对于每个参数的梯度来优化网络。
2、反向传播
反向传播(Back Propagation,简称BP)通过计算损失函数相对于每个参数的梯度来调整权重,使模型在训练数据上的表现逐渐优化。
反向传播结合了链式求导法则和梯度下降算法,是神经网络模型训练过程中更新参数的关键步骤。
1.原理
利用链式求导法则对每一层进行求导,直到求出输入层x的导数,然后利用导数值进行梯度更新。
2.链式法则
链式求导法则(Chain Rule)是微积分中的一个重要法则,用于求复合函数的导数。在深度学习中,链式法则是反向传播算法的基础,这样就可以通过分层的计算求得损失函数相对于每个参数的梯度,以下面函数为例:
import torch
def test01():
"""
链式求导
"""
x = torch.tensor(1.0)
w = torch.tensor(0.0,required_grad=True)
b = torch.tensor(0.0,required_grad=True)
fx = torch.sigmoid(w*x + b)
fx.backward() # fx对w求导
print(w.grad) # tensor(0.2500)
print(b.grad) # tensor(0.2500)
if __name__ == '__main__':
test01()
3.案例
神经元计算
输入层到隐藏层:
同理 h2=0.3925, k2=0.5969
隐藏层到输出层:
同理 o2=1.2249, m2 =0.7729
所以,最终的预测结果分别为: 0.7514、0.7729
损失计算
预测值和真实值(target)进行比较计算损失:
import torch
"""手动计算代码过程"""
i1 = 0.05
i2 = 0.10
b1 = 0.35
b2 = 0.60
def h1():
w1 = 0.15
w2 = 0.20
l1 = i1 * w1 + i2 * w2 + b1
l1 = torch.tensor(l1)
return torch.sigmoid(l1) # 1/(1+torch.e**(-l1))
print(f'h1:{h1()}') # 0.5933
def h2():
w3 = 0.25
w4 = 0.30
l2 = i1 * w3 + i2 * w4 + b1
l2 = torch.tensor(l2)
return torch.sigmoid(l2) # 1/(1+torch.e**(-l2))
print(f'h2:{h2()}') # 0.5969
def o1():
w5 = 0.40
w6 = 0.45
l3 = h1() * w5 + h2() * w6 + b2
l3 = torch.tensor(l3)
m1 = torch.sigmoid(l3) # 1/(1+torch.e**(-l3))
return m1
print(f'o1:{o1()}') # 0.7514
def o2():
w7 = 0.50
w8 = 0.55
l4 = h1() * w7 + h2() * w8 + b2
l4 = torch.tensor(l4)
m2 = torch.sigmoid(l4) # 1/(1+torch.e**(-l4))
return m2
print(f'o2:{o2()}') # 0.7729
# 手动反向传播
def mse():
o1_target = 0.01
o2_target = 0.99
return 0.5*((o1() - 0.01)**2 + (o2() - 0.99)**2)
loss = mse()
梯度计算
使用链式法则计算w1,w5,w7梯度:
import torch
"""反向传播api手动实现"""
def train():
# 前向传播
# x1 x2
i=torch.tensor([[0.05,0.1]])
model1=torch.nn.Linear(2,2)
# w1 w2 w3 w4
model1.weight.data=torch.tensor([[0.15,0.20],
[0.25,0.30]])
# b1
model1.bias.data=torch.tensor([0.35,0.35])
l1_l2=model1(i)
h1_h2=torch.sigmoid(l1_l2)
model2=torch.nn.Linear(2,2)
# w5 w6 w7 w8
model2.weight.data=torch.tensor([[0.40,0.45],
[0.50,0.55]])
# b2
model2.bias.data=torch.tensor([0.60,0.60])
l3_l4=model2(h1_h2)
o1_o2=torch.sigmoid(l3_l4)
# 误差计算
o1_o2_true=torch.tensor([[0.01,0.99]])
loss=torch.sum((o1_o2 - o1_o2_true) ** 2) / 2
# mse=torch.nn.MSELoss()
# loss=mse(o1_o2,o1_o2_true)
# 反向传播
loss.backward()
print(model1.weight.grad)
print(model2.weight.grad)
train()
参数更新
现在就可以进行权重更新了:假设学习率是0.5:
整合一下,完整的反向传播写法:
反向传播代码写法01
import torch
import torch.nn as nn
import torch.optim as optim
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
# 定义网络结构
self.linear1 = nn.Linear(2, 2)
self.linear2 = nn.Linear(2, 2)
self.activation = torch.sigmoid
# 网络参数初始化
# 隐藏层权重
self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
# 输出层权重
self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
# 隐藏层偏置
self.linear1.bias.data = torch.tensor([0.35, 0.35])
# 输出层偏置
self.linear2.bias.data = torch.tensor([0.60, 0.60])
def forward(self, x):
x = self.linear1(x)
x = self.activation(x)
x = self.linear2(x)
x = self.activation(x)
return x
def train():
net = MyNet()
# 优化方法
optimizer = optim.SGD(net.parameters(), lr=0.1)
inputs = torch.tensor([[0.05, 0.10]])
target = torch.tensor([[0.01, 0.99]])
# 获得网络输出值
pred = net(inputs) # module父类已实现前向传播
mes = torch.nn.MSELoss()
# 计算误差
loss = mes(pred, target)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 打印(w1-w8)观察w5、w7、w1 的梯度值是否与手动计算一致
print(net.linear1.weight.grad.data)
print(net.linear2.weight.grad.data)
#更新梯度
optimizer.step()
# 打印更新后的网络参数
# print(net.state_dict())
train()
反向传播代码写法02(更常用)
import torch
import torch.nn as nn
import torch.optim as optim
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
# 定义网络结构
# self.linear1 = nn.Linear(2, 2)
# self.linear2 = nn.Linear(2, 2)
# self.activation = torch.sigmoid
self.hide1 = torch.nn.Sequential(nn.Linear(2, 2),torch.nn.Sigmoid())
self.out = torch.nn.Sequential(nn.Linear(2, 2),torch.nn.Sigmoid())
# 网络参数初始化 线性层
# # 隐藏层权重
# self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
# # 输出层权重
# self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
# # 隐藏层偏置
# self.linear1.bias.data = torch.tensor([0.35, 0.35])
# # 输出层偏置
# self.linear2.bias.data = torch.tensor([0.60, 0.60])
self.hide1[0].weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
self.out[0].weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
self.hide1[0].bias.data = torch.tensor([0.35, 0.35])
self.out[0].bias.data = torch.tensor([0.60, 0.60])
def forward(self, x):
# x = self.linear1(x)
# x = self.activation(x)
# x = self.linear2(x)
# x = self.activation(x)
x = self.hide1(x)
x = self.out(x)
return x
def train():
net = MyNet()
# 优化方法
optimizer = optim.SGD(net.parameters(), lr=0.1)
inputs = torch.tensor([[0.05, 0.10]])
target = torch.tensor([[0.01, 0.99]])
# 获得网络输出值
pred = net(inputs) # module父类已实现前向传播
mes = torch.nn.MSELoss()
# 计算误差
loss = mes(pred, target)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 打印(w1-w8)观察w5、w7、w1 的梯度值是否与手动计算一致
print(net.hide1[0].weight.grad.data) # 0:线性层 1:激活函数
print(net.out[0].weight.grad.data) # 0:线性层 1:激活函数
#更新梯度
optimizer.step()
# 打印更新后的网络参数
# print(net.state_dict())
train()