【深度学习】经典算法解读及代码复现AlexNet-VGG-GoogLeNet-ResNet(二)

news2024/12/22 23:00:10

链接: 【深度学习】经典算法解读及代码复现AlexNet-VGG-GoogLeNet-ResNet(一)

4.GoogLeNet

4.1.网络模型

GoogLeNet的名字不是GoogleNet,而是GoogLeNet,这是为了致敬LeNet。GoogLeNet和AlexNet/VGGNet这类依靠加深网络结构的深度的思想不完全一样。GoogLeNet在加深度的同时做了结构上的创新,引入了一个叫做Inception的结构来代替之前的卷积加激活的经典组件。
GoogLeNet中的基础卷积块叫作Inception块,得名于同名电影《盗梦空间》(Inception)。Inception块在结构比较复杂,如下图所示:
在这里插入图片描述
需要说明四点:
1 . 采用不同大小的卷积核意味着不同大小的感受野,最后拼接意味着不同尺度特征的融合;
2 . 之所以卷积核大小采用1、3和5,主要是为了方便对齐。设定卷积步长stride=1之后,只要分别设定pad=0、1、2,那么卷积之后便可以得到相同维度的特征,然后这些特征就可以直接拼接在一起了;
3. 直接max pooling
4. 如果简单的将这些应用到feature map上的话,concat起来的feature map厚度将会很大,所以在googlenet中为了避免这一现象提出的inception具有如下结构,在3x3前,5x5前,max pooling后分别加上了1x1的卷积核起到了降低feature map厚度的作用,如下图所示。
在这里插入图片描述
了解完Inception后,我们看看GoogLeNet的整个结构,如下:

在这里插入图片描述

4.2.代码复现

代码如下:

# 导入相应的包
import tensorflow as tf
import numpy as np
import cv2
from tensorflow.keras.datasets import mnist
# 定义inception模块
# 定义Inception模块
class Inception(tf.keras.layers.Layer):
    # 输入参数为各个卷积的卷积核个数
    def __init__(self, c1, c2, c3, c4):
        super(Inception,self).__init__()
        # 线路1:1 x 1卷积层,激活函数是RELU,padding是same
        self.p1_1 = tf.keras.layers.Conv2D(
            c1, kernel_size=1, activation='relu', padding='same')
        # 线路2,1 x 1卷积层后接3 x 3卷积层,激活函数是RELU,padding是same
        self.p2_1 = tf.keras.layers.Conv2D(
            c2[0], kernel_size=1, padding='same', activation='relu')
        self.p2_2 = tf.keras.layers.Conv2D(c2[1], kernel_size=3, padding='same',
                                           activation='relu')
        # 线路3,1 x 1卷积层后接5 x 5卷积层,激活函数是RELU,padding是same
        self.p3_1 = tf.keras.layers.Conv2D(
            c3[0], kernel_size=1, padding='same', activation='relu')
        self.p3_2 = tf.keras.layers.Conv2D(c3[1], kernel_size=5, padding='same',
                                           activation='relu')
        # 线路4,3 x 3最大池化层后接1 x 1卷积层,激活函数是RELU,padding是same
        self.p4_1 = tf.keras.layers.MaxPool2D(
            pool_size=3, padding='same', strides=1)
        self.p4_2 = tf.keras.layers.Conv2D(
            c4, kernel_size=1, padding='same', activation='relu')
    # 完成前向传播过程
    def call(self, x):
        # 线路1
        p1 = self.p1_1(x)
        # 线路2
        p2 = self.p2_2(self.p2_1(x))
        # 线路3
        p3 = self.p3_2(self.p3_1(x))
        # 线路4
        p4 = self.p4_2(self.p4_1(x))
        # 在通道维上concat输出
        outputs = tf.concat([p1, p2, p3, p4], axis=-1)
        return outputs  

搭建网络模型

# B1模块
# 第一模块使用一个64通道的7×7卷积层
# 定义模型的输入
inputs = tf.keras.Input(shape=(224,224,1),name = "input")
# b1 模块
# 卷积层7*7的卷积核,步长为2,pad是same,激活函数RELU
x = tf.keras.layers.Conv2D(64, kernel_size=7, strides=2, padding='same', activation='relu')(inputs)
# 最大池化:窗口大小为3*3,步长为2,pad是same
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
# b2 模块
# 第二模块使用2个卷积层:首先是64通道的1×1卷积层,然后是将通道增大3倍的3×3卷积层
# 卷积层1*1的卷积核,步长为2,pad是same,激活函数RELU
x = tf.keras.layers.Conv2D(64, kernel_size=1, padding='same', activation='relu')(x)
# 卷积层3*3的卷积核,步长为2,pad是same,激活函数RELU
x = tf.keras.layers.Conv2D(192, kernel_size=3, padding='same', activation='relu')(x)
# 最大池化:窗口大小为3*3,步长为2,pad是same
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
# B3模块
# 第三模块串联2个完整的Inception块。第一个Inception块的输出通道数为64+128+32+32=256。第二个Inception块输出通道数增至128+192+96+64=480
# Inception
x = Inception(64, (96, 128), (16, 32), 32)(x)
# Inception
x = Inception(128, (128, 192), (32, 96), 64)(x)
# 最大池化:窗口大小为3*3,步长为2,pad是same
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
# B4模块
# 第四模块更加复杂。它串联了5个Inception块,其输出通道数分别是192+208+48+64=512、160+224+64+64=512、128+256+64+64=512、
# 112+288+64+64=528和256+320+128+128=832
def aux_classifier(x, filter_size):
    #x:输入数据,filter_size:卷积层卷积核个数,全连接层神经元个数
    # 池化层
    x = tf.keras.layers.AveragePooling2D(
        pool_size=5, strides=3, padding='same')(x)
    # 1x1 卷积层
    x = tf.keras.layers.Conv2D(filters=filter_size[0], kernel_size=1, strides=1,
                               padding='valid', activation='relu')(x)
    # 展平
    x = tf.keras.layers.Flatten()(x)
    # 全连接层1
    x = tf.keras.layers.Dense(units=filter_size[1], activation='relu')(x)
    # softmax输出层
    x = tf.keras.layers.Dense(units=10, activation='softmax')(x)
    return x
# b4 模块
# Inception
x = Inception(192, (96, 208), (16, 48), 64)(x)
# 辅助输出1
aux_output_1 = aux_classifier(x, [128, 1024])
# Inception
x = Inception(160, (112, 224), (24, 64), 64)(x)
# Inception
x = Inception(128, (128, 256), (24, 64), 64)(x)
# Inception
x = Inception(112, (144, 288), (32, 64), 64)(x)
# 辅助输出2
aux_output_2 = aux_classifier(x, [128, 1024])
# Inception
x = Inception(256, (160, 320), (32, 128), 128)(x)
# 最大池化
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
# B5模块
# 第五模块有输出通道数为256+320+128+128=832和384+384+128+128=1024的两个Inception块。后面紧跟输出层,
# 该模块使用全局平均池化层(GAP)来将每个通道的高和宽变成1。最后输出变成二维数组后接输出个数为标签类别数的全连接层
# b5 模块
# Inception
x = Inception(256, (160, 320), (32, 128), 128)(x)
# Inception
x = Inception(384, (192, 384), (48, 128), 128)(x)
# GAP
x = tf.keras.layers.GlobalAvgPool2D()(x)
# 输出层
main_outputs = tf.keras.layers.Dense(10,activation='softmax')(x)
# 使用Model来创建模型,指明输入和输出
model = tf.keras.Model(inputs=inputs, outputs=[main_outputs]) 
print(model.summary())


# 指定优化器,损失函数和评价指标
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.0)

model.compile(optimizer=optimizer,
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

数据预处理:将mnist数据转换为GoogLeNet需要的数据,由于笔记本电脑性能有限,挑选部分数据进行训练展示。代码如下:


# 获取手写数字数据集
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images_224 = []
test_images_224 = []
for i in train_images[:1000]:
    train_images = cv2.resize(i,(224,224))
    train_images_224.append(train_images)
    new_train_image = np.expand_dims(np.array(train_images_224),axis=3)
for i in test_images[:200]:
    test_images = cv2.resize(i,(224,224))
    test_images_224.append(test_images)
    new_test_image = np.expand_dims(np.array(test_images_224),axis=3)
print(new_train_image.shape,new_test_image.shape)

在这里插入图片描述
训练模型

# 模型训练:指定训练数据,batchsize,epoch,验证集
model.fit(new_train_image,train_labels[:1000],batch_size=128,epochs=3,verbose=1,validation_split=0.1)

在这里插入图片描述
验证模型

model.evaluate(new_test_image,test_labels[:200])

在这里插入图片描述

5.ResNet

5.1.网络模型

VGGNet和GoogLeNet都显示了网络有足够的深度是模型表现良好的前提,但是在网络达到一定深度之后,简单的网络堆叠反而效果变差了。ResNet指出,在许多的数据库上都显示出一个普遍的现象:增加网络深度到一定程度时,更深的网络意味着更高的训练误差。误差升高的原因是网络越深,梯度消失的现象就越明显,所以在后向传播的时候,无法有效的把梯度更新到前面的网络层,靠前的网络层参数无法更新,导致训练和测试效果变差。所以ResNet面临的问题是怎样在增加网络深度的情况下有可以有效解决梯度消失的问题。
在这里插入图片描述
ResNet中解决深层网络梯度消失的问题的核心结构是残差网络。假设 F(x) 代表某个只包含有两层的映射函数, x 是输入, F(x)是输出。假设他们具有相同的维度。在训练的过程中我们希望能够通过修改网络中的 w和b去拟合一个理想的 H(x)(从输入到输出的一个理想的映射函数)。也就是我们的目标是修改F(x) 中的 w和b逼近 H(x) 。如果我们改变思路,用F(x) 来逼近 H(x)-x ,那么我们最终得到的输出就变为 F(x)+x(这里的加指的是对应位置上的元素相加,也就是element-wise addition),这里将直接从输入连接到输出的结构也称为shortcut,那整个结构就是残差块,ResNet的基础模块。
在这里插入图片描述
ResNet沿用了VGG全3×3卷积层的设计。残差块里首先有2个有相同输出通道数的3×3卷积层。每个卷积层后接BN层和ReLU激活函数,然后将输入直接加在最后的ReLU激活函数前,这种结构用于层数较少的神经网络中,比如ResNet34。若输入通道数比较多,就需要引入1×1卷积层来调整输入的通道数,这种结构也叫作瓶颈模块,通常用于网络层数较多的结构中。如下图所示:
在这里插入图片描述

5.2.代码复现

代码如下:

# 导入相关的工具包
import tensorflow as tf
from tensorflow.keras import layers, activations


# 定义ResNet的残差块
class Residual(tf.keras.Model):
    # 指明残差块的通道数,是否使用1*1卷积,步长
    def __init__(self, num_channels, use_1x1conv=False, strides=1):
        super(Residual, self).__init__()
        # 卷积层:指明卷积核个数,padding,卷积核大小,步长
        self.conv1 = layers.Conv2D(num_channels,
                                   padding='same',
                                   kernel_size=3,
                                   strides=strides)
        # 卷积层:指明卷积核个数,padding,卷积核大小,步长
        self.conv2 = layers.Conv2D(num_channels, kernel_size=3, padding='same')
        if use_1x1conv:
            self.conv3 = layers.Conv2D(num_channels,
                                       kernel_size=1,
                                       strides=strides)
        else:
            self.conv3 = None
        # 指明BN层
        self.bn1 = layers.BatchNormalization()
        self.bn2 = layers.BatchNormalization()

    # 定义前向传播过程
    def call(self, X):
        # 卷积,BN,激活
        Y = activations.relu(self.bn1(self.conv1(X)))
        # 卷积,BN
        Y = self.bn2(self.conv2(Y))
        # 对输入数据进行1*1卷积保证通道数相同
        if self.conv3:
            X = self.conv3(X)
        # 返回与输入相加后激活的结果
        return activations.relu(Y + X)
    
# ResNet网络中模块的构成
class ResnetBlock(tf.keras.layers.Layer):
    # 网络层的定义:输出通道数(卷积核个数),模块中包含的残差块个数,是否为第一个模块
    def __init__(self,num_channels, num_residuals, first_block=False):
        super(ResnetBlock, self).__init__()
        # 模块中的网络层
        self.listLayers=[]
        # 遍历模块中所有的层
        for i in range(num_residuals):
            # 若为第一个残差块并且不是第一个模块,则使用1*1卷积,步长为2(目的是减小特征图,并增大通道数)
            if i == 0 and not first_block:
                self.listLayers.append(Residual(num_channels, use_1x1conv=True, strides=2))
            # 否则不使用1*1卷积,步长为1 
            else:
                self.listLayers.append(Residual(num_channels))      
    # 定义前向传播过程
    def call(self, X):
        # 所有层依次向前传播即可
        for layer in self.listLayers.layers:
            X = layer(X)
        return X
# ResNet的前两层跟之前介绍的GoogLeNet中的一样:在输出通道数为64、步幅为2的7×7卷积层后接步幅为2的3×3的最大池化层。
# 不同之处在于ResNet每个卷积层后增加了BN层,接着是所有残差模块,最后,与GoogLeNet一样,加入全局平均池化层(GAP)后接上全连接层输出。
# 构建ResNet网络
class ResNet(tf.keras.Model):
    # 初始化:指定每个模块中的残差快的个数
    def __init__(self,num_blocks):
        super(ResNet, self).__init__()
        # 输入层:7*7卷积,步长为2
        self.conv=layers.Conv2D(64, kernel_size=7, strides=2, padding='same')
        # BN层
        self.bn=layers.BatchNormalization()
        # 激活层
        self.relu=layers.Activation('relu')
        # 最大池化层
        self.mp=layers.MaxPool2D(pool_size=3, strides=2, padding='same')
        # 第一个block,通道数为64
        self.resnet_block1=ResnetBlock(64,num_blocks[0], first_block=True)
        # 第二个block,通道数为128
        self.resnet_block2=ResnetBlock(128,num_blocks[1])
        # 第三个block,通道数为256
        self.resnet_block3=ResnetBlock(256,num_blocks[2])
        # 第四个block,通道数为512
        self.resnet_block4=ResnetBlock(512,num_blocks[3])
        # 全局平均池化
        self.gap=layers.GlobalAvgPool2D()
        # 全连接层:分类
        self.fc=layers.Dense(units=10,activation=tf.keras.activations.softmax)
    # 前向传播过程
    def call(self, x):
        # 卷积
        x=self.conv(x)
        # BN
        x=self.bn(x)
        # 激活
        x=self.relu(x)
        # 最大池化
        x=self.mp(x)
        # 残差模块
        x=self.resnet_block1(x)
        x=self.resnet_block2(x)
        x=self.resnet_block3(x)
        x=self.resnet_block4(x)
        # 全局平均池化
        x=self.gap(x)
        # 全链接层
        x=self.fc(x)
        return x
# 模型实例化:指定每个block中的残差块个数 
mynet=ResNet([2,2,2,2])

X = tf.random.uniform(shape=(1,  224, 224 , 1))
y = mynet(X)
mynet.summary()

在这里插入图片描述

6.总结

到这里,我将经典的深度学习算法AlexNet,VGG,GoogLeNet,ResNet模型进行了原理介绍,以及使用pytorch和tensorflow完成代码的复现,希望对大家有所帮助。

走过路过记得点赞哦

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

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

相关文章

创建Vue3项目以及引入Element-Plus

创建Vue3项目以及引入Element-Plus 前提条件:本地需要有node环境以及安装了npm,最好设置了镜像,这样下载包的时候会快些。 1、安装vue脚手架vue-cli3 npm install vue/cli -g2、安装后查看vue的版本 vue -V3、创建Vue项目,项目…

通信电子、嵌入式类面试题刷题计划01

文章目录001——什么是奈奎斯特采样定理?002——有源滤波器和无源滤波器的区别是什么?003——什么是反馈电路?请举出相关应用004——什么是竞争冒险现象?如何消除和避免此类现象005——什么是基尔霍夫定理?006——if e…

揣着一口袋的阳光满载而归--爱摸鱼的美工(13)

-----------作者:天涯小Y 揣着一口袋的阳光满载而归! 慷懒周末 睡到自然醒,阳光洒在书桌上 套进宽松自在的衣服里 出门,去楼下坐坐 在阳光里吃午餐 在阳光里打个盹 在阳光里看猫咪上蹿下跳 在阳光里点个咖啡外卖 虚度时光&#xf…

【CANN训练营第三季】TBE算子开发

文章目录直播学习结业考核直播学习 安装准备:https://www.hiascend.com/document/detail/zh/mindstudio/50RC3/instg/instg_000022.html 开发参考: https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha003/operatordevelopment/opdevg/atla…

基础算法(八)——离散化

离散化 介绍 这里的离散化,特指整数的、保序的离散化 有些题目可能需要以数据作为下标来操作,但题目给出的数据的值比较大,但是数据个数比较小。此时就需要将数据映射到和数据个数数量级相同的区间,这就是离散化,即…

Java学习笔记——继承(上)

目录继承入门继承的好处继承的特点继承中成员变量的访问特点this和super访问成员的格式继承中成员方法的访问特点方法重写概述和应用场景方法重写的注意事项权限修饰符继承入门 继承的好处 好处: 提高了代码的复用性。 提高了代码的维护性。 让类与类之间产生了关系…

static关键字分别在C和C++中的作用

static用于实现多个对象之间的数据共享 隐藏使用静态成员不会破坏隐藏规则默认初始化为0 1. C语言中static的特性(面向过程设计中) 局部变量:在任意一个函数内部定义的变量(不加static),初始值不确定&am…

11、JS笔记-内置内置对象

1.内置对象 js中对象分为三种: 自定义对象、内置对象、浏览器对象(js独有) 内置对象: js语言自带的对象,供开发者使用,提供一些常用或基本的功能(属性和方法) 2.Math对象 Math中所…

Docker核心概念总结

文章目录容器容器概念物理机,虚拟机与容器的区别Docker简介Docker介绍Docker思想Docker容器的特点使用Docker的原因容器VS虚拟机对比图容器与虚拟机总结容器与虚拟机可以共存Docker 基本概念镜像(Image)一个特殊的文件系统容器(Container&…

SpringSecurity认证功能的快速上手

简介 SpringSecurity是Spring家族中的一个安全管理框架。相比于另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。一般来说中大型项目都是使用SpringSecurity来做安全框架,小项目用Shiro的比较多,因为相比于S…

LabVIEW VI服务器功能

LabVIEW VI服务器功能VI 服务器是在LabVIEW 5.0中引入的,它提供了允许用户动态控制前面板控件、VI和LabVIEW环境的一系列函数。使用VI服务器,您还可以在同一台机器或通过网络动态加载运行VI和LabVIEW。 VI服务器函数位于 函数应用程序控制 子面板上。所有…

3-1内存管理-内存管理概念

文章目录一.内存管理的基本原理和要求1.逻辑地址和物理地址2.程序的装入和链接/从写程序到程序运行/将程序和数据装入内存/将用户源程序变为可在内存中执行的程序需要经过的步骤3.程序的链接方式4.内存的装入模块在装入内存时的方式5.操作系统对内存的管理二.覆盖与交换三.连续…

C#,图像二值化(17)——全局阈值的ISODATA算法(亦称作InterMeans法)及其源程序

二值算法综述请阅读: C#,图像二值化(01)——二值化算法综述与二十三种算法目录https://blog.csdn.net/beijinghorn/article/details/128425225?spm1001.2014.3001.5502 支持函数请阅读: C#,图像二值化&…

Smart Finance成为火必投票竞选项目,参与火必投票获海量奖励

最近,Huobi推出了新一期的“投票上币”活动,即用户可以通过HT为候选项目投票,在投票截止后,符合条件的优质项目将直接上线Huobi。而Smart Finance成为了新一期投票上币活动的竞选项目之一,并备受行业关注,与…

android 11+后台启动FGS的while-in-use权限限制

while-in-use权限限制 为了帮助保护用户隐私,Android 11(API 级别 30)对前台服务何时可以访问设备的位置、摄像头或麦克风进行了限制。 当您的应用程序在后台运行时启动前台服务时,前台服务有以下限制: 除非用户已向您…

智能家居创意产品一智能插座

WiFi智能插座对于新手接触智能家居产品更加友好,不需要额外购买网关设备 很多智能小配件也给我们得生活带来极大的便捷,智能插座就是其中之一,比如外出忘记关空调,可以拿起手机远程关闭。 简单说就是:插座可以连接wi…

python深拷贝和浅拷贝

python深拷贝和浅拷贝(一) 定义 直接赋值:其实就是对象的引用。浅拷贝:拷贝父对象,不会拷贝对象的内部的子对象。深拷贝: copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。 浅拷贝&am…

【自学C++】C++变量

C变量 C变量教程 不论是使用哪种高级程序语言编写程序,变量都是其程序的基本组成单位。变量相当于内存中一个数据存储空间的表示,通过变量名可以访问到变量的具体的值。 C 的变量(variable)是有明确 类型 的。编译器会检查 函数…

Linux 安装OpenSSL及解决遇到的问题

OpenSSL下载openssl安装包解压配置相应检查编译安装测试创建软链接N次测试下载openssl安装包 wget https://www.openssl.org/source/openssl-3.0.1.tar.gz执行后如果拉不下来,出现证书过期 需要加上 --no-check-certificate 不做检查 wget https://www.openssl.o…

C语言char类型的存储

目录char是如何存储的char的类型char的取值范围例题char是如何存储的 字符型(char)用于储存字符(character),如英文字母或标点。但是char类型在内存中并不是以字符的形式储存,而是以ASII码的形式储存&…