Pytorch从零开始实战03

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 # device(type='cuda')

设置随机数种子

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

数据集

本次实验使用的天气图片数据集,共有1127张天气图片,分别存在'cloudy', 'sunrise', 'shine', 'rain'四个文件夹中,其中文件夹名代表类别。数据集获取可联系K同学。

导入数据集。

根据自己数据集存放的路径,转换为pathlib.Path对象,然后获取路径下的所有文件路径,使用字符串分割函数获取文件名,也就是类别名。

python 复制代码
data_dir = './data/weather_photos'
data_dir = pathlib.Path(data_dir) # 转换为pathlib.Path对象

data_paths = list(data_dir.glob('*')) # 获取data_dir路径下的所有文件路径
data_paths # data/weather_photos/xxxx
classNames = [str(path).split("/")[2] for path in data_paths]
classNames # ['cloudy', 'sunrise', 'shine', 'rain']

对数据集进行预处理。调整到相同的尺寸,转换为张量对象,并进行标准化处理。使用torchvision.datasets.ImageFolder函数读取数据集,并且使用文件名当做数据集的标签。

python 复制代码
total_dir = './data/weather_photos'
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_dir, transform=train_transforms) # 通过total_dir下的子文件夹当做标签
total_data

我们根据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) # (901, 226)

又是前面几篇出现的函数,随机查看五张图片。

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)

使用DataLoder将它按照batch_size批量划分,并将数据集顺序打乱。

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) # 3 0 2 0 3 2 0 0 2 1....
    break

模型选择

本文使用卷积神经网络,大致流程是卷积->卷积->池化->卷积->卷积->池化->线性层,并进行数据归一化处理,本文选用的卷积核大小为5 * 5。

python 复制代码
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__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.pool2 = nn.MaxPool2d(2, 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.pool4 = nn.MaxPool2d(2, 2)
        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.pool2(x)
        x = F.relu(self.bn3(self.conv3(x)))
        x = F.relu(self.bn4(self.conv4(x)))
        x = self.pool4(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))

模型训练

定义超参数,本次选择的学习率为0.0001,经实验,最初设置为0.01效果并不是很好。

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

训练函数。

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

开始训练,训练20轮,在测试集准确率达到94.7%,还是很不错的。

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

T1 = time.time()

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)
        
    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))

数据可视化

使用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()

总结

经过几次实验,发现三个问题:

1.经过实验,将学习率从0.01改为0.0001,模型效果会好很多。

2.有的时候每轮epoch准确率一直为百分之20多,可能是模型陷入局部最小值或鞍点,所以后续可以引入提前停止。

3.无脑的增加层数并不会使模型效果变好。

相关推荐
大有数据可视化1 分钟前
人工智能如何革新数据可视化领域?探索未来趋势
人工智能·信息可视化
AI technophile1 小时前
OpenCV计算机视觉实战(4)——计算机视觉核心技术全解析
人工智能·opencv·计算机视觉
云和数据.ChenGuang1 小时前
人工智能 机器学习期末考试题
开发语言·人工智能·python·机器学习·毕业设计
珊珊而川2 小时前
3.1监督微调
人工智能
我是小伍同学2 小时前
基于卷积神经网络和Pyqt5的猫狗识别小程序
人工智能·python·神经网络·qt·小程序·cnn
lllsure4 小时前
Python基础语法
开发语言·python
界面开发小八哥4 小时前
界面控件DevExpress WinForms v25.1新功能预览 - 功能区组件全新升级
人工智能·.net·界面控件·winform·devexpress
zhz52145 小时前
开源数字人框架 AWESOME-DIGITAL-HUMAN 技术解析与应用指南
人工智能·ai·机器人·开源·ai编程·ai数字人·智能体
1296004525 小时前
pytorch基础的学习
人工智能·pytorch·学习
沉默媛5 小时前
RuntimeError: expected scalar type ComplexDouble but found Float
人工智能·pytorch·深度学习