第P5周:运动鞋品牌识别

🍺要求:

  1. 了解如何设置动态学习率(重点)
  1. 调整代码使测试集accuracy到达84%。

🍻拔高(可选):

  1. 保存训练过程中的最佳模型权重
  1. 调整代码使测试集accuracy到达86%。

🏡 我的环境:

  • 语言环境:Python3.8
  • 编译器:jupyter notebook
  • 深度学习环境:Pytorch

一、 前期准备

1. 导入必要的包
import os
import pathlib
import PIL
import random
 
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torchvision import transforms, datasets

设置GPU,如果设备上支持GPU就使用GPU,否则使用CPU  

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

  输出:(当前设备没有GPU,先用CPU凑合...)

device(type='cpu')
2. 导入数据

获取并查看数据的标签类别名称

data_dir = './运动鞋识别/train'
# 使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象
data_dir = pathlib.Path(data_dir)
# 使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中
data_paths = list(data_dir.glob('*'))
# 通过split()函数对data_paths中的每个文件路径执行分割操作,获得各个文件所属的类别名称,并存储在classeNames中
classeNames = [str(path).split('\\')[1] for path in data_paths]
# 打印classeNames列表,显示每个文件所属的类别名称
classeNames

 输出:      数据集中有'adidas' 和' nike'两种类别

['adidas', 'nike']

由于数据集中训练集和验证集已经划分好,无需再有划分数据集的步骤,直接导入训练集和验证集数据。否则要先导入全部数据,然后用torch.utils.data.random_split划分数据集。

# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863
train_transforms = transforms.Compose([
    transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸
    # transforms.RandomHorizontalFlip(), # 随机水平翻转
    transforms.ToTensor(),          # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
    transforms.Normalize(           # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
        mean=[0.485, 0.456, 0.406], 
        std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])

test_transform = transforms.Compose([
    transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸
    transforms.ToTensor(),          # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
    transforms.Normalize(           # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
        mean=[0.485, 0.456, 0.406], 
        std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])

train_dataset = datasets.ImageFolder("./5-data/train/",transform=train_transforms)
test_dataset  = datasets.ImageFolder("./5-data/test/",transform=train_transforms)
train_dataset.class_to_idx

输出:

{'adidas': 0, 'nike': 1}

torchvision相关

torchvision是pytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision.transforms主要是用于常见的一些图形变换。以下是torchvision的构成:

1.torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
2.torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;
3.torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
4.torchvision.utils: 其他的一些有用的方法。

本文的主题是其中的torchvision.transforms.Compose()类。这个类的主要作用是串联多个图片变换的操作
from torchvision.transforms import transforms

train_transforms = transforms.Compose([
transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
transforms.RandomRotation(degrees=(-10, 10)), # 随机旋转,-10到10度之间随机选
transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转 选择一个概率概率
transforms.RandomVerticalFlip(p=0.5), # 随机垂直翻转
transforms.RandomPerspective(distortion_scale=0.6, p=1.0), # 随机视角
transforms.GaussianBlur(kernel_size=(5, 9), sigma=(0.1, 5)), # 随机选择的高斯模糊模糊图像
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
mean=[0.485, 0.456, 0.406],
std = [0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
 

os文件路径相关操作:

os.getcwd():得到当前工作目录,即当前Python脚本工作的目录路径
os.listdir():返回指定目录下的所有文件和目录名
os.chdir("path") :换路径
os.path.join(path1, path2):连接两个str格式路径
 
其他图片加载操作:
Image.open(path):path为图片地址,打开该图片
 3. 加载数据
batch_size = 32
train_dl = torch.utils.data.DataLoader(train_dataset,
                                       batch_size=batch_size,
                                       shuffle=True,
                                       num_workers=1)
test_dl = torch.utils.data.DataLoader(test_dataset,
                                       batch_size=batch_size,
                                       shuffle=True,
                                       num_workers=1)
for X, y in test_dl:
    print("Shape of X [N, C, H, W]: ", X.shape)
    print("Shape of y: ", y.shape, y.dtype)
    break

输出: 

Shape of X [N C H W]:  torch.Size([32, 3, 224, 224])
Shape of y:  torch.Size([32])

torch.utils.data.DataLoader()参数详解

torch.utils.data.DataLoader 是 PyTorch 中用于加载和管理数据的一个实用工具类。它允许你以小批次的方式迭代你的数据集,这对于训练神经网络和其他机器学习任务非常有用。DataLoader 构造函数接受多个参数,下面是一些常用的参数及其解释:

  • dataset(必需参数):这是你的数据集对象,通常是 torch.utils.data.Dataset 的子类,它包含了你的数据样本
  • batch_size(可选参数):指定每个小批次中包含的样本数。默认值为 1。
  • shuffle(可选参数):如果设置为 True,则在每个 epoch 开始时对数据进行洗牌,以随机打乱样本的顺序。这对于训练数据的随机性很重要,以避免模型学习到数据的顺序性。默认值为 False
  • num_workers(可选参数):用于数据加载的子进程数量。通常,将其设置为大于 0 的值可以加快数据加载速度,特别是当数据集很大时。默认值为 0,表示在主进程中加载数据
  • pin_memory(可选参数):如果设置为 True,则数据加载到 GPU 时会将数据存储在 CUDA 的锁页内存中,这可以加速数据传输到 GPU。默认值为 False
  • drop_last(可选参数):如果设置为 True,则在最后一个小批次可能包含样本数小于 batch_size 时,丢弃该小批次。这在某些情况下很有用,以确保所有小批次具有相同的大小。默认值为 False
  • timeout(可选参数):如果设置为正整数,它定义了每个子进程在等待数据加载器传递数据时的超时时间(以秒为单位)。这可以用于避免子进程卡住的情况。默认值为 0,表示没有超时限制
  • worker_init_fn(可选参数):一个可选的函数,用于初始化每个子进程的状态。这对于设置每个子进程的随机种子或其他初始化操作很有用

 
关于shuffle流程:

 4. 数据可视化 
import matplotlib.pyplot as plt
from PIL import Image
 
 
# 指定图像文件夹路径
image_folder = './运动鞋识别/train/'
 
 
# 获取文件夹中的所有图像文件
image_files = [f for f in os.listdir(image_folder) if f.endswith((".jpg", ".png", ".jpeg"))]
 
 
# 创建Matplotlib图像
fig, axes = plt.subplots(3, 8, figsize=(16, 6))
 
 
# 使用列表推导式加载和显示图像
for ax, img_file in zip(axes.flat, image_files):
    img_path = os.path.join(image_folder, img_file)
    img = Image.open(img_path)
    ax.imshow(img)
    ax.axis('off') # 调用 ax.axis('off') 来隐藏所有的坐标轴和网格线,从而使图形看起来更简洁
 
 
# 显示图像
plt.tight_layout()
plt.show()

二、构建简单的CNN网络

对于一般的CNN网络来说,都是由特征提取网络和分类网络构成,其中特征提取网络用于提取图片的特征,分类网络用于将图片进行分类

⭐1. torch.nn.Conv2d()详解

函数原型:

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)

关键参数说明:

in_channels ( int ) – 输入图像中的通道数
out_channels ( int ) – 卷积产生的通道数
kernel_size ( int or tuple ) – 卷积核的大小
stride ( int or tuple , optional ) -- 卷积的步幅。默认值:1
padding ( int , tuple或str , optional ) – 添加到输入的所有四个边的填充。默认值:0
padding_mode (字符串,可选) – 'zeros', 'reflect', 'replicate'或'circular'. 默认:'zeros'

⭐2. torch.nn.Linear()详解

函数原型:

torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)

关键参数说明:

in_features:每个输入样本的大小
out_features:每个输出样本的大小

⭐3. torch.nn.MaxPool2d()详解

函数原型:

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

关键参数说明:

kernel_size:最大的窗口大小
stride:窗口的步幅,默认值为kernel_size
padding:填充值,默认为0
dilation:控制窗口中元素步幅的参数
大家注意一下在卷积层和全连接层之间,我们可以使用之前是torch.flatten()也可以使用我下面的x.view()亦或是torch.nn.Flatten()。torch.nn.Flatten()与TensorFlow中的Flatten()层类似,前两者则仅仅是一种数据集拉伸操作(将二维数据拉伸为一维),torch.flatten()方法不会改变x本身,而是返回一个新的张量。而x.view()方法则是直接在原有数据上进行操作。

网络结构图:

import torch.nn.functional as F

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 12, kernel_size=5, padding=0, stride=1),
            nn.BatchNorm2d(12),
            nn.ReLU())
        self.conv2 = nn.Sequential(
            nn.Conv2d(12, 12, kernel_size=5, padding=0, stride=1),
            nn.BatchNorm2d(12),
        nn.ReLU())
        self.pool3 = nn.MaxPool2d(2, 2)
        self.conv4 = nn.Sequential(
            nn.Conv2d(12, 24, kernel_size=5, padding=0, stride=1),
            nn.BatchNorm2d(24),
        nn.ReLU())
        self.conv5 = nn.Sequential(
            nn.Conv2d(24, 24, kernel_size=5, padding=0, stride=1),
            nn.BatchNorm2d(24),
        nn.ReLU())
        self.pool6 = nn.Sequential(
            nn.MaxPool2d(2, 2))
        self.dropout = nn.Sequential(
            nn.Dropout(0.2))
        self.fc = nn.Linear(24*50*50, len(classesName))

    def forward(self, x):
        batch_size = x.size(0)
        x = self.conv1(x) # 卷积-BN-激活
        x = self.conv2(x) # 卷积-BN-激活
        x = self.pool3(x)
        x = self.conv4(x) # 卷积-BN-激活
        x = self.conv5(x) # 卷积-BN-激活
        x = self.pool6(x)
        x = self.dropout(x)
        x = x.view(batch_size, -1) #  # flatten 变成全连接网络需要的输入 (batch, 24*50*50) ==> (batch, -1), -1 此处自动算出的是24*50*50
        x = self.fc(x)
        return x

nn.Dropout

n.Dropout(p=0.5) 是 PyTorch 中的一个层,它可以用来随机丢弃一部分激活值,以此来防止过拟合。 该层接受一个可选参数 p,表示丢弃激活值的概率。例如,nn.Dropout(0.2) 表示丢弃激活值的概率为 20%。 在训练期间,nn.Dropout 会在每次前向传播过程中随机丢弃一部分激活值。具体而言,对于每个激活值 x,它都会计算一个概率 p_drop = p / (1 - p)。如果 p_drop < random_uniform(),则丢弃该激活值;否则保留该激活值。 这样做的目的是通过引入一些噪声来破坏潜在的空间结构,从而降低过拟合的风险。需要注意的是,只有在训练期间才会应用 nn.Dropout 层;在测试期间,它将被禁用,以便获得更准确的结果。 

 device = 'cuda' if torch.cuda.is_available() else 'cpu'
 print('Use {} device'.format(device))

 model = Model().to(device)
 model

输出:

Use cpu device

Model(
  (conv1): Sequential(
    (0): Conv2d(3, 12, kernel_size=(5, 5), stride=(1, 1))
    (1): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU()
  )
  (conv2): Sequential(
    (0): Conv2d(12, 12, kernel_size=(5, 5), stride=(1, 1))
    (1): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU()
  )
  (pool3): Sequential(
    (0): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (conv4): Sequential(
    (0): Conv2d(12, 24, kernel_size=(5, 5), stride=(1, 1))
    (1): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU()
  )
  (conv5): Sequential(
    (0): Conv2d(24, 24, kernel_size=(5, 5), stride=(1, 1))
    (1): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (2): ReLU()
  )
  (pool6): Sequential(
    (0): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (dropout): Sequential(
    (0): Dropout(p=0.2, inplace=False)
  )
  (fc1): Sequential(
    (0): Linear(in_features=60000, out_features=2, bias=True)
  )
)

三、 训练模型

1. 编写训练函数
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    train_loss, train_acc = 0, 0

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

        optimizer.zero_grad()
        loss.backward()
        optimizer.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
2. 编写测试函数

测试函数和训练函数大致相同,但是由于不进行梯度下降对网络权重进行更新,所以不需要传入优化器

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss, test_acc = 0, 0
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
            target_pred = model(imgs)
            loss = loss_fn(target_pred, target)

            test_acc += (target_pred.argmax(1)==target).type(torch.float).sum().item()
            test_loss += loss.item()

        test_acc /= size
        test_loss /= num_batches
        return test_acc, test_loss
3. 设置动态学习率
def adjust_learning_rate(optimizer, epoch, start_lr):
    # 每 2 个epoch衰减到原来的 0.98
    lr = start_lr * (0.92 ** (epoch // 2))
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr

learn_rate = 1e-4 # 初始学习率
optimizer  = torch.optim.SGD(model.parameters(), lr=learn_rate)

for param_group in optimizer.param_groups:

param_group['lr'] = lr

这段代码的作用是遍历优化器中的所有参数组,并将每个参数组的学习率 (lr) 设置为您指定的值。 首先,for param_group in optimizer.param_groups: 这行代码用于迭代优化器中的所有参数组。param_group 是当前正在处理的参数组。 然后,param_group['lr'] = lr 这行代码将当前参数组的学习率设置为 lr 变量中的值。这将在优化器的每次迭代过程中使用该学习率。 需要注意的是,您可能需要确保 lr 变量中的值是一个合理的数值。通常来说,学习率应该是一个较小的正数,如 0.01 或 0.001。

optimizer.param_groups 是 PyTorch 中的一个对象,它是优化器中参数组的列表。每个参数组都包含一组参数(权重),并且具有一个或多个超参数,如学习率、动量等。 通常情况下,当您使用PyTorch时,您可以指定一组参数来创建优化器,例如 optimizer = torch.optim.Adam(model.parameters())。这时,optimizer.param_groups 将返回一个只包含一个参数组的对象。如果您想要为不同的参数设置不同的学习率,那么您可以手动添加更多参数组,如下所示:

params1 = list(model.named_parameters())
params2 = [p for n, p in model.named_parameters() if 'bias' not in n]
group1 = {'params': params1, 'lr': 0.1}
group2 = {'params': params2, 'lr': 0.01}
optimizer = torch.optim.Adam([group1, group2])

调用官方动态学习率接口时使用:

lambda1 = lambda epoch: (0.92 ** (epoch // 2)
optimizer = torch.optim.SGD(model.parameters(), lr=learn_rate)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda1) #选定调整方法

官方接口(和上面是等价的)

1. torch.optim.lr_scheduler.StepLR

等间隔动态调整方法,每经过step_size个epoch,做一次学习率decay,以gamma值为缩小倍数。

函数原型:

torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1)

关键参数详解

  • optimizer(Optimizer):是之前定义好的需要优化的优化器的实例名
  • step_size(int):是学习率衰减的周期,每经过step_size个epoch,做一次学习率decay
  • gamma(float):学习率衰减的乘法因子。Default:0.1

用法示例:

optimizer = torch.optim.SGD(net.parameters(), lr=0.001 )
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
2. lr_scheduler.LambdaLR

根据自己定义的函数更新学习率。

函数原型

torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1, verbose=False)

关键参数详解

  • optimizer(Optimizer):是之前定义好的需要优化的优化器的实例名
  • lr_lambda(function):更新学习率的函数

用法示例:

lambda1 = lambda epoch: (0.92 ** (epoch // 2) # 第二组参数的调整方法
optimizer = torch.optim.SGD(model.parameters(), lr=learn_rate)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda1) #选定调整方法

3. lr_scheduler.MultiStepLR


在特定的 epoch 中调整学习率

函数原型:

torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1, last_epoch=-1, verbose=False)

关键参数详解:

●optimizer(Optimizer):是之前定义好的需要优化的优化器的实例名
●milestones(list):是一个关于epoch数值的list,表示在达到哪个epoch范围内开始变化,必须是升序排列
●gamma(float):学习率衰减的乘法因子。Default:0.1

用法示例:

optimizer = torch.optim.SGD(net.parameters(), lr=0.001 )
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, 
                                                 milestones=[2,6,15], #调整学习率的epoch数
                                                 gamma=0.1)

更多的官方动态学习率设置方式可参考:torch.optim — PyTorch 2.1 documentation

4. 正式训练 
loss_fn    = nn.CrossEntropyLoss() # 创建损失函数
epochs     = 40

train_loss = []
train_acc  = []
test_loss  = []
test_acc   = []

for epoch in range(epochs):
    # 更新学习率(使用自定义学习率时使用)
    adjust_learning_rate(optimizer, epoch, learn_rate)
    
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
    # scheduler.step() # 更新学习率(调用官方动态学习率接口时使用)
    
    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)
    
    # 获取当前的学习率
    lr = optimizer.state_dict()['param_groups'][0]['lr']
    
    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, 
                          epoch_test_acc*100, epoch_test_loss, lr))
print('Done')

输出: 

Epoch:  1, Train_acc: 53.4%, Train_loss: 0.749, Test_acc: 52.6%, Test_loss: 0.699, Lr: 1.00E-04
Epoch:  2, Train_acc: 61.6%, Train_loss: 0.669, Test_acc: 51.3%, Test_loss: 0.724, Lr: 1.00E-04
Epoch:  3, Train_acc: 64.9%, Train_loss: 0.623, Test_acc: 55.3%, Test_loss: 0.702, Lr: 9.80E-05
Epoch:  4, Train_acc: 69.5%, Train_loss: 0.580, Test_acc: 71.1%, Test_loss: 0.630, Lr: 9.80E-05
Epoch:  5, Train_acc: 75.1%, Train_loss: 0.525, Test_acc: 69.7%, Test_loss: 0.609, Lr: 9.60E-05
Epoch:  6, Train_acc: 75.7%, Train_loss: 0.503, Test_acc: 73.7%, Test_loss: 0.601, Lr: 9.60E-05
Epoch:  7, Train_acc: 75.1%, Train_loss: 0.500, Test_acc: 78.9%, Test_loss: 0.592, Lr: 9.41E-05
Epoch:  8, Train_acc: 79.5%, Train_loss: 0.456, Test_acc: 78.9%, Test_loss: 0.581, Lr: 9.41E-05
Epoch:  9, Train_acc: 82.7%, Train_loss: 0.440, Test_acc: 77.6%, Test_loss: 0.550, Lr: 9.22E-05
Epoch: 10, Train_acc: 84.5%, Train_loss: 0.413, Test_acc: 71.1%, Test_loss: 0.682, Lr: 9.22E-05
Epoch: 11, Train_acc: 84.9%, Train_loss: 0.409, Test_acc: 75.0%, Test_loss: 0.585, Lr: 9.04E-05
Epoch: 12, Train_acc: 86.9%, Train_loss: 0.391, Test_acc: 77.6%, Test_loss: 0.501, Lr: 9.04E-05
Epoch: 13, Train_acc: 86.7%, Train_loss: 0.364, Test_acc: 77.6%, Test_loss: 0.512, Lr: 8.86E-05
Epoch: 14, Train_acc: 89.6%, Train_loss: 0.362, Test_acc: 78.9%, Test_loss: 0.519, Lr: 8.86E-05
Epoch: 15, Train_acc: 90.8%, Train_loss: 0.337, Test_acc: 80.3%, Test_loss: 0.484, Lr: 8.68E-05
Epoch: 16, Train_acc: 91.4%, Train_loss: 0.332, Test_acc: 76.3%, Test_loss: 0.496, Lr: 8.68E-05
Epoch: 17, Train_acc: 90.6%, Train_loss: 0.330, Test_acc: 78.9%, Test_loss: 0.504, Lr: 8.51E-05
Epoch: 18, Train_acc: 91.2%, Train_loss: 0.315, Test_acc: 80.3%, Test_loss: 0.516, Lr: 8.51E-05
Epoch: 19, Train_acc: 92.6%, Train_loss: 0.312, Test_acc: 80.3%, Test_loss: 0.533, Lr: 8.34E-05
Epoch: 20, Train_acc: 93.2%, Train_loss: 0.296, Test_acc: 80.3%, Test_loss: 0.479, Lr: 8.34E-05
Epoch: 21, Train_acc: 94.0%, Train_loss: 0.276, Test_acc: 80.3%, Test_loss: 0.522, Lr: 8.17E-05
Epoch: 22, Train_acc: 94.6%, Train_loss: 0.274, Test_acc: 77.6%, Test_loss: 0.541, Lr: 8.17E-05
Epoch: 23, Train_acc: 94.2%, Train_loss: 0.270, Test_acc: 81.6%, Test_loss: 0.449, Lr: 8.01E-05
Epoch: 24, Train_acc: 94.2%, Train_loss: 0.274, Test_acc: 80.3%, Test_loss: 0.469, Lr: 8.01E-05
Epoch: 25, Train_acc: 95.2%, Train_loss: 0.257, Test_acc: 80.3%, Test_loss: 0.476, Lr: 7.85E-05
Epoch: 26, Train_acc: 95.8%, Train_loss: 0.252, Test_acc: 80.3%, Test_loss: 0.441, Lr: 7.85E-05
Epoch: 27, Train_acc: 96.0%, Train_loss: 0.241, Test_acc: 80.3%, Test_loss: 0.496, Lr: 7.69E-05
Epoch: 28, Train_acc: 96.2%, Train_loss: 0.236, Test_acc: 80.3%, Test_loss: 0.477, Lr: 7.69E-05
Epoch: 29, Train_acc: 96.2%, Train_loss: 0.239, Test_acc: 81.6%, Test_loss: 0.446, Lr: 7.54E-05
Epoch: 30, Train_acc: 96.6%, Train_loss: 0.230, Test_acc: 81.6%, Test_loss: 0.492, Lr: 7.54E-05
Epoch: 31, Train_acc: 96.8%, Train_loss: 0.214, Test_acc: 81.6%, Test_loss: 0.445, Lr: 7.39E-05
Epoch: 32, Train_acc: 95.6%, Train_loss: 0.223, Test_acc: 78.9%, Test_loss: 0.469, Lr: 7.39E-05
Epoch: 33, Train_acc: 97.0%, Train_loss: 0.218, Test_acc: 81.6%, Test_loss: 0.523, Lr: 7.24E-05
Epoch: 34, Train_acc: 96.0%, Train_loss: 0.217, Test_acc: 81.6%, Test_loss: 0.515, Lr: 7.24E-05
Epoch: 35, Train_acc: 97.2%, Train_loss: 0.207, Test_acc: 81.6%, Test_loss: 0.461, Lr: 7.09E-05
Epoch: 36, Train_acc: 97.0%, Train_loss: 0.203, Test_acc: 81.6%, Test_loss: 0.465, Lr: 7.09E-05
Epoch: 37, Train_acc: 97.4%, Train_loss: 0.199, Test_acc: 80.3%, Test_loss: 0.440, Lr: 6.95E-05
Epoch: 38, Train_acc: 96.6%, Train_loss: 0.202, Test_acc: 82.9%, Test_loss: 0.509, Lr: 6.95E-05
Epoch: 39, Train_acc: 97.0%, Train_loss: 0.197, Test_acc: 81.6%, Test_loss: 0.440, Lr: 6.81E-05
Epoch: 40, Train_acc: 97.8%, Train_loss: 0.189, Test_acc: 80.3%, Test_loss: 0.501, Lr: 6.81E-05
Done

四、 结果可视化

1. Loss与Accuracy图
import matplotlib.pyplot as plt
#隐藏警告
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()

 

2. 指定图片进行预测
 from PIL import Image
    classes = list(train_dataset.class_to_idx)
    def predict_one_image(image_path, model, trainsform, classes):
        test_img = Image.open(image_path).convert('RGB')
        plt.imshow(test_img)
        test_img = trainsform(test_img)
        img = test_img.to(device).unsqueeze(0)

        model.eval()
        output = model(img)
        _, pred = torch.max(output, 1)
        pred_class = classes[pred]
        img_name = str(image_path).split('\\')[-1]
        print(f"图片'{img_name}'的预测结果是:'{pred_class}'")
        #print(output)
        #print(_)
        #print(pred)

预测训练集中的单张图片 

predict_one_image(r'.\运动鞋识别\train\adidas\1 (11).jpg', model, train_transforms, classes)

输出:

图片'1 (11).jpg'的预测结果是:'adidas'

一次预测多张图片

img_path = [path for path in data_paths[1].glob('*')]
    for i in range(10):
        predict_one_image(img_path[i], model, train_transforms, classes)
图片'1 (1).jpg'的预测结果是:'nike'
图片'1 (10).jpg'的预测结果是:'nike'
图片'1 (100).jpg'的预测结果是:'nike'
图片'1 (101).jpg'的预测结果是:'nike'
图片'1 (102).jpg'的预测结果是:'nike'
图片'1 (103).jpg'的预测结果是:'nike'
图片'1 (104).jpg'的预测结果是:'nike'
图片'1 (105).jpg'的预测结果是:'nike'
图片'1 (106).jpg'的预测结果是:'nike'
图片'1 (107).jpg'的预测结果是:'nike'

⭐torch.unsqueeze()

对数据维度进行扩充。给指定位置加上维数为一的维度

函数原型:

torch.unsqueeze(input, dim)

关键参数说明:

  • input (Tensor):输入Tensor
  • dim (int):插入单例维度的索引

 预测本地图片时做unsqueeze操作的原因:

在预测图片时,常常需要将图片张量 unsqueeze(0),这是因为大多数深度学习模型(如卷积神经网络)通常期望输入数据的形状为 (batch_size, channels, height, width),其中 batch_size 表示批处理大小。 如果预测的图片是单张图像,则它的形状通常是 (channels, height, width)。因此,为了将其转换为模型所需的格式,需要在其前添加一个维度,即 unsqueeze(0)。这样就可以让模型正确地处理这张单张图像了。 例如,假设有一个 RGB 图像,其形状为 (3, 224, 224),那么可以使用以下代码将其转换为所需的格式:

import torch

image = ... # Your image tensor.
image.unsqueeze(0)

五、保存并加载模型

# 模型保存
PATH = './model.pth'  # 保存的参数文件名
torch.save(model.state_dict(), PATH)

# 将参数加载到model当中
model.load_state_dict(torch.load(PATH, map_location=device))

输出:

<All keys matched successfully>

小结:

1. 本项目相比之前新增动态学习率的设置

2. 在预测本地图片时,尝试一次预测多张图片

PS: 用pycharm直接运行项目会报错RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

   

        这个报错表示在当前进程完成引导阶段之前,尝试启动了一个新的进程。这通常是因为使用了多线程或多进程库(如 multiprocessingthreading),并且在一个子进程中尝试创建另一个子进程。 要解决这个问题,可以尝试以下方法:

  1. 检查代码中是否有多线程或多进程的调用,并确保它们是在正确的上下文中使用的。
  2. 确保程序没有死锁或其他资源竞争条件,这些条件可能会导致子进程无法正常启动。
  3. 如果正在使用 Python 的 multiprocessing 库,请尝试将 start_method 设置为 'spawn' 而不是默认的 'fork'。这样可以避免在这个问题上出现错误。

例如,可以通过if __name__ == '__main__':封装来避免错误

import multiprocessing

if __name__ == '__main__':
    multiprocessing.set_start_method('spawn')
    # Your code here...

        在本项目中,torchvision.datasetstorch.utils.data.DataLoader 库可以并行加载数据集,并在多个 CPU 核心上执行数据处理和预处理操作。

        在某些情况下,torchvision.datasetstorch.utils.data.DataLoader 库可以创建并行进程来加载数据集,这可能会导致上述错误。 为了避免这个问题,可以考虑更改这些库的行为,使其不要在启动新进程之前等待现有进程完成其初始化过程。为此,可以尝试以下解决方案:

  • 使用 num_workers 参数限制 DataLoader 创建的工作进程的数量,例如:dataloader = DataLoader(dataset, num_workers=0)。这样可以确保 DataLoader 不再创建任何工作进程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值