利用CNN进行运动鞋识别

本文详细介绍了使用PyTorch构建CNN模型进行运动鞋识别的过程,探讨了不同学习率策略对模型性能的影响,包括动态学习率和静态学习率,并分析了基础理论如卷积层、池化层和全连接层的作用。最后,针对训练中遇到的问题进行了总结。
摘要由CSDN通过智能技术生成


🍨 本文为🔗365天深度学习训练营 中的学习记录博客
🍖 原作者:K同学啊 | 接辅导、项目定制

一、代码

import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,random,pathlib


#设置GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")


#导入数据
data_dir = '/home/kaijiang/zlf/CSDN task/the fifth week/'
data_dir = pathlib.Path(data_dir)

data_paths  = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[0] for path in data_paths]
classeNames

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("/home/kaijiang/zlf/CSDN task/the fifth week/train/",transform=train_transforms)
test_dataset  = datasets.ImageFolder("/home/kaijiang/zlf/CSDN task/the fifth week/test/",transform=train_transforms)

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

#构建模型
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)

#编写训练函数
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

#编写测试函数
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

#设置超参数
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)

#开始训练
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')

#将训练结果进行可视化
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()

二、模型评估

1、动态学习率 learn_rate = 1e-4,epoch=40

在这里插入图片描述

2、静态 learn_rate = 1e-4,epoch=30

在这里插入图片描述
在这里插入图片描述

3、动态 learn_rate = 5e-2,epoch=30

在这里插入图片描述
在这里插入图片描述

4、恒定 learn_rate = 5e-2,epoch=30

在这里插入图片描述
在这里插入图片描述

5、动态 learn_rate = 1e-1,epoch=30

在这里插入图片描述
在这里插入图片描述

6、恒定 learn_rate = 1e-1,epoch=30

在这里插入图片描述
在这里插入图片描述

7、动态 learn_rate = 1e-3,epoch=30

在这里插入图片描述
在这里插入图片描述

8、恒定 learn_rate = 1e-3,epoch=30

在这里插入图片描述
在这里插入图片描述

三、基础理论

卷积层

在进行卷积操作时,通常会根据输入和输出的尺寸,以及所需的填充和步幅,来确定合适的卷积核尺寸。 图像数据越靠近边缘,对特征提取的影响越小,越靠近中间,计算的次数会越多,对于特征提取的结果影响越大。边界填充,可以将原来的图像的边界置内,一定程度上增大特征提取的程度,避免了部分信息的缺失,填充0可以在增加填充时不影响结果,因而填充的时候都是添加0。卷积层 的作用是提取一个局部区域的特征,而不同卷积核相当于不同的特征提取器。特征映射为一幅图像经过卷积提取到的特征,每个特征映射可以作为一类抽取的图像特征,有时候为了提高卷积网络的表示能力,可以在每一层卷积层使用多个不同的特征映射,更好地表示图像的特征。

池化层

池化层(Pooling Layer)有时也叫汇聚层、子 采样层(Subsample Layer),是CNN中的一种常见的层类型。池化层的作用是进行特征选择,降低特征数量,减小特征图的空间尺寸,同时保留重要的信息。池化层通过在输入的局部区域上应用某种池化函数实现这一作用。常见的池化函数有最大池化(Max Pooling)和平均池化(Average Pooling)。在最大池化中,池化操作是在每一个池化的窗口上选择最大值作为输出,因为最大值的特征最明显,这样既可以保留最显著的特征,也可以减少计算量和参数的数量。最大池化通过选择最显著的值来保留主要的特征信息,使得卷积神经网络对输入图像的平移尺度和旋转等变换具有部分不变性,提高了模型的鲁棒性和泛化能力。最大池化最明显的问题就是信息丢失,特别是对于细节和局部信息的丢失,为了克服这一缺陷而引入了一些改进的方案,比如平均池化(Average Pooling)和自适应池化(Adaptive Pooling)。在平均池化中,池化操作在每个池化窗口上计算窗口的内所有值的平均值作为输出,这种池化方式可以保留更多的信息。

全连接层

全连接层通常出现在卷积神经网络(CNN)的末尾。在全连接层中,每个神经元都与上一层的所有神经元相连接,形成一个完全连接的网络结构。这意味着每个神经元都会接收到上一层所有神经元的输出,并通过一组可学习的权重进行加权求和,然后经过激活函数的处理产生输出。全连接层的主要作用是将前面卷积层或者其他特征提取层提取到的特征进行组合和抽象,从而得到最终的输出。在图像分类任务中,经过卷积层和池化层处理后的特征图会被展平成一维向量,并输入到全连接层中进行分类。在自然语言处理等任务中,全连接层也扮演着重要角色,用于对文本进行分类、情感分析等任务。全连接层的参数量通常较大,这意味着它有较强的表达能力,可以学习到数据的复杂非线性关系。然而,大量的参数也容易导致过拟合问题,因此在实际应用中,通常会结合正则化技术,如Dropout等,来减少过拟合。总的来说,全连接层在深度学习模型中扮演着连接特征提取和分类(或回归)任务的桥梁角色,它的设计和优化直接影响了模型的性能和泛化能力。

四、训练结论

1、恒定学习率:

缺点明显,简单易用,不需要额外调整参数,适用于训练简单模型或者数据集变化不大的情形;缺点是可能会导致训练过程不够稳定,在训练的后期会出现学习率过大导致出现震荡或者过小导致收敛速度慢的情形;

2、动态学习率

常用的动态学习率调整策略包括指数衰减、学习率衰减和余弦退火等,这些策略的目的是在训练的初期使用 较大的学习率加快收敛速度,训练后期减小学习率提高模型稳定性和精度,动态学习率能够 自动适应训练过程中数据的变化和模型的状态,提高稳定性,缺点是需要额外调整参数和计算成本,增加训练的复杂度。

五、问题总结

1、后期出现测试集的准确率一直持续百分之五十的情形,可能的原因是出现欠拟合或者模型实现错误或者数据不平衡导致,后期尝试增加模型复杂度,调整超参数再重新进行训练和测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值