6. pytorch损失函数和优化器

损失函数,又叫目标函数,用于计算真实值和预测值之间差异的函数,和优化器是编译一个神经网络模型的重要要素。本篇文章主要对 pytorch 中的 损失函数和优化器进行讲解。

1. 损失函数

     损失函数简介

  神经网络进行前向传播阶段,依次调用每个Layer的Forward函数,得到逐层的输出,最后一层与目标数值比较得到损失函数,计算误差更新值,通过反向传播逐层到达第一层,所有权值在反向传播结束时一起更新。
  也就是说损失函数主要有两大作用,

  1. 计算实际输出和目标输出的差距;
  2. 为我们的更新输出提供一定的依据;

下面我们通过 查阅 pytorch 的官方文档来介绍一下 pytorch 中的损失函数:

     pytorch 中的损失函数

  进入torch.nn 的官方帮助文档,找到损失函数 Loss function 这一项。我们主要介绍 损失函数中的 nn.L1Loss、nn.MSELoss、nn.CrossEntropyLoss这三种。


     nn.L1Loss

查看官方帮助文档:



设置三个 reduction 参数进行使用:

import torch
import torchvision
import torch.nn as nn

# 这个 torch 的类型一定要设置,可以是浮点数、也可以是虚数
input = torch.tensor([1, 2, 3], dtype=torch.float32)
target = torch.tensor([1, 4, 9], dtype=torch.float32)

loss_function_none = nn.L1Loss(reduction="none")
loss_function_mean = nn.L1Loss(reduction="mean")
loss_function_sum = nn.L1Loss(reduction="sum")
loss_none = loss_function_none(input, target)
loss_mean = loss_function_mean(input, target)
loss_sum = loss_function_sum(input, target)

print("\nloss_none\n", loss_none)
print("\nloss_mean\n", loss_mean)
print("\nloss_sum\n", loss_sum)

结果也是非常的好理解,如下所示:


     nn.MSELoss

  查看MSELoss 官方文档



代码演示:

import torch
import torchvision
import torch.nn as nn

# 这个 torch 的类型一定要设置,可以是浮点数、也可以是虚数
input = torch.tensor([1, 2, 3], dtype=torch.float32)
target = torch.tensor([1, 4, 9], dtype=torch.float32)

loss_function_none = nn.MSELoss(reduction="none")
loss_function_mean = nn.MSELoss(reduction="mean")
loss_function_sum = nn.MSELoss(reduction="sum")
loss_none = loss_function_none(input, target)
loss_mean = loss_function_mean(input, target)
loss_sum = loss_function_sum(input, target)

print("\nloss_none\n", loss_none)
print("\nloss_mean\n", loss_mean)
print("\nloss_sum\n", loss_sum)


     CrossEntropyLoss

  CrossEntropyLoss,翻译过来就是交叉熵损失。首先,我们来介绍一下什么是交叉熵。


给定输入向量 x,x[i] 表明类别是 i 的可能性强度,那么 对于输入 x,他对于类别 class 的交叉熵如上图所示,
经过化简之后为:

\[-x[class] + log(\sum_j exp(x[j])), 0\leq j <n \]

其中n表示为种类的数量,class 为某一个特定的类别。
不难得知 \(loss(x, class)\) 肯定是大于 0 的,而且我们可以分析一下这个函数的合法性:

  • 倘若 x[class] 比重越大,那么损失函数计算的数值将越接近于0,也就说是他越为合理
  • 倘若其他 j 比重越大, 即 x[j] 越大,损失函数计算数值 也会变大,说明也比较合理
    也就是说,该损失函数,可以将提高识别为目标类的概率,降低识别为其他类的概率。

下面,我们来看一下官方的帮助文档(pytorch的该帮助文档有些恐怖!!!):
有能力的看一下官方文档,我翻译的可能不是很准确,但也是尽力的标注和翻译了。



因为二分类交叉熵可以用作分类问题的求解,我们将其用到 我们 CIFAR10 写过的网络中去。

import torch
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter


images_path = "../../data_cifar10"
logs_path = "../../logs"
dataset = torchvision.datasets.CIFAR10(root=images_path, train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64, drop_last=False)


class MyModel(torch.nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.model = torch.nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2, padding=0),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2, padding=0),
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2, padding=0),
            nn.Flatten(start_dim=1, end_dim=-1), # 将它展平之后, dimension0 是 batch,后面的特征需要改变
            nn.Linear(in_features=1024, out_features=64),
            nn.Linear(in_features=64, out_features=10)
        )

    def forward(self, x):
        return self.model(x)


my_model = MyModel()
cross_entropy_loss_func = nn.CrossEntropyLoss()


# 这是一个简单的测试
# 一定要注意我们 output 和 target 的 shape 情况
output = torch.tensor([0.1, 0.2, 0.3])
target = torch.tensor([1])
output = torch.reshape(output, (1, 3))
print(cross_entropy_loss_func(output, target))



for images, targets in dataloader:
    images_proceed = my_model(images)
    cross_entropy_loss = cross_entropy_loss_func(images_proceed, targets)
    cross_entropy_loss.backward()
    print("this is the cross entropy loss")

在 cross_entropy_loss.backward() 后面打入断点,可以看到 backward 的效果:
查看 my_model -> model -> Protected Attributes -> _modules -> 任选其中一个,这里我们选择 0,
查看他的 bias 和 weight ,下面的 grad 梯度属性都是 None



  断点接着往下执行,一走到 cross_entropy_loss.backward()后面,可以看到


数值有所更新,backward 起到了相应的效果。

2. 优化器

     优化器的简介和文档

  我们知道,神经网络的学习的目的就是寻找合适的参数,使得损失函数的值尽可能小。解决这个问题的过程为称为最优化。解决这个问题使用的算法叫做优化器

  还是老规矩,我们首先查看 pytorch 优化器的官网

帮助文档:TORCH.OPTIM

相比于之前 torch.nn 中的帮助文档,torch.optim 显得更为工程化,直接就是教你使用 优化器的步骤。


     优化其部分代码

小建议
lr: learning rate 学习速率,不可太大,也不可太小,太大的话或导致算法的不稳定,太小的话又导致学习的速度太慢。推荐,一开始的时候 ,我们采取比较大的学习速率来进行学习,学习到后面,采用比较小的学习速率来进行学习

使用优化器的步骤
1、先定义一个优化器
2、对优化器的参数进行清零 optim.zero_grad()
3、调用损失函数的 backward,反向传播,求出每一个节点的梯度情况 result_lose.backward()
4、优化器.step 对每个参数进行调优 optim.step()

SGD : 梯度下降法(stochastic gradient descent,SGD)
根据梯度进行控制输出,每一个需要调整的参数,对于神经网络来说,或者是对于卷积层来说,其中的每一个卷积核都是需要进行调整的,通过设置grad 梯度,采用反向传播,每一个节点、或者说是每一个需要更新的参数,他都要求出一个对应的梯度,然后在优化的过程中根据梯度尽心进行优化,最终将整个loss进行降低。

下面我们以 SGD 优化器为例写一个小代码,跑一下我们之前的 CIFAR10 model 搭建的网络。

首先,还是瞄一眼 SGD 优化器的文档:


torch.optim.SGD 优化器加入到CIFAR10代码中

import torch
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter


images_path = "../../data_cifar10"
logs_path = "../../logs"
dataset = torchvision.datasets.CIFAR10(root=images_path, train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64, drop_last=False)


class MyModel(torch.nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.model = torch.nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2, padding=0),
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2, padding=0),
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2, padding=0),
            nn.Flatten(start_dim=1, end_dim=-1), # 将它展平之后, dimension0 是 batch,后面的特征需要改变
            nn.Linear(in_features=1024, out_features=64),
            nn.Linear(in_features=64, out_features=10)
        )

    def forward(self, x):
        return self.model(x)


my_model = MyModel()
cross_entropy_loss_func = nn.CrossEntropyLoss()
optim_SGD = torch.optim.SGD(my_model.parameters(), lr=0.02)
for epoch in range(100):
    sum_loss = 0
    for images, targets in dataloader:
        images_proceed = my_model(images)
        cross_entropy_loss = cross_entropy_loss_func(images_proceed, targets)
        optim_SGD.zero_grad()   # 梯度清空
        cross_entropy_loss.backward()
        optim_SGD.step() # 前进
        # print("this is the cross entropy loss")
        sum_loss += cross_entropy_loss
    print(f"epoch = {epoch}, sum_loss = {sum_loss}")

运行结果:


下面,让我们打一个断点查看一下优化器的具体优化内容,主要是参看 `weight`` 也就是我们卷积核的变化:
从下面的两个图片可以看到确实是发生了变化!



接着查看 optim_SGD.zero_grad() 作用,请看函数执行前 module grad 参数的数值



参考文献

参考博客:Pytorch 的损失函数Loss function

Author:luckylight(xyg)
Date:2021/11/12
posted @ 2021-11-12 23:00  lucky_light  阅读(1238)  评论(0编辑  收藏  举报