VGG网络模型

VGG网络模型

诞生背景

VGGNet是牛津大学计算机视觉组核谷歌DeepMind一起研究出来的深度卷积神经网络。VGG是一种被广泛使用的卷积神经网络结构,其在2014年的ImageNet大规模视觉识别挑战中获得亚军。

通常所说的VGG是指VGG-16(13层卷积层+3层全连接层)。具有规律的设计,简洁可堆叠的卷积块,且在其他数据集上都有着良好的表现,从而被广泛的使用。

VGG和AlexNet相比,深度更深,参数更多(1.38亿),效果和可移植性更好。

VGG网络模型的特点,每层网络模型,是以块为单位的。

模型架构图

在这里插入图片描述

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

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

环境搭建

创建一个文件夹,作为项目的根目录,就叫做VGG16吧。

pytorch v10.1

cuda 11.3

额外库:

torchsummary == 1.5.1

sklearn == 0.0

pytorch环境安装命令

conda install pytorch==1.10.1 torchvision==0.11.2 torchaudio==0.10.1 cudatoolkit=11.3 -c pytorch

额外安装库的命令

conda install -i https://pypi.tuna.tsinghua.edu.cn/simple/ torchsummary==1.5.1 sklearn==0.0
或在激活环境的情况下:
pip install -i http://mirrors.aliyun.com/pypi/simple/ torchsummary==1.5.1 sklearn==0.0

数据集准备

然后新建一个python文件,就叫plot.py吧,往里面写入以下代码,用于下载数据集:

# FashionMNIST里面包含了许多数据集
from click.core import batch
from spacy.cli.train import train
from torchvision.datasets import FashionMNIST
from torchvision import transforms # 处理数据集,归一化
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt


# 下载FashionMMIST数据集
train_data = FashionMNIST(
    root="./data", # 指定数据集要下载的路径
    train=True,# 要训练集
    # 将数据进行归一化操作
    transform=transforms.Compose([
        transforms.Resize(size=224), # 调整数据的大小
        transforms.ToTensor() # 将数据转换为tensor
    ]),
    download=True # 开启下载
)

# 加载数据集集
train_loader = Data.DataLoader(
    dataset=train_data,# 要加载的数据集
    batch_size=64 ,# 批量数据大小
    shuffle=True, # 打乱数据顺序
    num_workers=0, # 加载数据线程数量
)

# 绘制出训练集
for step,(b_x,b_y) in enumerate(train_loader):
    if step > 0:
        break
    batch_x = b_x.squeeze().numpy() # 将四维张量移除第一维,将数据转换为numpy格式
    batch_y = b_y.numpy() # 将张量数据转成numpy格式
    class_label = train_data.classes # 训练集标签
print("class_label,",class_label)

# 绘图
plt.figure(figsize=(12,5))
for ii in np.arange(len(batch_y)):
    plt.subplot(4,16,ii+1)
    plt.imshow(batch_x[ii, : , :],cmap=plt.cm.gray)
    plt.title(class_label[batch_y[ii]],size=10)
    plt.axis('off')
    plt.subplots_adjust(wspace=0.05)

plt.show()

执行上述代码后,就会开始下载所需要的数据集文件,只不过下载的速度比较慢,然后下载完成,项目的根目录会多出data文件夹,以下是data的目录结构:

--data
  --FashionMNIST
    --raw # 该文件夹下就存放数据集文件

模型搭建

创建一个model.py文件,来搭建模型

import torch
from torch import nn
from torchsummary import summary

class VGG16(nn.Module):
    def __init__(self):
        super(VGG16,self).__init__()
        
        # 定义第一层块
        # Sequential是一个序列,里面包含卷积,激活函数,池化等,相当于封装
        self.block1 = nn.Sequential(
        	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), # 全连接层 输入原先是7*7*512(没变),输出原先是4096
            nn.ReLU(),
            nn.Linear(256,128), # 全连接层 输入原先是4096,输出原先是4096
            nn.ReLU(),
            nn.Linear(128,10), # 全连接层 作为输出层 输入原先是4096,输出原先是10
            
        )
        
        # ====权重初始化操作,解决模型的不收敛的问题====
        for m in self.modules(): # 循环遍历模型
            # print(m) # 打印模型的每一块,每一层
            if isinstance(m,nn.Conv2d): # 如果m是卷积
                # m.weight 模型每一层的参数,nonlinearity激活函数
                nn.init.kaiming_normal_(m.weight,nonlinearity='relu') # 使用 凯明初始化 方式
                if m.bias is not None: # 判断b参数(偏置)是否为空 约定俗成的操作
                    nn.init.constant_(m.bias,0) # 让b的值为0
            
            elif isinstance(m,nn.Linear):
                # m.weight表示w参数,0表示均值,0.01表示方差
                nn.init.normal_(m.weight,0,0.01) # 正太分布初始化 归一化
                if m.bias is not None: # 判断b参数(偏置)是否为空 约定俗成的操作
                    nn.init.constant_(m.bias,0) # 让b的值为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)))

模型不收敛的问题:

当模型的参数比较深的时候,就会出现该类问题。

先进行权重初始化(上面代码实现了),如果权重初始化设置了,模型还不收敛,那就尝试修改批次大小(batch_size) 和 尝试修改block6层的输入输出大小,这里就有点玄学的概念了,哈哈哈哈

模型训练

创建一个model_train.py文件,用作模型的训练

import copy
import time

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

from model import VGG16


# 处理训练集核数据集
def train_val_data_process():
    # 加载数据集
    train_data = FashionMNIST(
        root="./data",# 数据集所在路径
        train=True, # 要训练集
        # 将数据进行归一化操作
        transform=transforms.Compose([
            transforms.Resize(size=224), # 修改数据的大小
            transforms.ToTensor() # 将数据转成Tensor格式
        ]),
        download=True # 开启加载
    )

    # 随机 划分训练集 和 验证集
    train_data,val_data = Data.random_split(
        train_data, # 要划分的数据集
        [
            round(0.8*len(train_data)), # 划分80%给训练集
            round(0.2*len(train_data)) # 划分20%给验证集
        ]
    )

    # 加载训练集数据
    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):
    # model:需要训练的模型,train_dataloader:训练集数据,val_dataloader:验证集数据,num_epochs:训练轮数
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 定义优化器
    optimizer = torch.optim.Adam(model.parameters(),lr=0.001)
    # 定义交叉熵损失函数
    criterion = nn.CrossEntropyLoss()
    # 将模型放入到设备中进行训练
    model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数,记录模型的的精确度和损失值
    best_acc = 0.0 # 最高精确度

    train_loss_all = [] # 训练集的总损失
    train_acc_all = [] # 训练集的总精度

    val_loss_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_corrents = 0 # 验证集的准确度

        train_num = 0 # 本轮训练集的数量
        val_num = 0 # 本轮验证集的数量

        # 取出每轮中的数据集进行训练
        for step,(b_x,b_y) in enumerate(train_dataloader):
            b_x = b_x.to(device) # 将训练集数据放入到设备当中
            b_y = b_y.to(device) # 将标签数据放入到设备当中

            model.train() # 开启模型训练模式

            # 将每批次中的标签数据放入到模型中,进行前向传播
            output = model(b_x)

            # 查找每一行中最大值对应的行标,即预测值
            pre_lab = torch.argmax(output,dim=1)

            # 计算当前批次的损失值(模型的输出,标签)
            loss = criterion(output,b_y)

            # 每批次训练完后,将梯度初始化成0
            optimizer.zero_grad()

            # 反向传播计算
            loss.backward()

            # 更新参数
            optimizer.step()

            # 本批次损失值的累加
            train_loss += loss.item() * b_x.size(0)

            # 如果模型预测的结果正确,本批次的准确度+1
            train_corrects += torch.sum(pre_lab == b_y.data)

            # 本此次的训练数据累加
            train_num += b_y.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() # 设置模型为评估模式

            # 前向传播,输入一个批次,输出该批次的对应的预测值
            output = model(b_x)

            # 查找每一行中最大值对应的行标,即预测值
            pre_lab = torch.argmax(output,dim=1)

            # 计算本此次的损失函数
            loss = criterion(output,b_y)

            # 本批次的损失函数累加
            val_loss += loss.item() * b_x.size(0)

            # 如果预测正确,那就本批次的精度度累加
            val_corrents += torch.sum(pre_lab==b_y.data)

            # 当前用于验证的样本数累加
            val_num += b_x.size(0)


        # 计算每轮次的损失值和准确率
        train_loss_all.append(train_loss / train_num) # 本轮训练集的loss值
        train_acc_all.append(train_corrects.double().item() / train_num) # 本轮训练集的准确率

        val_loss_all.append(val_loss / val_num) # 本轮验证集的loss值
        val_acc_all.append(val_corrents.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))

    # 将最佳的模型参数保存
    torch.save(best_model_wts,"best_model.pth") # .pth是权重文件的后缀

    # 保存训练好的模型参数
    train_process = pd.DataFrame(
        data={
            "epoch":range(num_epochs),
            "train_loss_all":train_loss_all,
            "train_acc_all":train_acc_all,
            "val_loss_all":val_loss_all,
            "val_acc_all":val_acc_all
        }
    )
    return train_process


# 定义绘图的函数,绘制loss和准确度
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_acc_all,"bs-",label="val loss")
    plt.legend() # 图例
    plt.xlabel("epoch") # x轴标签
    plt.ylabel("loss") # y轴标签


    # 绘制训练集和验证集的准确度图像
    plt.subplot(1,2,2) # 一行两列,第二列
    plt.plot(train_process['epoch'],train_process.val_loss_all,"ro-",label="train acc")
    plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="va acc")



if __name__ == '__main__':
    # 实例化自定义模型类
    model = VGG16()
    # 加载数据集
    train_dataloader,val_dataloader = train_val_data_process()
    # 训练模型
    train_process = train_model_process(model,train_dataloader,val_dataloader,num_epochs=20)
    # 绘制图像
    matplot_acc_loss(train_process)

训练时间也许要四五个小时。

模型训练完毕之后,就会在本地生成best_model.pth文件,这个文件存放着模型的最佳参数,用于下面的模型测试。

模型测试

创建一个model_test.py文件,用于模型的测试

import torch
import torch.utils.data as Data
from numpy.random import shuffle
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), # 将数据转成224*224大小
            transforms.ToTensor(),# 将数据转成Tensor格式
        ]),
        download=True # 加载数据
    )

    # 通过DataLoader加载器 来加载数据
    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):
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 将模型放入设备中
    model.to(device)

    # 初始化模型训练的每轮参数
    test_correct = 0.0 # 准确度
    test_num = 0 # 测试样本数量

    # 只进行前向传播
    with torch.no_grad(): # 将梯度设置为0
        for test_data_x,test_data_y in test_dataloader: # 遍历每轮次
            # 由于上面设置批次为1,所以这里就不需要循环批次了

            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)

            # 模型预测的结果 将pre_lab 与 标签数据 进行比较
            # 如果预测正确,则加1
            test_correct += torch.sum(pre_lab==test_data_y.data)

            # 测试样本数量累加
            test_num += test_data_y.size(0)

    # 计算最终测试的准确率 每轮的准确度 / 总样本数量
    test_acc = test_correct.double().item() / test_num

    print("测试模型的准确率为:",test_acc)





if __name__ == '__main__':
    # 加载模型
    model = VGG16()
    # 模型具体的训练过程
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 加载训练好的模型最佳参数
    model.load_state_dict(torch.load('best_model.pth',map_location=device))

    # 加载测试的数据集
    test_dataloader = test_data_process()

    # 开始训练
    # test_model_process(model, test_dataloader)

    # 模型具体的训练过程
    # device = torch.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')

    # 梯度设置为0
    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])


如果用的是cpu,而不是gpu,那么可能是pytorch的环境版本问题。

也就是cuda的版本和pytorch不适合

为0
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])



> 如果用的是cpu,而不是gpu,那么可能是pytorch的环境版本问题。
>
> 也就是cuda的版本和pytorch不适合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值