Tensorflow2.0之深度残差网络resnet18实现

Tensorflow2.0之深度残差网络resnet18实战

深度残差网络

AlexNet、VGG、GoogLeNet 等网络模型的出现将神经网络的发展带入了几十层的阶段,研究人员发现网络的层数越深,越有可能获得更好的泛化能力。但是当模型加深以后,网络变得越来越难训练,这主要是由于梯度弥散和梯度爆炸现象造成的。在较深层数的神经网络中,梯度信息由网络的末层逐层传向网络的首层时,传递的过程中会出现梯度接近于0 或梯度值非常大的现象。网络层数越深,这种现象可能会越严重。

那么怎么解决深层神经网络的梯度弥散和梯度爆炸现象呢?一个很自然的想法是,既然浅层神经网络不容易出现这些梯度现象,那么可以尝试给深层神经网络添加一种回退到浅层神经网络的机制。当深层神经网络可以轻松地回退到浅层神经网络时,深层神经网络可以获得与浅层神经网络相当的模型性能,而不至于更糟糕。

通过在输入和输出之间添加一条直接连接的Skip Connection 可以让神经网络具有回退的能力。以VGG13 深度神经网络为例,假设观察到VGG13 模型出现梯度弥散现象,而10 层的网络模型并没有观测到梯度弥散现象,那么可以考虑在最后的两个卷积层添加SkipConnection,通过这种方式,网络模型可以自动选择是否经由这两个卷积层完成特征变换,还是直接跳过这两个卷积层而选择Skip Connection,亦或结合两个卷积层和Skip Connection 的输出

ResNet原理

ResNet 通过在卷积层的输入和输出之间添加Skip Connection 实现层数回退机制,如下图所示,输入𝒙通过两个卷积层,得到特征变换后的输出ℱ(𝒙),与输入𝒙进行对应元素的相加运算,得到最终输出ℋ(𝒙):ℋ(𝒙) = 𝒙 + ℱ(𝒙)
在这里插入图片描述

ResBlock 实现

深度残差网络并没有增加新的网络层类型,只是通过在输入和输出之间添加一条SkipConnection,因此并没有针对ResNet 的底层实现。在TensorFlow 中通过调用普通卷积层即可实现残差模块。
首先创建一个新类,在初始化阶段创建残差块中需要的卷积层、激活函数层等,首先新建ℱ(𝑥)卷积层,代码如下:

class BasicBlock(layers.Layer):
	# 残差模块类
	def __init__(self, filter_num, stride=1):
	super(BasicBlock, self).__init__()
	# f(x)包含了2 个普通卷积层,创建卷积层1
	self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, paddi
	ng='same')
	self.bn1 = layers.BatchNormalization()
	self.relu = layers.Activation('relu')
	# 创建卷积层2
	self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='s
	ame')
	self.bn2 = layers.BatchNormalization()

当ℱ(𝒙)的形状与𝒙不同时,无法直接相加,我们需要新建identity(𝒙)卷积层,来完成𝒙的形状转换。紧跟上面代码,实现如下:

	if stride != 1: # 插入identity 层
	self.downsample = Sequential()
	self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=st
	ride))
	else: # 否则,直接连接
	self.downsample = lambda x:x

在前向传播时,只需要将ℱ(𝒙)与identity(𝒙)相加,并添加ReLU 激活函数即可。前向计算函数代码如下:

	def call(self, inputs, training=None):
		# 前向传播函数
		out = self.conv1(inputs) # 通过第一个卷积层
		out = self.bn1(out)
		out = self.relu(out)
		out = self.conv2(out) # 通过第二个卷积层
		out = self.bn2(out)
		# 输入通过identity()转换
		identity = self.downsample(inputs)
		# f(x)+x 运算
		output = layers.add([out, identity])
		# 再通过激活函数并返回
		output = tf.nn.relu(output)
		return output

DenseNet

Skip Connection 的思想在ResNet 上面获得了巨大的成功,研究人员开始尝试不同的Skip Connection 方案,其中比较流行的就是DenseNet [11]。DenseNet 将前面所有层的特征图信息通过Skip Connection 与当前层输出进行聚合,与ResNet 的对应位置相加方式不同,DenseNet 采用在通道轴𝑐维度进行拼接操作,聚合特征信息
如下图 所示,输入𝑿 通过H1卷积层得到输出𝑿1,𝑿1与𝑿 在通道轴上进行拼接,得到聚合后的特征张量,送入H2卷积层,得到输出𝑿2,同样的方法,𝑿2与前面所有层的特征信息 𝑿1与𝑿 进行聚合,再送入下一层。如此循环,直至最后一层的输出𝑿4和前面所有层的特征信息:{𝑿𝑖}𝑖= 1 2 3进行聚合得到模块的最终输出。这样一种基于SkipConnection稠密连接的模块叫做Dense Block。

在这里插入图片描述
DenseNet 通过堆叠多个Dense Block 构成复杂的深层神经网络
在这里插入图片描述
比较了不同版本的DenseNet 的性能、DenseNet 与ResNet 的性能比较,以及DenseNet 与ResNet的训练曲线
在这里插入图片描述

ResNet18实战

网络的搭建

我们将实现18 层的深度残差网络ResNet18,并在CIFAR10 图片数据集上训练与测试。并将与13 层的普通神经网络VGG13 进行简单的性能比较。
标准的 ResNet18 接受输入为22 × 22 大小的图片数据,我们将ResNet18 进行适量调整,使得它输入大小为32×32,输出维度为10。调整后的ResNet18 网络结构如图所示。
在这里插入图片描述
我们将renet的网络搭建放在一个py文件里,将网络训练放在另一个文件里训练的时候通过import导入进来

首先实现中间两个卷积层,Skip Connection 1x1 卷积层的残差模块。代码如下:

class BasicBlock(layers.Layer):
    # 残差类模块
    def __init__(self, filter_num, strides=1):
        super(BasicBlock, self).__init__()
        # f(x)包含两个普通卷积层,创建卷积层1
        self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=strides, padding='same')
        self.bh1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        # 创建卷积层2
        self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
        self.bh2 = layers.BatchNormalization()
        # 插入identity
        if strides != 1:
            self.downsample = Sequential()
            self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=strides))
        else:  # 否者就直接连接
            self.downsample = lambda x: x

    def call(self, inputs, training=None):
        # 前向传播函数
        out = self.conv1(inputs)
        out = self.bh1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bh2(out)
        # 输入通过identity() 转化
        identity = self.downsample(inputs)
        # f(x) + x操作
        output = layers.add([out, identity])
        # 再通过激活函数并返回
        output = tf.nn.relu(output)
        return output

在创建resnet的之前,一般按照特征图高宽ℎ/𝑤逐渐减少,通道数𝑐逐渐增大的经验法则。可以通过堆叠通道数逐渐增大的Res Block 来实现高层特征的提取,通过build_resblock 可以一次完成多个残差模块的新建。代码如下:

	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

下面我们来设计resnet的网络层

class ResNet(keras.Model):
    def __init__(self, layer_dims, num_classes=10):
        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[0], strides=2)
        self.layer3 = self.build_resblock(256, layer_dims[0], strides=2)
        self.layer4 = self.build_resblock(512, layer_dims[0], strides=2)
        # 通过Pooling层将高管降低为1x1
        self.avgpool = layers.GlobalAveragePooling2D()
        # 最后连接一个全连接层分类
        self.fc = layers.Dense(num_classes)

    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

    def build_resblock(self, filter_num, blocks, strides=1):
        # 辅助函数,堆叠filter_num个BasicBlock
        # 只有第一个BasicBlock的步长可能不为1
        res_block = Sequential()
        res_block.add(BasicBlock(filter_num, strides))
        for _ in range(1, blocks):  # 其他BasicBlock步长都为1
            res_block.add(BasicBlock(filter_num, strides))
        return res_block

通过给ResNet类传参可以构造出resnet18的网络

def resnet18():
    return ResNet([2, 2, 2, 2])

网络训练

我们把训练结果通过tensorboard来显示acc和loss,训练的代码差别都不是太大,之前的几篇都有讲解,这里就不作过多的说明了

import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequential
import datetime
import os
from resnet import resnet18
devices = tf.config.experimental.list_physical_devices('GPU')
tf.config.experimental.set_memory_growth(devices[0], True)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
tf.random.set_seed(2345)
current_time = datetime.datetime.now().strftime(('%Y%m%d-%H%M%S'))
log_dir = 'logs/'+current_time
summary_writer = tf.summary.create_file_writer(log_dir)


def preprocess(x, y):
    # [0~1]
    x = 2 * tf.cast(x, dtype=tf.float32) / 255. - 1
    y = tf.cast(y, dtype=tf.int32)
    return x, y


(x, y), (x_test, y_test) = datasets.cifar10.load_data()
y = tf.squeeze(y, axis=1)
y_test = tf.squeeze(y_test, axis=1)
print(x.shape, y.shape, x_test.shape, y_test.shape)

train_db = tf.data.Dataset.from_tensor_slices((x, y))
train_db = train_db.shuffle(1000).map(preprocess).batch(256)

test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_db = test_db.map(preprocess).batch(256)

sample = next(iter(train_db))
print('sample:', sample[0].shape, sample[1].shape,
      tf.reduce_min(sample[0]), tf.reduce_max(sample[0]))


def main():
    # [b, 32, 32, 3] => [b, 1, 1, 512]
    model = resnet18()
    model.build(input_shape=(None, 32, 32, 3))
    model.summary() # 统计网络参数
    optimizer = optimizers.Adam(lr=1e-3)
    # [1, 2] + [3, 4] => [1, 2, 3, 4]
    variables = model.trainable_variables
    for epoch in range(100):
        for step, (x, y) in enumerate(train_db):

            with tf.GradientTape() as tape:
                # [b, 32, 32, 3] => [b, 1, 1, 512]
                out = model(x)
                # [b] => [b, 10]
                y_onehot = tf.one_hot(y, depth=10)
                # compute loss
                loss = tf.losses.categorical_crossentropy(y_onehot, out, from_logits=True)
                loss = tf.reduce_mean(loss)

            grads = tape.gradient(loss, variables)
            optimizer.apply_gradients(zip(grads, variables))

            if step % 100 == 0:
                with summary_writer.as_default():
                    tf.summary.scalar('loss', loss, step=step)

        total_num = 0
        total_correct = 0
        for x, y in test_db:
            out = model(x)
            prob = tf.nn.softmax(out, axis=1)
            pred = tf.argmax(prob, axis=1)
            pred = tf.cast(pred, dtype=tf.int32)

            correct = tf.cast(tf.equal(pred, y), dtype=tf.int32)
            correct = tf.reduce_sum(correct)

            total_num += x.shape[0]
            total_correct += int(correct)

        acc = total_correct / total_num
        with summary_writer.as_default():
            tf.summary.scalar('acc', float(acc), step=epoch)


if __name__ == '__main__':
    main()

训练结果

在这里插入图片描述

  • 5
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
深度残差收缩网络是一种用于图像分类和物体识别的神经网络模型。它结合了残差网络和通道压缩技术,实现了更高的准确率和更小的模型规模。下面是使用TensorFlow 2.0实现深度残差收缩网络的简单示例代码。 首先,导入必要的库: ``` import tensorflow as tf from tensorflow.keras import layers ``` 定义一个ResNet模块的函数: ``` def resnet_module(input_tensor, filters, stride, reduce=False): shortcut = input_tensor bn_axis = 3 if reduce: shortcut = layers.Conv2D(filters, (1, 1), strides=stride)(shortcut) shortcut = layers.BatchNormalization(axis=bn_axis)(shortcut) x = layers.Conv2D(filters, (3, 3), strides=stride, padding="same")(input_tensor) x = layers.BatchNormalization(axis=bn_axis)(x) x = layers.Activation("relu")(x) x = layers.Conv2D(filters, (3, 3), padding="same")(x) x = layers.BatchNormalization(axis=bn_axis)(x) x = layers.add([x, shortcut]) x = layers.Activation("relu")(x) return x ``` 接下来,定义一个深度残差收缩网络模型: ``` def ResNet(input_shape, num_classes): input_tensor = layers.Input(shape=input_shape) bn_axis = 3 x = layers.Conv2D(64, (7, 7), strides=(2, 2), padding="same")(input_tensor) x = layers.BatchNormalization(axis=bn_axis)(x) x = layers.Activation("relu")(x) x = layers.MaxPooling2D((3, 3), strides=(2, 2), padding="same")(x) x = resnet_module(x, 64, stride=(1, 1)) x = resnet_module(x, 64, stride=(1, 1)) x = resnet_module(x, 128, stride=(2, 2), reduce=True) x = resnet_module(x, 128, stride=(1, 1)) x = resnet_module(x, 256, stride=(2, 2), reduce=True) x = resnet_module(x, 256, stride=(1, 1)) x = resnet_module(x, 512, stride=(2, 2), reduce=True) x = resnet_module(x, 512, stride=(1, 1)) x = layers.GlobalAveragePooling2D()(x) x = layers.Dense(num_classes, activation="softmax")(x) model = tf.keras.Model(inputs=input_tensor, outputs=x, name="ResNet") return model ``` 最后,编译并训练模型: ``` model = ResNet(input_shape=(224, 224, 3), num_classes=1000) model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.01), loss="categorical_crossentropy", metrics=["accuracy"]) # train the model model.fit(train_data, epochs=10, validation_data=val_data) ``` 以上代码只是一个简单示例,可以根据实际需求进行修改和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值