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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM