第四章:PyTorch基础实战之FashionMNIST时装分类

第四章:PyTorch基础实战

基础实战——FashionMNIST时装分类

我们这里的任务是对10个类别的“时装”图像进行分类,使用FashionMNIST数据集。 上图给出了FashionMNIST中数据的若干样例图,其中每个小图对应一个样本。
FashionMNIST数据集中包含已经预先划分好的训练集和测试集,其中训练集共60,000张图像,测试集共10,000张图像。每张图像均为单通道黑白图像,大小为28*28pixel,分属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,这里有两种方式
# 使用“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 = 0   # 对于Windows用户,这里应设置为0,否则会出现多线程错误
lr = 1e-4
epochs = 20
数据读入和加载

torchvision包是PyTorch官方用于图像处理的工具库

介绍transforms中的函数:

Resize把给定的图片resize到given size
Normalize用均值和标准差归一化张量图像
ToTensorconvert a PIL image to tensor (HWC) in range [0,255] to a torch.Tensor(CHW) in the range [0.0,1.0]
CenterCrop在图片的中间区域进行裁剪
RandomCrop在一个随机的位置进行裁剪
FiceCrop把图像裁剪为四个角和一个中心
RandomResizedCrop将PIL图像裁剪成任意大小和纵横比
ToPILImageconvert a tensor to PIL image
RandomHorizontalFlip以0.5的概率水平翻转给定的PIL图像
RandomVerticalFlip以0.5的概率竖直翻转给定的PIL图像
Grayscale将图像转换为灰度图像
RandomGrayscale将图像以一定的概率转换为灰度图像
ColorJitter随机改变图像的亮度对比度和饱和度

一般用Compose把多个步骤整合到一起

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

image_size = 28
data_transform = transforms.Compose([
    transforms.ToPILImage(),  
     # 这一步取决于后续的数据读取方式,如果使用内置数据集读取方式则不需要
    transforms.Resize(image_size),
    transforms.ToTensor()
])

在自己些论文的过程中,都是用的自己下载好的数据集,自行构建Dataset类

class FMDataset(Dataset):
    def __init__(self, df, transform=None):
        self.df = df
        self.transform = transform
        self.images = df.iloc[:,1:].values.astype(np.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)
        label = int(self.labels[idx])
        if self.transform is not None:
            image = self.transform(image)
        else:
            image = torch.tensor(image/255., dtype=torch.float)
        label = torch.tensor(label, dtype=torch.long)
        return image, label

image-20220915142813035

从数据集中可以看出第0列是lable,从第1列开始是每一个图片的像素值,因此用.iloc取出想要的value

self.images = df.iloc[:,1:].values.astype(np.uint8)
self.labels = df.iloc[:, 0].values

读取下载到本地的数据

train_df = pd.read_csv("./fashion-mnist/fashion-mnist_train.csv")
test_df = pd.read_csv("./fashion-mnist/fashion-mnist_test.csv")
train_data = FMDataset(train_df, data_transform)
test_data = FMDataset(test_df, data_transform)

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

train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers, drop_last=True)
test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)

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

涉及到iter()next()函数用来一个个获取dataloader的数据

  • iter()
    定义:iter() 函数用来生成迭代器。
    语法:iter(object[, sentinel])
    object – 支持迭代的集合对象。
    sentinel – 如果传递了第二个参数,则参数 object 必须是一个可调用的对象(如,函数),此时,iter 创建了一个迭代器对象,每次调用这个迭代器对象的__next__()方法时,都会调用 object。
    返回值:iter()函数返回值为一个迭代器对象。

注:list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。

  • next()
    定义:next()函数用来返回迭代器的下一个项目,通常与生成迭代的iter()一起使用。
    语法:next(iterable[, default])
    iterable – 可迭代对象
    default – 可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常。
    返回值:下一个迭代器项目。
import matplotlib.pyplot as plt
image, label = next(iter(train_loader))
print(image.shape, label.shape)
plt.imshow(image[0][0], cmap="gray")

image-20220915144832142

模型设计

手搭一个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()
设定损失函数

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

看一下CrossEntropyLoss的weighting等策略

?nn.CrossEntropyLoss

image-20220915145409410

criterion = nn.CrossEntropyLoss()
# criterion = nn.CrossEntropyLoss(weight=[1,1,1,1,3,1,1,1,1,1])
设定优化器

使用Adam优化器

optimizer = optim.Adam(model.parameters(), lr=0.001)
训练和测试(验证)

各自封装成函数,方便后续调用
关注两者的主要区别:

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

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

  • 训练
def train(epoch):
    model.train()
    train_loss = 0
    for data, label in train_loader:
        data, label = data.cuda(), label.cuda()
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()
        train_loss += loss.item()*data.size(0)
    train_loss = train_loss/len(train_loader.dataset)
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
  • .item()

理解:取出张量具体位置的元素元素值,并且返回的是该位置元素值的高精度值,保持原元素类型不变;必须指定位置,即:原张量元素为整形,则返回整形,原张量元素为浮点型则返回浮点型,etc.
使用:求loss,以及accuracy rate的时候一般用item(),因为获取的值精度高

  • data.size(0)

for data, label in train_loader:这个里面,每一个for计算的loss,是取出batchsize个数据后计算再平均后的loss,所以最后要乘一个当前的batch的大小

  • 验证
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()
            output = model(data)
            preds = torch.argmax(output, 1)
            gt_labels.append(label.cpu().data.numpy())
            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)
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
    acc = np.sum(gt_labels==pred_labels)/len(pred_labels)
    print('Epoch: {} \tValidation Loss: {:.6f}, Accuracy: {:6f}'.format(epoch, val_loss, acc))
  • .cpu().data.numpy()

.cpu()是把数据转移到CPU上,.data()是读取Variable中的tensor,.numpy()把tensor变成numpy

下面将将tensor转成numpy的几种情况

变量位置转为numpy的代码
GPU中的Variable变量a.cuda().data.cpu().numpy()
GPU中的tensor变量a.cuda().cpu().numpy()
CPU中的tensor变量a.numpy()
CPU中的Variable变量a.data.numpy()

注:通常不能在GPU阵列上运行numpy函数

开始训练及验证

for epoch in range(1, epochs+1):
    train(epoch)
    val(epoch)

image-20220915153923572

模型保存

训练完成后,可以使用torch.save保存模型参数或者整个模型,也可以在训练过程中保存模型

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

文章参考:https://datawhalechina.github.io/thorough-pytorch/%E7%AC%AC%E5%9B%9B%E7%AB%A0/index.html

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值