【PyTorch】学习【深入浅出PyTorch | GitHub】

发布时间:2023-12-30 19:30

开源项目:【深入浅出PyTorch | GitHub】

FashionMNIST时装分类

参考【GitHub-第四章 基础实战——FashionMNIST时装分类】

经过前面三章内容的学习,我们完成了以下的内容:

  • 对PyTorch有了初步的认识
  • 学会了如何安装PyTorch以及对应的编程环境
  • 学习了PyTorch最核心的理论基础(张量&自动求导)
  • 梳理了利用PyTorch完成深度学习的主要步骤和对应实现方式

现在,我们通过一个基础实战案例,将第一部分所涉及的PyTorch入门知识串起来,便于大家加深理解。同时为后续的进阶学习打好基础。

我们这里的任务是对10个类别的“时装”图像进行分类,使用FashionMNIST数据集(https://github.com/zalandoresearch/fashion-mnist/tree/master/data/fashion )。上图给出了FashionMNIST中数据的若干样例图,其中每个小图对应一个样本。
FashionMNIST数据集中包含已经预先划分好的训练集和测试集,其中训练集共60,000张图像,测试集共10,000张图像。每张图像均为单通道黑白图像,大小为32*32pixel,分属10个类别。

下面让我们一起将第三章各部分内容逐步实现,来跑完整个深度学习流程。

首先导入必要的包

import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

配置训练环境和超参数

# 配置GPU,这里有两种方式
## 方案一:使用os.environ
# os.environ['CUDA_VISIBLE_DEVICES'] = '0'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")

## 配置其他超参数,如batch_size, num_workers, learning rate, 以及总的epochs
batch_size = 256
num_workers = 4   # 对于Windows用户,这里应设置为0,否则会出现多线程错误
lr = 1e-4
epochs = 20

数据读入和加载
这里同时展示两种方式:

  • 下载并使用PyTorch提供的内置数据集
  • 从网站下载以csv格式存储的数据,读入并转成预期的格式
    第一种数据读入方式只适用于常见的数据集,如MNIST,CIFAR10等,PyTorch官方提供了数据下载。这种方式往往适用于快速测试方法(比如测试下某个idea在MNIST数据集上是否有效)
    第二种数据读入方式需要自己构建Dataset,这对于PyTorch应用于自己的工作中十分重要

同时,还需要对数据进行必要的变换,比如说需要将图片统一为一致的大小,以便后续能够输入网络训练;需要将数据格式转为Tensor类,等等。

这些变换可以很方便地借助torchvision包来完成,这是PyTorch官方用于图像处理的工具库,上面提到的使用内置数据集的方式也要用到。PyTorch的一大方便之处就在于它是一整套“生态”,有着官方和第三方各个领域的支持。这些内容我们会在后续课程中详细介绍。

# 首先设置数据变换
from torchvision import transforms

image_size = 28
data_transform = transforms.Compose([
    transforms.ToPILImage(),   # 这一步取决于后续的数据读取方式,如果使用内置数据集则不需要
    transforms.Resize(image_size),
    transforms.ToTensor()
])
## 读取方式一:使用torchvision自带数据集,下载可能需要一段时间
from torchvision import datasets

train_data = datasets.FashionMNIST(root='./', train=True, download=True, transform=data_transform)
test_data = datasets.FashionMNIST(root='./', train=False, download=True, transform=data_transform)
/data1/ljq/anaconda3/envs/smp/lib/python3.8/site-packages/torchvision/datasets/mnist.py:498: UserWarning: The given NumPy array is not writeable, and PyTorch does not support non-writeable tensors. This means you can write to the underlying (supposedly non-writeable) NumPy array using the tensor. You may want to copy the array to protect its data or make it writeable before converting it to a tensor. This type of warning will be suppressed for the rest of this program. (Triggered internally at  /opt/conda/conda-bld/pytorch_1623448234945/work/torch/csrc/utils/tensor_numpy.cpp:180.)
  return torch.from_numpy(parsed.astype(m[2], copy=False)).view(*s)

数据读入

【PyTorch】学习【深入浅出PyTorch | GitHub】_第1张图片

要记得把数据变成tensor格式

## 读取方式二:读入csv格式的数据,自行构建Dataset类
# csv数据下载链接:https://www.kaggle.com/zalando-research/fashionmnist
class FMDataset(Dataset):
    def __init__(self, df, transform=None):
        self.df = df
        self.transform = transform
        self.images = df.iloc[:,1:].values.astype(np.uint8) # uint8是图片的一种格式
        self.labels = df.iloc[:, 0].values
        
    def __len__(self):
        return len(self.images)
    
    def __getitem__(self, idx):
        image = self.images[idx].reshape(28,28,1) # 黑白图像,通道数为1
        label = int(self.labels[idx])
        if self.transform is not None:
            image = self.transform(image) # transform内部已经把数据变成tensor了
        else:
            image = torch.tensor(image/255., dtype=torch.float) # /255 归一化
        label = torch.tensor(label, dtype=torch.long)
        return image, label

# csv格式里面都是一些像素值 第一列是label
train_df = pd.read_csv("./FashionMNIST/fashion-mnist_train.csv")
test_df = pd.read_csv("./FashionMNIST/fashion-mnist_test.csv")
train_data = FMDataset(train_df, data_transform)
test_data = FMDataset(test_df, data_transform)
# train_data=FMDataset(df=train_df,transform=data_transform)
# test_data=FMDataset(df=test_df,transform=data_transform)

【PyTorch】学习【深入浅出PyTorch | GitHub】_第2张图片

在构建训练和测试数据集完成后,需要定义DataLoader类,以便在训练和测试时加载数据

train_loader=DataLoader(dataset=train_data,batch_size=batch_size,shuffle=True,num_workers=num_workers,drop_last=True)

test_loader=DataLoader(dataset=test_data,batch_size=batch_size,shuffle=True,num_workers=num_workers)

读入后,我们可以做一些数据可视化操作,主要是验证我们读入的数据是否正确

import matplotlib.pyplot as plt
image, label = next(iter(train_loader))
print(image.shape, label.shape)
plt.imshow(image[0][0], cmap="gray") # image[0][0] 表示第一张图片的第一个通道 
# 如果直接输出image[0]会报错 TypeError: Invalid shape (1, 28, 28) for image data
torch.Size([256, 1, 28, 28]) torch.Size([256])  # [一批次的样本数,通道数,宽,高]


【PyTorch】学习【深入浅出PyTorch | GitHub】_第3张图片

模型设计
由于任务较为简单,这里我们手搭一个CNN,而不考虑当下各种模型的复杂结构
模型构建完成后,将模型放到GPU上用于训练

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 32, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, stride=2),
            nn.Dropout(0.3),
            nn.Conv2d(32, 64, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, stride=2),
            nn.Dropout(0.3)
        )
        self.fc = nn.Sequential(
            nn.Linear(64*4*4, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )
        
    def forward(self, x):
        x = self.conv(x)
        x = x.view(-1, 64*4*4)
        x = self.fc(x)
        # x = nn.functional.normalize(x)
        return x

model = Net()
# model = model.cuda() # GPU
# model = nn.DataParallel(model).cuda()   # 多卡训练时的写法,之后的课程中会进一步讲解

设定损失函数
使用torch.nn模块自带的CrossEntropy损失
PyTorch会自动把整数型的label转为one-hot型,用于计算CE loss
这里需要确保label是从0开始的,同时模型不加softmax层(使用logits计算),这也说明了PyTorch训练中各个部分不是独立的,需要通盘考虑

criterion = nn.CrossEntropyLoss()
# criterion = nn.CrossEntropyLoss(weight=[1,1,1,1,3,1,1,1,1,1]) # weight惩罚项
?nn.CrossEntropyLoss # 这里方便看一下weighting等策略

设定优化器
这里我们使用Adam优化器

optimizer = optim.Adam(model.parameters(), lr=0.001)

训练和评估

【PyTorch】学习【深入浅出PyTorch | GitHub】_第4张图片

训练和测试(验证)
各自封装成函数,方便后续调用
关注两者的主要区别:

  • 模型状态设置
  • 是否需要初始化优化器
  • 是否需要将loss传回到网络
  • 是否需要每步更新optimizer

此外,对于测试或验证过程,可以计算分类准确率

def train(epoch):
    model.train() # 进入训练模式
    train_loss = 0
    for data, label in train_loader: # 这里未使用 batch
        # data, label = data.cuda(), label.cuda() # GPU
        optimizer.zero_grad() # 梯度清零
        output = model(data) # 模型输出
        loss = criterion(output, label) # 计算损失
        loss.backward() # 反向传播
        optimizer.step() # 更新参数权重
        train_loss += loss.item()*data.size(0)  # data : torch.Size([256, 1, 28, 28]) 即一批次有256个样本
        
    train_loss = train_loss/len(train_loader.dataset) # 计算平均loss
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
def val(epoch):       
    model.eval() # 进入评估模式
    val_loss = 0
    gt_labels = [] # 真实标签
    pred_labels = [] # 预测的标签
    with torch.no_grad(): # 不做梯度的计算
        for data, label in test_loader:
            # data, label = data.cuda(), label.cuda() # GPU
            output = model(data) # [256,10]
            preds = torch.argmax(output, 1) # shape:256
            
            gt_labels.append(label.cpu().data.numpy()) # .cpu()是把GPU上的数据传到cpu上 因为GPU和CPU的数据不通用 .numpy() 1=>[1]
            pred_labels.append(preds.cpu().data.numpy())
            
            loss = criterion(output, label)
            val_loss += loss.item()*data.size(0)
            
    val_loss = val_loss/len(test_loader.dataset)
    # concatenate 把多个列表拼接成一个列表
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
    # np.sum(gt_labels==pred_labels) 返回相等的个数
    acc = np.sum(gt_labels==pred_labels)/len(pred_labels)
    print('Epoch: {} \tValidation Loss: {:.6f}, Accuracy: {:6f}'.format(epoch, val_loss, acc))

【参考:torch.argmax中dim详解_zouxiaolv的博客-CSDN博客】
【参考:关于torch.argmax的在深度学习中应用的理解_LionZYT的博客-CSDN博客】

torch.argmax(output, 1) 去第二维度的最大值(即取每列的最大值),并把该维度去掉

print(output)
tensor([[-1.3925, -3.5659,  4.4531,  ..., -9.2642, -2.6278, -7.5197],
        [ 6.2580, -2.8332,  0.5089,  ..., -8.0440, -2.2303, -7.6651],
        [-1.7071, -7.6177, -4.6044,  ...,  8.7656, -2.0922,  1.7447],
        ...,
        [ 3.7714, -1.8730, -0.5047,  ..., -7.4784, -3.1637, -6.0430],
        [-0.3005, -1.9909, -3.1440,  ..., -1.9098,  7.6858, -2.5872],
        [ 0.4112,  1.7114, -0.8286,  ..., -6.5673, -4.4493, -4.3344]])

print(preds)
tensor([2, 0, 7, 1, 0, 7, 9, 2, 0, 2, 0, 8, 3, 1, 3, 9, 1, 3, 8, 6, 9, 3, 9, 4,
        1, 4, 6, 0, 7, 5, 6, 9, 6, 3, 5, 4, 7, 5, 0, 3, 1, 2, 9, 1, 2, 5, 3, 3,
        3, 8, 8, 5, 0, 7, 0, 2, 4, 8, 0, 7, 3, 9, 1, 7, 3, 0, 8, 2, 0, 0, 3, 8,
        7, 6, 5, 8, 9, 2, 6, 6, 4, 1, 7, 8, 2, 5, 7, 9, 6, 2, 0, 6, 9, 7, 4, 7,
        2, 7, 3, 5, 3, 5, 4, 3, 9, 8, 7, 0, 5, 8, 1, 9, 5, 9, 2, 2, 8, 4, 4, 7,
        1, 8, 1, 3, 0, 0, 8, 4, 1, 4, 1, 1, 0, 7, 5, 3, 9, 8, 0, 5, 5, 3, 3, 8,
        1, 8, 3, 5, 9, 5, 5, 7, 2, 2, 8, 7, 5, 2, 7, 2, 8, 8, 7, 1, 3, 7, 0, 7,
        7, 0, 7, 5, 8, 6, 6, 0, 4, 5, 1, 9, 7, 4, 3, 6, 9, 4, 1, 1, 6, 1, 5, 0,
        7, 8, 8, 7, 6, 6, 7, 0, 8, 4, 8, 1, 4, 0, 8, 0, 4, 1, 1, 4, 3, 8, 4, 0,
        9, 3, 1, 0, 0, 3, 7, 2, 5, 3, 9, 3, 9, 6, 1, 6, 9, 6, 6, 3, 5, 7, 2, 5,
        4, 7, 5, 4, 3, 7, 2, 8, 5, 7, 3, 2, 4, 6, 8, 3])

print('gt_labels:',gt_labels)
print('gt_labels shape:',gt_labels.shape)
print('pred_labels:',pred_labels)
print('pred_labels shape:',pred_labels.shape)

gt_labels: [2 2 9 ... 5 1 2]
gt_labels shape: (10000,)
pred_labels: [2 2 9 ... 5 1 2]
pred_labels shape: (10000,)
for epoch in range(1, epochs+1):
    train(epoch)
    val(epoch)
Epoch: 1 	Training Loss: 0.659050
Epoch: 1 	Validation Loss: 0.420328, Accuracy: 0.852000
Epoch: 2 	Training Loss: 0.403703
Epoch: 2 	Validation Loss: 0.350373, Accuracy: 0.872300
Epoch: 3 	Training Loss: 0.350197
Epoch: 3 	Validation Loss: 0.293053, Accuracy: 0.893200
Epoch: 4 	Training Loss: 0.322463
Epoch: 4 	Validation Loss: 0.283335, Accuracy: 0.892300
Epoch: 5 	Training Loss: 0.300117
Epoch: 5 	Validation Loss: 0.268653, Accuracy: 0.903500
Epoch: 6 	Training Loss: 0.282179
Epoch: 6 	Validation Loss: 0.247219, Accuracy: 0.907200
Epoch: 7 	Training Loss: 0.268283
Epoch: 7 	Validation Loss: 0.242937, Accuracy: 0.907800
Epoch: 8 	Training Loss: 0.257615
Epoch: 8 	Validation Loss: 0.234324, Accuracy: 0.912200
Epoch: 9 	Training Loss: 0.245795
Epoch: 9 	Validation Loss: 0.231515, Accuracy: 0.914100
Epoch: 10 	Training Loss: 0.238739
Epoch: 10 	Validation Loss: 0.229616, Accuracy: 0.914400
Epoch: 11 	Training Loss: 0.230499
Epoch: 11 	Validation Loss: 0.228124, Accuracy: 0.915200
Epoch: 12 	Training Loss: 0.221574
Epoch: 12 	Validation Loss: 0.211928, Accuracy: 0.921200
Epoch: 13 	Training Loss: 0.217924
Epoch: 13 	Validation Loss: 0.209744, Accuracy: 0.921700
Epoch: 14 	Training Loss: 0.206033
Epoch: 14 	Validation Loss: 0.215477, Accuracy: 0.921400
Epoch: 15 	Training Loss: 0.203349
Epoch: 15 	Validation Loss: 0.215550, Accuracy: 0.919400
Epoch: 16 	Training Loss: 0.196319
Epoch: 16 	Validation Loss: 0.210800, Accuracy: 0.923700
Epoch: 17 	Training Loss: 0.191969
Epoch: 17 	Validation Loss: 0.207266, Accuracy: 0.923700
Epoch: 18 	Training Loss: 0.185466
Epoch: 18 	Validation Loss: 0.207138, Accuracy: 0.924200
Epoch: 19 	Training Loss: 0.178241
Epoch: 19 	Validation Loss: 0.204093, Accuracy: 0.924900
Epoch: 20 	Training Loss: 0.176674
Epoch: 20 	Validation Loss: 0.197495, Accuracy: 0.928300

模型保存
训练完成后,可以使用torch.save保存模型参数或者整个模型,也可以在训练过程中保存模型
这部分会在后面的课程中详细介绍

save_path = "./FahionModel.pkl"
torch.save(model, save_path)

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号