VGGNet详细原理讲解及代码实现(PyTorch)

 一、开发背景

VGGNet在2014年由牛津大学计算机视觉组VGG (Visual Geometry Group) 提出,斩获该年ImageNet竞赛中Localization Task (定位任务) 第一名和Classification Task(分类任务)第二名(第一名是GoogLeNet)。

VGGNet探索了卷积神经网络的深度与其性能之间的关系,成功地构筑了16~19层深的卷积神经网络,证明了增加网络的深度能够在一定程度上影响网络最终的性能,使错误率大幅下降,同时拓展性又很强,迁移到其它图片数据上的泛化性也非常好。到目前为止,VGG仍然被用来提取图像特征。

该网络继承了LeNet以及AlexNet的一些框架,其中最为著名的为VGG16,有16层网络,以此类推。虽然在分类成功率上稍显逊色,但在多个迁移学习任务中表现优于googlenet,同时VGGNet的拓展性很强,迁移到其他图片数据上的泛化性非常好。而且 从图像中提取CNN特征,VGGNet是首选方法。VGG的缺点是需要更大的存储空间,参数总量达140M

二、模型结构

输入是224*224*3的RGB图像,基本是3*3的卷积核和2*2的最大池化层,少部分也引入了1*1卷积核。

结构:VGGNet可以看作是一个加深版本的AlexNet,把网络分成了5段,每段都将多个尺寸为3*3的卷积核串联起来,每段卷积接一个尺寸为2*2的最大池化层,最后面时3个全连接层和一个softmax层,所有神经元的激活函数都采用的ReLU函数。

该网络有很多种类型的网络,深度从11层到19层不等,为了解决权重初始化问题,VGG采用的是一种预训练的方式,这一个过程涉及了迁移学习的思想,其核心思想就是利用一个预训练好的模型(比如VGG11)的权重参数来初始化一个新的模型(比如VGG13),然后在新的任务上进行微调。

步骤如下:

  1. 训练浅层的简单网络 VGG11:首先,你会使用一个相对较浅的简单模型(比如 VGG11)来训练你的任务。这个模型可能在你的任务上表现得还不够好,因为它的容量较小,学习到的特征可能不够丰富。

  2. 利用 VGG11 权重初始化 VGG13:一旦 VGG11 训练完成,你将会使用 VGG11 的权重参数来初始化一个更深的模型,比如 VGG13。这意味着 VGG13 的初始参数将与 VGG11 相似,但它有更多的层次和参数。这个初始化过程可以帮助 VGG13 更快地找到一个好的局部最优解,因为它已经从 VGG11 那里获得了一些有用的特征表示。

  3. 微调 VGG13:接下来,你会使用新任务的数据集来微调 VGG13 模型。在微调过程中,你可以冻结一些层(比如底层的卷积层),只训练顶层的几个全连接层,以避免过拟合和加速收敛。

  4. 重复以上步骤,迁移到更深的模型:你可以继续这个过程,用已经微调好的模型来初始化一个更深的模型,比如用微调好的 VGG13 来初始化 VGG19。这样做可以逐步地增加模型的容量和复杂度,从而更好地适应你的任务。

优点:预训练模型已经学习了一些通用的特征,这些特征对于新任务一般是有用的,会加快模型的收敛。

三、对比试验

放上作者用来做对比实验的6组网络,

实验中图像尺寸设置方式有两种:

  • 固定S:指在训练过程中,将所有图像的短边长度固定为S。这意味着在训练期间,所有输入图像都将被缩放到相同的尺寸。
  • Scale jittering:这是一种数据增强技术,它通过在一个范围内随机选择不同的尺寸(在[Smin, Smax]范围内),对图像进行缩放。这种方法可以增加训练数据的多样性,提高模型的鲁棒性。

然后就是各向同性缩放:这意味着图像将等比例缩放,以保持图像的纵横比。这样做是为了避免图像变形。

然后就是裁剪,在缩放后,图像被裁剪为固定大小的224x224区域。这种尺寸通常用于深度学习模型的训练。Single Scale Evaluation:在测试时,图像的短边被固定为单一尺寸Q,例如256或384。这意味着在测试时,所有图像都将被缩放到相同的尺寸进行评估。

实验结果分析:

1、A、B对比:LRN对网络性能提升没有帮助。
2、从A到E对比: 增加一定程度的网络深度可以提高网络精度,进一步地说,用小卷积核构建出更深的网络,比使用大卷积核的浅层网络要好。
3、B、C对比:引入1 * 1卷积层可以提高网络精度。
4、C、D对比: 有一定大小的感受野(3*3)可以更好地捕捉上下文语境。
5、C\D\E的内部对比:针对训练集图像的Scale jittering也可以提升网络精度。

四、结构特点

最主要的贡献点:发现使用小尺寸的卷积核能够设计比较深的网络。

1、3*3是最小的能够捕捉上下左右和中心概念的尺寸;

2、两个3 * 3的卷积层串联相当于1个5 * 5的卷积层,3个3 * 3的卷积层串联的效果则相当于1个7*7的卷积层;

3、多个3*3的卷积层比1个大尺寸的卷积层有更多的非线性变换(前者可以使用多次ReLU激活函数),使判决函数更加具有判决性;

4、多个3*3的卷积层比1个大尺寸的卷积层有更少的参数;

五、代码

(一)数据集划分

#  划分给定数据集卫训练集和测试集也就是验证集
import os
from shutil import copy, rmtree
import random


def mk_file(file_path: str):
    if os.path.exists(file_path):
        # 如果文件夹存在,则先删除原文件夹在重新创建
        rmtree(file_path)
    os.makedirs(file_path)


def main():
    # 保证随机可复现
    random.seed(0)

    # 将数据集中10%的数据划分到验证集中
    split_rate = 0.1

    # 指向解压后的flower_photos文件夹
    # getcwd():该函数不需要传递参数,获得当前所运行脚本的路径
    cwd = os.getcwd()
    # join():用于拼接文件路径,可以传入多个路径
    data_root = os.path.join(cwd, "flower_data")
    origin_flower_path = os.path.join(data_root, "flower_photos")
    # 确定路径存在,否则反馈错误
    assert os.path.exists(origin_flower_path), "path '{}' does not exist.".format(origin_flower_path)
    # isdir():判断某一路径是否为目录
    # listdir():返回指定的文件夹包含的文件或文件夹的名字的列表
    flower_class = [cla for cla in os.listdir(origin_flower_path)
                    if os.path.isdir(os.path.join(origin_flower_path, cla))]

    # 创建训练集train文件夹,并由类名在其目录下创建子目录
    train_root = os.path.join(data_root, "train")
    mk_file(train_root)
    for cla in flower_class:
        # 建立每个类别对应的文件夹
        mk_file(os.path.join(train_root, cla))

    # 创建验证集val文件夹,并由类名在其目录下创建子目录
    val_root = os.path.join(data_root, "val")
    mk_file(val_root)
    for cla in flower_class:
        # 建立每个类别对应的文件夹
        mk_file(os.path.join(val_root, cla))

    # 遍历所有类别的图像并按比例分成训练集和验证集
    for cla in flower_class:
        cla_path = os.path.join(origin_flower_path, cla)
        # iamges列表存储了该目录下所有图像的名称
        images = os.listdir(cla_path)
        num = len(images)
        # 随机采样验证集的索引
        # 从images列表中随机抽取k个图像名称
        # random.sample:用于截取列表的指定长度的随机数,返回列表
        # eval_index保存验证集val的图像名称
        eval_index = random.sample(images, k=int(num * split_rate))
        for index, image in enumerate(images):
            if image in eval_index:
                # 将分配至验证集中的文件复制到相应目录
                image_path = os.path.join(cla_path, image)
                new_path = os.path.join(val_root, cla)
                copy(image_path, new_path)
            else:
                # 将分配至训练集中的文件复制到相应目录
                image_path = os.path.join(cla_path, image)
                new_path = os.path.join(train_root, cla)
                copy(image_path, new_path)
                # '\r'回车,回到当前行的行首,而不会换到下一行,如果接着输出,本行以前的内容会被逐一覆盖
                # end="":将print自带的换行用end中指定的str代替
            print("\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="")
        print()

    print("processing done!")


if __name__ == '__main__':
    main()

(2)模型代码

# 定义VGGNet网络模型
import torch
import torch.nn as nn

class VGG(nn.Module):# 括号里面是父类
    # init():进行初始化,申明模型中各层的意义
    # features:生成提取特征的网络结构
    # num_class :需要分类的类别个数  VGG网络的不同之处,只是在特征提取上有所不同之处 VGG16,VGG19只是提取特征不同
    # init_weights:是否对网络进行权重初始化
    def __init__(self,features,num_classes=1000,init_weights=False):
        # super引入父类的初始化方法对子类进行初始化
        super(VGG,self).__init__()  # 调用父类的__init__方法,通常是用于定义神经网络框架的一个类
        # 生成提取特征的网络结构
        self.features = features  # 参数赋值给一个实例对象
        # 生成分类的网络结构
        # sequential:自定义顺序连接成网络,生成网络结构
        self.classifier = nn.Sequential(
            # Dropout:随机地将输入中50%地神经元激活为0,即去掉一些神经节点,防止过拟合
            nn.Dropout(p=0.5),
            nn.Linear(512*7*7,4096),
            #RELU(INPLACE=TRUE):将tensor直接修改,不找变量做中间的传递,节省运算内存,不用多存储额外地变量
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096,4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096,num_classes)
        )
        # 如果为真,则对网络参数进行初始化
        if init_weights:
            self._initialize_weights()
        # forward():定义前向传播过程,描述了各层之间恶的关系
    def forward(self,x):
        # 将数据输入到提取特征地网络结构,N*3*224*224
        x = self.features(x)
        #N*512*512*7
        # 图像经过提取地特征网络之后,进行展平,将多维数组进行平坦化处理,神经网络中第0维是batch_size,所以从第二位开始平坦化
        x = torch.flatten(x,start_dim=1)
        # 将数据输入分类网络结构,N*512*512*7
        x = self.classifier(x)
        return x
    #网络结构参数初始化
    def _initialize_weights(self):
         # 遍历网络中的每一层
        # 集成nn。moudle类的一个方法是self.moudles(),他会返回该网络中的所有moudles
        for m in self.modules(): # 遍布了模型的所有模块
            # isijnstance(object,type):如果指定对象是指定类型,则instance()函数返回True
            # 如果是卷积层
            if isinstance(m,nn.Conv2d):
                # unifrom_(tensor,a=0,b=1):服从U(a,b)均匀分布,进行初始化
                nn.init.xavier_uniform_(m.weight) # 使用 Xavier 均匀分布初始化当前卷积层的权重。m.weight 表示当前层的权重参数。
                # 如果偏置不是0,将偏置设为0,对偏置进行初始化
                if m.bias is not None:
                    #constant_(tensor,val):初始化整个矩阵为常熟l
                    nn.init.constant_(m.bias,0)
            elif isinstance(m,nn.Linear):  # 全连接层一定有w和b
                # 正太分布齿梳化
                nn.init.xavier_uniform_(m.weight)
                # 将所有偏置置为0
                nn.init.constant_(m.bias,0)
# 生成提取特征的网络结果
# 参数是网络配置变量,传入对应配置的列表(list类型)
def make_features(cfg: list):
    # 定义空列表,用于创建的每一层的结构
    layers = []  # 因为每个层都是pytorch的对象,添加到layers,这个列表的元素就是一个个的pytorch层对象,然后通过nn。sequential(*layers)来构建网络
    # 输入图像是RGB彩色图片
    in_channels = 3
    # for循环遍历配置列表,得到由卷积层和池化层组成的一个列表
    for v in cfg:
        # 如果列表的值是M字符,说明该层是最大池化层
        if v =="M":
            # 创建一个最大池化层,在VGG中所有的最大池化层的kernel_size=2,stride=2
            layers +=[nn.MaxPool2d(kernel_size=2,stride=2)]
        # 否则是卷积层
        else:
            # inchannels:输入的特征矩阵的深度,v:输出特征矩阵的深度,深度也就是卷积核的个数
            # 在VGG中,所有卷积层的填充为1,步长为1
            conv2d = nn.Conv2d(in_channels,v,kernel_size=3,stride=1,padding=1)
            # 将卷积层和RELU放入列表
            layers+=[conv2d,nn.ReLU(True)]
            in_channels = v
        # 将列表通过非关键字参数的形式返回,*layers可以接受任意数量的参数
    return nn.Sequential(*layers)
# *是用来解包列表的操作符,这个操作会将列表中的所有元素解包成单独的参数传递nn。sequential

# 定义cfgs字典文件,每一个key代表一个模型的配置文件,如:VGG11代表A配置,也就是一个11层的网络
# 数字代表卷积层中卷积核的个数,'M'代表池化层的结构
# 通过函数make_features(cfg: list)生成提取特征网络结构
cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}
# 键是一个字符串,值是一个列表
# 实例化给定的配置模型,这里使用的是VGG16
# **kwargs表示可变长度的字典变量。在调用VGG函数时传入的字典变量
def vgg(model_name="vgg16",**kwargs): # 第二个参数的意思是接受任意数量的关键字参数,这意味着你可以
    # 如果model_name不在cfgs,序会抛出AssertionError错误,报错为参数内容“ ”
    assert model_name in cfgs, "Warning: model number {} not in cfgs dict!".format(model_name)
    # 得到VGG16对应的列表
    cfg = cfgs[model_name]
    # 实例化VGG网络
    # 这个字典变量包含了分类的个数以及是否初始化权重的布尔变量
    model = VGG(make_features(cfg), **kwargs)
    return model


(3)训练代码

# 加载数据集并训练,计算loss和accuracy,保存训练好的网络参数
import os
import sys
import json
import torch
import torch.nn as nn
from torchvision import transforms,datasets
import torch.optim as optim
from tqdm import tqdm

from model import vgg
def main():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))
    data_transfroms ={
        #Compose():将多个transforms的操作整合在仪器
        #训练
        "train":transforms.Compose([
            # RandomResizedCrop(224):将给定图像随机裁剪为不同的大小和宽高比,然后缩放所剪裁得到的图像为给定的大小
            transforms.RandomSizedCrop(224),
            # 以0.5的概率竖直翻转给定的PIL图像
            transforms.RandomHorizontalFlip(),
            # 转化成张量
            transforms.ToTensor(),
            # 图像的像素进行归一化,使模型更容易收敛
            transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),
        ]),
        "val":transforms.Compose([
            transforms.Resize((224,224)),
            transforms.ToTensor(),
            transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
        ])
    }
    #abspath():获取文件当前目录的绝对路径
    # join():用于拼接文件路径,可以传入多个路径
    # getcwd():该函数不需要传递参数,获得当前所运行脚本的路径
    data_root = os.path.abspath(os.getcwd())
    # 得到数据集的路径
    image_path = os.path.join(data_root,"flower_data")
    # exist():判断括号里的文件是否存在,可以是文件路径
    # 如果image_path不存在,则会抛出AssertionError错误,报错为参数内容
    assert os.path.exists(image_path),"{}path does not exist.".format(image_path)
    # 加载训练数据集
    ## ImageFolder:假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名,其构造函数如下:
    # ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
    # root:在指定路径下寻找图片,transform:对PILImage进行的转换操作,输入是使用loader读取的图片
    train_dataset = datasets.ImageFolder(root=os.path.join(image_path,"train"),transform=data_transfroms["train"])
    # 训练集长度
    train_num = len(train_dataset)
    # {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
    # class_to_idx:获取分类名称对应索引
    flower_list = train_dataset.class_to_idx # 获取类别到索引的银蛇关系,返回值是一个字典,键是类别名称,值是对应的索引
    #dict():创建一个新的字典
    # 循环遍历数组索引并交换val和key的值重新赋值给数组,这样模型预测的直接就是value类别值
    cla_dict = dict((val,key)for key,val in flower_list.items())
    # 把字典编码成json格式
    json_str = json.dumps(cla_dict,indent=4) # 将cla_dict转换为JSON格式的字符串,并且通过第二个参数指定了缩进为4个空格,以便让生成的json字符串更具可读性
    # 把字典类别索引写入json文件
    with open('class_indices.json','w')as json_file:
        json_file.write(json_str)
    # 一次训练载入32张图像
    batch_size = 32
    # 确定进程数
    # min():返回给定参数的最小值,参数可以为序列
    #cpu_count():返回一个整数值,表示系统中cpu数量,如果不确定cpu数量,则不返回任何内容
    nw=min([os.cpu_count(),batch_size if batch_size>1 else 0,8])
    print('Using {} dataloader workers every process'.format(nw))
    # DataLoader:将读取的数据按照batch_size大小封装给训练集
    # dataset(DATASET):输入的数据集
    # batch_size(int,optional):每个batch加载多少个样本,默认为1
    # shuffle(bool,optional):设置为True时会在每个epoch重新打乱数据,默认为False
    # num_workers(int,optional):决定了有几个进程来处理,默认为0意味着所有的数据都会被load进主进程
    train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,num_workers=nw
                                               )
    validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
                                            transform=data_transform["val"])
    # 测试集长度
    val_num = len(validate_dataset)

    validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                                  batch_size=batch_size, shuffle=False,
                                                  num_workers=nw)
    print("using {} images for training, {} images for validation.".format(train_num,
                                                                           val_num))
    #模型实例化,将模型转到device
    model_name = "vgg16"
    net = vgg(model_name=model_name,num_class=5,init_weight=True)
    net.to(device)

    # 定义损失函数(交叉熵损失)
    loss_function = nn.CrossEntropyLoss()

    # 定义adam优化器
    #params(iterable):要训练的参数,一般传入的时model。parameters()
    #lr(float):learning_rate学习率,也就是步长,
    optimizer = optim.Adam(net.parameters(),lr=0.0001)

    # 迭代次数(训练次数)
    epochs = 30
    # 用于判断最佳模型
    best_acc = 0.0
    # 最佳模型保存地址
    save_path = './{}Net.pth'.format(model_name)
    train_steps = len(train_loader)
    for epoch in range(epochs):
        # 训练
        net.train()
        running_loss=0.0
        # tqdm:进度条显示
        train_bar = tqdm(train_loader,file=sys.stdout)
        # train_bar:传入数据(数据包括:训练数据和标签)
        # enumerate():将一个可遍历的数据对象(如列表。元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环当中
        # 返回值有两个,一个是序号,一个是数据(训练数据和标签)
        # x:训练数据(jnputs)(tensor类型的),y:标签(labels)(tensor类型)
        for step, data in enumerate(train_bar):
            #前向传播
            image,labels=data
            outputs = net(images.to(device))
            loss = loss_function(outputs,labels.to(device))
            #反向传播
            # 清空过往梯度)
            optimizer.zero_grad()
            # 反向传播,计算当前梯度
            loss.backward()
            # 根据梯度更新网络参数
            optimizer.step()
            # item():得到元素张量的元素值
            running_loss+=loss.item()
            # 进度条的前缀
            # .3f:表示浮点数的精度为3(小数位保留3位)
            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
                                                                     epochs,
                                                                     loss)
            # 测试
            # eval():如果模型中有batchnormalization和dropout,则不启用,为了防止改变权值
            net.eval()
            acc=0.0
            # 清空历史梯度,与训练最大的区别是测试过程中取消了反向传播
            with torch.no_grad():
                val_bar = tqdm(validate_loader, file=sys.stdout)
                for val_data in val_bar:
                    val_images, val_labels = val_data
                    outputs = net(val_images.to(device))
                    # torch.max(input, dim)函数
                    # input是具体的tensor,dim是max函数索引的维度,0是每列的最大值,1是每行的最大值输出
                    # 函数会返回两个tensor,第一个tensor是每行的最大值;第二个tensor是每行最大值的索引
                    predict_y = torch.max(outputs, dim=1)[1]
                    # 对两个张量Tensor进行逐元素的比较,若相同位置的两个元素相同,则返回True;若不同,返回False
                    # .sum()对输入的tensor数据的某一维度求和
                    acc += torch.eq(predict_y, val_labels.to(device)).sum().item()

            val_accurate = acc / val_num
            print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %
                  (epoch + 1, running_loss / train_steps, val_accurate))

            # 保存最好的模型权重
            if val_accurate > best_acc:
                best_acc = val_accurate
                # torch.save(state, dir)保存模型等相关参数,dir表示保存文件的路径+保存文件名
                # model.state_dict():返回的是一个OrderedDict,存储了网络结构的名字和对应的参数
                torch.save(net.state_dict(), save_path)

        print('Finished Training')

if __name__ == '__main__':
    main()

(4)测试代码

#用自己的书记进行分类测试
import os
import json

import torch
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt

from model import vgg

def main():
    # 如果有NVIDA显卡,转到GPU训练,否则用CPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # 将多个transforms的操作整合在一起
    data_transform = transforms.Compose(
        [transforms.Resize((224, 224)),
         transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

    # 加载图片
    img_path = "./tulip.jpg"
    # 确定图片存在,否则反馈错误
    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    img = Image.open(img_path)
    # imshow():对图像进行处理并显示其格式,show()则是将imshow()处理后的函数显示出来
    plt.imshow(img)
    # [C, H, W],转换图像格式
    img = data_transform(img)
    # [N, C, H, W],增加一个维度N
    img = torch.unsqueeze(img, dim=0)

    # 获取结果类型
    json_path = './class_indices.json'
    # 确定路径存在,否则反馈错误
    assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)
    # 读取内容
    with open(json_path, "r") as f:
        class_indict = json.load(f)

    # 模型实例化,将模型转到device,结果类型有5种
    model = vgg(model_name="vgg16", num_classes=5).to(device)
    # 载入模型权重
    weights_path = "./vgg16Net.pth"
    # 确定模型存在,否则反馈错误
    assert os.path.exists(weights_path), "file: '{}' dose not exist.".format(weights_path)
    model.load_state_dict(torch.load(weights_path, map_location=device))

    # 进入验证阶段
    model.eval()
    with torch.no_grad():
        # 预测类别
        # squeeze():维度压缩,返回一个tensor(张量),其中input中大小为1的所有维都已删除
        output = torch.squeeze(model(img.to(device))).cpu()
        # softmax:归一化指数函数,将预测结果输入进行非负性和归一化处理,最后将某一维度值处理为0-1之内的分类概率
        predict = torch.softmax(output, dim=0)
        # argmax(input):返回指定维度最大值的序号
        # .numpy():把tensor转换成numpy的格式
        predict_cla = torch.argmax(predict).numpy()

    # 输出的预测值与真实值
    print_res = "class: {}   prob: {:.3}".format(class_indict[str(predict_cla)],
                                                 predict[predict_cla].numpy())
    # 图片标题
    plt.title(print_res)
    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(class_indict[str(i)],
                                                  predict[i].numpy()))
    plt.show()


if __name__ == '__main__':
    main()

还没写完,随时都会更改。

六、参考内容

CNN经典网络模型(三):VGGNet简介及代码实现(PyTorch超详细注释版)_vggnet是哪年发明的-CSDN博客

VGG详解-CSDN博客

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是用PyTorch实现VGG网络的示例代码: ```python import torch import torch.nn as nn # 定义VGG网络的结构 cfg = { 'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], 'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'] } class VGG(nn.Module): def __init__(self, vgg_name): super(VGG, self).__init__() self.features = self._make_layers(cfg[vgg_name]) self.classifier = nn.Linear(512, 10) # 假设分类任务有10个类别 def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x def _make_layers(self, cfg): layers = [] in_channels = 3 # 输入图像的通道数 for x in cfg: if x == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] else: layers += [nn.Conv2d(in_channels, x, kernel_size=3, padding=1), nn.BatchNorm2d(x), nn.ReLU(inplace=True)] in_channels = x layers += [nn.AvgPool2d(kernel_size=1, stride=1)] return nn.Sequential(*layers) # 创建VGG网络实例 model = VGG('VGG16') # 打印VGG网络结构 print(model) ``` 此代码定义了一个VGG类,可以通过构造函数中的参数来选择不同的VGG版本(VGG11、VGG13、VGG16或VGG19)。然后,使用_make_layers函数创建VGG网络的层,并在forward方法中定义网络的前向传播过程。最后,利用VGG类创建了一个model实例,并打印出网络结构。你可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值