查看: 89|回复: 0

残差网络(ResNet)

[复制链接]

2

主题

5

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 2023-7-20 19:05:18 | 显示全部楼层 |阅读模式
残差网络(ResNet)是一种深度卷积神经网络,通过引入残差连接(residual connection)来解决深度网络中的梯度消失问题。在ResNet中,每个卷积层都不是直接映射输入到输出,而是将输入添加到输出上,即通过跨越几个层的“捷径”(shortcut)连接来实现。
这种设计可以让模型更加容易优化,并且在超过100层的深度网络中仍然能够取得很好的性能。ResNet在2015年的ImageNet图像分类比赛上获得了冠军,并且被广泛应用于计算机视觉任务中。
以下是一个简单的ResNet实现示例:
import tensorflow as tf

# 定义ResNet中的基本残差块
def resnet_block(inputs, filters, kernel_size, strides=(1, 1), activation=tf.nn.relu):
    shortcut = inputs
    conv1 = tf.layers.conv2d(inputs=inputs, filters=filters, kernel_size=kernel_size, strides=strides, padding='same', activation=activation)
    conv2 = tf.layers.conv2d(inputs=conv1, filters=filters, kernel_size=kernel_size, strides=(1, 1), padding='same', activation=None)
    if strides != (1, 1) or inputs.shape[-1] != filters:
        shortcut = tf.layers.conv2d(inputs=inputs, filters=filters, kernel_size=(1, 1), strides=strides, padding='valid', activation=None)
    output = activation(conv2 + shortcut)
    return output

# 使用ResNet基本残差块定义一个简单的ResNet模型
def resnet_model():
    inputs = tf.placeholder(tf.float32, shape=[None, 28, 28, 1])
    conv1 = tf.layers.conv2d(inputs=inputs, filters=32, kernel_size=(3, 3), strides=(1, 1), padding='same', activation=tf.nn.relu)
    block1 = resnet_block(conv1, 32, (3, 3))
    block2 = resnet_block(block1, 32, (3, 3))
    block3 = resnet_block(block2, 64, (3, 3), strides=(2, 2))
    block4 = resnet_block(block3, 64, (3, 3))
    pool = tf.layers.average_pooling2d(block4, (7, 7), strides=(1, 1))
    flatten = tf.layers.flatten(pool)
    dense = tf.layers.dense(flatten, 10)
    logits = tf.nn.softmax(dense)
    return inputs, logits残差块

残差块(Residual block)是残差网络(ResNet)中的基本构建单元,用于实现深度网络中的跨层连接。残差块包含了一个或多个卷积层和一个跳跃连接(shortcut connection),使得网络可以在不丢失信息的情况下更容易地训练。


常见的残差块包含两个3x3的卷积层,每个卷积层后面跟着一个批量规范化层和ReLU激活函数。在这两个卷积层之间的跳跃连接可以通过简单的加法来实现,它将输入直接添加到网络输出上。
以下是一个简单的残差块实现示例:
import tensorflow as tf

def residual_block(inputs, filters, kernel_size, strides=(1,1)):
    shortcut = inputs
    conv1 = tf.layers.conv2d(inputs=inputs, filters=filters, kernel_size=kernel_size, strides=strides, padding='same', activation=tf.nn.relu)
    conv2 = tf.layers.conv2d(inputs=conv1, filters=filters, kernel_size=kernel_size, strides=(1,1), padding='same', activation=None)
    if strides != (1,1) or inputs.shape[-1] != filters:
        shortcut = tf.layers.conv2d(inputs=inputs, filters=filters, kernel_size=(1,1), strides=strides, padding='valid', activation=None)
    output = tf.nn.relu(conv2 + shortcut)
    return outputResNet模型

ResNet的前两层跟之前介绍的GoogLeNet中的一样: 在输出通道数为64、步幅为2的7×7卷积层后,接步幅为2的3×3的最大汇聚层。 不同之处在于ResNet每个卷积层后增加了批量规范化层。
b1 = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(64, kernel_size=7, strides=2, padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Activation('relu'),
    tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')])GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为2的最大汇聚层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。来实现这个模块。注意,对第一个模块做了特别处理。
class ResnetBlock(tf.keras.layers.Layer):
    def __init__(self, num_channels, num_residuals, first_block=False,
                 **kwargs):
        super(ResnetBlock, self).__init__(**kwargs)
        self.residual_layers = []
        for i in range(num_residuals):
            if i == 0 and not first_block:
                self.residual_layers.append(
                    Residual(num_channels, use_1x1conv=True, strides=2))
            else:
                self.residual_layers.append(Residual(num_channels))

    def call(self, X):
        for layer in self.residual_layers.layers:
            X = layer(X)
        return X接着在ResNet加入所有残差块,这里每个模块使用2个残差块。
b2 = ResnetBlock(64, 2, first_block=True)
b3 = ResnetBlock(128, 2)
b4 = ResnetBlock(256, 2)
b5 = ResnetBlock(512, 2)最后,与GoogLeNet一样,在ResNet中加入全局平均汇聚层,以及全连接层输出。
def net():
    return tf.keras.Sequential([
        # Thefollowinglayersarethesameasb1thatwecreatedearlier
        tf.keras.layers.Conv2D(64, kernel_size=7, strides=2, padding='same'),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Activation('relu'),
        tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'),
        # Thefollowinglayersarethesameasb2,b3,b4,andb5thatwe
        # createdearlier
        ResnetBlock(64, 2, first_block=True),
        ResnetBlock(128, 2),
        ResnetBlock(256, 2),
        ResnetBlock(512, 2),
        tf.keras.layers.GlobalAvgPool2D(),
        tf.keras.layers.Dense(units=10)])每个模块有4个卷积层(不包括恒等映射的1×1卷积层)。 加上第一个7×7卷积层和最后一个全连接层,共有18层。 因此,这种模型通常被称为ResNet-18。 通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。 虽然ResNet的主体架构跟GoogLeNet类似,但ResNet架构更简单,修改也更方便。这些因素都导致了ResNet迅速被广泛使用。在训练ResNet之前,让我们观察一下ResNet中不同模块的输入形状是如何变化的。 在之前所有架构中,分辨率降低,通道数量增加,直到全局平均汇聚层聚集所有特征。
X = tf.random.uniform(shape=(1, 224, 224, 1))
for layer in net().layers:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)训练模型

同之前一样,我们在Fashion-MNIST数据集上训练ResNet。
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表