目录
1.损失函数
1.1 线性回归损失函数
1.1.1 MAE损失
MAE(Mean Absolute Error,平均绝对误差)通常也被称为 L1-Loss,通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。。
公式:
其中:
n 是样本的总数。
y_i 是第 i 个样本的真实值。
\hat{y}_i 是第 i 个样本的预测值。
\left| y_i - \hat{y}_i \right| 是真实值和预测值之间的绝对误差。
特点:
鲁棒性:与均方误差(MSE)相比,MAE对异常值(outliers)更为鲁棒,因为它不会像MSE那样对较大误差平方敏感。
物理意义直观:MAE以与原始数据相同的单位度量误差,使其易于解释。
应用场景:
MAE通常用于需要对误差进行线性度量的情况,尤其是当数据中可能存在异常值时,MAE可以避免对异常值的过度惩罚。
import torch
import torch.nn as nn
y_true = torch.tensor([1.0, 2.0, 3.0])
y_pred = torch.tensor([4.0, 5.0, 6.0])
mae = nn.L1Loss()
loss = mae(y_true,y_pred)
print(loss)
1.1.2 MSE损失
均方差损失,也叫L2Loss
MSE(Mean Squared Error,均方误差)通过对预测值和真实值之间的误差平方取平均值,来衡量预测值与真实值之间的差异。
公式:
其中:
n 是样本的总数。
y_i 是第 i 个样本的真实值。
\hat{y}_i 是第 i 个样本的预测值。
\left( y_i - \hat{y}_i \right)^2 是真实值和预测值之间的误差平方。
特点:
平方惩罚:因为误差平方,MSE 对较大误差施加更大惩罚,所以 MSE 对异常值更为敏感。
凸性:MSE 是一个凸函数,这意味着它具有一个唯一的全局最小值,有助于优化问题的求解。
应用场景:
MSE被广泛应用在神经网络中。
y_true = torch.tensor([1.0, 4.0, 5.0])
y_pred = torch.tensor([6.0, 3.0, 2.0])
mae = nn.MSELoss()
loss = mae(y_true,y_pred)
print(loss)
1.1.3 SmoothL1Loss
SmoothL1Loss可以做到在损失较小时表现为 L2 损失,而在损失较大时表现为 L1 损失。
公式:
其中,x 表示预测值和真实值之间的误差,即x = y_i - \hat{y}_i。
所有样本的平均损失为:
特点:
平滑过渡:当误差较小时,损失函数表现为 L2 Loss(平方惩罚);当误差较大时,损失函数逐渐向 L1 Loss过渡。这种平滑过渡既能对大误差有所控制,又不会对异常值过度敏感。
稳健性:对于异常值更加稳健,同时在小误差范围内提供了较好的优化效果。
应用场景:
常用于需要对大误差进行一定控制但又不希望完全忽略小误差的回归任务。特别适用于目标检测任务中的边界框回归,如 Faster R-CNN 等算法中。
predictions = torch.tensor([1.0, 2.0, 3.0, 4.0])
targets = torch.tensor([3.0, 2.5, 3.5, 4.5])
# 方式一:
smooth_loss = nn.SmoothL1Loss()
loss = smooth_loss(predictions,targets)
print(loss)
# 方式二:
loss1 = nn.functional.smooth_l1_loss(predictions,targets)
print(loss1)
1.2 CrossEntropyLoss
交叉熵损失函数,使用在输出层使用softmax激活函数进行多分类时,一般都采用交叉熵损失函数。
对于多分类问题,CrossEntropyLoss 公式如下:
其中:
C 是类别的总数。
y 是真实标签的one-hot编码向量,表示真实类别。
\hat{y} 是模型的输出(经过 softmax 后的概率分布)。
y_i 是真实类别的第 i 个元素(0 或 1)。
\hat{y}_i 是预测的类别概率分布中对应类别 i 的概率。
特点:
Softmax 就是将网络输出的 logits 通过 softmax 函数,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们将它理解成概率,选取概率最大(也就是值对应最大的)节点,作为我们的预测目标类别。
# 假设有三个类别,模型输出是未经softmax的logits
logits = torch.tensor([[1.5, 2.0, 0.5], [0.5, 1.0, 1.5]])
labels = torch.tensor([1,2])
criterion = nn.CrossEntropyLoss()
loss = criterion(logits,labels)
print(loss)
1.3 BCELoss
二分类交叉熵损失函数,使用在输出层使用sigmoid激活函数进行二分类时。
公式:
log的底数一般默认为e,y是真实类别目标,根据公式可知L是一个分段函数 :
以上损失函数是一个样本的损失值,总样本的损失值是求损失均值即可。
# y 是模型的输出,已经被sigmoid处理过,确保其值域在(0,1)
y = torch.tensor([[0.7], [0.2], [0.9], [0.7]],dtype=torch.float32)
# targets 是真实的标签,0或1
t = torch.tensor([[1], [0], [1], [0]],dtype=torch.float32)
# 计算损失方式一:
bceloss = nn.BCELoss()
loss = bceloss(y,t)
print(loss)
#计算损失方式二:
loss2 = nn.functional.binary_cross_entropy(y,t)
print(loss2)
1.4. 总结
(1)当输出层使用softmax多分类时,使用交叉熵损失函数;
(2)当输出层使用sigmoid二分类时,使用二分类交叉熵损失函数, 比如在逻辑回归中使用;
(3)当功能为线性回归时,使用smooth L1损失函数或均方差损失-L2 loss;
2.BP算法
多层神经网络的学习能力比单层网络强得多。想要训练多层网络,需要更强大的学习算法。误差反向传播算法(Back Propagation)是其中最杰出的代表,它是目前最成功的神经网络学习算法。BP 算法不仅可用于多层前馈神经网络,还可以用于其他类型的神经网络。通常说 BP 网络时,一般是指用 BP 算法训练的多层前馈神经网络。
误差反向传播算法(BP)的基本步骤:
(1)前向传播:正向计算得到预测值。
(2)计算损失:通过损失函数计算预测值和真实值的差距。
(3)梯度计算:反向传播的核心是计算损失函数 L 对每个权重和偏置的梯度。
(4)更新参数:一旦得到每层梯度,就可以使用梯度下降算法来更新每层的权重和偏置,使得损失逐渐减小。
(5)迭代训练:将前向传播、梯度计算、参数更新的步骤重复多次,直到损失函数收敛或达到预定的停止条件。
2.1 前向传播
前向传播(Forward Propagation)把输入数据经过各层神经元的运算并逐层向前传输,一直到输出层为止。
作用:
计算神经网络的输出结果,用于预测或计算损失。
在反向传播中使用,通过计算损失函数相对于每个参数的梯度来优化网络。
2.2 反向传播
反向传播(Back Propagation,简称BP)通过计算损失函数相对于每个参数的梯度来调整权重,使模型在训练数据上的表现逐渐优化。反向传播结合了链式求导法则和梯度下降算法,是神经网络模型训练过程中更新参数的关键步骤。
2.2.1 原理
利用链式求导法则对每一层进行求导,直到求出输入层x的导数,然后利用导数值进行梯度更新
2.2.2. 链式法则
链式求导法则(Chain Rule)是微积分中的一个重要法则,用于求复合函数的导数。在深度学习中,链式法则是反向传播算法的基础,这样就可以通过分层的计算求得损失函数相对于每个参数的梯度。以下面的复合函数为例:
其中 x 是输入数据,w 是权重,b 是偏置。
函数分解:
可以将该复合函数分解为:
链式求导:
x = torch.tensor(1.0)
w = torch.tensor(0.0,requires_grad =True)
b = torch.tensor(0.0,requires_grad =True)
# 函数
y = (torch.exp(-(w*x+b)+1))**-1
# 自动微分
y.backward()
print(w.grad)
2.4 重要性
反向传播算法极大地提高了多层神经网络训练的效率,使得训练深度模型成为可能。通过链式法则逐层计算梯度,反向传播可以有效地处理复杂的网络结构,确保每一层的参数都能得到合理的调整。
2.5 案例
2.5.1 数据准备
整体网络结构及神经元数据和权重参数如下图所示:
2.5.2 神经元计算
2.5.3 损失计算
2.5.4 梯度计算
2.5.5 参数更新
假设学习率是0.5
方式1:
import math
# 前向传播
i1 = 0.05
i2 = 0.10
b1 = 0.35
w1 = 0.15
w2 = 0.20
w3 = 0.25
w4 = 0.30
w5 = 0.40
w6 = 0.45
w7 = 0.50
w8 = 0.55
# 神经元h1
def h1():
# 线性
l1_1 = i1 * w1 + i2 * w2 + b1
# 激活函数
return 1 / (1 + math.exp(-l1_1))
# 神经元h2
def h2():
# 线性
l1_2 = i1 * w3 + i2 * w4 + b1
# 激活函数
return 1 / (1 + math.exp(-l1_2))
b2 = 0.60
# 神经元o1
def o1(h1_val, h2_val):
# 线性
l2_1 = h1_val * w5 + h2_val * w6 + b2
# 激活函数
return 1 / (1 + math.exp(-l2_1))
# 神经元o2
def o2(h1_val, h2_val):
# 线性
l2_2 = h1_val * w7 + h2_val * w8 + b2
# 激活函数
return 1 / (1 + math.exp(-l2_2))
# 计算前向传播的结果
h1_val = h1()
h2_val = h2()
o1_val = o1(h1_val, h2_val)
o2_val = o2(h1_val, h2_val)
print('h1:', h1_val)
print('h2:', h2_val)
print('o1:', o1_val)
print('o2:', o2_val)
# 计算MSE损失
def mse(o1_val, o2_val):
return 0.5 * ((o1_val - 0.01) ** 2 + (o2_val - 0.99) ** 2)
print('MSE Loss:', mse(o1_val, o2_val))
# 求w5的梯度 = mse()对o1求导 * o1对l2_1求导 * l2_1对w5求导
# dw5 = (o1 - 0.01) * [o1 * (1 - o1)] * h1
dw5 = (o1_val - 0.01) * (o1_val * (1.0 - o1_val)) * h1_val
print('dw5:', dw5)
# 求w7的梯度 = mse()对o2求导 * o2对l2_2求导 * l2_2对w7求导
# dw7 = (o2 - 0.99) * [o2 * (1 - o2)] * h1
dw7 = (o2_val - 0.99) * (o2_val * (1.0 - o2_val)) * h1_val
print('dw7:', dw7)
# 求w1的梯度 = mse()对o1求导 * o1对l2_1求导 * l2_1对h1求导 * h1对l1_1求导 *l1_1对w1求导 + mse()对o2求导 * o2对l2_2求导 * l2_2对h1求导 * h1对l1_1求导 *l1_1对w1求导
# dw1 = (o1() - 0.01) * [o1() * (1 - o1())] * w5 * [h1() * (1 - h1())] * i1 + (o2 - 0.99) * [o2 * (1 - o2)] *w7 * [h1() * (1 - h1())] * i1
dw1 = (o1_val - 0.01) * (o1_val * (1.0 - o1_val)) * w5 * h1_val*(1.0-h1_val) * 0.05 + (o2_val - 0.99) * (o2_val * (1.0 - o2_val)) * w7 *h1_val*(1.0-h1_val) * 0.05
print('dw1:', dw1)
方式2:
# 前向传播
i1_i2 = torch.tensor([0.05,0.10])
model1 = nn.Linear(2,2)
model1.weight.data = torch.tensor([[0.15,0.20],[0.25,0.30]])
model1.bias.data = torch.tensor([0.35])
l11_l12 = model1(i1_i2)
h1_h2 =torch.sigmoid(l11_l12)
model2 = nn.Linear(2,2)
model2.weight.data = torch.tensor([[0.40,0.45],[0.50,0.55]])
model2.bias.data = torch.tensor([0.60])
l21_l22 = model2(h1_h2)
o1_o2 = torch.sigmoid(l21_l22)
# 反向传播
true_o1_o2 = torch.tensor([0.01,0.99])
mse = nn.MSELoss()
loss = mse(o1_o2,true_o1_o2)
print(loss)
loss.backward()
print(model1.weight.grad)
print(model2.weight.grad)
方式3:
import torch
import torch.nn as nn
import torch.optim as optim
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.linear1 = nn.Linear(2,2)
self.linear2 = nn.Linear(2,2)
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], dtype=torch.float32)
self.linear2.bias.data = torch.tensor([0.60], dtype=torch.float32)
def forward(self,x):
x = self.linear1(x)
x = torch.sigmoid(x)
x = self.linear2(x)
x = torch.sigmoid(x)
return x
input = torch.tensor([[0.05, 0.10]])
target = torch.tensor([[0.01, 0.99]])
model = Net()
output = model(input)
print(output)
mse = torch.sum((output-target)**2)/2
print(mse)
optimizer = optim.SGD(model.parameters(), lr=0.5)
# 梯度清零
optimizer.zero_grad()
mse .backward()
print(model.linear1.weight.grad)
print(model.linear2.weight.grad)
#更新梯度
optimizer.step()
# 打印更新后的网络参数
print(model.state_dict())