Pytorch入门实战: 05-Pytorch实现运动鞋识别

🍨 本文为🔗365天深度学习训练营 中的学习记录博客

🍖 原作者:K同学啊

🏡 我的环境:

●语言环境:Python3.8

●编译器:Jupyter Lab

●深度学习环境:Pytorch

一、 前期准备

1. 设置GPU

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

python 复制代码
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
​
import os,PIL,pathlib
​
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
​
device

输出:

2. 导入数据

python 复制代码
import os,PIL,random,pathlib
​
data_dir = './5-data/'
data_dir = pathlib.Path(data_dir)
​
data_paths  = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[1] for path in data_paths]
classeNames

输出:

  • 第一步: 使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象。

  • 第二步: 使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中。

  • 第三步: 通过split()函数对data_paths中的每个文件路径执行分割操作,获得各个文件所属的类别名称,并存储在classeNames

  • 第四步: 打印classeNames列表,显示每个文件所属的类别名称。

python 复制代码
# 关于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

输出:

python 复制代码
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

输出:

二、构建简单的CNN网络

网络结构图

python 复制代码
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), # 12*220*220
            nn.BatchNorm2d(12),
            nn.ReLU())
        
        self.conv2=nn.Sequential(
            nn.Conv2d(12, 12, kernel_size=5, padding=0), # 12*216*216
            nn.BatchNorm2d(12),
            nn.ReLU())
        
        self.pool3=nn.Sequential(
            nn.MaxPool2d(2))                              # 12*108*108
        
        self.conv4=nn.Sequential(
            nn.Conv2d(12, 24, kernel_size=5, padding=0), # 24*104*104
            nn.BatchNorm2d(24),
            nn.ReLU())
        
        self.conv5=nn.Sequential(
            nn.Conv2d(24, 24, kernel_size=5, padding=0), # 24*100*100
            nn.BatchNorm2d(24),
            nn.ReLU())
        
        self.pool6=nn.Sequential(
            nn.MaxPool2d(2))                              # 24*50*50
​
        self.dropout = nn.Sequential(
            nn.Dropout(0.2))
        
        self.fc=nn.Sequential(
            nn.Linear(24*50*50, len(classeNames)))
        
    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
​
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))
​
model = Model().to(device)
model

输出:

python 复制代码
Using cuda 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)
  )
  (fc): Sequential(
    (0): Linear(in_features=60000, out_features=2, bias=True)
  )
)

三、 训练模型

1. 编写训练函数

python 复制代码
# 训练循环
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小
    num_batches = len(dataloader)   # 批次数目, (size/batch_size,向上取整)
​
    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)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
        
        # 反向传播
        optimizer.zero_grad()  # grad属性归零
        loss.backward()        # 反向传播
        optimizer.step()       # 每一步自动更新
        
        # 记录acc与loss
        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. 编写测试函数

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

python 复制代码
def test (dataloader, model, loss_fn):
    size        = len(dataloader.dataset)  # 测试集的大小
    num_batches = len(dataloader)          # 批次数目, (size/batch_size,向上取整)
    test_loss, test_acc = 0, 0
    
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
            
            # 计算loss
            target_pred = model(imgs)
            loss        = loss_fn(target_pred, target)
            
            test_loss += loss.item()
            test_acc  += (target_pred.argmax(1) == target).type(torch.float).sum().item()
​
    test_acc  /= size
    test_loss /= num_batches
​
    return test_acc, test_loss

3. 设置动态学习率

python 复制代码
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)

调用官方动态学习率接口

与上面方法是等价的

python 复制代码
# # 调用官方动态学习率接口时使用
# 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) #选定调整方法

4. 正式训练

python 复制代码
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')

输出:

python 复制代码
Epoch: 1, Train_acc:53.6%, Train_loss:0.756, Test_acc:50.0%, Test_loss:0.700, Lr:1.00E-04
Epoch: 2, Train_acc:63.1%, Train_loss:0.691, Test_acc:65.8%, Test_loss:0.648, Lr:1.00E-04
Epoch: 3, Train_acc:69.5%, Train_loss:0.598, Test_acc:68.4%, Test_loss:0.637, Lr:9.20E-05
Epoch: 4, Train_acc:69.1%, Train_loss:0.579, Test_acc:64.5%, Test_loss:0.600, Lr:9.20E-05
 ......
Epoch:37, Train_acc:92.0%, Train_loss:0.301, Test_acc:81.6%, Test_loss:0.463, Lr:2.23E-05
Epoch:38, Train_acc:94.6%, Train_loss:0.290, Test_acc:85.5%, Test_loss:0.436, Lr:2.23E-05
Epoch:39, Train_acc:93.2%, Train_loss:0.287, Test_acc:82.9%, Test_loss:0.444, Lr:2.05E-05
Epoch:40, Train_acc:93.8%, Train_loss:0.279, Test_acc:81.6%, Test_loss:0.435, Lr:2.05E-05
Done

四、 结果可视化

1. Loss与Accuracy图

python 复制代码
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. 指定图片进行预测

torch.squeeze()详解

对数据的维度进行压缩,去掉维数为1的的维度

函数原型:

python 复制代码
torch.squeeze(input, dim=None, *, out=None)

关键参数说明:

  • input (Tensor):输入Tensor
  • dim (int, optional):如果给定,输入将只在这个维度上被压缩

实战案例:

python 复制代码
>>> x = torch.zeros(2, 1, 2, 1, 2)
>>> x.size()
torch.Size([2, 1, 2, 1, 2])
>>> y = torch.squeeze(x)
>>> y.size()
torch.Size([2, 2, 2])
>>> y = torch.squeeze(x, 0)
>>> y.size()
torch.Size([2, 1, 2, 1, 2])
>>> y = torch.squeeze(x, 1)
>>> y.size()
torch.Size([2, 2, 1, 2])

torch.unsqueeze()

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

函数原型:

python 复制代码
torch.unsqueeze(input, dim)

关键参数说明:

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

实战案例:

python 复制代码
>>> x = torch.tensor([1, 2, 3, 4])
>>> torch.unsqueeze(x, 0)
tensor([[ 1,  2,  3,  4]])
>>> torch.unsqueeze(x, 1)
tensor([[ 1],
        [ 2],
        [ 3],
        [ 4]])
python 复制代码
from PIL import Image 
​
classes = list(train_dataset.class_to_idx)
​
def predict_one_image(image_path, model, transform, classes):
    
    test_img = Image.open(image_path).convert('RGB')
    # plt.imshow(test_img)  # 展示预测的图片
​
    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}')
    
# 预测训练集中的某张照片
predict_one_image(image_path='./5-data/test/adidas/1.jpg', 
                  model=model, 
                  transform=train_transforms, 
                  classes=classes)

输出:

python 复制代码
预测结果是:adidas

五、保存并加载模型

python 复制代码
# 模型保存
PATH = './model.pth'  # 保存的参数文件名
torch.save(model.state_dict(), PATH)
​
# 将参数加载到model当中
model.load_state_dict(torch.load(PATH, map_location=device))

输出:

python 复制代码
<All keys matched successfully>

六、动态学习率

1. torch.optim.lr_scheduler.StepLR

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

函数原型:

python 复制代码
torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1)

关键参数详解:

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

用法示例:

python 复制代码
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

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

函数原型:

python 复制代码
torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1, verbose=False)

关键参数详解

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

用法示例:

python 复制代码
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 中调整学习率

函数原型

python 复制代码
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

用法示例:

python 复制代码
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.2 documentation

👉 **调用官方接口示例:**👉

python 复制代码
model = [Parameter(torch.randn(2, 2, requires_grad=True))]
optimizer = SGD(model, 0.1)
scheduler = ExponentialLR(optimizer, gamma=0.9)
​
for epoch in range(20):
    for input, target in dataset:
        optimizer.zero_grad()
        output = model(input)
        loss = loss_fn(output, target)
        loss.backward()
        optimizer.step()
    scheduler.step()

七、个人总结

了解并学习了动态学习率相关知识,巩固了Pytorch入门实战的相关知识与步骤。

相关推荐
数据智能老司机19 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
逛逛GitHub19 小时前
飞书多维表“独立”了!功能强大的超出想象。
人工智能·github·产品
机器之心19 小时前
刚刚,DeepSeek-R1论文登上Nature封面,通讯作者梁文锋
人工智能·openai
数据智能老司机20 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机20 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机20 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i20 小时前
drf初步梳理
python·django
每日AI新事件20 小时前
python的异步函数
python
这里有鱼汤21 小时前
miniQMT下载历史行情数据太慢怎么办?一招提速10倍!
前端·python
aneasystone本尊21 小时前
学习 Chat2Graph 的知识库服务
人工智能