经典卷积网络进阶--ResNet详解

一.ResNet概述

resnet在2015名声大噪,微软公司提出了一种新的网络结构---残差网络(resnet)。残差模块结构图如下图1,图中曲线连接方式(X identity)称为近道连接,这种连接方式直接跳过了权重层;经过权重层的连接方式(F(X))与近道连接(X identity)构成了残差模块

                                                    图1

残差网络是由一系列残差块组成的(1式)。一个残差块可以用表示为:

 

残差块分成两部分直接映射部分和残差部分。h(x1)  是直接映射,反应在图1中是左边的曲线; 

F(X1,w1) 是残差部分,一般由两个或者三个卷积操作构成,即1式中右侧包含卷积的部分。

残差网络结构允许网络尽可能加深,较为常见的是:ResNet50和ResNet101。具体结构如下

https://arxiv.org/abs/1512.03385

从表中可以看出,所有ResNet网络主要被分为5个部分。

残差块可以大致分成2种,一种有bottleneck结构,即下图右中的1*1,3*3,1*1 的卷积层,用于先降维再升维,主要出于降低计算复杂度的现实考虑,称之为“bottleneck block”,另一种没有bottleneck结构,如下图左所示,称之为“basic block”。即下图左中的basic block由2个3×3和3×3卷积层构成。

3K34c8.png

近道连接(shortcut)也分为两种(如下图是以两个3*3的卷积核的残差块进行划分恒等块和卷积块的),一种是近道连接中有卷积模块,另一种是近道连接无卷积模块,残差块根据近道连接是否有卷积模块可以分为卷积块(convolutional block)和恒等快(identity block)。为什么在近道连接中加入卷积块?原因在于如果近道连接所连接的两组数据的通道(channel)个数不同,则可以在近道连接中加入1*1卷积模块对通道个数进行调整。

https://d2l.ai/chapter_convolutional-modern/resnet.html

在本篇文章中主要分享ResNet50,在ResNet50中,为了减少参数计算量,会使用1*1的卷积核对输入数据进行降维,再进行卷积运算,当输出时同样使用1*1的卷积核使数据维度恢复到输入时的维度。

ResNet50残差模块结构图如下:输入数据256维,在第一层1*1的卷积层中降维到64维,再经过3*3的卷积后,最后由1*1的卷积层将其恢复到256维

经典卷积网络进阶--ResNet详解_第1张图片

二.ResNet50实现MNIST分类

基于keras框架

代码如下:

from keras.models import Model
from keras.layers import Input,Dense,Dropout,Flatten,MaxPooling2D,Conv2D,AveragePooling2D,Activation,BatchNormalization,\
   ZeroPadding2D,Add
from keras.initializers import glorot_uniform
from keras.datasets import mnist
from keras.utils import np_utils
from matplotlib import pyplot as plt
import numpy as np

#数据集预处理
(X_train,Y_train),(X_test,Y_test)=mnist.load_data()
X_test1=X_test
Y_test1=Y_test
#处理特征数据
X_train=X_train.reshape(-1,28,28,1).astype("float32")/255.0
X_test=X_test.reshape(-1,28,28,1).astype("float32")/255.0
# 处理标签
Y_train=np_utils.to_categorical(Y_train,10)
Y_test=np_utils.to_categorical(Y_test,10)
print(X_train.shape)
print(Y_train.shape)
#搭建恒等快
#X代表输入数据,f代表该恒等快的第二个卷积的大小,因为第一个和第三个卷积核大小都是1*1,stage代表第几个卷积核,block代表卷积核的名字
def identity_block(X,f,filters,stage,block):
    #命名
    cov_name="res"+str(stage)+block+"branch"
    bn_name="bn"+str(stage)+block+"branch"
    F1,F2,F3=filters #该恒等快的各个卷积核的大小
    X_TEMP=X  #保存输入数据,为近道连接做准备
    #定义第一层卷积核
    X=Conv2D(filters=F1,kernel_size=(1,1),strides=1,padding="valid",name=cov_name+"2a",kernel_initializer=glorot_uniform
    (seed=0))(X)
    X=BatchNormalization(axis=3,name=bn_name+"2a")(X)
    X=Activation("relu")(X)
    #定义第二层卷积核
    X=Conv2D(filters=F2,kernel_size=(f,f),strides=1,padding="same",activation="relu",name=cov_name+"2b",kernel_initializer=
    glorot_uniform(seed=0))(X)
    X=BatchNormalization(axis=3,name=bn_name+"2b")(X)
    #定义第三层卷积核
    X=Conv2D(filters=F3,kernel_size=(1,1),strides=1,padding="valid",activation="relu",name=cov_name+"2c",kernel_initializer=
             glorot_uniform(seed=0))(X)
    X=BatchNormalization(axis=3,name=bn_name+"2c")(X) #归一化
    #将近道连接与经过权重的输出加起来
    X=Add()([X,X_TEMP])
    #经过激活函数输出值
    X=Activation("relu")(X)
    return X


#搭建卷积块
def cov_block(X,f,filters,stage,block,s=2):
    #卷积块命名
    cov_name="res"+str(stage)+block+"branch"
    bn_name="bn"+str(stage)+block+"branch"
    X_TEMP=X
    F1,F2,F3=filters
    #搭建固定的卷积模块
    #搭建第一层的卷积核,步长为s
    X=Conv2D(filters=F1,kernel_size=(1,1),strides=s,activation="relu",name=cov_name+"2a",kernel_initializer=
             glorot_uniform(seed=0))(X)
    X=BatchNormalization(axis=3,name=bn_name+"2a")(X)
    #搭建第二层卷积核
    X=Conv2D(filters=F2,kernel_size=(f,f),strides=1,padding="same",activation="relu",name=cov_name+"2b",kernel_initializer=
             glorot_uniform(seed=0))(X)
    X=BatchNormalization(axis=3,name=bn_name+"2b")(X)
    #搭建第三层卷积核
    X=Conv2D(filters=F3,kernel_size=(1,1),strides=1,name=cov_name+"2c",kernel_initializer=
             glorot_uniform(seed=0))(X)
    X=BatchNormalization(axis=3,name=bn_name+"2c")(X)

    #搭建近道连接的卷积核,加入卷积层和归一化层
    X_TEMP=Conv2D(filters=F3,kernel_size=(1,1),strides=(s,s),name=cov_name+"1",kernel_initializer=glorot_uniform(seed=0))(X_TEMP)
    X_TEMP=BatchNormalization(axis=3,name=bn_name+"1")(X_TEMP)  #归一化层
    #将近道连接与经过卷积的输出加在一起
    X=Add()([X,X_TEMP])
    #激活层
    X=Activation("relu")(X)
    return X
#利用恒等快和卷积块搭建resnet50网络结构
def resnet():
    X_input=Input(shape=(28,28,1)) #输入
    X=ZeroPadding2D((3,3))(X_input) #填充0
    #搭建stage1:卷积层(卷积层,归一化层,激活层,池化层)
    X=Conv2D(filters=64,kernel_size=(7,7),strides=2,activation="relu",name="cov1")(X)
    X=BatchNormalization(axis=3,name="bn_cov1")(X)
    X=MaxPooling2D(pool_size=(3,3),strides=2)(X)
    #搭建stage2:一个卷积块和两个恒等快,卷积块中卷积核大小均为3*3,卷积核个数分别为64,64,256,恒等快的卷积核大小均为3*3,卷积核大小为3*3
    #卷积核个数分别为64,64,256
    X=cov_block(X,f=3,filters=[64,64,256],stage=2,block="a",s=1)
    X=identity_block(X,f=3,filters=[64,64,256],stage=2,block="b")
    X=identity_block(X,f=3,filters=[64,64,256],stage=2,block="c")
    #搭建stage3:一个卷积块和3个恒等快,卷积块的卷积核的大小为3*3,卷积核的个数128,128,512;恒等块的卷积核大小为3*3,卷积核的个数为
    #128,128,512
    X=cov_block(X,f=3,filters=[128,128,512],stage=3,block="a",s=2)
    X=identity_block(X,f=3,filters=[128,128,512],stage=3,block="b")
    X=identity_block(X,f=3,filters=[128,128,512],stage=3,block="c")
    X=identity_block(X,f=3,filters=[128,128,512],stage=3,block="d")
    #搭建stage4:一个卷积块和5个恒等块,卷积块中卷积核的大小为3*3,卷积核个数为256,256,1024,恒等快卷积核的大小为3*3,卷积核个数分别为256
    #256,1024
    X=cov_block(X,f=3,filters=[256,256,1024],stage=4,block="a",s=2)
    X=identity_block(X,f=3,filters=[256,256,1024],stage=4,block="b")
    X=identity_block(X,f=3,filters=[256,256,1024],stage=4,block="c")
    X=identity_block(X,f=3,filters=[256,256,1024],stage=4,block="d")
    X=identity_block(X,f=3,filters=[256,256,1024],stage=4,block="e")
    X=identity_block(X,f=3,filters=[256,256,1024],stage=4,block="f")
    #搭建stage5:一个卷积块与两个恒等快,卷积块中卷积核大小为3*3,卷积核个数为512,512,2048,恒等快中卷积核的大小为3*3,卷积核的个数分别为512
    #512,2048
    X=cov_block(X,f=3,filters=[512,512,2048],stage=5,block="a",s=2)
    X=identity_block(X,f=3,filters=[512,512,2048],stage=5,block="b")
    X=identity_block(X,f=3,filters=[512,512,2048],stage=5,block="c")

    #搭建平均池化层
    X=AveragePooling2D(pool_size=(2,2),name="avg_pool",strides=1,padding="same")(X)
    #搭建平坦层
    X=Flatten()(X)
    #输出层
    X=Dense(units=10,activation="softmax",name="fc")(X)
    #调用model函数,定义所搭建的网络模型
    model=Model(inputs=X_input,outputs=X,name="resnet50")
    return model
model=resnet()
#模型编译
model.compile(
    loss="categorical_crossentropy",
    optimizer="adam",
    metrics=["accuracy"]
)
model.summary()
#模型训练
n_epoch=4
batch_size=128
def run_resnet():
    training=model.fit(
        X_train,
        Y_train,
        epochs=n_epoch,
        batch_size=batch_size,
        validation_split=0.25,
        verbose=1
    )
    test=model.evaluate(X_train,Y_train,verbose=1)
    return training,test
training,test=run_resnet()
print("误差:",test[0])
print("准确率:",test[1])

def show_history(training_history,train,validation):
    plt.plot(training.history[train],linstyle="-",color="b")
    plt.plot(training.history[validation],linstyle="--",color="r")
    plt.title("Training history")
    plt.xlabel("epoch")
    plt.ylabel("accuracy")
    plt.legend(["train","validation"],loc="lower right")
    plt.show()
show_history(training,"accuracy","val-accuracy")

def show_history1(training_history,train,validation):
    plt.plot(training.history[train],linstyle="-",color="b")
    plt.plot(training.history[validation],linstyle="--",color="r")
    plt.title("Training history")
    plt.xlabel("epoch")
    plt.ylabel("loss")
    plt.legend(["train","validation"],loc="upper right")
    plt.show()
show_history1(training,"loss","val-loss")

prediction=model.predict(X_test)
def image_show(image):
    fig=plt.gcf()
    fig.set_size_inches(2,2)
    fig.imshow(image,cmap="binary")
    plt.show()
def result(i):
    image_show(X_test1[i])
    print("真实值:",Y_test1[i])
    print("预测值:",np.argmax(prediction[i]))
result(1)
result(0)

 

你可能感兴趣的:(深度学习,keras)