Pytorch入门实战第七周:咖啡豆识别(VGG-16复现)

目录

前言

一、前期准备

二、手动搭建VGG-16模型

2.1 VGG-16的基本介绍

2.2 手动搭建VGG-16模型

2.3 查看模型详情

三、训练模型

3.1 编写训练函数

3.2 编写测试函数

3.3 正式训练

四、结果可视化

4.1 Loss和Accuracy图

4.2 指定图片进行预测

4.3 模型评估

五、尝试

5.1 调用官方的VGG-16框架

5.2 调整使得验证集准确率达到100%

5.3 轻量化模型

总结


前言

  • 🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客
  • 🍖 原作者:[K同学啊](https://mtyjkh.blog.csdn.net/)

说在前面:

  • 本周学习目标:基本要求——自己搭建VGG-16网络框架、调用官方的VGG-16网络框架、如何查看模型的参数量以及相关指标;拔高要求——验证集准确率达到100%、使用PPT画出VGG-16算法框架图;探索——在不影响准确率的前提下轻量化模型(目前VGG-16的total params是134276932
  • 我的环境:Python3.8、Pycharm2020、torch1.12.1+cu113
  • 数据来源:[K同学啊](https://mtyjkh.blog.csdn.net/))

一、前期准备

1.1 导入所需的包并设置GPU

(有GPU优先使用GPU,没有的话就使用CPU)

代码如下:

#一、前期准备
'''
1.1设置GPU
'''
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,warnings
#import torch.nn.functional as F
import matplotlib.pyplot as plt
from PIL import Image
from torchvision.models import vgg16
import copy

warnings.filterwarnings("ignore")        #忽略警告信息--???怎么突然增加一个这个

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

打印输出:cuda

1.2 导入数据

1.数据下载并设置相应的文件目录以便图片读取,将数据下载放在对应代码目录下新建的data目录

  • 数据集介绍:一共是四类的咖啡豆的照片,每类对应200张人脸图,一共是800张图片,分别放在4个子文件下,每个子文件夹即为咖啡豆的种类

导入数据的步骤:

 1)使用函数将字符串类型的文件夹路径转换为pathlib.Path对象

 2)使用glob方法获取data_dir路径下的所有文件路径,并以列表的形式存储在data_paths中

 3)利用split()函数对data_paths中的每个文件路径执行分割操作,获取各个文件所属的类别名称并储存在classNames中

数据文件导入代码如下:

'''
1.2 导入数据
'''
data_dir = './data/'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classNames = [str(path).split("\\")[1] for path in data_paths]
print(classNames)

打印结果为:['Dark', 'Green', 'Light', 'Medium']

1.3 图片处理

torchvision.transforms是pytorch中的图像预处理包。一般用Compose把多个步骤整合到一起 
1)Resize:将输入图片resize成统一尺寸
2)ToTensor:将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
3)Normalize:标准化处理-->转换为标准正态分布,使模型更容易收敛;其中mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的
ImageFolder类来创建一个数据集对象,total_data将是一个包含所有图像数据的数据集对象,可以用于训练神经网络模型

代码如下:

'''
1.3 图片转换
'''
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] 从数据集中随机抽样计算得到的。
])

total_data = datasets.ImageFolder("./data/",transform=train_transforms)
print(total_data)
print(total_data.class_to_idx)

打印如下:

Dataset ImageFolder
    Number of datapoints: 1200
    Root location: ./data/
    StandardTransform
Transform: Compose(
               Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=warn)
               ToTensor()
               Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
           )
{'Dark': 0, 'Green': 1, 'Light': 2, 'Medium': 3}

1.4 划分数据集

取全部数据的80%作为训练集、剩下的20%作为测试集,并随机分割数据集为训练集测试集

torch.utils.data.random_split(dataset, lengths, generator=<torch._C.Generator object>)

随机将一个数据集分割成给定长度的不重叠的新数据集。可选择固定生成器以获得可复现的结果(效果同设置随机种子)。
dataset (Dataset) – 要划分的数据集。
lengths (sequence) – 要划分的长度。
generator (Generator) – 用于随机排列的生成器。

代码如下:

'''
1.4 划分数据集
'''
train_size = int(0.8 * len(total_data))
test_size = len(total_data) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
print(train_dataset, test_dataset)

输出结果:<torch.utils.data.dataset.Subset object at 0x00000155ABDFA760> <torch.utils.data.dataset.Subset object at 0x00000155ABDFA7C0>

1.5 查看一个batch_size的数据结构

torch.utils.data.DataLoader():这是Pytorch中用于加载和管理数据的一个实用工具类,它允许以小批次的方式迭代数据集,这对于训练神经网络和其他机器学习任务非常有用。具体参数解释如下:

1)dataset(必需参数):这是你的数据集对象,通常是 torch.utils.data.Dataset 的子类,它包含了你的数据样本。

2)batch_size(可选参数):指定每个小批次中包含的样本数。默认值为 1。

3)shuffle(可选参数):如果设置为 True,则在每个 epoch 开始时对数据进行洗牌,以随机打乱样本的顺序。这对于训练数据的随机性很重要,以避免模型学习到数据的顺序性。默认值为 False。

4)num_workers(可选参数):用于数据加载的子进程数量。通常,将其设置为大于 0 的值可以加快数据加载速度,特别是当数据集很大时。默认值为 0,表示在主进程中加载数据。

5)pin_memory(可选参数):如果设置为 True,则数据加载到 GPU 时会将数据存储在 CUDA 的锁页内存中,这可以加速数据传输到 GPU。默认值为 False。

6)drop_last(可选参数):如果设置为 True,则在最后一个小批次可能包含样本数小于 batch_size 时,丢弃该小批次。这在某些情况下很有用,以确保所有小批次具有相同的大小。默认值为 False。

7)timeout(可选参数):如果设置为正整数,它定义了每个子进程在等待数据加载器传递数据时的超时时间(以秒为单位)。这可以用于避免子进程卡住的情况。默认值为 0,表示没有超时限制。

8)worker_init_fn(可选参数):一个可选的函数,用于初始化每个子进程的状态。这对于设置每个子进程的随机种子或其他初始化操作很有用

代码如下:

batch_size = 32
train_dl = torch.utils.data.DataLoader(train_dataset,
                                       batch_size=batch_size,
                                       shuffle=True,
                                       num_workers=0)
test_dl = torch.utils.data.DataLoader(test_dataset,
                                      batch_size=batch_size,
                                      shuffle=True,
                                      num_workers=0)
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.int64

二、手动搭建VGG-16模型

2.1 VGG-16的基本介绍

1.简介

VGG-16(Visual Geometry Group-16)是由牛津大学视觉几何组(Visual Geometry Group)提出的一种深度卷积神经网络架构,用于图像分类和对象识别任务。VGG-16在2014年被提出,是VGG系列中的一种。VGG-16之所以备受关注,是因为它在ImageNet图像识别竞赛中取得了很好的成绩,展示了其在大规模图像识别任务中的有效性
2.模型结构分析

VGG-16一共包含了16个隐藏层(13个卷积层和3个全连接层)

13个卷积层:分别用blockX_convX表示

3个全连接层:用classifier表示

5个池化层

2.2 手动搭建VGG-16模型

代码如下:

'''
2.1 搭建模型
'''
class vgg16_diy(nn.Module):
    def __init__(self):
        super(vgg16_diy, self).__init__()
        #nn.Sequential是一个Sequential容器,模块将按照构造函数中传递的顺序添加到模块中
        #卷积块1:224*224*64
        self.conv1 = nn.Sequential(
            nn.Conv2d(3,64,kernel_size=(3,3),stride=(1, 1),padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))
        #卷积块2:112*112*128
        self.conv2 = nn.Sequential(
            nn.Conv2d(64,128,kernel_size=(3,3),stride=(1, 1),padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(128,128,kernel_size=(3,3),stride=(1, 1),padding=(1, 1)),
            nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))
        #卷积块3:56*56*256
        self.conv3 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))
        #卷积块4:28*28*512
        self.conv4 = nn.Sequential(
            nn.Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))
        # 卷积块5:14*14*512
        self.conv5 = nn.Sequential(
            nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))
        #avgpool
        #self.avgpool = nn.AdaptiveAvgPool2d(output_size=(7, 7))
        #全连接层,用于分类
        self.classifier = nn.Sequential(
            nn.Linear(in_features=512*7*7, out_features=4096),
            nn.ReLU(),
            #nn.Dropout(p=0.5),
            nn.Linear(in_features=4096, out_features=4096, bias=True),
            nn.ReLU(),
            #nn.Dropout(p=0.5),
            nn.Linear(in_features=4096, out_features=4)
        )

        # 前向传播
    def forward(self, x):
        x = self.conv1(x)  # 卷积-BN-激活
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        #x = self.avgpool(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)

        return x

device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

model = vgg16_diy().to(device)
print(model)

模型打印如下:

Using cuda device
vgg16_diy(
  (conv1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (conv2): Sequential(
    (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (conv3): Sequential(
    (0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (conv4): Sequential(
    (0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (conv5): Sequential(
    (0): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU()
    (2): Linear(in_features=4096, out_features=4096, bias=True)
    (3): ReLU()
    (4): Linear(in_features=4096, out_features=4, bias=True)
  )
)

2.3 查看模型详情

TorchSummary提供了更详细的信息分析,包括模块信息(每一层的类型、输出shape和参数量)、模型整体的参数量、模型大小、一次前向或者反向传播需要的内存大小等

代码如下:

'''
2.2 查看模型详情
'''
#统计模型参数量以及其他指标
import torchsummary as summary
summary.summary(model, (3, 224, 224))

输出打印如下:

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 224, 224]           1,792
              ReLU-2         [-1, 64, 224, 224]               0
            Conv2d-3         [-1, 64, 224, 224]          36,928
              ReLU-4         [-1, 64, 224, 224]               0
         MaxPool2d-5         [-1, 64, 112, 112]               0
            Conv2d-6        [-1, 128, 112, 112]          73,856
              ReLU-7        [-1, 128, 112, 112]               0
            Conv2d-8        [-1, 128, 112, 112]         147,584
              ReLU-9        [-1, 128, 112, 112]               0
        MaxPool2d-10          [-1, 128, 56, 56]               0
           Conv2d-11          [-1, 256, 56, 56]         295,168
             ReLU-12          [-1, 256, 56, 56]               0
           Conv2d-13          [-1, 256, 56, 56]         590,080
             ReLU-14          [-1, 256, 56, 56]               0
           Conv2d-15          [-1, 256, 56, 56]         590,080
             ReLU-16          [-1, 256, 56, 56]               0
        MaxPool2d-17          [-1, 256, 28, 28]               0
           Conv2d-18          [-1, 512, 28, 28]       1,180,160
             ReLU-19          [-1, 512, 28, 28]               0
           Conv2d-20          [-1, 512, 28, 28]       2,359,808
             ReLU-21          [-1, 512, 28, 28]               0
           Conv2d-22          [-1, 512, 28, 28]       2,359,808
             ReLU-23          [-1, 512, 28, 28]               0
        MaxPool2d-24          [-1, 512, 14, 14]               0
           Conv2d-25          [-1, 512, 14, 14]       2,359,808
             ReLU-26          [-1, 512, 14, 14]               0
           Conv2d-27          [-1, 512, 14, 14]       2,359,808
             ReLU-28          [-1, 512, 14, 14]               0
           Conv2d-29          [-1, 512, 14, 14]       2,359,808
             ReLU-30          [-1, 512, 14, 14]               0
        MaxPool2d-31            [-1, 512, 7, 7]               0
           Linear-32                 [-1, 4096]     102,764,544
             ReLU-33                 [-1, 4096]               0
           Linear-34                 [-1, 4096]      16,781,312
             ReLU-35                 [-1, 4096]               0
           Linear-36                    [-1, 4]          16,388
================================================================
Total params: 134,276,932
Trainable params: 134,276,932
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 218.52
Params size (MB): 512.23
Estimated Total Size (MB): 731.32

三、训练模型

3.1 编写训练函数

代码如下(示例):

#三、训练模型
'''
3.1.训练函数
'''
def train(dataloader, model, loss_fn, optimier):
    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)
        #反向传播
        optimier.zero_grad()   #grad属性归零
        loss.backward()        #反向传播
        optimier.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

3.2 编写测试函数

代码如下:

'''
3.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)

            # 计算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.3 正式训练

代码如下:

'''
3.3 正式训练
'''
import copy

optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
loss_fn = nn.CrossEntropyLoss()

epochs = 40
train_loss = []
train_acc = []
test_loss = []
test_acc = []

best_acc = 0        #先设置一个最佳准确率,作为最佳模型的判别指标

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)

    #保存最佳模型到best_model
    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        best_model = copy.deepcopy(model)

    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(best_acc)
#保存最佳模型到文件中
PATH = './best_model.pth'        #保存的参数文件名
torch.save(model.state_dict(), PATH)

print('Done')

训练过程如下:

最佳结果为:test_acc=99.2%

四、结果可视化

4.1 Loss和Accuracy图

代码如下:

#四、结果可视化
'''
4.1 Loss和Accuracy图
'''
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))     #figsize 设置图形的大小,a 为图形的宽, b 为图形的高,单位为英寸
plt.subplot(1, 2, 1)   #plt.subplot(nrows, ncols, index),nrows:表示分割画布的行数,ncols:表示分割画布的列数,index:表示子图在画布中的索引,从左往右,从上往下编号

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.savefig('./data/loss_acc01.png')
plt.show()

打印如下:

4.2 指定图片进行预测

代码如下:

'''
4.2 指定图片进行预测
'''
classes = list(total_data.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='./data/Green/green (9).png', model=model, transform=train_transforms, classes=classes)

打印输出为——预测结果是:Green(预测正确)

4.3 模型评估

代码如下:

'''
4.3 模型评估
'''
best_model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, best_model, loss_fn)
print(epoch_test_acc, epoch_test_loss)

结果输出:0.9916666666666667 0.0591967583168298

我们前面保存的best_acc=0.9916666666666667

两者一致,所以模型加载没有问题

五、尝试

5.1 调用官方的VGG-16框架

修改上面的2.2节为下面,来调用官方的VGG-16

#二、调用官方VGG-16模型
'''
2.1 搭建模型
'''
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

#加载预训练模型,并且对模型进行微调
model = vgg16(pretrained=True).to(device)      #加载预训练的vgg16

for param in model.parameters():
    param.requires_grad = False            #冻结模型的参数,这样在训练的时候只训练最后一层的参数

#修改classfier模块的第六层(即:(6):Linear(in_features=4096, out_features=2, bias=True))
#注意下面打印出来的模型
model.classifier._modules['6'] = nn.Linear(4096,len(classNames))   #修改vgg-16模型最后一层全连接层,输出目标类别个数
model.to(device)
print(model)

'''
2.2 查看模型详情
'''
#统计模型参数量以及其他指标
import torchsummary as summary
summary.summary(model, (3, 224, 224))

模型打印结果

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU(inplace=True)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU(inplace=True)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU(inplace=True)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=5, bias=True)
  )
)

模型的参数打印如下:

      Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 224, 224]           1,792
              ReLU-2         [-1, 64, 224, 224]               0
            Conv2d-3         [-1, 64, 224, 224]          36,928
              ReLU-4         [-1, 64, 224, 224]               0
         MaxPool2d-5         [-1, 64, 112, 112]               0
            Conv2d-6        [-1, 128, 112, 112]          73,856
              ReLU-7        [-1, 128, 112, 112]               0
            Conv2d-8        [-1, 128, 112, 112]         147,584
              ReLU-9        [-1, 128, 112, 112]               0
        MaxPool2d-10          [-1, 128, 56, 56]               0
           Conv2d-11          [-1, 256, 56, 56]         295,168
             ReLU-12          [-1, 256, 56, 56]               0
           Conv2d-13          [-1, 256, 56, 56]         590,080
             ReLU-14          [-1, 256, 56, 56]               0
           Conv2d-15          [-1, 256, 56, 56]         590,080
             ReLU-16          [-1, 256, 56, 56]               0
        MaxPool2d-17          [-1, 256, 28, 28]               0
           Conv2d-18          [-1, 512, 28, 28]       1,180,160
             ReLU-19          [-1, 512, 28, 28]               0
           Conv2d-20          [-1, 512, 28, 28]       2,359,808
             ReLU-21          [-1, 512, 28, 28]               0
           Conv2d-22          [-1, 512, 28, 28]       2,359,808
             ReLU-23          [-1, 512, 28, 28]               0
        MaxPool2d-24          [-1, 512, 14, 14]               0
           Conv2d-25          [-1, 512, 14, 14]       2,359,808
             ReLU-26          [-1, 512, 14, 14]               0
           Conv2d-27          [-1, 512, 14, 14]       2,359,808
             ReLU-28          [-1, 512, 14, 14]               0
           Conv2d-29          [-1, 512, 14, 14]       2,359,808
             ReLU-30          [-1, 512, 14, 14]               0
        MaxPool2d-31            [-1, 512, 7, 7]               0
AdaptiveAvgPool2d-32            [-1, 512, 7, 7]               0
           Linear-33                 [-1, 4096]     102,764,544
             ReLU-34                 [-1, 4096]               0
          Dropout-35                 [-1, 4096]               0
           Linear-36                 [-1, 4096]      16,781,312
             ReLU-37                 [-1, 4096]               0
          Dropout-38                 [-1, 4096]               0
           Linear-39                    [-1, 5]          20,485
================================================================
Total params: 134,281,029
Trainable params: 20,485
Non-trainable params: 134,260,544
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 218.77
Params size (MB): 512.24
Estimated Total Size (MB): 731.59

对比手动搭建的模型可知,官方调用的VGG-16模型,需要训练的参数大大减少(我想是因为我们选用了预训练模型)

模型运行结果如下:

测试集上的最好Accuracy为:95.8%

5.2 调整使得验证集准确率达到100%

代码如下:

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

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

1)设置动态学习率---初始学习率=0.001

test_acc最佳结果能达到99.6%

2)设置动态学习率---初始学习率=0.0001

test_acc的最佳结果为100%

5.3 轻量化模型

模型的轻量化是指通过各种技术手段,减少深度学习模型的参数量和计算量,以在资源受限的环境中实现更高效的部署和执行;通过查资料发现有如下几种方法

但这里由于还是实施还有些困难,暂时没有实际操作(不明确该怎么去实际操作)

也有看到之前有人是通过BatchNormalization+Dropout层+全局平均池化层代替全连接层来实现模型的轻量化


总结

  • 本周手动搭建了VGG-16模型,并且成功运行了代码,一开始的结果就能达到99.2%,识别的准确率还是很高的;同时调用了官方的VGG-16作为对比,官方的模型很奇怪没有手动搭建的好,也发现官方的VGG-16的待训练参数数量远远小于手动搭建的
  • 学会了模型的参数的查看方法
  • 尝试增加动态学习率和修改初始学习率的方法使得在测试集上的准确率达到了100%
  • 去了解了模型轻量化的相关知识,后续继续尝试实操
  • 22
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值