PyTorch基础教程12图像多分类问题(学不会来打我啊)

Posted 山顶夕景

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyTorch基础教程12图像多分类问题(学不会来打我啊)相关的知识,希望对你有一定的参考价值。

学习总结

(1)本次图像多分类中的最后一层网络不需要加激活,因为在最后的Torch.nn.CrossEntropyLoss已经包括了激活函数softmax。
(2)本次pytorch在读图像时,将PIL图像(现在都用Pillow了)转为Tensor,神经网络一般希望input比较小,最好在-1到1之间,最好符合正态分布。三种主流图像处理库的比较:

函数/方法返回值图像像素格式像素值范围图像矩阵表示
skimageio.imread(xxx)numpy.ndarrayRGB[0, 255](H X W X C)
cv2cv2.imread(xxx)numpy.ndarrayBGR[0, 255](H X W X C)
Pillow(PIL)Image.open(xxx)PIL.Image.Image对象根据图像格式,一般为RGB[0, 255]

一、多分类问题

注意验证/测试的流程基本与训练过程大体一致,不同点在于:

  • 需要预先设置torch.no_grad,以及将model调至eval模式
  • 不需要将优化器的梯度置零
  • 不需要将loss反向回传到网络
  • 不需要更新optimizer

二、分布和API


法一:把每一个类别的确定看作是一个二分类问题。利用交叉熵。

为了解决抑制问题,就不要输出每个类别的概率,且满足每个概率大于0和概率之和为1的条件。(二分类我们输出的是分布,求出一个然后用1减去即可,多分类虽然也可以这样,但是最后1减去其他所有概率的计算,还需要构建计算图有点麻烦)。
之前二分类中的交叉熵的两项中只能有一项为0.


(1)NLLLoss函数计算如下红色框:

(2)可以直接使用torch.nn.CrossEntropyLoss(将下列红框计算纳入)。注意右侧是由类别生成独热编码向量。

交叉熵,最后一层网络不需要激活,因为在最后的Torch.nn.CrossEntropyLoss已经包括了激活函数softmax。
(1)交叉熵手写版本

import numpy as np
y = np.array([1, 0, 0])
z = np.array([0.2, 0.1, -0.1])
y_predict = np.exp(z) / np.exp(z).sum()
loss = (- y * np.log(y_predict)).sum()
print(loss)
# 0.9729189131256584

(2)交叉熵pytorch栗子

交叉熵损失和NLL损失的区别(读文档):

  • https://pytorch.org/doc s/stable/nn.html#crossentropyloss
  • https://pytorch.org/docs/stable/nn.html#nllloss
  • 搞懂为啥:CrossEntropyLoss <==> LogSoftmax + NLLLoss

三、交叉熵代码实践

前面是sigmoid,后面是softmax(使得概率大于0,概率值和为1)。 两点注意。

# -*- coding: utf-8 -*-
"""
Created on Mon Oct 18 22:48:55 2021

@author: 86493
"""
# 用CrossEntropyLoss计算交叉熵
import torch 
# 注意1:设定的第0个分类
y = torch.LongTensor([0])
z = torch.Tensor([[0.2, 0.1, -0.1]])
# 注意2:CrossEntropyLoss
criterion = torch.nn.CrossEntropyLoss()
loss = criterion(z, y)
print(loss.item())
# 0.9729189276695251

# 举栗子:mini-batch:batch_size = 3
import torch
criterion = torch.nn.CrossEntropyLoss()
# 三个样本
Y = torch.LongTensor([2, 0, 1]) 

# 第一个样本比较吻合,loss会较小
Y_pred1 = torch.Tensor([[0.1, 0.2, 0.9], # 2 该层为原始的线性层的输出
                        [1.1, 0.1, 0.2], # 0
                        [0.2, 2.1, 0.1]])# 1
# 第二个样本差的比较远,loss较大
Y_pred2 = torch.Tensor([[0.8, 0.2, 0.3], # 2 
                        [0.2, 0.3, 0.5], # 0 
                        [0.2, 0.2, 0.5]])# 1

l1 = criterion(Y_pred1, Y)
l2 = criterion(Y_pred2, Y)
print("Batch Loss1 = ", l1.data,
      "\\nBatch Loss2 =", l2.data)
# Batch Loss1 =  tensor(0.4966) 
# Batch Loss2 = tensor(1.2389)

四、代码实践

(1)pytorch在读图像时,将PIL图像(现在都用Pillow了)转为Tensor,神经网络希望input比较小,最好在-1到1之间,最好符合正态分布。

三种主流图像处理库的比较:

函数/方法返回值图像像素格式像素值范围图像矩阵表示
skimageio.imread(xxx)numpy.ndarrayRGB[0, 255](H X W X C)
cv2cv2.imread(xxx)numpy.ndarrayBGR[0, 255](H X W X C)
Pillow(PIL)Image.open(xxx)PIL.Image.Image对象根据图像格式,一般为RGB[0, 255]


(2)transforms.Normalize,转为标准正态分布即是一种映射到(0, 1)函数。  Pixel  norm  =  Pixel  origin  −  mean   std  \\text { Pixel }_{\\text {norm }}=\\frac{\\text { Pixel }_{\\text {origin }}-\\text { mean }}{\\text { std }}  Pixel norm = std  Pixel origin  mean 
(3)激活层改用relu,最后一层不用加,因为交叉熵损失函数里面已经包括了。
(4)_, predicted = torch.max(outputs.data, dim = 1)求出每一行(样本)的最大值的下标,dim = 1即行的维度;返回最大值和最大值所在的下标。

# -*- coding: utf-8 -*-
"""
Created on Mon Oct 18 19:25:46 2021

@author: 86493
"""
import torch
from torchvision import transforms 
from torchvision import datasets
from torch.utils.data import DataLoader 
# 使用relu激活函数
import torch.nn.functional as F
import torch.optim as optim 
import torch.nn as nn
import matplotlib.pyplot as plt

losslst = []
batch_size = 64 
transform = transforms.Compose([
    # 将PIL图片转为Tensor
    transforms.ToTensor(),
    # 归一化,分别为均值和标准差
    transforms.Normalize((0.1307, ), 
                         (0.3081, ))
])

# 训练集数据
train_dataset = datasets.MNIST(root = '../dataset/mnist/',
                               train = True,
                               download = True,
                               transform = transform)
train_loader = DataLoader(train_dataset,
                          shuffle = True,
                          batch_size = batch_size)

# 测试集数据
test_dataset = datasets.MNIST(root = '../dataset/mnist/',
                              train = False,
                              download = True,
                              transform = transform)

test_loader = DataLoader(test_dataset,
                         shuffle = False,
                         batch_size = batch_size)


# 模型
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.l1 = nn.Linear(784, 512)
        self.l2 = nn.Linear(512, 256)
        self.l3 = nn.Linear(256, 128)
        self.l4 = nn.Linear(128, 64)
        self.l5 = nn.Linear(64, 10)
        
    def forward(self, x):
        # -1位置会自动算出N,即变成(N, 784)矩阵
        x = x.view(-1, 784)
        x = F.relu(self.l1(x))
        x = F.relu(self.l2(x))
        x = F.relu(self.l3(x))
        x = F.relu(self.l4(x))
        # 注意最后一层不用加上relu,因为交叉熵已经含有softmax
        return self.l5(x)
    
model = Net()
# 交叉熵作为loss
criterion = torch.nn.CrossEntropyLoss()
# 优化器SGD加上冲量以优化训练过程
optimizer = optim.SGD(model.parameters(),
                      lr = 0.01,
                      momentum = 0.5)

def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        # 1.准备数据
        inputs, labels = data  # data为元组
        # 2.向前传递
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        losslst.append(loss)
        # print(losslst,"+++++++++++++")
        # 3.反向传播
        optimizer.zero_grad()
        loss.backward()
        # 4.更新参数
        optimizer.step()
        # 记得取loss值用item(),否则会构建计算图  
        running_loss += loss.item()
        losslst.append(loss.item())
        
        # 每300个batch打印一次
        if batch_idx % 300 == 299:
            print('[%d, %5d] loss: %.3f' % 
                  (epoch + 1, 
                   batch_idx + 1,
                   running_loss / 300))
            running_loss = 0.0
    

def test():
    correct = 0
    total = 0 
    # 加上with这句后面就不会产生计算图
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            outputs = model(images)
            # 求出每一行(样本)的最大值的下标,dim = 1即行的维度
            # 返回最大值和最大值所在的下标
            _, predicted = torch.max(outputs.data,
                                     dim = 1)
            # label矩阵为N × 1
            total += labels.size(0)
            # 猜对的数量,后面算准确率
            correct += (predicted == labels).sum().item()
    print('Accuracy on test set: %d %%' % 
          (100 * correct / total))


# print("losslst的长度为:", len(losslst))

if __name__ == '__main__':
    for epoch in range(10):
        # if epoch % 10 == 9: # 每10个输出一次
        train(epoch)
        test()

代码打印的loss值:

[1,   300] loss: 2.216
[1,   600] loss: 0.872
[1,   900] loss: 0.423
Accuracy on test set: 89 %
[2,   300] loss: 0.333
[2,   600] loss: 0.272
[2,   900] loss: 0.232
Accuracy on test set: 93 %
[3,   300] loss: 0.189
[3,   600] loss: 0.176
[3,   900] loss: 0.160
Accuracy on test set: 95 %
[4,   300] loss: 0.135
[4,   600] loss: 0.127
[4,   900] loss: 0.121
Accuracy on test set: 95 %
[5,   300] loss: 0.104
[5,   600] loss: 0.096
[5,   900] loss: 0.097
Accuracy on test set: 96 %
[6,   300] loss: 0.080
[6,   600] loss: 0.071
[6,   900] loss: 0.083
Accuracy on test set: 97 %
[7,   300] loss: 0.062
[7,   600] loss: 0.062
[7,   900] loss: 0.062
Accuracy on test set: 97 %
[8,   300] loss: 0.050
[8,   600] loss: 0.052
[8,   900] loss: 0.053
Accuracy on test set: 97 %
[9,   300] loss: 0.034
[9,   600] loss: 0.045
[9,   900] loss: 0.043
Accuracy on test set: 97 %
[10,   300] loss: 0.032
[10,   600] loss: 0.032
[10,   900] loss: 0.036
Accuracy on test set: 97 %

Reference

(1)pytorch中with torch.no_grad()
(2)https://www.bilibili.com/video/BV1Y7411d7Ys?p=9
(3)pytorch学习(五)—图像的加载/读取方式
(4)解决Python报错:RuntimeError: Can’t call numpy() on Variable that requires grad. Use var.detach().numpy()

以上是关于PyTorch基础教程12图像多分类问题(学不会来打我啊)的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学PyTorch:一文学会线性回归逻辑回归及图像分类

PyTorch基础教程1线性模型(学不会来打我啊)

PyTorch基础教程7多维特征input(学不会来打我啊)

PyTorch图像分类教程专栏目录

PyTorch图像分类教程专栏目录

PyTorch进阶之路:使用logistic回归实现图像分类