tensorflow ResNet

ResNet是何恺明团队提出的深度残差网络,通过残差结构避免了梯度消失问题,使得训练数百层的网络成为可能。网络不再直接拟合底层映射,而是拟合残差映射,简化了优化过程。ResNet与InceptionNet的相加方式有别,它是在特征图层面进行元素相加。文章还介绍了ResNet在网络深度增加时如何保持性能,以及在CIFAR10数据集上的应用示例。
部署运行你感兴趣的模型镜像

        借鉴点:层间残差跳连,引入前方信息,减少梯度消失,使神经网络层数变身成为可能。

        ResNet 即深度残差网络,由何恺明及其团队提出,是深度学习领域又一具有开创性的 工作,通过对残差结构的运用,ResNet 使得训练数百层的网络成为了可能,从而具有非常 强大的表征能力,其网络结构如图 下所示。

        图 上 ResNet18 网络结构图 ResNet 的核心是残差结构,如图下所示。在残差结构中,ResNet 不再让下一层直接 拟合我们想得到的底层映射,而是令其对一种残差映射进行拟合。若期望得到的底层映射为 H(x),我们令堆叠的非线性层拟合另一个映射 F(x) := H(x) – x,则原有映射变为 F(x) + x。 对这种新的残差映射进行优化时,要比优化原有的非相关映射更为容易。不妨考虑极限情况, 如果一个恒等映射是最优的,那么将残差向零逼近显然会比利用大量非线性层直接进行拟合 更容易。

        值得一提的是,这里的相加与 InceptionNet 中的相加是有本质区别的,Inception 中的相 加是沿深度方向叠加,像“千层蛋糕”一样,对层数进行叠加;ResNet 中的相加则是特征图 对应元素的数值相加,类似于 python 语法中基本的矩阵相加。

        ResNet 引入残差结构最主要的目的是解决网络层数不断加深时导致的梯度消失问题, 从之前介绍的 4 种 CNN 经典网络结构我们也可以看出,网络层数的发展趋势是不断加深的。 这是由于深度网络本身集成了低层/中层/高层特征和分类器,以多层首尾相连的方式存在, 所以可以通过增加堆叠的层数(深度)来丰富特征的层次,以取得更好的效果。

         但如果只是简单地堆叠更多层数,就会导致梯度消失(爆炸)问题,它从根源上导致了 函数无法收敛。然而,通过标准初始化(normalized initialization)以及中间标准化层 (intermediate normalization layer),已经可以较好地解决这个问题了,这使得深度为数十层 的网络在反向传播过程中,可以通过随机梯度下降(SGD)的方式开始收敛。

        但是,当深度更深的网络也可以开始收敛时,网络退化的问题就显露了出来:随着网络 深度的增加,准确率先是达到瓶颈(这是很常见的),然后便开始迅速下降。需要注意的是, 这种退化并不是由过拟合引起的。对于一个深度比较合适的网络来说,继续增加层数反而会 导致训练错误率的提升,图 下 就是一个例子。

                                cifar10 数据集训练集错误率(左)及测试集错误率(右)

        ResNet 解决的正是这个问题,其核心思路为:对一个准确率达到饱和的浅层网络,在 它后面加几个恒等映射层(即 y = x,输出等于输入),增加网络深度的同时不增加误差。 这使得神经网络的层数可以超越之前的约束,提高准确率。图下展示了 ResNet 中残差结 构的具体用法。         上图中的实线和虚线均表示恒等映射,实线表示通道相同,计算方式为 H(x) = F(x) + x; 虚线表示通道不同,计算方式为 H(x) = F(x) + Wx,其中 W 为卷积操作,目的是调整 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

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.0, x_test / 255.0


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为True时,对输入进行下采样,即用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


class ResNet18(Model):

    def __init__(self, block_list, initial_filters=64):  # block_list表示每个block有几个卷积层
        super(ResNet18, self).__init__()
        self.num_blocks = len(block_list)  # 共有几个block
        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):
        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


model = ResNet18([2, 2, 2, 2])

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


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

         

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.9

TensorFlow-v2.9

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

### 使用 TensorFlow 实现 ResNet18 模型 #### 定义 Basic Block 结构 为了构建 ResNet18,首先需要定义基本的残差单元 `BasicBlock`。这个组件包含了两个卷积层和一个用于跳跃连接的身份映射路径。 ```python import tensorflow as tf from tensorflow.keras.layers import Conv2D, BatchNormalization, ReLU, Add, Input from tensorflow.keras.models import Model class BasicBlock(tf.keras.Model): expansion = 1 def __init__(self, planes, stride=1, downsample=None): super(BasicBlock, self).__init__() # First convolutional layer with batch normalization and activation function self.conv1 = Conv2D(planes, kernel_size=3, strides=stride, padding='same') self.bn1 = BatchNormalization() # Second convolutional layer followed by another set of BN and Activation self.conv2 = Conv2D(planes, kernel_size=3, strides=1, padding='same') self.bn2 = BatchNormalization() # Shortcut connection that can be used when dimensions change due to striding or number of filters changing. self.downsample = downsample self.relu = ReLU() def call(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) if self.downsample is not None: identity = self.downsample(x) out += identity out = self.relu(out) return out ``` #### 构建完整的 ResNet18 模型架构 接下来,基于上述定义好的 `BasicBlock` 来搭建整个网络结构。ResNet18 总共有四个阶段,每个阶段由若干个相同的 `BasicBlock` 组成。 ```python def make_layer(block, planes, blocks, stride=1): layers = [] # Only the first block may have a non-unit stride which changes spatial size, # so we handle it separately here before adding remaining identical blocks. downsample = None if stride != 1 or input_channels != planes * block.expansion: downsample = Sequential([ Conv2D(planes * block.expansion, kernel_size=1, strides=stride), BatchNormalization(), ]) layers.append(block(planes, stride, downsample)) for _ in range(1, blocks): layers.append(block(planes)) return Sequential(layers) class ResNet(Model): def __init__(self, block, num_blocks, num_classes=1000): super().__init__() self.in_planes = 64 # Initial convolutions and max pooling operation at start of network self.init_conv = Conv2D(filters=self.in_planes, kernel_size=(7, 7), strides=(2, 2), padding="same", use_bias=False) self.maxpool = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding="same") # Four stages each containing multiple basic blocks according to specified counts per stage self.layer1 = make_layer(block, 64, num_blocks[0], stride=1) self.layer2 = make_layer(block, 128, num_blocks[1], stride=2) self.layer3 = make_layer(block, 256, num_blocks[2], stride=2) self.layer4 = make_layer(block, 512, num_blocks[3], stride=2) # Final average pool over feature maps after all residual learning has been applied self.avg_pool = GlobalAveragePooling2D() # Fully connected output layer mapping features into classification space self.fc = Dense(units=num_classes) def call(self, inputs): x = self.init_conv(inputs) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avg_pool(x) x = Flatten()(x) x = self.fc(x) return x # Define specific instantiation of ResNet architecture corresponding to ResNet-18 variant resnet_18 = ResNet(BasicBlock, [2, 2, 2, 2]) ``` #### 数据预处理与加载 对于图像分类任务来说,通常会涉及到数据增强操作来提高泛化能力。这里展示了一个简单的例子,展示了如何准备 CIFAR-10 数据集并将其输入到模型中进行训练。 ```python (train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.cifar10.load_data() train_ds = ( tf.data.Dataset.from_tensor_slices((train_images.astype(np.float32)/255., train_labels)) .shuffle(buffer_size=len(train_images)) .batch(batch_size=32) .map(lambda img, lbl: ((img - mean) / std, lbl)) # Normalize images based on dataset statistics ) val_ds = ( tf.data.Dataset.from_tensor_slices((test_images.astype(np.float32)/255., test_labels)) .batch(batch_size=32) .map(lambda img, lbl: ((img - mean) / std, lbl)) ) ``` #### 编译与训练过程 最后一步就是编译模型设置优化器、损失函数以及评价指标,并启动实际的训练循环。 ```python loss_fn = SparseCategoricalCrossentropy(from_logits=True) optimizer = Adam(lr=0.001) model.compile(optimizer=optimizer, loss=loss_fn, metrics=['accuracy']) history = model.fit( train_ds, epochs=epochs, validation_data=val_ds ) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XMM-struggle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值