Pytorch从零开始实战04

Pytorch从零开始实战------猴痘病识别

本系列来源于365天深度学习训练营

原作者K同学

文章目录

环境准备

本文基于Jupyter notebook,使用Python3.8,Pytorch2.0.1+cu118,torchvision0.15.2,需读者自行配置好环境且有一些深度学习理论基础。本次实验的目的是学习模型的保存和预测单张图片的结果。

第一步,导入常用包。

python 复制代码
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
import torch.nn.functional as F
import torchvision.transforms as transforms
import random
import time
import numpy as np
import pandas as pd
import datetime
import gc
import pathlib
import os
import PIL
os.environ['KMP_DUPLICATE_LIB_OK']='True'  # 用于避免jupyter环境突然关闭
torch.backends.cudnn.benchmark=True  # 用于加速GPU运算的代码

创建设备对象

python 复制代码
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

设置随机数种子

python 复制代码
torch.manual_seed(428)
torch.cuda.manual_seed(428)
torch.cuda.manual_seed_all(428)
random.seed(428)
np.random.seed(428)

数据集

本次实验使用猴痘病图片数据集,共2142张图片,分别为有猴痘病的图片和没有猴痘病的图片,

两种类别的图片分别存在两个文件夹中。

python 复制代码
data_dir = './data/monkeydata'
data_dir = pathlib.Path(data_dir)

data_paths = list(data_dir.glob('*'))
classNames = [str(path).split("/")[2] for path in data_paths]
classNames # ['Monkeypox', 'Others']

对数据通过dataset读取,并且将文件夹名设置为标签。

python 复制代码
total_datadir = './data/monkeydata'
train_transforms = transforms.Compose([
    transforms.Resize([224, 224]),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    )
])
total_data = torchvision.datasets.ImageFolder(total_datadir, transform=train_transforms)
total_data

我们可以查看所有标签

python 复制代码
total_data.class_to_idx # {'Monkeypox': 0, 'Others': 1}

接下来划分数据集,以8比2划分训练集和测试集

python 复制代码
# 划分数据集
train_size = int(0.8 * len(total_data))
test_size = len(total_data) - train_size
train_ds, test_ds = torch.utils.data.random_split(total_data, [train_size, test_size])
len(train_ds), len(test_ds)

随机查看5张图片

python 复制代码
def plotsample(data):
    fig, axs = plt.subplots(1, 5, figsize=(10, 10)) #建立子图
    for i in range(5):
        num = random.randint(0, len(data) - 1) #首先选取随机数,随机选取五次
        #抽取数据中对应的图像对象,make_grid函数可将任意格式的图像的通道数升为3,而不改变图像原始的数据
        #而展示图像用的imshow函数最常见的输入格式也是3通道
        npimg = torchvision.utils.make_grid(data[num][0]).numpy()
        nplabel = data[num][1] #提取标签 
        #将图像由(3, weight, height)转化为(weight, height, 3),并放入imshow函数中读取
        axs[i].imshow(np.transpose(npimg, (1, 2, 0))) 
        axs[i].set_title(nplabel) #给每个子图加上标签
        axs[i].axis("off") #消除每个子图的坐标轴

plotsample(train_ds)

使用DataLoader划分批次和打乱数据集

python 复制代码
batch_size = 32
train_dl = torch.utils.data.DataLoader(train_ds, batch_size=batch_size, shuffle=True)
test_dl = torch.utils.data.DataLoader(test_ds, batch_size=batch_size, shuffle=True)
for X, y in test_dl:
    print(X.shape) # 32, 3, 224, 224
    print(y) # 1, 0, 1, 1, 1, 1, 0....
    break
print(len(train_dl.dataset) + len(test_dl.dataset)) # 2142

模型选择

本次实验第一次选择的是一个简单的卷积神经网络,经过卷积+卷积+池化+卷积+卷积+池化+线性层,并中间进行数据归一化处理。

python 复制代码
class Model(nn.Module):
    def __init__(self):
        super().__init__()

        self.conv1 = nn.Conv2d(3, 12, kernel_size=5, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(12)
        self.conv2 = nn.Conv2d(12, 12, kernel_size=5, stride=1, padding=0)
        self.bn2 = nn.BatchNorm2d(12)
        self.pool = nn.MaxPool2d(2)
        self.conv3 = nn.Conv2d(12, 24, kernel_size=5, stride=1, padding=0)
        self.bn3 = nn.BatchNorm2d(24)
        self.conv4 = nn.Conv2d(24, 24, kernel_size=5, stride=1, padding=0)
        self.bn4 = nn.BatchNorm2d(24)

        self.fc1 = nn.Linear(24 * 50 * 50, len(classNames))

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))  
        x = self.pool(x)
        x = F.relu(self.bn3(self.conv3(x)))     
        x = F.relu(self.bn4(self.conv4(x))) 
        x = self.pool(x)  
        x = x.view(-1, 24 * 50 * 50)
        x = self.fc1(x)
        return x;

使用summary查看模型

python 复制代码
from torchsummary import summary
# 将模型转移到GPU中
model = Model().to(device)
summary(model, input_size=(3, 224, 224))

模型训练

训练函数

python 复制代码
def train(dataloader, model, loss_fn, opt):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    train_acc, train_loss = 0, 0

    for X, y in dataloader:
        X, y = X.to(device), y.to(device)
        pred = model(X)
        loss = loss_fn(pred, y)

        opt.zero_grad()
        loss.backward()
        opt.step()

        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()

    train_acc /= size
    train_loss /= num_batches
    return train_acc, train_loss

测试函数

python 复制代码
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_acc, test_loss = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            loss = loss_fn(pred, y)
    
            test_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
            test_loss += loss.item()

    test_acc /= size
    test_loss /= num_batches
    return test_acc, test_loss

定义一些超参数,经实验,将学习率设置为0.01效果最好。

python 复制代码
loss_fn = nn.CrossEntropyLoss()
learn_rate = 0.01
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)

开始训练,epochs设置为20,并且将训练集的最优结果保存。

python 复制代码
import time
epochs = 20
train_loss = []
train_acc = []
test_loss = []
test_acc = []

T1 = time.time()

best_acc = 0
PATH = './my_model.pth'

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
    
    model.eval() # 确保模型不会进行训练操作
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)

    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        torch.save(model.state_dict(), PATH)
        print("model save")
        
        
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    print("epoch:%d, train_acc:%.1f%%, train_loss:%.3f, test_acc:%.1f%%, test_loss:%.3f"
          % (epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))
print("Done")
T2 = time.time()
print('程序运行时间:%s毫秒' % ((T2 - T1)*1000))

可以看到,最好的时候,测试集准确率达到百分之91.8

数据可视化

使用matplotlib进行数据可视化。

python 复制代码
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

其他模型

本次实验也使用了ResNet模型,虽然参数量较大,但训练效果较好

定义模型

python 复制代码
class Model(nn.Module):
    def __init__(self):
        super().__init__()
        
        # 创建预训练的ResNet-18模型
        self.resnet = torchvision.models.resnet18(pretrained=True)
        
        # 将ResNet的最后一层(全连接层)替换为适合二分类问题的新全连接层
        self.resnet.fc = nn.Linear(self.resnet.fc.in_features, len(classes))
        
    def forward(self, x):
        return self.resnet(x)

from torchsummary import summary
# 将模型转移到GPU中
model = Model().to(device)

经实验,把学习率设置为0.001,效果较好

python 复制代码
import time
epochs = 50
train_loss = []
train_acc = []
test_loss = []
test_acc = []

loss_fn = nn.CrossEntropyLoss()
learn_rate = 0.001
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)

T1 = time.time()

best_acc = 0
PATH = './my_model.pth'

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
    
    model.eval() # 确保模型不会进行训练操作
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)

    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        torch.save(model.state_dict(), PATH)
        print("model save")
        
        
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    print("epoch:%d, train_acc:%.1f%%, train_loss:%.3f, test_acc:%.1f%%, test_loss:%.3f"
          % (epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))
print("Done")
T2 = time.time()
print('程序运行时间:%s毫秒' % ((T2 - T1)*1000))

最终在测试集的准确率可达到97.2%。

可视化训练过程

python 复制代码
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

图片预测

img_path:要进行预测的图像文件的路径。

model:用于进行图像分类预测的深度学习模型。

transform:用于对图像进行预处理的数据转换函数。

classes:包含类别标签的列表,用于将模型的输出索引映射回类别标签。

大致意思是图像与训练时的输入数据格式相匹配,模型接受批量输入,因此我们需要在维度上添加一个批次维度,从而进行预测

python 复制代码
classes = list(total_data.class_to_idx)
def predict_img(img_path, model, transform, classes):
    test_img = Image.open(img_path).convert('RGB')
    test_img = transform(test_img)
    img = test_img.to(device).unsqueeze(0)
    model.eval()
    output = model(img)
    _, pred = torch.max(output, 1) # 在张量的第一个维度上取最大值操作
    pred_class = classes[pred]
    print(f'预测结果是:{pred_class}')

开始预测

python 复制代码
predict_img(img_path='./data/monkeydata/Monkeypox/M01_01_00.jpg', 
                  model=model, 
                  transform=train_transforms, 
                  classes=classes)
# 预测结果是:Monkeypox
相关推荐
Chiandra_Leong几秒前
Python-Pandas、Numpy
python·pandas
BoBoZz191 分钟前
ParametricObjectsDemo多种参数曲面展示及面上部分点法线展示
python·vtk·图形渲染·图形处理
苍何7 分钟前
越来越对 AI 做的 PPT 敬佩了!(附7大用法)
人工智能
苍何12 分钟前
超全Nano Banana Pro 提示词案例库来啦,小白也能轻松上手
人工智能
quikai198138 分钟前
python练习第三组
开发语言·python
阿杰学AI1 小时前
AI核心知识39——大语言模型之World Model(简洁且通俗易懂版)
人工智能·ai·语言模型·aigc·世界模型·world model·sara
智慧地球(AI·Earth)1 小时前
Vibe Coding:你被取代了吗?
人工智能
ULTRA??1 小时前
初学protobuf,C++应用例子(AI辅助)
c++·python
CHANG_THE_WORLD2 小时前
Python 字符串全面解析
开发语言·python
大、男人2 小时前
DeepAgent学习
人工智能·学习