在数字化转型中,选择合适的跨平台开发框架不仅能提高效率,还有助于确保数据安全与合规性。
846
2022-11-23
CNN基本步骤以及经典卷积(LeNet、AlexNet、VGGNet、InceptionNet 和 ResNet)网络讲解以及tensorflow代码实现
文章目录
前言1、卷积神经网络的基本步骤
1、卷积神经网络计算convolution2、感受野以及卷积核的选取3、全零填充Padding4、tf描述卷积层5、批标准化(BN操作)6、池化Pooling7、舍弃Dropout8、卷积神经网络搭建以及参数分析
2、经典卷积网络讲解
1、LeNet2、AlexNet3、VGGNet4、InceptionNet5、ResNet6、经典卷积网络总结
总结
前言
本讲目标:讲解卷积神经网络的基本步骤以及分析比较经典的网络架构,希望对你有所帮助 经典的5个论文的-链接:链接: 提取码:kbd8
1、卷积神经网络的基本步骤
1、卷积神经网络计算convolution
卷积概念:
卷积的概念:卷积可以认为是一种有效提取图像特征的方法。一般会用一个正方形的 卷积核,按指定步长,在输入特征图上滑动,遍历输入特征图中的每个像素点。每一个步长, 卷积核会与输入特征图出现重合区域,重合区域对应元素相乘、求和再加上偏置项得到输出 特征的一个像素点
卷积注意点:
输入特征图的深度,决定了卷积核的深度 当前层的卷积核个数,决定了当前层的输出特征图深度 如果觉得某一层的特征提取能力不足,可以在这一层多用几个卷积核 卷积:使用立体卷积核实现了参数的空间共享 执行卷积计算时,卷积核里的参数是固定的,反向传播时会更新参数
2、感受野以及卷积核的选取
这个之前有思考过,见下面链接:为什么两层33卷积核效果比1层55卷积核效果要好?
3、全零填充Padding
4、tf描述卷积层
tf.keras.layers.Conv2D(filters=卷积核个数,kernel_size=卷积核尺寸、#正方形写核长整数,或(核高h,核宽w)strides=滑动步长,#横纵向相同写步长整数,或(纵向步长h,横向步长w),默认1padding ="same" or "valid", #使用全零填充是"same",不使用是"valid"(默认)activation ="relu" or "sigmoid" or"train" or"softmax",#如果有BN此处不填input_shape =(高,宽,通道数) #输入特征维度,可省略)
调用方法如下: 代码解释:
1、使用6个(5,5)的卷积核卷积,不全零填充,使用sigmoid作为激活函数 2、使用(2,2)的池化核,步长为2,选用最大池化 3、使用Flatten将输出拉直成一维数组 4、使用10个神经元构成的全连接,激活函数使用softmax
model=tf.keras.models.Sequential([ Conv2D(6,5,padding='valid',activation='sigmoid'), MaxPool2D(2,2), #或者这样调用 #Conv2D(6,(5,5),padding='valid',activation='sigmoid'), #MaxPool2D(2,(2,2), #也可以这样调用 #Conv2D(filters=6,kenrnel_size=(5,5),padding='valid',activation='sigmoid'), #MaxPool2D(pool_size=(2,2),strides=2), Flatten(), Dense(10,activation='softmax')])
在利用 Tensorflow 框架构建卷积网络时,一般会利用 BatchNormalization 函数来构建 BN 层,进行批归一化操作,所以在 Conv2D 函数中经常不写 BN
5、批标准化(BN操作)
神经网络对0附近的数据更敏感 但是随着网络层数的增加,特征数据会出现偏离0均值的情况 使用标准化,将偏移的数据重新拉回 批标准化:是对一个batch的数据做标准化处理,常用在卷积操作和激活操作之间
BN层位于卷积层与激活层之间 tf提供了BN操作的函数BatchNormalization()
model=tf.keras.models.Sequential([ Conv2D(filters=6,kernel_size=(5,5),padding='same'),#卷积层 BatchNormalization(), #BN层 Activation('relu'), #激活层 MaxPool2D(pool_size=(2,2),strides=2,padding='same'),#池化层 Dropout(0.2), #dropout层])
6、池化Pooling
池化用于减少特征数据量 最大值池化可以提取图片纹理,均值池化可以保留背景特征 如果用2 * 2的池化核对输入图片进行步长为2的池化操作,输出图片将变为输入图片的四分之一大小 tf描述池化操作:
tf.keras.layers.MaxPool2D(pool_size=池化核尺寸,strides=池化步长,padding='valid'or'same'#same全零填充,valid不全零填充、)tf.keras.layers.AveragePooling2D(pool_size=池化核尺寸,strides=池化步长,padding='valid'or'same'#same全零填充,valid不全零填充)
调用例子:
model=tf.keras.models.Sequential([ Conv2D(filters=6,kernel_size=(5,5),padding='same'),#卷积层 BatchNormalization(), #BN层 Activation('relu'), #激活层 MaxPool2D(pool_size=(2,2),strides=2,padding='same'),#池化层 Dropout(0.2), #dropout层])
7、舍弃Dropout
Dropout函数:
tf.keras.layers.Dropout(舍弃的概率)
8、卷积神经网络搭建以及参数分析
卷积神经网络:借助卷积核提取特征后,送入全连接网络 卷积神经网络的主要模块: 卷积(conv) 批标准化(BN) 激活(Activation) 池化(Pooling) 卷积是什么:卷积就是特征提取器,就是CBAPD
在这里我们仍然套用网络八股的构架:
1、import引入tensorflow及keras、numpy等所需模块 2、读取数据集 3、搭建所需的网络结构,当网络结构比较简单时,可以利用keras模块中的tf.keras.Sequential来搭建顺序网络模型;但是当网络不再是简单的顺序结构,而是有其他特殊的结构出现(如ResNet中的跳连结构),急需要利用class来定义自己的网络结构 4、对搭建好的网络进行编译(compile),通常在这一步指定所采用的优化器(Adam、SGD、RMSdrop)以及损失函数(交叉熵函数、均方差函数等) 5、将数据输入编译好的网络来训练(model.fit),在这一步中指定训练轮数epochs以及batch_size等信息。由于参数量和计算量一般都比较大,训练所需的时间也会比较长,这一步中通常会加入断点续训以及模型参数保存 6、将神经网络模型具体信息打印出来(model.summary),包括网络结构、网络各层参数
这里还要讲一下flatten函数:
Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten不影响batch的大小。
接下来我们使用类搭建5x5的卷积核,6个,池化核2x2,步长2的网络结构:
#使用类搭建网络结构,CBAPDclass Baseline(Model): def _init_(self): super(Baseline,self)._init_() self.c1 =Conv2D(filters=6,kernel_size=(5,5),padding='same') self.b1 =BatchNormalization() self.a1 =Activaction('relu') self.p1 =MaxPool2D(pool_size=(2,2),strides=2,padding ='same') self.d1 =Dropout(0.2) self.flatten =Flatten() self.f1 =Dense(128,activation='relu') self.d2 =Dropout(0.2) self.f2 =Dense(10,activation='softmax')#call函数调用init函数中搭建好的每层网络结构def call(self,x): x=self.c1(x) x=self.b1(x) x=self.a1(x) x=self.p1(x) x=self.d1(x) x=self.flatten(x) x=self.f1(x) x=self.d2(x) y=self.f2(x) return y#从输入到输出过一次前向传播,返回推理结果
我们来分析一下参数:
打开weights文件: baseline/conv2d/kernel:0 第一层网络 5x5x3的卷积核 一共6个 (5, 5, 3, 6)=>450 baseline/conv2d/bias:0 每个卷积核的偏置项b (6,)=>6 baseline/batch_normalization/gamma:0 BN操作中的缩放因子γ,每个卷积核一个γ (6,)=>6 baseline/batch_normalization/beta:0 BN操作中的偏移因子γ,每个卷积核一个γ (6,)=>6 baseline/dense/kernel:0 第一层全连接网络 (1536, 128)=>196608 baseline/dense/bias:0 第一层全连接网络128个偏置b (128,)=>128 baseline/dense_1/kernel:0 第二层全连接网络 (128, 10)=>1280 baseline/dense_1/bias:0 第二层全连接网络10个偏置b (10,)=>10 总共:450+6+6+6+196608+128+1280+10=198494 有了这些参数就可以复现出神经网络的前向传播实现应用 可以发现,神经网络的网络参数十分多,多大十几万甚至更多(当网络复杂层数增加),并且可以发现绝大部分参数都集中在全连接层上,卷积层的参数占比较小。然而卷积核的参数却是非常重要的(因为卷积是特征提取器,特征的参数才是图片识别的重点)。所以减少全连接网络的参数或许会是一个不错的网络优化方法。
至此,基本的神经网络的搭建方法已经讲解完毕。接下来会逐一讲解LeNet、AlexNet、VGGNet、InceptionNet 和 ResNet的特点,并且用基于tensorflow的代码复现出网络架构。
2、经典卷积网络讲解
1、LeNet
class LeNet5(Model): def __init__(self): super(LeNet5,self).__init__() self.c1=Conv2D(filters=6,kernel_size=(5,5),padding='valid',input_shape=(32,32,3),activation='sigmoid') self.p1=MaxPool2D(pool_size=(2,2),strides=2) self.c2=Conv2D(filters=16,kernel_size=(5,5),padding='valid',activation='sigmoid') self.p2=MaxPool2D(pool_size=(2,2),strides=2) self.flatten =Flatten() self.f1 =Dense(120,activation='sigmoid') self.f1 =Dense(84,activation='sigmoid') self.f1 =Dense(10,activation='softmax') def call(self, x): x = self.c1(x) x = self.p1(x) x = self.c2(x) x = self.p2(x) x = self.flatten(x) x = self.f1(x) x = self.f2(x) y = self.f3(x) return ymodel = LeNet5()
优点:共享卷积核,减少网络参数.如何理解卷积神经网络中的权值共享?
2、AlexNet
class AlexNet8(Model): def __init__(self): super(AlexNet8,self).__init__() self.c1=Conv2D(filters=96,kernel_size=(3,3)) self.b1=BatchNormalization() self.a1=Activation('relu') self.p1=MaxPool2D(pool_size=(3,3),strides=2) self.c2=Conv2D(filters=256,kernel_size=(3,3)) self.b2=BatchNormalization() self.a2=Activation('relu') self.p2=MaxPool2D(pool_size=(3,3),strides=2) self.c3=Conv2D(filters=384,kernel_size=(3,3,padding='same',activation='relu') self.c4=Conv2D(filters=384,kernel_size=(3,3,padding='same',activation='relu') self.c5=Conv2D(filters=256,kernel_size=(3,3,padding='same',activation='relu') self.p3=MaxPool2D(pool_size=(3,3),strides=2) self.flatten =Flatten() self.f1 =Dense(2048,activation='relu') self.d1 =Dropout(0.5) self.f1 =Dense(2048,activation='relu') self.d1 =Dropout(0.5) self.f1 =Dense(10,activation='softmax')def call(self, x): x = self.c1(x) x = self.b1(x) x = self.a1(x) x = self.p1(x) x = self.c2(x) x = self.b2(x) x = self.a2(x) x = self.p2(x) x = self.c3(x) x = self.c4(x) x = self.c5(x) x = self.p3(x) x = self.flatten(x) x = self.f1(x) x = self.d1(x) x = self.f2(x) x = self.d2(x) y = self.f3(x) return ymodel = AlexNet8()
优点:激活函数使用 Relu,提升训练速度;Dropout 防止过拟合
3、VGGNet
class VGG16(Model): def __init__(self): super(VGG16, self).__init__() self.c1 = Conv2D(filters=64, kernel_size=(3, 3), padding='same') # 卷积层1 self.b1 = BatchNormalization() # BN层1 self.a1 = Activation('relu') # 激活层1 self.c2 = Conv2D(filters=64, kernel_size=(3, 3), padding='same', ) self.b2 = BatchNormalization() # BN层1 self.a2 = Activation('relu') # 激活层1 self.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') self.d1 = Dropout(0.2) # dropout层 self.c3 = Conv2D(filters=128, kernel_size=(3, 3), padding='same') self.b3 = BatchNormalization() # BN层1 self.a3 = Activation('relu') # 激活层1 self.c4 = Conv2D(filters=128, kernel_size=(3, 3), padding='same') self.b4 = BatchNormalization() # BN层1 self.a4 = Activation('relu') # 激活层1 self.p2 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') self.d2 = Dropout(0.2) # dropout层 self.c5 = Conv2D(filters=256, kernel_size=(3, 3), padding='same') self.b5 = BatchNormalization() # BN层1 self.a5 = Activation('relu') # 激活层1 self.c6 = Conv2D(filters=256, kernel_size=(3, 3), padding='same') self.b6 = BatchNormalization() # BN层1 self.a6 = Activation('relu') # 激活层1 self.c7 = Conv2D(filters=256, kernel_size=(3, 3), padding='same') self.b7 = BatchNormalization() self.a7 = Activation('relu') self.p3 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') self.d3 = Dropout(0.2) self.c8 = Conv2D(filters=512, kernel_size=(3, 3), padding='same') self.b8 = BatchNormalization() # BN层1 self.a8 = Activation('relu') # 激活层1 self.c9 = Conv2D(filters=512, kernel_size=(3, 3), padding='same') self.b9 = BatchNormalization() # BN层1 self.a9 = Activation('relu') # 激活层1 self.c10 = Conv2D(filters=512, kernel_size=(3, 3), padding='same') self.b10 = BatchNormalization() self.a10 = Activation('relu') self.p4 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') self.d4 = Dropout(0.2) self.c11 = Conv2D(filters=512, kernel_size=(3, 3), padding='same') self.b11 = BatchNormalization() # BN层1 self.a11 = Activation('relu') # 激活层1 self.c12 = Conv2D(filters=512, kernel_size=(3, 3), padding='same') self.b12 = BatchNormalization() # BN层1 self.a12 = Activation('relu') # 激活层1 self.c13 = Conv2D(filters=512, kernel_size=(3, 3), padding='same') self.b13 = BatchNormalization() self.a13 = Activation('relu') self.p5 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') self.d5 = Dropout(0.2) self.flatten = Flatten() self.f1 = Dense(512, activation='relu') self.d6 = Dropout(0.2) self.f2 = Dense(512, activation='relu') self.d7 = Dropout(0.2) self.f3 = Dense(10, activation='softmax') def call(self, x): x = self.c1(x) x = self.b1(x) x = self.a1(x) x = self.c2(x) x = self.b2(x) x = self.a2(x) x = self.p1(x) x = self.d1(x) x = self.c3(x) x = self.b3(x) x = self.a3(x) x = self.c4(x) x = self.b4(x) x = self.a4(x) x = self.p2(x) x = self.d2(x) x = self.c5(x) x = self.b5(x) x = self.a5(x) x = self.c6(x) x = self.b6(x) x = self.a6(x) x = self.c7(x) x = self.b7(x) x = self.a7(x) x = self.p3(x) x = self.d3(x) x = self.c8(x) x = self.b8(x) x = self.a8(x) x = self.c9(x) x = self.b9(x) x = self.a9(x) x = self.c10(x) x = self.b10(x) x = self.a10(x) x = self.p4(x) x = self.d4(x) x = self.c11(x) x = self.b11(x) x = self.a11(x) x = self.c12(x) x = self.b12(x) x = self.a12(x) x = self.c13(x) x = self.b13(x) x = self.a13(x) x = self.p5(x) x = self.d5(x) x = self.flatten(x) x = self.f1(x) x = self.d6(x) x = self.f2(x) x = self.d7(x) y = self.f3(x) return ymodel = VGG16()
总体来看,VGGNet的结构是相当规整的,它继承了 AlexNet中的Relu激活函数、Dropout操作等有效的方法,同时采用了单一尺寸的 3 * 3 小卷积核,形成了规整的 C(Convolution,卷积)、B(Batch normalization)、A(Activation,激活)、P(Pooling,池化)、D(Dropout)结构,这一典型结构在卷积神经网络中的应用是非常广的 优点:小卷积核减少参数的同时,提高识别准确率;网络结构规整,适合并行加速。
4、InceptionNet
class ConvBNRelu(Model): def __init__(self,ch,kernelsz=3,strides=1,padding='same'): super(ConvBNRelu,self).__init__() self.model=tf.keras.models.Sequential([ Conv2D(ch,kernelsz,strides=strides,padding=padding), BatchNormalization(), Activation('relu') ]) def call(self,x,training=None): x=self.model(x,training=training) return x#ch 特征图的通道数,即卷积核个数class InceptionBlk(Model): def __init__(self,ch,strides=1): super(InceptionBlk,self).__init__() self.ch=ch self.strides=strides self.c1=ConvBNRelu(ch,kernelsz=1,strides=strides) self.c2_1=ConvBNRelu(ch,kernelsz=1,strides=strides) self.c2_2=ConvBNRelu(ch,kernelsz=3,strides=1) self.c3_1=ConvBNRelu(ch,kernelsz=1,strides=strides) self.c3_2=ConvBNRelu(ch,kernelsz=5,strides=1) self.p4_1=MaxPool2D(3,strides=1,padding='same') self.c4_2=ConvBNRelu(ch,kernelsz=1,strides=strides) def call(self,x): x1=self.c1(x) x2_1=self.c2_1(x) x2_2=self.c2_2(x2_1) x3_1=self.c3_1(x) x3_2=self.c3_2(c3_1) x4_1=self.p4_1(x) x4_2=self.c4_2(x4_1) #concat along axis=channel x=tf.concat([x1,x2_2,x3_2,x4_2],axis=3) return x
其实基本单元一开始是长这样的,通过不同尺寸的卷积层和池化层的横向组合(卷积和池化后的尺寸相同,便于叠加)来拓宽网络深度,增强网络对尺寸的适应性。但由于卷积核都是在上一层的输出上直接计算的,导致参数变多以及运算变得复杂,所以加入1 * 1卷积核,减少特征厚度。 以5 * 5的卷积运算为例说明这个 问题。假设网络上一层的输出为 100 * 100 * 128(H * W * C),通过 32 * 5 * 5(32 个大小 为 5 * 5 的卷积核)的卷积层(步长为 1、全零填充)后,输出为 100 * 100 * 32,卷积层的 参数量为 32 * 5 * 5 * 128 = 102400;如果先通过 32 * 1 * 1 的卷积层(输出为 100 * 100 * 32), 再通过 32 * 5 * 5 的卷积层,输出仍为 100 * 100 * 32,但卷积层的参数量变为 32 * 1 * 1 * 128
class Inception10(Model): def __init__(self,num_blocks,num_classes,init_ch=16,**kwargs): super(Inception10,self).__init__(**kwargs) self.in_channels=init_ch self.out_channels=init_ch self.num_blocks=num_blocks self.init_ch=init_ch self.c1=ConvBNRelu(init_ch) self.blocks=tf.keras.models.Sequential() for block_id in range(num_blocks): for layer_id in range (2): if layer_id==0: block=InceptionBlk(self.out_channels,strides=2) else: block=InceptionBlk(self.out_channels,strides=1) self.blocks.add(block) # self.out_channels*=2 self.p1 = GlobalAveragePooling2D() self.f1 = Dense(num_classes,activation='softmax') def call(self,x): x=self.c1(x) x=self.blocks(x) x=self.p1(x) y=self.f1(x) return ymodel=Inception10(num_blocks=2,num_classes=10)
参数num_blocks代表InceptionNet的Block数,每个Block由两个基本单元构成,每经过一个 Block,特征图尺寸变为1/2,通道数变为原来的两倍; num_classes代表分类数 init_ch代表初始通道数,代表InceptionNet基本单元的初始卷积核个数
InceptionNet采用"全局平均池化+全连接层",VGGNet(有三层全连接层) 平均池化:在特征图上以窗口的形式滑动,取窗口内的平均值为采样值 全局平均池化:直接针对特征图取平均值,每一个特征图输出一个值,通过这种方式,每个特征图都与分类概率直接联系起来替代了全连接层的功能,并且不会产生额外的训练参数,减少了过拟合的可能,但会导致网络收敛速度变慢。 InceptionNet采用多尺寸卷积再聚合的方式拓宽了网络结构,并通过 1 * 1卷积运算来减少参数量。
5、ResNet
class ResnetBlock(Model): def __init__(self,filters,strides=1,residual_path=False): super(ResnetBlock,self).__init__() self.filters = filters self.strides = strides self.residual_path = residual_path self.c1 = Conv2D(filters,(3,3),strides=strides,padding='same',use_bias=False) self.b1 = BatchNormalization() self.a1 = Activation('relu') self.c2 = Conv2D(filters,(3,3),strides=1,padding='same',use_bias=False) self.b2 = BatchNormalization() #residual_path为Ture时,对输入进行下采样,用1x1的卷积核卷积,保证x和F(x)维度相同 if residual_path: self.down_c1 = Conv2D(filters,(1,1),strides=strides,padding='same',use_bias=False) self.down_b1 = BatchNormalization() self.a2=Activation('relu') def call(self,inputs): residual = inputs #residual等于输入值本身,即residual=x #将输入通过卷积、BN层,激活层,计算F(X) x=self.c1(inputs) x=self.b1(x) x=self.a1(x) x=self.c2(x) y=self.b2(x) if self.residual_path: residual=self.down_c1(inputs) residual=self.down_b1(residual) out = self.a2(y+residual) #最后输出两部分的和,F(x)+x或者F(x)+Wx,再过激活函数 return out
ResNet18网络结构以及其利用tf构建模型示意:
class ResNet(Model): def __init__(self,block_list,initial_filters=64): #block_list表示每个block有几个卷积层 super(ResNet,self).__init__() self.num_blocks =len(block_list) self.block_list =block_list self.out_filters = initial_filters self.c1 = Conv2D(self.out_filters,(3,3),strides=1,padding='same',use_bias=False,kernel_initialize='he_normal') self.b1 = tf.keras.layers.BatchNormalization() self.a1 = Activation('relu') self.blocks = tf.keras.models.Sequential() #构建ResNet网络结构 for block_id in range(len(block_list)): #第几个resnet_block for layer_id in range(block_list(block_list[block_id])): #第几个卷积层 if block_id!=0 and layer_id==0 : #对除第一个block以外的每个block的输入进行下采样,对于一个样值序列间隔几个样值取样一次,这样得到新序列就是原序列的下采样 block = ResnetBlock(self.out_filters,strides=2,residual_path = True) else: block = ResnetBlock(self.out_filters,residual_path=False) self.blocks.add(block) #将构建好的block加入resnet self.out_filters *=2 #下一个block的卷积核个数是上一个block的2倍 self.p1 = tf.keras.layers.GlobalAveragePooling2D() self.f1 = tf.keras.layers.Dense(10) def call(self,inputs): x=self.c1(inputs) x=self.b1(x) x=self,a1(x) x=self.blocks(x) x=self.p1(x) y=self.f1(x) #由于使用了GAP,所以不需要dropout操作(不再是黑箱操作了) return y
6、经典卷积网络总结
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~