resnet-50介绍(一)

article/2025/9/18 5:34:46

这篇文章讲解的是使用Tensorflow实现残差网络resnet-50. 侧重点不在于理论部分,而是在于代码实现部分。在github上面已经有其他的开源实现,如果希望直接使用代码运行自己的数据,不建议使用本人的代码。但是如果希望学习resnet的代码实现思路,那么阅读本文将是一个不错的选择,因为本文的代码的思路是很清晰的。如果你刚刚阅读完resnet的那篇论文,非常建议你进一步学习如何使用代码实现resnet。本文包含源码的数据集。

resnet只是在CNN上面增加了shortcut,所以,resnet和CNN是很相似的。

##1. model
下面将要实现的是resnet-50。下面是网络模型的整体模型图。其中的CONV表示卷积层,Batch Norm表示Batch 归一化层,ID BLOCK表示Identity块,由多个层构成,具体见第二个图。Conv BLOCK表示卷积块,由多个层构成。为了使得model个结构更加清晰,才提取出了conv block 和id block两个‘块’,分别把它们封装成函数。

如果不了解batch norm,可以暂时滤过这部分的内容,可以把它看作是一个特殊的层,它不会改变数据的维度。这将不影响对resnet实现的理解。

具体见第三个图。
这里写图片描述
上图表示Resnet-50的整体结构图

这里写图片描述
上图表示ID block

这里写图片描述
上图表示conv block

##2. 数据
这里写图片描述

输入的是类似上图所示的手势图片数据,总共有6个类。所给的数据已经加工过,是‘.h5’格式的数据。有1080张图片,120张测试数据。每一张图片是一个64x64的RGB图片。具体的数据格式为:

number of training examples = 1080
number of test examples = 120
X_train shape: (1080, 64, 64, 3)
Y_train shape: (1080, 6)
X_test shape: (120, 64, 64, 3)
Y_test shape: (120, 6)
x train max,  0.956; x train min,  0.015
x test max,  0.94; x test min,  0.011

3. 目标

训练一个模型,使之能够判别图片中的手指所代表的数字。实质上这个是属于多分类问题。所以,模型的输入是一个64x64x3的图片;模型的输出层为6个节点,每一个节点表示一种分类。

4. 模型实现

identity block的实现,对于上图2。需要注意的是,X_shortcut一开始就保存了所传入的数据,然后在函数的末尾部分再加上X_shortcut。除了这一点,其他点跟CNN是一样的。

    def identity_block(self, X_input, kernel_size, in_filter, out_filters, stage, block, training):"""Implementation of the identity block as defined in Figure 3Arguments:X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)kernel_size -- integer, specifying the shape of the middle CONV's window for the main pathfilters -- python list of integers, defining the number of filters in the CONV layers of the main pathstage -- integer, used to name the layers, depending on their position in the networkblock -- string/character, used to name the layers, depending on their position in the networktraining -- train or testReturns:X -- output of the identity block, tensor of shape (n_H, n_W, n_C)"""# defining name basisblock_name = 'res' + str(stage) + blockf1, f2, f3 = out_filterswith tf.variable_scope(block_name):X_shortcut = X_input#firstW_conv1 = self.weight_variable([1, 1, in_filter, f1])X = tf.nn.conv2d(X_input, W_conv1, strides=[1, 1, 1, 1], padding='SAME')X = tf.layers.batch_normalization(X, axis=3, training=training)X = tf.nn.relu(X)#secondW_conv2 = self.weight_variable([kernel_size, kernel_size, f1, f2])X = tf.nn.conv2d(X, W_conv2, strides=[1, 1, 1, 1], padding='SAME')X = tf.layers.batch_normalization(X, axis=3, training=training)X = tf.nn.relu(X)#thirdW_conv3 = self.weight_variable([1, 1, f2, f3])X = tf.nn.conv2d(X, W_conv3, strides=[1, 1, 1, 1], padding='VALID')X = tf.layers.batch_normalization(X, axis=3, training=training)#final stepadd = tf.add(X, X_shortcut)add_result = tf.nn.relu(add)return add_result

下面是conv block,对应于上面图片3.

    def convolutional_block(self, X_input, kernel_size, in_filter,out_filters, stage, block, training, stride=2):"""Implementation of the convolutional block as defined in Figure 4Arguments:X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)kernel_size -- integer, specifying the shape of the middle CONV's window for the main pathfilters -- python list of integers, defining the number of filters in the CONV layers of the main pathstage -- integer, used to name the layers, depending on their position in the networkblock -- string/character, used to name the layers, depending on their position in the networktraining -- train or teststride -- Integer, specifying the stride to be usedReturns:X -- output of the convolutional block, tensor of shape (n_H, n_W, n_C)"""# defining name basisblock_name = 'res' + str(stage) + blockwith tf.variable_scope(block_name):f1, f2, f3 = out_filtersx_shortcut = X_input#firstW_conv1 = self.weight_variable([1, 1, in_filter, f1])X = tf.nn.conv2d(X_input, W_conv1,strides=[1, stride, stride, 1],padding='VALID')X = tf.layers.batch_normalization(X, axis=3, training=training)X = tf.nn.relu(X)#secondW_conv2 = self.weight_variable([kernel_size, kernel_size, f1, f2])X = tf.nn.conv2d(X, W_conv2, strides=[1,1,1,1], padding='SAME')X = tf.layers.batch_normalization(X, axis=3, training=training)X = tf.nn.relu(X)#thirdW_conv3 = self.weight_variable([1,1, f2,f3])X = tf.nn.conv2d(X, W_conv3, strides=[1, 1, 1,1], padding='VALID')X = tf.layers.batch_normalization(X, axis=3, training=training)#shortcut pathW_shortcut = self.weight_variable([1, 1, in_filter, f3])x_shortcut = tf.nn.conv2d(x_shortcut, W_shortcut, strides=[1, stride, stride, 1], padding='VALID')#finaladd = tf.add(x_shortcut, X)add_result = tf.nn.relu(add)return add_result

下面是模型的整合,对应于上图1。

    def deepnn(self, x_input, classes=6):"""Implementation of the popular ResNet50 the following architecture:CONV2D -> BATCHNORM -> RELU -> MAXPOOL -> CONVBLOCK -> IDBLOCK*2 -> CONVBLOCK -> IDBLOCK*3-> CONVBLOCK -> IDBLOCK*5 -> CONVBLOCK -> IDBLOCK*2 -> AVGPOOL -> TOPLAYERArguments:Returns:"""x = tf.pad(x_input, tf.constant([[0, 0], [3, 3, ], [3, 3], [0, 0]]), "CONSTANT")with tf.variable_scope('reference') :training = tf.placeholder(tf.bool, name='training')#stage 1w_conv1 = self.weight_variable([7, 7, 3, 64])x = tf.nn.conv2d(x, w_conv1, strides=[1, 2, 2, 1], padding='VALID')x = tf.layers.batch_normalization(x, axis=3, training=training)x = tf.nn.relu(x)x = tf.nn.max_pool(x, ksize=[1, 3, 3, 1],strides=[1, 2, 2, 1], padding='VALID')assert (x.get_shape() == (x.get_shape()[0], 15, 15, 64))#stage 2x = self.convolutional_block(x, 3, 64, [64, 64, 256], 2, 'a', training, stride=1)x = self.identity_block(x, 3, 256, [64, 64, 256], stage=2, block='b', training=training)x = self.identity_block(x, 3, 256, [64, 64, 256], stage=2, block='c', training=training)#stage 3x = self.convolutional_block(x, 3, 256, [128,128,512], 3, 'a', training)x = self.identity_block(x, 3, 512, [128,128,512], 3, 'b', training=training)x = self.identity_block(x, 3, 512, [128,128,512], 3, 'c', training=training)x = self.identity_block(x, 3, 512, [128,128,512], 3, 'd', training=training)#stage 4x = self.convolutional_block(x, 3, 512, [256, 256, 1024], 4, 'a', training)x = self.identity_block(x, 3, 1024, [256, 256, 1024], 4, 'b', training=training)x = self.identity_block(x, 3, 1024, [256, 256, 1024], 4, 'c', training=training)x = self.identity_block(x, 3, 1024, [256, 256, 1024], 4, 'd', training=training)x = self.identity_block (x, 3, 1024, [256, 256, 1024], 4, 'e', training=training)x = self.identity_block(x, 3, 1024, [256, 256, 1024], 4, 'f', training=training)#stage 5x = self.convolutional_block(x, 3, 1024, [512, 512, 2048], 5, 'a', training)x = self.identity_block(x, 3, 2048, [512, 512, 2048], 5, 'b', training=training)x = self.identity_block(x, 3, 2048, [512, 512, 2048], 5, 'c', training=training)x = tf.nn.avg_pool(x, [1, 2, 2, 1], strides=[1,1,1,1], padding='VALID')flatten = tf.layers.flatten(x)x = tf.layers.dense(flatten, units=50, activation=tf.nn.relu)# Dropout - controls the complexity of the model, prevents co-adaptation of# features.with tf.name_scope('dropout'):keep_prob = tf.placeholder(tf.float32)x = tf.nn.dropout(x, keep_prob)logits = tf.layers.dense(x, units=6, activation=tf.nn.softmax)return logits, keep_prob, training

5. 代价函数

使用交叉熵来计算损失函数和代价函数。这里没有使用L2正则化。

    def cost(self, logits, labels):with tf.name_scope('loss'):# cross_entropy = tf.losses.sparse_softmax_cross_entropy(labels=y_, logits=y_conv)cross_entropy = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits)cross_entropy_cost = tf.reduce_mean(cross_entropy)return cross_entropy_cost

在训练模型的时候,应该控制迭代的次数,以避免过度的过拟合。刚开始的时候,所打印出来的cost值会上下浮动,这个是正常的(一开始本人以为是模型有问题,后来才知道这是正常的)耐心等待便好。训练的模型将保存在硬盘中,在预测的时候可直接读取这些数据。

    def train(self, X_train, Y_train):features = tf.placeholder(tf.float32, [None, 64, 64, 3])labels = tf.placeholder(tf.int64, [None, 6])logits, keep_prob, train_mode = self.deepnn(features)cross_entropy = self.cost(logits, labels)with tf.name_scope('adam_optimizer'):update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)with tf.control_dependencies(update_ops):train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)graph_location = tempfile.mkdtemp()print('Saving graph to: %s' % graph_location)train_writer = tf.summary.FileWriter(graph_location)train_writer.add_graph(tf.get_default_graph())mini_batches = random_mini_batches(X_train, Y_train, mini_batch_size=32, seed=None)saver = tf.train.Saver()with tf.Session() as sess:sess.run(tf.global_variables_initializer())for i in range(1000):X_mini_batch, Y_mini_batch = mini_batches[np.random.randint(0, len(mini_batches))]train_step.run(feed_dict={features: X_mini_batch, labels: Y_mini_batch, keep_prob: 0.5, train_mode: True})if i % 20 == 0:train_cost = sess.run(cross_entropy, feed_dict={features: X_mini_batch,labels: Y_mini_batch, keep_prob: 1.0, train_mode: False})print('step %d, training cost %g' % (i, train_cost))saver.save(sess, self.model_save_path)

模型预测。先初始化graph,然后读取硬盘中模型参数数据。

    def evaluate(self, test_features, test_labels, name='test '):tf.reset_default_graph()x = tf.placeholder(tf.float32, [None, 64, 64, 3])y_ = tf.placeholder(tf.int64, [None, 6])logits, keep_prob, train_mode = self.deepnn(x)accuracy = self.accuracy(logits, y_)saver = tf.train.Saver()with tf.Session() as sess:saver.restore(sess, self.model_save_path)accu = sess.run(accuracy, feed_dict={x: test_features, y_: test_labels,keep_prob: 1.0, train_mode: False})print('%s accuracy %g' % (name, accu))

这个本人测试的结果
这里写图片描述

本算法只是单纯地实现和演示ResNet-50神经网络模型,所以在运行地时候出现结果时好时坏地现象,也就是结果不稳定。如果加上滑动平均模型,就会稳定很多。

本文的思路来自吴恩达老师关于深度学习第四课的课程。

数据集下载:链接:https://pan.baidu.com/s/1iA004kLU1gocvA-gaiwSWw
提取码:sqj3

完整源码下载:https://github.com/liangyihuai/my_tensorflow/blob/master/com/huai/converlution/resnets/hand_classifier_with_resnet.py


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

相关文章

ResNet50 网络结构搭建(PyTorch)

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

ResNet-50网络理解

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

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

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

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

Java类加载器介绍

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

类加载与类加载器概述

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

十一、类加载器的作用

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

2.类加载器

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

Java类加载器

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

Java类加载器的使用

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

JVM 类加载器

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

类加载器深入理解

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

java获取类加载器

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

类加载器的种类

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

Java类加载器详解

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

【类加载器】java类加载器

类装载器ClassLoader(一个抽象类) 描述一下JVM加载class文件的原理机制 类装载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java中类装载器把一个类装入JVM,经过以下步骤: 1、装载&#xff…

什么是类加载器?

类加载器 什么是类加载器,作用是什么? 类加载器就是加载字节码文件(.class)的类 Java语言是一种具有动态性的解释语言,类(CLASS) 只有被加载到 JVM 中后才能运行。当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规…

类加载器

类加载过程 加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。 类加载器分类 JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader: 启动类加载器&…

类加载器作用

深入探讨 Java 类加载器 成 富, 软件工程师, IBM 中国软件开发中心 成富任职于 IBM 中国软件开发中心,目前在 Lotus 部门从事 IBM Mashup Center 的开发工作。他毕业于北京大学信息科学技术学院,获得计算机软件与理论专业硕士学位。他的个人网站是 http:…