【自用】VGG-16网络模型搭建以及训练测试

一、VGG-16模型简介

经典卷积神经网络的基本组成部分是下面的这个序列:

(1)带填充以保持分辨率的卷积层; (2)非线性激活函数,如ReLU; (3)池化层,最大池化层。

而一个VGG块与之类似,由一系列卷积层组成,后面再加上用于空间下采样的最大池化层。

VGG特点: vgg-block内的卷积层都是同结构的 池化层都得上一层的卷积层特征缩减一半 深度较深,参数量够大

来源:B站up主 【炮哥带你学】

网络参数结构示意图(其中conv3-256表示:这是一个卷积层,卷积核尺寸为3×3,通道数为256)

不同VGG版本对应的参数:

VGG共有6个模型,A是VGG11 B是VGG13参数个数都是1.33亿,C是VGG16也就是我们本次搭建的,参数个数为1.34亿,E是VGG19参数个数是1.44亿

总结:

二、模型搭建


    import torch
    from torch import nn
    from torchsummary import summary

    class VGG16(nn.Module):
        def __init__(self):
            super(VGG16, self).__init__()
            self.block1 = nn.Sequential(
                #卷积 -> 激活函数 卷积 -> 激活函数
                #步幅为1这个参数可以省略不写 默认为1  填充为1这个参数要写
                nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1),
                nn.ReLU(),
                #最大池化
                nn.MaxPool2d(kernel_size=2, stride=2)
            )
            self.block2 = nn.Sequential(
                nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=2, stride=2)
            )
            self.block3 = nn.Sequential(
                nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=2, stride=2)
            )
            self.block4 = nn.Sequential(
                nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=2, stride=2)
            )
            self.block5 = nn.Sequential(
                nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=2, stride=2)
            )

            self.block6 = nn.Sequential(
                #全连接之前先进行展平
                nn.Flatten(),
                nn.Linear(7 * 7 * 512, 256),
                nn.ReLU(),
                nn.Linear(256, 128),
                nn.ReLU(),
                nn.Linear(128, 10),
            )

            #权重初始化!!!
            for m in self.modules():
                # 这一行代码使用self.modules()方法遍历了神经网络模型self中的所有模块。
                # self.modules()是一个生成器,它会递归地遍历模型中的所有层(包括子模块、子层等),并返回它们的引用。
                # 对于卷积层的初始化
                if isinstance(m, nn.Conv2d):
                    # 对于每一个模块m,代码首先检查它是否是nn.Conv2d类型,即二维卷积层。如果是,那么执行以下操作:
                    # 对于权重参数 w进行初始化
                    nn.init.kaiming_normal_(m.weight, nonlinearity='relu')
                    # 对于偏置 b 进行初始化
                    if m.bias is not None:
                        # 如果卷积层有偏置项m.bias(不是所有卷积层都有偏置项,这取决于在创建层时是否指定了bias = True),则使用nn.init.constant_函数将其初始化为0
                        nn.init.constant_(m.bias, 0)
                # 对于全连接层的初始化 mean是平均值  std是方差
                elif isinstance(m, nn.Linear):
                    nn.init.normal_(m.weight, 0, 0.01)
                    if m.bias is not None:
                        nn.init.constant_(m.bias, 0)

        def forward(self, x):
            x = self.block1(x)
            x = self.block2(x)
            x = self.block3(x)
            x = self.block4(x)
            x = self.block5(x)
            x = self.block6(x)
            return x

    if __name__ == "__main__":
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        model = VGG16().to(device)
        print(summary(model, (1, 224, 224)))


if __name__=="__main__":
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = VGG16().to(device)
    print(summary(model, (1, 224, 224)))

注意VGG16的模型搭建和之前的LetNet-6以及AlexNet的网络搭建有所不同:

1.我们把这些层分成一个个的块,只需要在每个块内部按顺序排列好各个参数层,然后直接调用块进行卷积运算就可以了 ,调用块的时候就会按照块中的顺序执行下来

2.除此之外还有一块代码是进行权重初始化,也就是对权重w和偏置b进行初始化,可是为什么要这样呢,在之前的两个网络中为什么没有这部分代码呢?

第一个原因就是之前学习的网络深度是比较浅的,在神经网络中,尤其是当网络结构较深时,如果所有权重都被初始化为相同的值,那么在训练开始时,所有神经元的行为将会是相同的。这会导致网络学习到相同的特征,从而无法充分利用网络的深度和宽度。通过初始化权重为不同的值,可以确保每个神经元在训练开始时就能学习到不同的特征。

第二个原因就是如果权重初始化为过大或过小的值,可能会导致在反向传播过程中梯度消失或爆炸。梯度消失意味着权重更新变得非常小,导致网络学习非常缓慢;而梯度爆炸则可能导致权重更新过大,使得网络无法稳定学习。

合适的权重初始化可以使得模型在训练初期就接近一个较好的状态,从而加速训练过程。例如,使用Kaiming初始化(也称为He初始化)时,它考虑了ReLU激活函数的特点,使得每一层的输出方差保持不变,这有助于维持梯度的稳定性,从而加快训练速度

但是仅仅注意要权重初始化,根据炮哥视频中的讲解,当没有进行权重初始化的时候,他先是在6G的显存上训练批次为12的数据集,效果不好,当换到24G显存训练批次为64的时候效果也不好,但当进行以上的权重初始化的时候,后者效果就变好了,但是前者效果依旧不好,又接着把24G显存上的批次换为12,效果不好,此时分析训练效果应该与批次(batch_size)也有关

为什么会与批次有关呢?

  1. 批次统计量的不稳定性

    当批次很小时,批次统计量(如均值和方差)会变得非常不稳定。这些统计量在批归一化(Batch Normalization)等过程中起着关键作用,用于标准化输入数据,使其具有一致的分布。不稳定的统计量会导致模型训练过程中的不稳定,从而影响收敛性。
  2. 训练过程中的梯度波动

    小的批次可能导致梯度在每次迭代中波动较大。这种波动可能使得模型在训练过程中难以找到稳定的下降方向,从而导致收敛速度缓慢甚至不收敛。
  3. 模型泛化能力的下降

    虽然小的批次有助于模型更好地泛化到未见过的数据(通过引入更多的随机性),但当批次太小以至于无法提供足够的信息来更新模型参数时,模型的泛化能力可能会下降。这可能导致模型在训练集上表现良好,但在测试集上表现不佳。

所以既要关注权重初始化,也要关注训练集批次大小,训练批次不能太小,但是矛盾又来了,本来我是显存是6G跑批次为12的数据就特别慢,要是跑批次为32的还不要卡死啊,这里为了让数据跑通,我们可以对全连接层的中间几层的输入输出进行调整(因为绝大部分的参数都集中在线性全连接层,虽然论文中模型的参数是4096,但是对于我们练习代码这个结果只有十分类的数据来说,根本用不着那么多参数),把代码参数改一下:

    
把:
self.block6 = nn.Sequential(
                #全连接之前先进行展平
                nn.Flatten(),
                nn.Linear(7 * 7 * 512, 4096),
                nn.ReLU(),
                nn.Linear(4096, 4096),
                nn.ReLU(),
                nn.Linear(4096, 10),
            )

改为:

self.block6 = nn.Sequential(
                #全连接之前先进行展平
                nn.Flatten(),
                nn.Linear(7 * 7 * 512, 256),
                nn.ReLU(),
                nn.Linear(256, 128),
                nn.ReLU(),
                nn.Linear(128, 10),
            )

这样改完运行发现效果就好了很多

三、模型训练+测试

import copy
import time

import torch
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
from model import VGG16
import torch.nn as nn
import pandas as pd


def train_val_data_process():
    train_data = FashionMNIST(root='./data',
                              train=True,
                              transform=transforms.Compose([transforms.Resize(size=224), transforms.ToTensor()]),
                              download=True)

    train_data, val_data = Data.random_split(train_data, [round(0.8*len(train_data)), round(0.2*len(train_data))])
    train_dataloader = Data.DataLoader(dataset=train_data,
                                       batch_size=28,
                                       shuffle=True,
                                       num_workers=2)

    val_dataloader = Data.DataLoader(dataset=val_data,
                                       batch_size=28,
                                       shuffle=True,
                                       num_workers=2)

    return train_dataloader, val_dataloader


def train_model_process(model, train_dataloader, val_dataloader, num_epochs):
    # 设定训练所用到的设备,有GPU用GPU没有GPU用CPU
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 使用Adam优化器,学习率为0.001
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    # 损失函数为交叉熵函数
    criterion = nn.CrossEntropyLoss()
    # 将模型放入到训练设备中
    model = model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数
    # 最高准确度
    best_acc = 0.0
    # 训练集损失列表
    train_loss_all = []
    # 验证集损失列表
    val_loss_all = []
    # 训练集准确度列表
    train_acc_all = []
    # 验证集准确度列表
    val_acc_all = []
    # 当前时间
    since = time.time()

    for epoch in range(num_epochs):
        print("Epoch {}/{}".format(epoch, num_epochs-1))
        print("-"*10)

        # 初始化参数
        # 训练集损失函数
        train_loss = 0.0
        # 训练集准确度
        train_corrects = 0
        # 验证集损失函数
        val_loss = 0.0
        # 验证集准确度
        val_corrects = 0
        # 训练集样本数量
        train_num = 0
        # 验证集样本数量
        val_num = 0

        # 对每一个mini-batch训练和计算
        for step, (b_x, b_y) in enumerate(train_dataloader):
            # 将特征放入到训练设备中
            b_x = b_x.to(device)
            # 将标签放入到训练设备中
            b_y = b_y.to(device)
            # 设置模型为训练模式
            model.train()

            # 前向传播过程,输入为一个batch,输出为一个batch中对应的预测
            output = model(b_x)
            # 查找每一行中最大值对应的行标
            pre_lab = torch.argmax(output, dim=1)
            # 计算每一个batch的损失函数
            loss = criterion(output, b_y)

            # 将梯度初始化为0
            optimizer.zero_grad()
            # 反向传播计算
            loss.backward()
            # 根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值的作用
            optimizer.step()
            # 对损失函数进行累加
            train_loss += loss.item() * b_x.size(0)
            # 如果预测正确,则准确度train_corrects加1
            train_corrects += torch.sum(pre_lab == b_y.data)
            # 当前用于训练的样本数量
            train_num += b_x.size(0)
        for step, (b_x, b_y) in enumerate(val_dataloader):
            # 将特征放入到验证设备中
            b_x = b_x.to(device)
            # 将标签放入到验证设备中
            b_y = b_y.to(device)
            # 设置模型为评估模式
            model.eval()
            # 前向传播过程,输入为一个batch,输出为一个batch中对应的预测
            output = model(b_x)
            # 查找每一行中最大值对应的行标
            pre_lab = torch.argmax(output, dim=1)
            # 计算每一个batch的损失函数
            loss = criterion(output, b_y)


            # 对损失函数进行累加
            val_loss += loss.item() * b_x.size(0)
            # 如果预测正确,则准确度train_corrects加1
            val_corrects += torch.sum(pre_lab == b_y.data)
            # 当前用于验证的样本数量
            val_num += b_x.size(0)

        # 计算并保存每一次迭代的loss值和准确率
        # 计算并保存训练集的loss值
        train_loss_all.append(train_loss / train_num)
        # 计算并保存训练集的准确率
        train_acc_all.append(train_corrects.double().item() / train_num)

        # 计算并保存验证集的loss值
        val_loss_all.append(val_loss / val_num)
        # 计算并保存验证集的准确率
        val_acc_all.append(val_corrects.double().item() / val_num)

        print("{} train loss:{:.4f} train acc: {:.4f}".format(epoch, train_loss_all[-1], train_acc_all[-1]))
        print("{} val loss:{:.4f} val acc: {:.4f}".format(epoch, val_loss_all[-1], val_acc_all[-1]))

        if val_acc_all[-1] > best_acc:
            # 保存当前最高准确度
            best_acc = val_acc_all[-1]
            # 保存当前最高准确度的模型参数
            best_model_wts = copy.deepcopy(model.state_dict())

        # 计算训练和验证的耗时
        time_use = time.time() - since
        print("训练和验证耗费的时间{:.0f}m{:.0f}s".format(time_use//60, time_use%60))

    # 选择最优参数,保存最优参数的模型
    model.load_state_dict(best_model_wts)
    # torch.save(model.load_state_dict(best_model_wts), "C:/Users/86159/Desktop/LeNet/best_model.pth")
    torch.save(best_model_wts, "./best_model.pth")


    train_process = pd.DataFrame(data={"epoch":range(num_epochs),
                                       "train_loss_all":train_loss_all,
                                       "val_loss_all":val_loss_all,
                                       "train_acc_all":train_acc_all,
                                       "val_acc_all":val_acc_all,})

    return train_process


def matplot_acc_loss(train_process):
    # 显示每一次迭代后的训练集和验证集的损失函数和准确率
    plt.figure(figsize=(12, 4))
    plt.subplot(1, 2, 1)
    plt.plot(train_process['epoch'], train_process.train_loss_all, "ro-", label="Train loss")
    plt.plot(train_process['epoch'], train_process.val_loss_all, "bs-", label="Val loss")
    plt.legend()
    plt.xlabel("epoch")
    plt.ylabel("Loss")
    plt.subplot(1, 2, 2)
    plt.plot(train_process['epoch'], train_process.train_acc_all, "ro-", label="Train acc")
    plt.plot(train_process['epoch'], train_process.val_acc_all, "bs-", label="Val acc")
    plt.xlabel("epoch")
    plt.ylabel("acc")
    plt.legend()
    plt.show()


if __name__ == '__main__':
    # 加载需要的模型
    VGG16 = VGG16()
    # 加载数据集
    train_data, val_data = train_val_data_process()
    # 利用现有的模型进行模型的训练
    train_process = train_model_process(VGG16, train_data, val_data, num_epochs=20)
    matplot_acc_loss(train_process)

运行结果

(我租的是AutoDL上面的2080Ti的GPU,好慢好慢,出去跑了步回来才训练了13轮次。。。。然后没跑完我就停止了,应该是没什么问题,有条件的可以用4090)

内存占用数据

四、推理验证

import torch
import torch.utils.data as Data
from torchvision import transforms
from torchvision.datasets import FashionMNIST
from model import VGG16



def test_data_process():
    test_data = FashionMNIST(root='./data',
                              train=False,
                              transform=transforms.Compose([transforms.Resize(size=224), transforms.ToTensor()]),
                              download=True)

    test_dataloader = Data.DataLoader(dataset=test_data,
                                       batch_size=1,
                                       shuffle=True,
                                       num_workers=0)
    return test_dataloader


def test_model_process(model, test_dataloader):
    # 设定测试所用到的设备,有GPU用GPU没有GPU用CPU
    device = "cuda" if torch.cuda.is_available() else 'cpu'

    # 讲模型放入到训练设备中
    model = model.to(device)

    # 初始化参数
    test_corrects = 0.0
    test_num = 0

    # 只进行前向传播计算,不计算梯度,从而节省内存,加快运行速度
    with torch.no_grad():
        for test_data_x, test_data_y in test_dataloader:
            # 将特征放入到测试设备中
            test_data_x = test_data_x.to(device)
            # 将标签放入到测试设备中
            test_data_y = test_data_y.to(device)
            # 设置模型为评估模式
            model.eval()
            # 前向传播过程,输入为测试数据集,输出为对每个样本的预测值
            output= model(test_data_x)
            # 查找每一行中最大值对应的行标
            pre_lab = torch.argmax(output, dim=1)
            # 如果预测正确,则准确度test_corrects加1
            test_corrects += torch.sum(pre_lab == test_data_y.data)
            # 将所有的测试样本进行累加
            test_num += test_data_x.size(0)

    # 计算测试准确率
    test_acc = test_corrects.double().item() / test_num
    print("测试的准确率为:", test_acc)


if __name__ == "__main__":
    # 加载模型
    model = VGG16()
    model.load_state_dict(torch.load('best_model.pth'))
    # # 利用现有的模型进行模型的测试
    test_dataloader = test_data_process()
    test_model_process(model, test_dataloader)


    # 设定测试所用到的设备,有GPU用GPU没有GPU用CPU
    device = "cuda" if torch.cuda.is_available() else 'cpu'
    model = model.to(device)

    classes = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
    with torch.no_grad():
        for b_x, b_y in test_dataloader:
            b_x = b_x.to(device)
            b_y = b_y.to(device)

            # 设置模型为验证模型
            model.eval()
            output = model(b_x)
            pre_lab = torch.argmax(output, dim=1)
            result = pre_lab.item()
            label = b_y.item()
            print("预测值:",  classes[result], "------", "真实值:", classes[label])








### YOLOv7 学习教程推荐 #### 环境搭建指南 对于初次接触YOLOv7的学习者来说,环境配置是一个重要的起点。B站上有一个详细的安装视频教程提供了清晰的操作指导[^1]。该视频不仅涵盖了必要的软件包安装过程,还针对可能出现的问题给出了有效的解决方案。 #### 基础功能实践 一旦完成了环境设置,就可以着手探索YOLOv7的核心特性——检测、推理以及训练。一篇深入浅出的文章介绍了如何利用已有的框架快速启动并运行这些基本功能[^2]。通过跟随文章中的步骤,读者能够掌握调用预训练模型执行图像识别任务的方法,并理解整个流程背后的原理。 #### 自定义数据集训练技巧 为了使YOLOv7更好地适应特定应用场景下的需求,训练自定义的数据集显得尤为重要。一份由多位开发者共同撰写的保姆级教程分享了许多宝贵的经验教训和实用建议[^3]。这份文档特别适合那些希望将自己的图片库转化为高效能目标检测系统的用户群体阅读参考。 #### 高阶应用案例分析 最后值得一提的是,存在一些专注于提升YOLOv7性能的应用实例可供进一步研究。例如,有人开发了一个带有PyQt图形界面的口罩佩戴情况监测工具,它展示了怎样将理论知识应用于实际问题解决当中去[^4]。此类项目不仅能加深对算法本身的理解程度,同时也为未来的研究方向指明了道路。 ```python # Python代码片段用于加载YOLOv7权重文件 import torch from models.experimental import attempt_load weights_path = 'path/to/yolov7/weights' device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = attempt_load(weights_path, map_location=device) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值