机器学习-卷积神经网络之深度残差网络(三)

背景介绍:MNIST数据集识别黑白的手写数字图片,不适合彩色模型的RGB三通道图片。用深度残差网络学习多通道图片。

简单介绍一下深度残差网络:普通的深度网络随着网络深度的加深,拟合效果可能会越来越好,也可能会变差,换句话说在不停地学习,但是有可能学歪了。本次介绍的深度残差网络最后输出H(x)=x+f(x)。其中x是本层网络的输入,f(x)是本层网络的输出,H(x)是最终得到的结果。由以上公式可以表明,最终结果包含输入x,也就是说不论怎么学习,起码效果不会变差,不会学歪。x和f(x)之间的变换的网络层就被成为残差模块。

有不懂的地方可以看代码下面的解释与讲解

目录

1.残差模块类的构建:

(1)残差模块:

(2)在残差模块中前向传播:

2.残差网络类:

(1)通用残差网络实现:

(2)调用残差模块建立方法:

(3)在整个网络中向前传播:

4.形成特殊网络:


1.残差模块类的构建:

(1)残差模块:

 # 残差模块
    def __init__(self, filter_num, stride=1):
        super(BasicBlock, self).__init__()
        #输入x经过两个卷积层得到f(x),f(x)+x=H(x),对应元素相加得到残差模块H(x)
        # 第一个卷积单元 卷积核大小3*3是超参数,需要学习,自己制定
        self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')
        self.bn1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        # 第二个卷积单元
        self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
        self.bn2 = layers.BatchNormalization()
        #当x与f(x)形状不同的时候,无法进行相加,新建identity(x)卷积层,完成x的形状转换
        if stride != 1:# 步长不为1,需要通过1x1卷积完成shape匹配
            self.downsample = Sequential()
            self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))
        else:# shape匹配,直接短接
            self.downsample = lambda x:x

再重复一遍残差模块是x和f(x)之间的网络变换,包括两个卷积单元。

第一个卷积单元:卷积核的数量由传入参数给定,使用3*3卷积核,步长设定为1,经过卷积变换后形状不变;经过BN层主要对参数进行标准化,对网络有益;最后经过激活层。第二个卷积单元同上,不过不需要激活函数了。

我们上面说过经过残差模块输出f(x)需要与x相加,因此需要保证二者形状相同。如果shape不相同:用1*1的卷积核对矩阵通道数进行变换。在此细说一下x与f(x)的形状:由于padding都是same因此矩阵形状是保持不变的,但是由于卷积层有多个卷积核,则导致最终的矩阵通道维数和卷积核数量一样。因此f(x)和x仅仅在通道维度上不同,则使用1*1卷积核变换。如果shape相同:直接拼接就行。

(2)在残差模块中前向传播:

    def call(self, inputs, training=None):
        #向前传播
        # [b, h, w, c],通过第一个卷积单元
        out = self.conv1(inputs)
        out = self.bn1(out)
        out = self.relu(out)
        # 通过第二个卷积单元
        out = self.conv2(out)
        out = self.bn2(out)
        # 通过identity模块,进行identity转换
        identity = self.downsample(inputs)
        # 2条路径输出直接相加;out-f(x),identity-x,实现f(x)+x
        output = layers.add([out, identity])
        output = tf.nn.relu(output) # 激活函数

        return output

原始输入x经过两个卷积单元一层一层输出。注意identity模块,需要进行通道数调整,因此输入不是上一个输出,而是原始输入x,要将x的shape修改为f(x)的shape,进行相加。另外注意第二个卷积单元的relu函数从残差模块中提取出来了,放在了H(x)后面,当然这个是不固定的,也可以放在第二个卷积单元内部。

2.残差网络类:

(1)通用残差网络实现:


    def __init__(self, layer_dims, num_classes=10): # [2, 2, 2, 2]
        super(ResNet, self).__init__()
        # 根网络,预处理    在这个容器中经过卷积层,标准化层,激活函数,池化层减半
        self.stem = Sequential([layers.Conv2D(64, (3, 3), strides=(1, 1)),
                                layers.BatchNormalization(),
                                layers.Activation('relu'),
                                layers.MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding='same')
                                ])
        # 堆叠4个Block,每个block包含了多个BasicBlock,设置步长不一样
        self.layer1 = self.build_resblock(64,  layer_dims[0])
        self.layer2 = self.build_resblock(128, layer_dims[1], stride=2)
        self.layer3 = self.build_resblock(256, layer_dims[2], stride=2)
        self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)

        # 通过Pooling层将高宽降低为1x1
        self.avgpool = layers.GlobalAveragePooling2D()
        # 最后连接一个全连接层分类
        self.fc = layers.Dense(num_classes)

先经过一个容器,不是残差网络的根网络:包括卷积层-BN层-激活层-池化层。

下面是四个残差层,每个残差层都利用build_resblock函数进行残差网络层的建立,并传入参数。

通过池化层完成高宽的转化,最后连接一个全连接层,转化为十个属性的输出,判断结果到底是什么。

(2)调用残差模块建立方法:

#通过该函数一次完成多个残差模块的建立
    def build_resblock(self, filter_num, blocks, stride=1):
        # 辅助函数,堆叠filter_num个BasicBlock
        res_blocks = Sequential()
        # 只有第一个BasicBlock的步长可能不为1,实现下采样
        res_blocks.add(BasicBlock(filter_num, stride))

        for _ in range(1, blocks):#其他BasicBlock步长都为1
            res_blocks.add(BasicBlock(filter_num, stride=1))

        return res_blocks

调用残差模块类,传入卷积核数量,步长。blocks表示要建立的残差模块的数量。这些参数都由调用该方法的代码传入。

该方法的调用完成表明残差神经网络构建完成。

(3)在整个网络中向前传播:

    def call(self, inputs, training=None):
        # 通过根网络
        x = self.stem(inputs)
        # 一次通过4个模块
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        # 通过池化层
        x = self.avgpool(x)
        # 通过全连接层
        x = self.fc(x)

        return x

这一部分注意区别于残差模块中的向前传播,先通过根网络,再逐步通过每一个残差模块,最后经过池化层和全连接层得到输出。

4.形成特殊网络:

def resnet18():
    # 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet
    return ResNet([2, 2, 2, 2])


def resnet34():
     # 通过调整模块内部BasicBlock的数量和配置实现不同的ResNet
    return ResNet([3, 4, 6, 3])

ResNet18直接调用产生网络的方法,传入参数[2,2,2,2],表明layer_dims是个一维矩阵,元素都是2。ResNet18是指17层卷积层,1层全连接层的网络。每个layer都调用两次建立残差模块的方法,每个残差模块有两个卷积单元也就是两个卷积层,如此一来四个layer,就有16个卷积层,再加上根网络的一个卷积层和最后的一个全连接层刚好是17+1。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值