08 - 损失函数&反向传播

概念

损失函数

损失函数:在深度学习模型中,损失函数(Loss Function)是一种衡量模型预测与真实值之间差距的函数。换句话说,它是模型的性能指标。在训练过程中,我们的目标就是找到一组参数,它们可以最小化损失函数。常见的损失函数有均方误差(MSE, Mean Squared Error),交叉熵(Cross Entropy)等。

我之前学的人工智能原理中的方差代价函数也属于这里的损失函数。

  1. 均方误差(MSE):通常用于回归问题,它计算的是模型预测值和真实值的平均平方差。
  2. 交叉熵(Cross Entropy):常用于处理二分类或多分类问题。在分类问题中,模型的预测通常是各个类别的可能性,交叉熵能够有效度量模型预测的可能性分布与真实分布之间的差异。

反向传播

反向传播(Backpropagation):反向传播是深度学习中的一种核心算法,用于计算损失函数对模型每一个参数的梯度。在优化模型参数(例如使用梯度下降算法)的过程中,我们需要知道模型每一项参数的梯度方向,以此来更新参数。

反向传播算法工作流程如下:

  1. 前向传播(Forward Pass):数据沿着从输入层至输出层的方向进行传播,经过每一层的操作,最终在输出层产生预测值。
  2. 损失计算:根据模型的预测值和真实值,使用损失函数计算损失。
  3. 反向传播(Backward Pass):根据链式法则(Chain Rule),从输出层开始,沿着网络的结构向反方向(输入层方向)回传,计算损失函数对每一层参数的梯度。

反向传播能够有效地计算损失函数关于每一个参数的梯度,结合优化算法(如SGD,Adam等),这使得我们可以通过梯度下降法更新模型的参数,进而训练我们的模型。(上面说的训练的目标就是找到一组参数可以最小化损失函数,这里更新参数就是为了这个目的)

示例

L1Loss

预测值和目标值之间的绝对差值的平均数,reduction='sum’时为绝对差值的和。

nn.L1Loss是一种在PyTorch库中实现的损失函数,它计算的是预测值和目标值之间的绝对差值的平均数,或者叫做L1范数。在数学上,给定两个n维向量x和y,它们之间的L1范数定义为每个维度上差的绝对值的和:

L1(x, y) = 1/n * Σ|xi - yi|

当我们在深度学习模型中使用L1Loss时,上述公式会在每一个数据点(即每一个xi和yi)上进行计算,然后将所有数据点上的结果求平均,从而得到最终的损失值。

此损失函数在回归问题中使用较多,尤其是当你关心预测值与目标值之间的实值差距(而非平方差)时,L1损失能非常有效。此外,与L2损失相比,L1损失对于异常值(outliers)的敏感性较低。

import torch
from torch.nn import L1Loss

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

# loss = L1Loss(reduction='sum')
loss = L1Loss()
res = loss(inputs, targets)
print(res)

img

MSELoss

均方误差,对于两个向量(比如预测标签和真实标签向量),MSELoss 计算的是这两个向量之间的均方误差。每一个元素误差的平方和然后再平均。数学上的表达如下:

假设 y 是真实值,f(x) 是预测值,则 MSE = 1/n * Σ(yi - f(xi))^2。

在这个表达式中,

  • yi 表示真实向量中的元素,
  • f(xi) 表示预测向量中的元素,
  • Σ 表示对向量中所有元素求和,
  • n 是元素的总数。
import torch
from torch.nn import L1Loss, MSELoss

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

# loss = L1Loss(reduction='sum')
loss = L1Loss()
res = loss(inputs, targets)

loss_mse = MSELoss()
res_mse = loss_mse(inputs, targets)

print(res)
print(res_mse)

img

CrossEntropyLoss

nn.CrossEntropyLoss是在PyTorch库中实现的一种损失函数,它被广泛用于处理多分类问题。具体来说,它的工作原理是对网络的输出先进行softmax操作,然后计算这个softmax概率分布与真实标签之间的交叉熵。

  • Softmax操作:将每个类别的输出转换成概率值,所有类别概率和为1。这样,每个类别的输出值都介于0-1之间,且所有类别的概率和为1。
  • 交叉熵(Cross Entropy): 是一个衡量两个概率分布之间差异的量。在分类问题中,真实的标签通常使用one-hot编码表示,比如对于三分类问题,类别1, 2, 3可能分别被表示为[1, 0, 0],[0, 1, 0],[0, 0, 1]。然后我们计算的是模型预测的概率分布和真实概率分布之间的交叉熵。

img

import torch
from torch.nn import L1Loss, MSELoss, CrossEntropyLoss

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

# loss = L1Loss(reduction='sum')
loss = L1Loss()
res = loss(inputs, targets)

loss_mse = MSELoss()
res_mse = loss_mse(inputs, targets)

x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
x = torch.reshape(x, (1, 3))
loss_cross = CrossEntropyLoss()
res_cross = loss_cross(x, y)
print(res)
print(res_mse)
print(res_cross)

img

反向传播

import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10(root="./dataset", train=True, download=True,
                                       transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=1)


class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


loss = nn.CrossEntropyLoss()
net = MyNet()
for data in dataloader:
    imgs, targets = data
    outputs = net(imgs)
    res_loss = loss(outputs, targets)
    res_loss.backward()
    print("ok")
res_loss.backward()的深层实现,仅作扩展
  1. 计算梯度:backward()方法首先计算 res_loss 的梯度。这是通过遍历从res_loss 到输入变量的计算图并应用链式规则完成的。计算图是许多函数(即PyTorch操作)的有向无环图,这些函数创建当前的Tensor变量。
  2. 累计梯度:然后,它将这些计算的梯度累积到各个张量的 .grad 属性中。累积是必要的,因为在许多情况下,如RNN之类的网络结构,一个张量可能会被计算图中的多个路径访问,因此其.grad 携带的是对整个计算图的梯度贡献的总和。
  3. 处理非叶节点:注意,PyTorch默认只保存并计算叶节点(即直接用户创建和需要梯度的节点)的梯度,而非叶节点(即通过某些操作从其他张量获取的张量)的 .grad 属性通常为None。这是为了节省内存,因为保存整个计算图的梯度非常昂贵。然而,如果您需要非叶节点的梯度,可以通过使用 retain_graph=True backward() 中进行设置。
  4. 删除计算图:除非设置 retain_graph = True,否则backward函数会默认删除计算图以释放内存。在这种情况下,如果您想要进行另一次反向传播操作,您需要重新进行前向传播,因为计算图已经不存在了。

神经网络的运行流程

  1. 初始化网络权重:首先,我们需要初始化神经网络的权重(连接不同神经元的参数)。这些权重通常是从随机分布(如正态分布或均匀分布)中抽取的。偏置通常初始化为零或很小的值。
  2. 输入和前向传播:在每次训练迭代中,我们将一批训练数据输入至网络的输入层,并通过所有隐藏层至输出层。隐藏层和输出层的每一层都包含一个线性变换和一个非线性激活函数。在每一层中,我们先根据当前层的权重和偏置对输入进行线性变换,然后将结果输入至非线性激活函数以得到该层的输出(也称为激活)。
  3. 计算损失:一旦数据通过了网络并得到了输出,我们可以对比网络的预测和实际的标签来计算一个损失值。这个损失值对于回归任务通常是均方误差(MSE),对于分类任务则常用交叉熵损失。损失值越小,表明模型的预测越接近实际标签,模型的性能越好。
  4. 反向传播和更新权重:在计算了损失后,我们需要计算损失关于每个权重和偏置的梯度,这就是反向传播过程。反向传播算法根据链式法则,从输出层回到输入层,计算并存储每个参数的梯度。然后使用这些梯度和一个步长(也称为学习率)来更新每一个权重和偏置。
  5. 重复迭代:以上的过程会持续进行,每一次都使用新的一批数据,直到达到了预设的迭代次数,或者模型的性能满足设定的标准为止。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐