关于ResNet50的解读

article/2025/9/18 5:39:40

说起ResNet必然要提起He大佬,这真是神一样的存在,这不,不久前又有新的突破RegNet,真是厉害啊。

ResNet开篇之作在此,后面又出了各种变形啥的,ResNeXt,inception-ResNet等等吧,He大佬推荐的tf版本代码在此。keras ResNet50的在此。

总体:

这个网络结构主要是解决加深网络而不能减小loss的问题,如图下:

网络越深越好吗?不是,加一层acc或者其他指标就好了??并不是,既然网络加深了,又难以训练,效果又不好,谁还用深的网络?He大佬的残差学习结构网络大体上比其他模型深,152层,8倍VGG,但复杂性不高,且以ImageNet 测试集3.57%error got first ILSVRC 2015分类任务,同时取得ImageNet 检测,定位,COCO数据集检测,分割的第一。

在keras构建模型中,大多数都是序列化堆叠一些层,但这样有效果吗?

网络结构:

一般定义底层映射H(x),然后堆叠非线性层拟合F(x):=H(x)-x,最初的映射则为F(x)+x,于是我们认为优化后者即F(x)+x,比无参考的前者H(x)更容易【这是直接翻译的,具体啥意思我还没转过来弯】下面就是这个的building block,即有名的identity mapping【恒等映射】

直接将x加过来既没有增加参数,也没有增加复杂度。仍旧可以采用SGD优化器就行反向传播。

对于每层堆叠都采用残差学习模块,正如上图所言,有两层,F=W2*theta(W1*x),theta是激活函数relu,为简化计,忽略偏置。

F+x就是直接相连,每个元素对应相加,然后再用relu激活。如果F和x的维度不同,可采用线性映射Ws,Ws也可为方阵,如下:

y=F+Ws*x,作者证实,这种简单的恒等映射对于处理图1问题是足够的,而且是经济的【划算】。残差结构也不是一成不变的,也可以是其他形式,比如下面:2层或3层都是可以的,或者更多层,但是只有一层【即y=W1*x+x】没有发现优势。

在卷积层和全连接层都是可用的。

下面看看keras版本的代码:其实我之前就看过了这个版本的代码了

1.恒等映射部分

def _shortcut(input, residual):"""Adds a shortcut between input and residual block and merges them with "sum""""# Expand channels of shortcut to match residual.# Stride appropriately to match residual (width, height)# Should be int if network architecture is correctly configured.input_shape = K.int_shape(input)residual_shape = K.int_shape(residual)stride_width = int(round(input_shape[ROW_AXIS] / residual_shape[ROW_AXIS]))stride_height = int(round(input_shape[COL_AXIS] / residual_shape[COL_AXIS]))equal_channels = input_shape[CHANNEL_AXIS] == residual_shape[CHANNEL_AXIS]shortcut = input# 1 X 1 conv if shape is different. Else identity.if stride_width > 1 or stride_height > 1 or not equal_channels:shortcut = Conv2D(filters=residual_shape[CHANNEL_AXIS],kernel_size=(1, 1),strides=(stride_width, stride_height),padding="valid",kernel_initializer="he_normal",kernel_regularizer=l2(0.0001))(input)return add([shortcut, residual])

如果不同width或height及channel,那么采用卷积使得结果相同,如果相同就是简单的相加(add)

下面验证一下不同时的情况:这里有个潜在的约束,就是residual是经过卷积后的,HW肯定比x要小(相同的情况则是上句的直接相加)至于是不是整数倍,这个不一定,因为上面的代码其实是固定了核的大小,如果不固定的话会更加灵活,那就要考虑余数与核大小的关系了。视input1为x,input2为residual。

>>> input1
<tf.Tensor 'input_4:0' shape=(?, 16, 32, 3) dtype=float32>
>>> input2
<tf.Tensor 'input_6:0' shape=(?, 5, 16, 3) dtype=float32>>>> _shortcut(input1,input2)
Traceback (most recent call last):File "<pyshell#54>", line 1, in <module>_shortcut(input1,input2)File "D:\python36\new\keras-resnet\resnet.py", line 92, in _shortcutreturn add([shortcut, residual])File "D:\python36\lib\site-packages\keras\layers\merge.py", line 555, in addreturn Add(**kwargs)(inputs)File "D:\python36\lib\site-packages\keras\engine\base_layer.py", line 425, in __call__self.build(unpack_singleton(input_shapes))File "D:\python36\lib\site-packages\keras\layers\merge.py", line 91, in buildshape)File "D:\python36\lib\site-packages\keras\layers\merge.py", line 61, in _compute_elemwise_op_output_shapestr(shape1) + ' ' + str(shape2))
ValueError: Operands could not be broadcast together with shapes (6, 16, 3) (5, 16, 3)

考虑卷积核大小可以改变,那么可以直接采用下面的卷积

>>> Conv2D(filters=3,kernel_size=(2,2),strides=(3,2))(input1)
<tf.Tensor 'conv2d_60/BiasAdd:0' shape=(?, 5, 16, 3) dtype=float32>>>> input1_=Conv2D(filters=3,kernel_size=(2,2),strides=(3,2))(input1)
>>> _shortcut(input1_,input2)
<tf.Tensor 'add_23/add:0' shape=(?, 5, 16, 3) dtype=float32>

2.关于残差模块,下面是bottleneck,也就是采用了3层卷积

def bottleneck(filters, init_strides=(1, 1), is_first_block_of_first_layer=False):"""Bottleneck architecture for > 34 layer resnet.Follows improved proposed scheme in http://arxiv.org/pdf/1603.05027v2.pdfReturns:A final conv layer of filters * 4"""def f(input):if is_first_block_of_first_layer:# don't repeat bn->relu since we just did bn->relu->maxpoolconv_1_1 = Conv2D(filters=filters, kernel_size=(1, 1),strides=init_strides,padding="same",kernel_initializer="he_normal",kernel_regularizer=l2(1e-4))(input)else:conv_1_1 = _bn_relu_conv(filters=filters, kernel_size=(1, 1),strides=init_strides)(input)conv_3_3 = _bn_relu_conv(filters=filters, kernel_size=(3, 3))(conv_1_1)residual = _bn_relu_conv(filters=filters * 4, kernel_size=(1, 1))(conv_3_3)return _shortcut(input, residual)return f

上面所指的第一层是

因为代码作者采用的是循环的操作,这个第一层就是开始循环的第一层。

he大佬的2层卷积即basic_block中,当维度不同时可以采用补零或者上面所提的投影【代码作者采用的是卷积,如下方框】

步长为2.至于为何为2,不是1,两个大佬都没说。

he大佬在卷积后激活前采用BN层,根据某个实践,没有用dropout,并没有解释或者对照试验。

下面的34层恰好就是repetition中的3463

resnet34=ResnetBuilder.build((299,299,3),20,basic_block, [3, 4, 6, 3])

3关于代码中细节

可能不同的tf版本运行结果报错,请直接修改数据格式,将判断是不是tf后台那里改了就好。

另外,随意增加循环层或者说resnet层数其实并不能提高acc或其他指标,可见我的具体实践。

最后的avg其实就gap

另外有相关问题可以加入QQ群讨论,不设微信群

QQ群:868373192 

语音图像视频推荐深度-学习群


http://chatgpt.dhexx.cn/article/ffDR1xTJ.shtml

相关文章

【深度学习】resnet-50网络结构

最近许多目标检测网络的backbone都有用到resnet-50的部分结构&#xff0c;于是找到原论文&#xff0c;看了一下网络结构&#xff0c;在这里做一个备份&#xff0c;需要的时候再来看看。 整体结构 layer0 首先是layer0&#xff0c;这部分在各个网络都一样&#xff0c;如图&…

一张图看懂Resnet50与Resnet101算法

直接上流程图&#xff0c;算法很清晰。 仅包括卷积层和全连接层&#xff0c;不包括池化层&#xff0c;正好50层。 相比于ResNet_50&#xff0c;ResNet_101就是在上图第3个大虚线框多了17个bottleneck&#xff0c;17*350101&#xff0c;说白了就是将下图复制17个加入上图的第3个…

什么是Resnet50模型?

1 深度残差网络 随着CNN的不断发展&#xff0c;为了获取深层次的特征&#xff0c;卷积的层数也越来越多。一开始的 LeNet 网络只有 5 层&#xff0c;接着 AlexNet 为 8 层&#xff0c;后来 VggNet 网络包含了 19 层&#xff0c;GoogleNet 已经有了 22 层。但仅仅通过增加网络层…

resnet-50介绍(一)

这篇文章讲解的是使用Tensorflow实现残差网络resnet-50. 侧重点不在于理论部分&#xff0c;而是在于代码实现部分。在github上面已经有其他的开源实现&#xff0c;如果希望直接使用代码运行自己的数据&#xff0c;不建议使用本人的代码。但是如果希望学习resnet的代码实现思路&…

ResNet50 网络结构搭建(PyTorch)

ResNet50是一个经典的特征提取网络结构&#xff0c;虽然Pytorch已有官方实现&#xff0c;但为了加深对网络结构的理解&#xff0c;还是自己动手敲敲代码搭建一下。需要特别说明的是&#xff0c;笔者是以熟悉网络各层输出维度变化为目的的&#xff0c;只对建立后的网络赋予伪输入…

ResNet-50网络理解

本文主要针对ResNet-50对深度残差网络进行一个理解和分析 ResNet已经被广泛运用于各种特征提取应用中&#xff0c;当深度学习网络层数越深时&#xff0c;理论上表达能力会更强&#xff0c;但是CNN网络达到一定的深度后&#xff0c;再加深&#xff0c;分类性能不会提高&#xff…

庖丁解牛-Resnet50 深度剖析,细致讲解,深入理解

背景介绍 ResNet-50侧边输出形状 假设输入为352,则 output2 256x88x88 output3 512x44x44 output4 1024x22x22 output5 2048x11x11 VGG-16侧边输出形状 假设输入为352,则 output1 64x320x320 output2 128x160x160 output3 256x88x88 output4 512x44x44 output5 512x22…

Resnet-50网络结构详解

解决的问题&#xff1a; 梯度消失&#xff0c;深层网络难训练。 因为梯度反向传播到前面的层&#xff0c;重复相乘可能使梯度无穷小。结果就是&#xff0c;随着网络的层数更深&#xff0c;其性能趋于饱和&#xff0c;甚至迅速下降。 关于为什么残差结构&#xff08;即多了一…

卷积神经网络学习—Resnet50(论文精读+pytorch代码复现)

前言一、Resnet论文精读引入残差残差块ResNet50模型基本构成BN层Resnet50总体结构 二、Resnet50代码复现完整代码 前言 如果说在CNN领域一定要学习一个卷积神经网络&#xff0c;那一定非Resnet莫属了。 接下来我将按照&#xff1a;Resnet论文解读、Pytorch实现ResNet50模型两部…

Java类加载器介绍

1.类加载器介绍 类加载器负责将class文件加载到内存中&#xff0c;并为之生成对应的java.lang.Class对象。对于任意一个类&#xff0c;都需要加载它的类加载器和这个类本身来确定该类在JVM中唯一性&#xff0c;也就是说&#xff0c;同一个class文件用两个不同的类加载器加载并…

类加载与类加载器概述

目录 一、类加载 类的加载&#xff1a; 类的连接&#xff1a; 类的初始化&#xff1a; 类初始化步骤&#xff1a; 类的初始化时机&#xff1a; 二、类加载器 类加载器的作用 JVM的类加载机制 Java运行时具有以下内置类加载器&#xff1a; 一、类加载 当程序要使用某…

十一、类加载器的作用

狂神说Java&#xff1a;https://www.bilibili.com/video/BV1p4411P7V3 1、类加载的作用 将class文件字节码内容加载到内存中&#xff0c;并将这些静态数据转换成方法区的运行时数据结构&#xff0c;然后生成一个代表这个类的java.lang.Class对象&#xff0c;作为方法区中类数据…

2.类加载器

回顾 上一节我们学习了JVM类加载机制&#xff0c;我们学习到大概的过程&#xff1a;通过类加载器将编译好的class文件加载到JVM进程中&#xff0c;通过字节码执行引擎去执行代码。这只是一个整体的过程&#xff0c;具体的细节我们从本节开始分析。 通过本节我们将掌握以下知识&…

Java类加载器

一.类的生命周期 1. 加载&#xff08;Loading&#xff09;&#xff1a;找 Class 文件 1. 通过一个类的全限定名来获取定义此类的二进制字节流。 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3.在内存中生成一个代表这个类的java.lang.Class对象&#xf…

Java类加载器的使用

Java类加载器 classloader顾名思义&#xff0c;即是类加载。虚拟机把描述类的数据从class字节码文件加载到内存&#xff0c;并对数据进行检验、转换解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的Java类型&#xff0c;这就是虚拟机的类加载机制。 先认识一下类加载…

JVM 类加载器

什么是类加载器 类加载器负责在运行时将Java类动态加载到Java虚拟机&#xff0c;他们也是JRE&#xff08;Java运行时环境&#xff09;的一部分。因此&#xff0c;借助类加载器&#xff0c;JVM无需了解底层文件或文件系统即可运行Java程序。此外&#xff0c;这些Java类不会一次…

类加载器深入理解

虚拟机设计团队把类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现&#xff0c;以便让应用程序自己决定如何去获取所需要的类。实现这个动作的模块称为“类加载器”。 类加载器在类层次划分、OSGI、热部署、代码加密等领域…

java获取类加载器

获取类加载器的方法: //扩展类加载器MainClassLoader classLoader MainTest.class.getClassLoader();//表示当前线程的类加载器——应用程序类加载器ClassLoader contextClassLoader Thread.currentThread().getContextClassLoader();//—启动类加载器ClassLoader systemClas…

类加载器的种类

类加载器的种类有四种&#xff0c;如下图所示&#xff1a; 1.启动类加载器&#xff08;引导类加载器&#xff0c;Bootstrap ClassLoader&#xff09; 这个类加载使用C/C语言实现的&#xff0c;嵌套在JVM内部它用来加载Java的核心库&#xff08;JAVA_HOME/jre/lib/rt.jar、res…

Java类加载器详解

1 特点 双亲委派&#xff1a; 如果一个类加载器收到了类加载的请求&#xff0c;它首先不会自己去尝试加载这个类&#xff0c;而是把这个请求委派给父类加载器去完成&#xff0c;每一个层次的类加载器都是如此&#xff0c;因此所有的加载请求最终都应该传送到顶层的启动类加载…