经典的卷积神经网络(VGG、ResNet、InceptionNet、MobileNet)

2024-03-29 10:32

本文主要是介绍经典的卷积神经网络(VGG、ResNet、InceptionNet、MobileNet),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、卷积网络发展

卷积神经网络的起源是神经认知机模型(neocongnitron),之后在1989年出现了卷积神经与网络的模型。直到2012年随着一些技术的成熟带来的机遇,卷积神经网络迎来了历史性的突破,AlexNet获得ImageNet大赛冠军引起了人们的注意,之后的卷积网络朝着四个方向发展

AlexNet:通过数据增强、Dropout来防止过拟合,所谓数据增强就是在原有的图片样本的基础上,通过对图片进行裁剪、反转、调整得到新的样本数据。Dropout是指在全连接层随机去掉某些神经元,从而产生不同的网络结构,相应的缺点是需要更多的训练时间。此外它还采用了非线性激活函数ReLU。以及在120万张大数据图像上进行训练。AlexNet在横向上通普通卷积网络一样经过多个卷积池化全连接操作,在纵向将一张图片数据分别在两个GPU上进行独立运算,并在第三个卷积层将数据交叉,这样可以极大增加数据运算的规模。

2、VGGNet

在AlexNet的基础上对卷积网络的层数进行加深就形成了多种VGG模型,最常用的是16层或19层的模型,此时已经达到了准确率提升的瓶颈,再增加层数已经无法提升正确率。如下左图所示,在VGG中使用两个3×3的叠代网络代替单个5×5网络的视野域,不仅可以学习到更多的内容,而且降低了参数。

 

下面在Cifar数据集上对VggNet进行实现

2.1、数据处理

首先定义一个类用于实现Cifar训练与测试数据集的读入,在初始化一个数据集对象时传入数据文件路径列表与是否需要重洗数据,对于训练集可以打乱数据顺序重用,而测试集则不需要。训练集数据有五个批次,依次遍历每个路径列表中的五个文件,通过load_data()函数读入五个批次中的所有数据。再将标签与数据值分别合并到_data与_labels变量中。对象可以通过next_batch()方法每次返回batch_size个数据。

import tensorflow as tf
import os
import pickle
import numpy as npCIFAR_DIR='D:\Temp\MachineLearning\data\cifar-10-batches-py'class   CifarData:def __init__(self,filelist,shuffle):all_data=[]all_labels=[]#循环读取多个批次的数据集文件for filename in filelist:data,labels=self.load_data(filename)      all_data.append(data)all_labels.append(labels)# 将所有data以纵向的方式合并到_dataself._data=np.vstack(all_data)# 数据归一化处理self._data=self._data/127.5 - 1# 将所有label以横向方式合并到_labelself._labels=np.hstack(all_labels)# 数据中样本个数self._example_num=self._data.shape[0]print(self._example_num)# 定位数据集遍历的位置指针self._indicator=0# 根据传入的shuffle决定是否对数据进行打乱self._shuffle=shuffleif self._shuffle:self.shuffle_data()def load_data(self,filename):"""读取一个批文件中的数据"""with open(filename,'rb') as f:data=pickle.load(f,encoding='bytes')return data[b'data'],data[b'labels']def shuffle_data(self):# 返回一个打乱的序号列表,例如将0~5 随机打乱为 [5,3,2,4,0,1]shuffle_list=np.random.permutation(self._example_num)# 根据随机列表重排数据和对应的标签值self._data=self._data[shuffle_list]self._labels=self._labels[shuffle_list]def next_batch(self,batch_size ):'''返回当前指针self._indicator之后batch_size个数据'''end_indicator=self._indicator+batch_size# 如果相加结果超过了样本个数if end_indicator>self._example_num:# 如果可以洗牌,打乱数据并从0开始重新取数据if self._shuffle:self.shuffle_data()self._indicator=0end_indicator=batch_sizeelse:raise Exception("没有更多数据了!")    # 返回从_indicator到end_indicator的data和labels,并将指针后移到end_indicatorbatch_data=self._data[self._indicator:end_indicator]batch_labels=self._labels[self._indicator:end_indicator]self._indicator=end_indicatorreturn batch_data,batch_labels# 拼接训练数据的路径名        
train_file=[os.path.join(CIFAR_DIR,'data_batch_%d'%i) for i in range (1,6)]
test_file=[os.path.join(CIFAR_DIR,'test_batch')]
# 创建训练数据对象
train_data=CifarData(train_file,True)

2.2、模型图的构建,

首先定义占位符x,y,并对x进行数据处理

x=tf.placeholder(tf.float32,[None,3072])
y=tf.placeholder(tf.int64,[None])
# 将一维向量x转化为具有三通道的多维图片,并交换向量通道
x_img=tf.reshape(x,[-1,3,32,32])
x_img=tf.transpose(x_img,[0,2,3,1])

接下来定义卷积网络,vgg的关键特点就是在普通卷积网络的基础上,增加了卷积层的数量,例如可以定义两个卷积层,在定义一个池化层,依次类推定义三个这样的结构

# 卷积层1_1
conv1_1=tf.layers.conv2d(x_img,   #输入32,      #输出通道数(3,3),   #卷积核大小padding='same',          #填充使图小大小不变activation=tf.nn.relu,   #激活函数name='conv1_1'             )
# VggNet两个卷积层,一个池化层
conv1_2=tf.layers.conv2d(conv1_1,32,(3,3),padding='same',activation=tf.nn.relu,name='conv1_2')
# 池化层,步长至少为2才能缩小图像,32×32输出为16×16
pool1=tf.layers.max_pooling2d(conv1_2,    #输入(2,2),    #池化核(2,2),    #步长name='pool1')
# 卷积层2_1、2_2与第二个池化层
conv2_1=tf.layers.conv2d(pool1,32,(3,3),padding='same',activation=tf.nn.relu,name='conv2_1')
conv2_2=tf.layers.conv2d(conv2_1,32,(3,3),padding='same',activation=tf.nn.relu,name='conv2_2')
pool2=tf.layers.max_pooling2d(conv2_2,(2,2),(2,2),name='pool2')   
# 卷积层3_1、3_2与第三个池化层
conv3_1=tf.layers.conv2d(pool2,32,(3,3),padding='same',activation=tf.nn.relu,name='conv3_1')
conv3_2=tf.layers.conv2d(conv3_1,32,(3,3),padding='same',activation=tf.nn.relu,name='conv3_2')
pool3=tf.layers.max_pooling2d(conv3_2,(2,2),(2,2),name='pool3')   

2.3、进行训练

定义损失函数、准确率、优化方法:

# 将输出的张量扁平化
flatten=tf.layers.flatten(pool3)
y_predict=tf.layers.dense(flatten,10)
# 使用交叉熵作为损失函数
loss=tf.losses.sparse_softmax_cross_entropy(y,y_predict)# 将标签预测向量中最大的下标作为预测值,例如[0.1,0.8...0.01],则预测为第二类
predict=tf.math.argmax(y_predict,1)
# 通过equal函数逐一比较predict,y_reshape的每一个元素
correction=tf.equal(predict,y)
accuracy=tf.reduce_mean(tf.cast(correction,tf.float64))
#定义优化方法
with tf.name_scope('train_op'):train_op=tf.train.AdamOptimizer(1e-3).minimize(loss)

接下来便可以开始训练:

with tf.Session() as sess:sess.run(init)for i in range(train_steps):batch_data,batch_labels=train_data.next_batch(batch_size)loss_val,acc_val,_=sess.run([loss,accuracy,train_op],feed_dict={x:batch_data,y:batch_labels})if (i+1) % 500==0:print("第%d步:损失:%.5f,精确度:%.5f"%(i,loss_val,acc_val))#每训练1000次,在test数据集上进行一次测试if (i+1)%1000==0:# 定义测试集数据对象test_data=CifarData(test_file,False)   all_acc=[]for j in range(test_steps):test_batch_data,test_batch_labels=test_data.next_batch(batch_size)test_acc=sess.run([accuracy],feed_dict={x:test_batch_data,y:test_batch_labels})all_acc.append(test_acc)# 将测得的多个准确率求均值test_acc_mean=np.mean(all_acc)print("第%d步测试集准确率%.4f"%(i,test_acc_mean))

3、ResNet

ResNet结合了Vgg与GoogleNet的特点。随着其卷积层深度增加,出现了训练误差反而增加的问题,这是由于梯度消失导致的后面的网络层无法对前面的层进行调整。为了解决这个问题,ResNet提出了shortcut捷径连接,将输入跳过中间多层与卷积核结果相加,这一层的神经网络可以不用学习整个上一层的输出,而是学习上一个网络输出的残差,因此ResNet又叫做残差网络。

如下左图所示,本层输出H(x)=F(x)+x,F(x)为残差函数,通过F去学习模型与目标的差距,如果模型以及很好地拟合目标,则F(x)=0,即H(x)=0+x,本层实现恒等输出。如果模型和目标存在差距,则通过F(x)学习再加上输入x,与直接用H(x)拟合更为方便。

在上面VggNet的基础上修改网络模型,实现ResNet对Cifar数据集的训练,残差网络最重要的是残差连接块的定义,通过函数res_block()定义残差块。输出结果由两部分相加组成,一部分进行卷积操作,另一部分恒等变换,卷积操作可能降采样,会使输出的通道数会翻倍,导致两部分的矩阵维度不一样,无法进行相加。所以这个时候需要对恒等变化也要做一次降采样。

如果输出通道数是输入的两倍,说明卷积的步长为2,在卷积的过程中进行了降采样,并将increase_dim设为True,在之后对x也进行降采样。否则卷积步长为1,卷积过程不会降采样,x不需要处理,直接将x和卷积结果相加得到输出。

def res_block(x,out_chanel):'''实现残差连接块'''in_chanel=x.shape[-1]# 如果输出通道是输入的两倍,说明需要进行降采样操作if in_chanel*2==out_chanel:increase_dim=Truestrides=(2,2)           # 将步长设为2,卷积操作会进行降采样elif in_chanel==out_chanel:increase_dim=Falsestrides=(1,1)           # 步长为1,不进行降采样else:raise Exception('输入输出通道数目无法匹配')# 对输入进行卷积操作conv1=tf.layers.conv2d(x,out_chanel,(3,3),strides,padding='same',activation=tf.nn.relu,name='conv1')conv2=tf.layers.conv2d(conv1,out_chanel,(3,3),(1,1),padding='same',activation=tf.nn.relu,name='conv2')# 如果需要降采样,则对恒等变换部分进行降采样,并补充通道数if increase_dim:pool_x=tf.layers.average_pooling2d(x,(2,2),(2,2))# pool_x为[None,img_width,img_height,chanel],通过tf.pad()对第四维的左右各补充半个in_chanel,使其翻倍pad_x=tf.pad(pool_x,[[0,0],[0,0],[0,0],[in_chanel//2,in_chanel//2]])else:pad_x=x         # 不需要降采样,残差直接为xout_x=conv2+pad_x   # 将卷积结果和残差相加得到输出return out_x

有了残差连接块后便可以实现残差网络,例如上面右边图ResNet中的34-layer所示,首先将输入进行一次卷积操作,然后经过四个残差连接层,每个残差连接层中的块数不同,通过两个循环嵌套调用res_block()实现残差连接块,最后经过一个全连接层将结果输出。

def res_net(x,block_num_list,filter_base,class_num):'''构建残差网络-x,输入-block_num_list,残差连接块数列表,例如[3,4,6,3]-filter_base,初始通道数-class_num,Cifar数据集的类别数,即最后网络输出的通道数'''layer_num=len(block_num_list)layers=[] # 截取x的后三个维度信息,[None,width,height,chanel]->[width,height,chanel]input_size=x.shape[1:]with tf.variable_scope('conv0'):# 首先进行一次卷积操作,并将结果放到layers列表conv0=tf.layers.conv2d(x,filter_base,(3,3),(1,1),padding='same',activation=tf.nn.relu,name='conv0')layers.append(conv0)# 根据残差连接块数列表,循环实现多个残差连接层for layer_id in range(layer_num):for i in range(block_num_list[layer_id]):with tf.variable_scope('conv%d_%d'%(layer_id,i)):# 输出通道数是在初始通道的基础上,每经过一层翻一倍,乘以2的layer_id次方out_chanel=filter_base*(2**layer_id)conv=res_block(layers[-1],out_chanel)layers.append(conv)# 通过断言检查输出维度是否符合预期,如不符合,报错停止multiplier=2 ** (layer_num - 1)    assert layers[-1].get_shape().as_list()[1:] \== [input_size[0] // multiplier,input_size[1] // multiplier,filter_base * multiplier]# 最后经过一个全连接层将结果输出with tf.variable_scope('fc'):# layers[-1]:[None,width,height,chanel],在width,height上求平均global_pool=tf.reduce_mean(layers[-1],[1,2])logits=tf.layers.dense(global_pool,class_num)layers.append(logits)return layers[-1]

4、InceptionNet

GoogleNet在每个卷积层中设置了不同大小的卷积核来增加卷积层功能,例如有5×5、3×3等多种卷积核。卷积核种类多了之后造成特征值的厚度非常大,为了解决这个问题,提出了Inception版本,在卷积核之前通过一个1×1的卷积核进行降维。如下图所示,InceptionNet最大的特点就是在同一卷积层使用不同的卷积核处理数据。

例如下面实现inception块,每个inception块由1×1、3×3、5×5的卷积核与max_pooling构成,将输入x经过不同的卷积操作后将结果拼接起来得到输出

def inception_block(x,out_chanel_list,name):'''实现 inception网络块:param x: 输入:param out_chanel_list: 不同卷积核输出通道列表:param name: 命名域名称:return: 多个卷积层的拼接结果'''with tf.variable_scope(name):# 定义1×1、3×3、5×5卷积操作与max_poolingconv1_1=tf.layers.conv2d(x,out_chanel_list[0],(1,1),(1,1),padding='same',activation=tf.nn.relu,name='conv1_1')conv3_3=tf.layers.conv2d(x,out_chanel_list[1],(3,3),(1,1),padding='same',activation=tf.nn.relu,name='conv3_3')conv5_5=tf.layers.conv2d(x,out_chanel_list[2],(5,5),(1,1),padding='same',activation=tf.nn.relu,name='conv5_5')max_pooling=tf.layers.max_pooling2d(x,(2,2),(2,2),name='max_pooling')# 对max_pooling的结果进行填充,将mx与input相差的部分填充到mx的两侧mx_shape=max_pooling.shape[1:]input_shape=x.shape[1:]width_padding=(input_shape[0]-mx_shape[0])//2height_padding=(input_shape[1]-mx_shape[1])//2# max_pooling->[None,width,height,chanel],对中间的宽和高进行填充mx_padded=tf.pad(max_pooling,[[0,0],[width_padding,width_padding],[height_padding,height_padding],[0,0]])# 将四个输出结果在第三个维度上进行拼接concat_layer=tf.concat([conv1_1,conv3_3,conv5_5,mx_padded],axis=3)   return concat_layer

接下来利用inception块构建inception网络,将输入x_img首先经过一个卷积、池化层,接着将池化结果传入联系的两个inception块,之后再经过一个池化层。类似地,再经过第三层两个inception块和池化。将结果展开,经过全连接层,得到y的预测值。

# 先经过一个卷积层和池化层
conv1=tf.layers.conv2d(x_img,32,(3,3),padding='same',activation=tf.nn.relu,name='conv1')
pooling1=tf.layers.max_pooling2d(conv1,(2,2),(2,2),name='pooling1')   
# 第二层经过两个inception块和池化层
inception_2a=inception_block(pooling1,[16,16,16],'inception_2a')
inception_2b=inception_block(inception_2a,[16,16,16],'inception_2b')
pooling2=tf.layers.max_pooling2d(inception_2b,(2,2),(2,2),name='pooling2')
# 第三层
inception_3a=inception_block(pooling2,[16,16,16],'inception_3a')
inception_3b=inception_block(inception_3a,[16,16,16],'inception_3b')
pooling3=tf.layers.max_pooling2d(inception_3b,(2,2),(2,2),name='pooling3')
# 将结果展开,进行全连接层
flatten=tf.layers.flatten(pooling3)
y_predict=tf.layers.dense(flatten,10)

5、MobileNet

MobileNet引入了深度可分离卷积操作,如下图所示,其思想是每个卷积核不关注所有通道,而是只处理几个通道的输出。通过这样的优化减少计算量

如下实现可分离卷积块的操作,将输入的x张量在第四维度上拆分成单个向量并分别进行卷积操作,将卷积后的结果再在第四维上拼接称为一个张量,卷积后返回结果

def separate_block(x,out_chanel,name):'''可分离卷积块:param x: 输入:param out_chanel: 输出通道数:param name: 命名域名称:return: 经过可分离卷积的结果'''with tf.variable_scope(name):input_chanel=x.shape[-1]# 输入的x->[None,width,height,chanel],在第四维(axis=3)上将x拆分为单个向量x_split=tf.split(x,input_chanel,axis=3)output_list=[]for i in range(input_chanel):# 分别对拆分后的每个向量进行卷积操作,并将结果添加到output_listoutput=tf.layers.conv2d(x_split[i],1,(3,3),(1,1),padding='same',activation=tf.nn.relu,name='con_%d'%i)output_list.append(output)# 将单个向量在第四维上拼接起来concat_layer=tf.concat(output_list,axis=3)# 将拼接后的结果再进行一次卷积后输出conv2=tf.layers.conv2d(concat_layer,out_chanel,(1,1),(1,1),padding='same',activation=tf.nn.relu,name='conv2')       return conv2

利用可分离卷积块实现MobileNet网络,其过程与InceptionNet类似,首先经过一次卷积和池化,之后每经过两个可分离卷积块separate_block()做一次池化操作,最后经全连接层输出。

# 先经过一个卷积层和池化层
conv1=tf.layers.conv2d(x_img,32,(3,3),padding='same',activation=tf.nn.relu,name='conv1')
pooling1=tf.layers.max_pooling2d(conv1,(2,2),(2,2),name='pooling1')   
# 第二层经过两个separate_block和池化层
separate_2a=separate_block(pooling1,32,'separate_2a')
separate_2b=separate_block(separate_2a,32,'separate_2b')
pooling2=tf.layers.max_pooling2d(separate_2b,(2,2),(2,2),name='pooling2')
# 第三层
separate_3a=separate_block(pooling2,32,'separate_3a')
separate_3b=separate_block(separate_3a,32,'separate_3b')
pooling3=tf.layers.max_pooling2d(separate_3b,(2,2),(2,2),name='pooling3')# 将结果展开,进行全连接层
flatten=tf.layers.flatten(pooling3)
y_predict=tf.layers.dense(flatten,10)

 

 

这篇关于经典的卷积神经网络(VGG、ResNet、InceptionNet、MobileNet)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/858362

相关文章

YOLOv8改进 | SPPF | 具有多尺度带孔卷积层的ASPP【CVPR2018】

💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录 :《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有40+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进——点击即可跳转 Atrous Spatial Pyramid Pooling (ASPP) 是一种在深度学习框架中用于语义分割的网络结构,它旨

人工智能机器学习算法总结神经网络算法(前向及反向传播)

1.定义,意义和优缺点 定义: 神经网络算法是一种模仿人类大脑神经元之间连接方式的机器学习算法。通过多层神经元的组合和激活函数的非线性转换,神经网络能够学习数据的特征和模式,实现对复杂数据的建模和预测。(我们可以借助人类的神经元模型来更好的帮助我们理解该算法的本质,不过这里需要说明的是,虽然名字是神经网络,并且结构等等也是借鉴了神经网络,但其原型以及算法本质上还和生物层面的神经网络运行原理存在

一道经典Python程序样例带你飞速掌握Python的字典和列表

Python中的列表(list)和字典(dict)是两种常用的数据结构,它们在数据组织和存储方面有很大的不同。 列表(List) 列表是Python中的一种有序集合,可以随时添加和删除其中的元素。列表中的元素可以是任何数据类型,包括数字、字符串、其他列表等。列表使用方括号[]表示,元素之间用逗号,分隔。 定义和使用 # 定义一个列表 fruits = ['apple', 'banana

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

前端 CSS 经典:文字描边

前言:文字描边有两种实现方式 1. text-shadow 设置 8 个方向的文字阴影,缺点是只有八个方向,文字转角处可能有锯齿状。不支持文字透明,设置 color: transparent,文字会成描边颜色。 <!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta http-equiv="X-UA-Comp

神经网络第四篇:推理处理之手写数字识别

到目前为止,我们已经介绍完了神经网络的基本结构,现在用一个图像识别示例对前面的知识作整体的总结。本专题知识点如下: MNIST数据集图像数据转图像神经网络的推理处理批处理  MNIST数据集          mnist数据图像 MNIST数据集由0到9的数字图像构成。像素取值在0到255之间。每个图像数据都相应地标有“7”、“2”、“1”等数字标签。MNIST数据集中,

神经网络第三篇:输出层及softmax函数

在上一篇专题中,我们以三层神经网络的实现为例,介绍了如何利用Python和Numpy编程实现神经网络的计算。其中,中间(隐藏)层和输出层的激活函数分别选择了 sigmoid函数和恒等函数。此刻,我们心中不难发问:为什么要花一个专题来介绍输出层及其激活函数?它和中间层又有什么区别?softmax函数何来何去?下面我们带着这些疑问进入本专题的知识点: 1 输出层概述 2 回归问题及恒等函数 3

神经网络第一篇:激活函数是连接感知机和神经网络的桥梁

前面发布的文章介绍了感知机,了解了感知机可以通过叠加层表示复杂的函数。遗憾的是,设定合适的、能符合预期的输入与输出的权重,是由人工进行的。从本章开始,将进入神经网络的学习,首先介绍激活函数,因为它是连接感知机和神经网络的桥梁。如果读者认知阅读了本专题知识,相信你必有收获。 感知机数学表达式的简化 前面我们介绍了用感知机接收两个输入信号的数学表示如下:

多层感知机不等于神经网络?

在前一章节(https://blog.csdn.net/u012132349/article/details/86166324),我们介绍了感知机可以实现与门、或门、非门。只需给定合适的参数(w1, w2, b)并利用Python就可以简单实现对输入的任意(x1,x2),输出0或1。     今天我们将介绍感知机的局限性(严格说是单层感知机的局限性)。这里我们想用感知机实现异或门,所谓异

LeetCode:经典题之141、142 题解及延伸

系列目录 88.合并两个有序数组 52.螺旋数组 567.字符串的排列 643.子数组最大平均数 150.逆波兰表达式 61.旋转链表 160.相交链表 83.删除排序链表中的重复元素 389.找不同 1491.去掉最低工资和最高工资后的工资平均值 896.单调序列 206.反转链表 92.反转链表II 141.环形链表 142.环型链表 目录 系列目录141. 环形链表常量因子 1