笔记:现代卷积神经网络之VGG

news2024/12/25 9:02:43

本文为李沐老师《动手学深度学习》笔记小结,用于个人复习并记录学习历程,适用于初学者

神经网络架构设计的模块化

然AlexNet证明深层神经网络卓有成效,但它没有提供一个通用的模板来指导后续的研究人员设计新的网络。 在下面的几个章节中,我们将介绍一些常用于设计深层神经网络的启发式概念。

与芯片设计中工程师从放置晶体管到逻辑元件再到逻辑块的过程类似,神经网络架构的设计也逐渐变得更加抽象。研究人员开始从单个神经元的角度思考问题,发展到整个层,现在又转向块,重复层的模式。

使用块的想法首先出现在牛津大学的视觉几何组(visual geometry group)的VGG网络中。通过使用循环和子程序,可以很容易地在任何现代深度学习框架的代码中实现这些重复的架构。

VGG架构

VGG块

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

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

而一个VGG块与之类似,由一系列卷积层组成,后面再加上用于空间下采样的最大汇聚层。在最初的VGG论文中 ,作者了带有3×3卷积核、填充为1(保持高度和宽度)的卷积层,和带有2×2汇聚窗口、步幅为2(每个块后的分辨率减半)的最大汇聚层。

import torch
from torch import nn

def vgg_block(num_convs, in_channels, out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels, out_channels,
                                kernel_size=3, padding=1))
        layers.append(nn.ReLU())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
    return nn.Sequential(*layers)
VGG网络

与AlexNet、LeNet一样,VGG网络可以分为两部分:第一部分主要由卷积层和汇聚层组成,第二部分由全连接层组成。

原始VGG网络有5个卷积块,其中前两个块各有一个卷积层,后三个块各包含两个卷积层。 第一个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。由于该网络使用8个卷积层和3个全连接层,因此它通常被称为VGG-11。

这里我们构建比书上还要小的网络用于训练。 

def vgg(conv_arch):
    conv_blks = []
    in_channels = 1
    # 卷积层部分
    for (num_convs, out_channels) in conv_arch:
        conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
        in_channels = out_channels

    return nn.Sequential(
        *conv_blks, nn.Flatten(),
        # 全连接层部分
        nn.Linear(out_channels * 5 * 5, 1024), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(1024, 128), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(128, 10))

conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512)) #这里(2,256)2指重复两次
net = vgg(conv_arch)

接下来,我们将构建一个高度和宽度为160的单通道数据样本,以[观察每个层输出的形状]。

X = torch.randn(size=(1, 1, 160, 160))
for blk in net:
    X = blk(X)
    print(blk.__class__.__name__,'output shape:\t',X.shape)

输出:

Sequential output shape:	 torch.Size([1, 64, 80, 80])
Sequential output shape:	 torch.Size([1, 128, 40, 40])
Sequential output shape:	 torch.Size([1, 256, 20, 20])
Sequential output shape:	 torch.Size([1, 512, 10, 10])
Sequential output shape:	 torch.Size([1, 512, 5, 5])
Flatten output shape:	 torch.Size([1, 12800])
Linear output shape:	 torch.Size([1, 1024])
ReLU output shape:	 torch.Size([1, 1024])
Dropout output shape:	 torch.Size([1, 1024])
Linear output shape:	 torch.Size([1, 128])
ReLU output shape:	 torch.Size([1, 128])
Dropout output shape:	 torch.Size([1, 128])
Linear output shape:	 torch.Size([1, 10])

训练模型

缩小模型

拿Fashion-MNIST数据集,我们可以再次减小通道数。

ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)

X = torch.randn(size=(1, 1, 160, 160))
for blk in net:
    X = blk(X)
    print(blk.__class__.__name__,'output shape:\t',X.shape)

输出:

Sequential output shape:	 torch.Size([1, 16, 80, 80])
Sequential output shape:	 torch.Size([1, 32, 40, 40])
Sequential output shape:	 torch.Size([1, 64, 20, 20])
Sequential output shape:	 torch.Size([1, 128, 10, 10])
Sequential output shape:	 torch.Size([1, 128, 5, 5])
Flatten output shape:	 torch.Size([1, 3200])
Linear output shape:	 torch.Size([1, 1024])
ReLU output shape:	 torch.Size([1, 1024])
Dropout output shape:	 torch.Size([1, 1024])
Linear output shape:	 torch.Size([1, 128])
ReLU output shape:	 torch.Size([1, 128])
Dropout output shape:	 torch.Size([1, 128])
Linear output shape:	 torch.Size([1, 10])
预备工作
from IPython import display
import torchvision
from torch.utils import data
from torchvision import transforms
import matplotlib.pyplot as plt

def load_data_fashion_mnist(batch_size, resize=None): 
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=0)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=0)
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))
 
def get_dataloader_workers():  
    """使用4个进程来读取数据"""
    return 4

batch_size = 128
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=160)

def accuracy(y_hat, y):  #@save
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = y_hat.argmax(axis=1) #找出输入张量(tensor)中最大值的索引
    cmp = y_hat.type(y.dtype) == y
    return float(cmp.type(y.dtype).sum())
class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n
 
    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]
 
    def reset(self):
        self.data = [0.0] * len(self.data)
 
    def __getitem__(self, idx):
        return self.data[idx]

import matplotlib.pyplot as plt
from matplotlib_inline import backend_inline
 
def use_svg_display(): 
    """使⽤svg格式在Jupyter中显⽰绘图"""
    backend_inline.set_matplotlib_formats('svg')
 
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
     """设置matplotlib的轴"""
     axes.set_xlabel(xlabel)
     axes.set_ylabel(ylabel)
     axes.set_xscale(xscale)
     axes.set_yscale(yscale)
     axes.set_xlim(xlim)
     axes.set_ylim(ylim)
     if legend:
         axes.legend(legend)
     axes.grid()
 
class Animator:  #@save
    """在动画中绘制数据"""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # 增量地绘制多条线
        if legend is None:
            legend = []
        use_svg_display()
        self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ]
        # 使用lambda函数捕获参数
        self.config_axes = lambda: set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts
 
    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)

def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
    """使用GPU计算模型在数据集上的精度"""
    if isinstance(net, nn.Module):
        net.eval()  # 设置为评估模式
        if not device:
            device = next(iter(net.parameters())).device
    # 正确预测的数量,总预测的数量
    metric = Accumulator(2)
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(X, list):
                # BERT微调所需的(之后将介绍)
                X = [x.to(device) for x in X]
            else:
                X = X.to(device)
            y = y.to(device)
            metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

import time
class Timer:  #@save
    """记录多次运行时间"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """启动计时器"""
        self.tik = time.time()

    def stop(self):
        """停止计时器并将时间记录在列表中"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """返回平均时间"""
        return sum(self.times) / len(self.times)

    def sum(self):
        """返回时间总和"""
        return sum(self.times)

    def cumsum(self):
        """返回累计时间"""
        return np.array(self.times).cumsum().tolist()

def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """用GPU训练模型(在第六章定义)"""
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = Timer(), len(train_iter)
    for epoch in range(num_epochs):
        # 训练损失之和,训练准确率之和,样本数
        metric = Accumulator(3)
        net.train()
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l * X.shape[0], accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (train_l, train_acc, None))
        test_acc = evaluate_accuracy_gpu(net, test_iter)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
          f'test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')

def try_gpu(i=0):  #@save
    """如果存在,则返回gpu(i),否则返回cpu()"""
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')
模型训练
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=160)
begin = time.time()
train_ch6(net, train_iter, test_iter, num_epochs, lr, try_gpu())
end = time.time()
print(end - begin)

这个结果可以说相当不错,对比我完全没调整参数的AlexNet,不仅训练速度更快,并且效果更好。

 小结

  • VGG-11使用可复用的卷积块构造网络。不同的VGG模型可通过每个块中卷积层数量和输出通道数量的差异来定义。
  • 块的使用导致网络定义的非常简洁。使用块可以有效地设计复杂的网络。
  • 在VGG论文中,Simonyan和Ziserman尝试了各种架构。特别是他们发现深层且窄的卷积(即3×3)比较浅层且宽的卷积更有效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1940590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言-栈和队列

文章目录 🎯引言👓栈和队列1.栈1.1栈的概念与结构1.2栈的实现 2.队列2.1队列的概念与结构2.2队列的实现 🥇结语 🎯引言 欢迎来到HanLop博客的C语言数据结构初阶系列。在之前的文章中,我们详细介绍了链表及其操作方法。…

LabVIEW多线圈电磁式振动发电机测试

开发了一种基于LabVIEW设计的多线圈电磁式振动发电机测试系统。系统通过高效的数据采集、波峰检测及相位差计算,优化了传统振动发电机的测试流程,提升了电压波形分析的精度和效率,具有较好的应用前景和推广价值。 项目背景 随着可再生能源技…

【python】Numpy运行报错详细分析:IndexError: too many indices for array

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

Hadoop-37 HBase集群 JavaAPI 操作3台云服务器 POM 实现增删改查调用操作 列族信息 扫描全表

点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: HadoopHDFSMapReduceHiveFlumeSqoopZookeeperHBase 正在 章节内容 上一节我们完成了: HBase …

docker tomcat 404

HTTP 404状态码表示“Not Found”,即服务器无法找到请求的页面。 当用户尝试访问一个不存在的网页时,服务器会返回这个状态码。这个状态码是HTTP协议的一部分,用于告知客户端(通常是浏览器)服务器无法完成请求。404状…

springboot校园跑腿服务系统-计算机毕业设计源码15157

摘要 本文介绍了一种基于Springboot和uniapp的校园跑腿服务系统的设计与实现。该系统旨在为大学校园提供一种方便快捷的跑腿服务,满足学生和教职员工的日常需求。首先,系统采用了Springboot作为后端框架,利用其轻量级、高效的特性&#xff0c…

抖音短视频seo矩阵系统源码开发技术分享(二)--SaaS开源

目录 市场背景分析 一、抖音短视频seo矩阵系统开发部署流程 二、 源码开发功能构思 三、 抖音短视频seo源码开发部署注意事项 四、 部分开发代码展示 市场背景分析 抖音短视频seo矩阵系统是通过不同平台不同账号之间建立联系,通过将同一品牌下不同平台不同账号…

操作系统(3)——内存管理

目录 小程一言专栏链接: [link](http://t.csdnimg.cn/6grrU)内存管理无存储器抽象存储器抽象实现以下几方面小结 虚拟内存实现以下方面总结 页面置换算法概述常见的页面置换算法先进先出(FIFO)算法最近最少使用(LRU)算法总结 小程…

idea如何让包结构分层

文章目录 前言1.选中前项目包结构2.取消后项目包结构3.情况二 前言 在大型项目中,代码的分层管理至关重要。IDEA编辑器提供了强大的package分层结构功能,帮助开发者更好地组织和管理代码。通过合理配置,我们可以清晰地看到各个package之间的…

Stable Diffusion 使用详解(1)---- 提示词及相关参数

目录 背景 提示词 内容提示词 人物及主体特征 场景 环境光照 画幅视角 注意事项及示例 标准化提示词 画质等级 风格与真实性 具体要求 背景处理 光线与色彩 负向提示词 小结 常用工具 另外几个相关参数 迭代步数 宽度与高度 提示词引导系数 图片数量 背景…

MongoDB教程(十三):MongoDB覆盖索引

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言什么是覆盖…

开放式牙耳机选哪种?2024五大新晋爆卖机型精选!

开放式蓝牙耳机不会因为耳机与耳朵的贴合度不够而影响音质。此外,开放式蓝牙耳机的外形设计更加时尚,更加符合现代人的审美需求。开放式蓝牙耳机的出现不仅解决了传统入耳式蓝牙耳机佩戴不适的问题,还具有更加舒适、健康、自然、时尚等多重优…

小白可用超稳定内网穿透工具——natapp全方位使用教程(合法合规)

本篇博客仅供学习参考使用!!! 2021年11月14日,国家网信办发布《网络数据安全管理条例(征求意见稿)》,《条例》第41条第2款规定,任何个人和组织不得提供用于穿透、绕过数据跨境安全网关的程序、工具、线路等…

实验07 接口测试postman

目录 知识点 1 接口测试概念 1.1为什么要做接口测试 1.2接口测试的优点 1.3接口测试概念 1.4接口测试原理和目的 2 接口测试内容 2.1测什么 2.1.1单一接口 2.1.2组合接口 2.1.3结构检查 2.1.4调用方式 2.1.5参数格式校验 2.1.6返回结果 2.2四大块 2.2.1功能逻辑…

降低物联网开发门槛的TuyaOS操作系统重磅更新:AI赋能设备升级,配网速度10倍提升,改变传统开发方式

作为降低智能解决方案开发门槛的 TuyaOS 操作系统,此次又迎来了重大更新(点击查看 TuyaOS 完整介绍)! 本次 TuyaOS 3.10.0 版本发布了超丰富的开发框架,覆盖多种协议连接和平台,可供开发者更快速便捷地接入…

直播领夹式麦克风哪个品牌好?直播麦克风十大排行榜推荐

​在这个充满活力与创意的时代,无线领夹麦克风成为了我们捕捉声音的得力助手。无论是在熙熙攘攘的美食街探店,还是在安静的书房进行录制,还是在嘈杂的户外采访,无线领夹麦克风都能出色地完成任务。很多朋友都曾为麦克风的选择而烦…

数据字典的解释

一、没有数据字典的时候,一般通过备注来标明,数据项不同数值所代表的不同含义。 如下图所示,但这样不够灵活。 二、引入数据字典的形式 数据字典由两张表组成,分别是字典类型表和字典数据表。 字典类型表中的字段都是存在多个值…

【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析

初阶数据结构相关知识点可以通过点击以下链接进行学习一起加油!时间与空间复杂度的深度剖析深入解析顺序表:探索底层逻辑深入解析单链表:探索底层逻辑深入解析带头双向循环链表:探索底层逻辑深入解析栈:探索底层逻辑深入解析队列:探索底层逻辑深入解析循环队列:探索…

部分功能的实现和算法

目录 1.雪花算法 2.MD5加密 3.小眼睛显示密码 4.发送验证码 5.倒计时 1.雪花算法 SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本 雪花算法的原理就是生成一个的 64 位比特…

探索XEX数字资产交易的优势与操作指南

随着数字资产市场的快速发展,越来越多的投资者开始关注并参与其中。XEX交易所作为一个新兴的数字资产交易平台,以其用户友好的界面和高效的交易服务,迅速吸引了大量用户。本文将介绍XEX数字资产交易的主要特点和优势,帮助新手更好…