tensorflow2.x第四篇

卷积神经网络实现,lenet,Alexnet, VGG,Resnet

  • baseline
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model
'''
构建神经网络的“八股”套路: 
A)import 引入 tensorflow 及 keras、numpy 等所需模块。
B)读取数据集
C)搭建所 需的网络结构,当网络结构比较简单时,可以利用 keras 模块中的tf.keras.Sequential 来搭建顺序网络模型;
但是当网络不再是简单的顺序结构,而是有其它特殊结构出现时(例如 ResNet 中的跳连结构),便需要利用 class 来定义自己的网络结构。
前者使用起来更加方便,但实际应用中往往需要利用后者来搭建网络。
D)对搭建好的网络进行编译(compile),通常在这一步指定所采用的优化器(如 Adam、sgd、RMSdrop 等)以及损失函数(如交叉熵函数、均方差函数等),
选择哪种优化器和损失函数往往对训练的速度和效果有很大的影响,至于具体如何进行选择,前面的章节中有比较详细的介绍。
E)将数据输入编译好的网络来进行训练(model.fit),在这一步中指定训练轮数 epochs 以 及 batch_size 等信息,
由于神经网络的参数量和计算量一般都比较大,训练所需的时间也会比较长,尤其是在硬件条件受限的情况下,
所以在这一步中通常会加入断点续训以及模型参数保存等功能,使训练更加方便,同时防止程序意外停止导致数据丢失的情况发生。
F)将神经网络模型的具体信息打印出来(model.summary),包括网络结构、网络各层的参数等,便于对网络进行浏览和检查。

'''
np.set_printoptions(threshold=np.inf)
cifar10 = tf.keras.datasets.cifar10
(x_train,y_train),(x_test,y_test) = cifar10.load_data()
x_train,x_test = x_train/255.,x_test/255.

class Baseline(Model):
    def __init__(self):
        super(Baseline, self).__init__()
        '''
        tf.keras.layers.Conv2D 函数,具体的使用方法如下:
            tf.keras.layers.Conv2D(
                input_shape = (高, 宽, 通道数), #仅在第一层有
                filters = 卷积核个数,
                kernel_size = 卷积核尺寸,
                strides = 卷积步长,
                padding = ‘SAME’ or ‘VALID’,
                activation = ‘relu’ or ‘sigmoid’ or ‘tanh’ or ‘softmax’等 #如有 BN 则此处不用写
                )
        使用此函数构建卷积层时,需要给出的信息有:
            A)输入图像的信息,即宽高和通道数;
            B)卷积核的个数以及尺寸,如 filters = 16, kernel_size = (3, 3)代表采用 16 个大小为 3×3 的卷积核;
            C)卷积步长,即卷积核在输入图像上滑动的步长,纵向步长与横向步长通常是相同的,默认值为 1; D)是否进行全零填充,全零填充的具体作用上文有描述;
            E)采用哪种激活函数,例如 relu、softmax 等,各种函数的具体效果在前面章节中有详细描述
        '''
        self.c1 = Conv2D(filters=6,kernel_size=(5,5),padding='same')
        '''
        Batch Normalization 将神经网络每层的输入都调整到均值为 0,方差为 1 的标准正态分布,其目的是解决神经网络中梯度消失的问题
        在调用此函数时,需要注意的一个参数是 training,此参数只在调用时指定,在模型进行前向推理时产生作用,
        当 training = True 时,BN 操作采用当前 batch 的均值和标准差;
        当 training = False 时,BN 操作采用滑动平均(running)的均值和标准差。在 Tensorflow 中,
        通常会指定 training = False,可以更好地反映模型在测试集上的真实效果。
        '''
        self.b1 = BatchNormalization()
        '''
        tf.keras.layers.MaxPool2D函数和 tf.keras.layers.AveragePooling2D 函数,
        具体的使用方法如下:
        tf.keras.layers.MaxPool2D(
            pool_size = 池化核尺寸,
            strides = 池化步长,
            padding = ‘SAME’ or ‘VALID’ )
            
        tf.keras.layers.AveragePooling2D(
            pool_size = 池化核尺寸,
            strides = 池化步长,
            padding = ‘SAME’ or ‘VALID’ )
        '''
        self.a1 = Activation('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')

    def call(self, x, training=None, mask=None):
        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
model = Baseline()

model.compile(optimizer='Adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])

checkpoint_save_path = "./checkpoint/Baseline.ckpt"
if os.path.exists(checkpoint_save_path+'.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_best_only=True,
                                                 save_weights_only=True)

history = model.fit(x_train,y_train,batch_size=32,epochs=5,validation_data=(x_test,y_test),validation_freq=1,callbacks=[cp_callback])
model.summary()

file = open('./weights_baseline.txt','w')
for v in model.trainable_variables:
    file.write(str(v.name) + '\n')
    file.write(str(v.shape) + '\n')
    file.write(str(v.numpy()) + '\n')
file.close()

acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()

  • lenet
class LeNet5(Model):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.c1 = Conv2D(filters=6, kernel_size=(5, 5),
                         activation='sigmoid')
        self.p1 = MaxPool2D(pool_size=(2, 2), strides=2)

        self.c2 = Conv2D(filters=16, kernel_size=(5, 5),
                         activation='sigmoid')
        self.p2 = MaxPool2D(pool_size=(2, 2), strides=2)

        self.flatten = Flatten()
        self.f1 = Dense(120, activation='sigmoid')
        self.f2 = Dense(84, activation='sigmoid')
        self.f3 = 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 y
  • AlexNet8
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.f2 = Dense(2048, activation='relu')
        self.d2 = Dropout(0.5)
        self.f3 = 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 y
  • VGG16
'''
小卷积核减少参数的同时,提高识别准确率;网络结构规整,适合并行加速。
在 AlexNet 之后,另一个性能提升较大的网络是诞生于 2014 年的 VGGNet,其 ImageNet Top5 错误率减小到了 7.3 %。
VGGNet 网络的最大改进是在网络的深度上,由 AlexNet 的 8 层增加到了 16 层和 19 层,更深的网络意味着更强的表达能力,
这得益于强大的运算能力支持。
VGGNet 的另一个显著特点是仅使用了单一尺寸的 3 * 3 卷积核,事实上,3 * 3 的小卷积核在很多卷积网络中都被量使用,
这是由于在感受野相同的情况下,小卷积核堆积的效果要优于大卷积核,同时参数量也更少。
VGGNet 就使用了 3 * 3 的卷积核替代了 AlexNet 中的大卷积核(11 * 11、7 * 7、5 * 5),
取得了较好的效果(事实上课程中利用 Keras 实现 AlexNet 时已经采取了这种方式),
VGGNet16 和 VGGNet19 并没有本质上的区别,只是网络深度不同,前者 16 层(13 层卷积、3 层全连接),
后者 19 层(16 层卷积、3 层全连接)

'''


'''
根据特征图尺寸的变化,可以将 VGG16 模型分为六个部分(在 VGG16 中,每进行一次池化操作,特征图的边长缩小为 1/2,
其余操作均未影响特征图尺寸)
'''
class VGG16(Model):
    def __init__(self):
        super(VGG16,self).__init__()
        #第一部分:两次卷积(64 个 3 * 3 卷积核、BN、Relu 激活)→最大池化→Dropout
        self.c1 = Conv2D(filters=64,kernel_size=(3,3),padding='same')
        self.b1 = BatchNormalization()
        self.a1 = Activation('relu')
        #p
        #d
        self.c2 = Conv2D(filters=64,kernel_size=(3,3),padding='same')
        self.b2 = BatchNormalization()
        self.a2 = Activation('relu')
        self.p1 = MaxPool2D(pool_size=(2,2),strides=2,padding='same')
        self.d1 = Dropout(0.2)

        #第二部分:两次卷积(128个3 * 3卷积核、BN、Relu激活)→最大池化→Dropout
        self.c3 = Conv2D(filters=128,kernel_size=(3,3),padding='same')
        self.b3 = BatchNormalization()
        self.a3 = Activation('relu')
        #p
        #d
        self.c4 = Conv2D(filters=128,kernel_size=(3,3),padding='same')
        self.b4 = BatchNormalization()
        self.a4 = Activation('relu')
        self.p2 = MaxPool2D(pool_size=(2,2),strides=2,padding='same')
        self.d2 = Dropout(0.2)

        #第三部分:三次卷积(256个3 * 3卷积核、BN、Relu激活)→最大池化→Dropout
        self.c5 = Conv2D(filters=256,kernel_size=(3,3),padding='same')
        self.b5 = BatchNormalization()
        self.a5 = Activation('relu')
        #p
        #d
        self.c6 = Conv2D(filters=256,kernel_size=(3,3),padding='same')
        self.b6 = BatchNormalization()
        self.a6 = Activation('relu')
        #p
        #d
        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)

        #第四部分:三次卷积(512个3 * 3卷积核、BN、Relu激活)→最大池化→Dropout
        self.c8 = Conv2D(filters=512,kernel_size=(3,3),padding='same')
        self.b8 = BatchNormalization()
        self.a8 = Activation('relu')
        #p
        #d
        self.c9 = Conv2D(filters=512,kernel_size=(3,3),padding='same')
        self.b9 = BatchNormalization()
        self.a9 = Activation('relu')
        #p
        #d
        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)

        #第五部分:三次卷积(512个3 * 3卷积核、BN、Relu激活)→最大池化→Dropout
        self.c11 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b11 = BatchNormalization()
        self.a11 = Activation('relu')
        # p
        # d
        self.c12 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b12 = BatchNormalization()
        self.a12 = Activation('relu')
        # p
        # d
        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)

        #第六部分:全连接(512 个神经元)→Dropout→全连接(512 个神经元)→Dropout→全连接(10 个神经元)
        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 y
  • Resnet
'''
层间残差跳连,引入前方信息,减少梯度消失,使神经网络层数变身成为可能。
ResNet 即深度残差网络,由何恺明及其团队提出,是深度学习领域又一具有开创性的工作,
通过对残差结构的运用,ResNet 使得训练数百层的网络成为了可能,从而具有非常强大的表征能力
'''
'''
ResNet 不再让下一层直接拟合我们想得到的底层映射,而是令其对一种残差映射进行拟合。
若期望得到的底层映射为H(x),我们令堆叠的非线性层拟合另一个映射 F(x) := H(x) – x,则原有映射变为 F(x) + x。
对这种新的残差映射进行优化时,要比优化原有的非相关映射更为容易。不妨考虑极限情况,
如果一个恒等映射是最优的,那么将残差向零逼近显然会比利用大量非线性层直接进行拟合更容易
'''
'''
ResNet 引入残差结构最主要的目的是解决网络层数不断加深时导致的梯度消失问题,
从之前介绍的 4 种 CNN 经典网络结构我们也可以看出,网络层数的发展趋势是不断加深的。
这是由于深度网络本身集成了低层/中层/高层特征和分类器,以多层首尾相连的方式存在,
所以可以通过增加堆叠的层数(深度)来丰富特征的层次,以取得更好的效果。
但如果只是简单地堆叠更多层数,就会导致梯度消失(爆炸)问题,它从根源上导致了函数无法收敛。
然而,通过标准初始化(normalized initialization)以及中间标准化层(intermediate normalization layer),
已经可以较好地解决这个问题了,这使得深度为数十层的网络在反向传播过程中,可以通过随机梯度下降(SGD)的方式开始收敛。
但是,当深度更深的网络也可以开始收敛时,网络退化的问题就显露了出来:随着网络
深度的增加,准确率先是达到瓶颈(这是很常见的),然后便开始迅速下降。需要注意的是,
这种退化并不是由过拟合引起的。对于一个深度比较合适的网络来说,继续增加层数反而会导致训练错误率的提升
'''
'''
ResNet 解决的正是这个问题,其核心思路为:对一个准确率达到饱和的浅层网络,
在它后面加几个恒等映射层(即 y = x,输出等于输入),增加网络深度的同时不增加误差。
这使得神经网络的层数可以超越之前的约束,提高准确率
'''
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model

#定义残差结构



class ResnetBlock(Model):

    def __init__(self,filters,strides,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()

        #1*1卷积通过步长改变特征图尺寸,通过卷积核个数改变特征图深度
        #residual_path为True时,对输入进行下采样,即用1*1的卷积核做卷积操作,保证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

class ResNet18(Model):

    def __init__(self,block_list,initial_filters=64):  #block_list表示每个block有几个卷积层
        super(ResNet18,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)
        self.b1 = 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_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,activation='softmax',kernel_regularizer=tf.keras.regularizers.l2())
    
    def call(self, inputs, training=None, mask=None):
        x = self.c1(inputs)
        x = self.b1(x)
        x = self.a1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值