ResNet-50算法

一、理论知识储备

1.CNN算法发展

  1. AlexNet是2012年ImageNet竞赛中,由Alex Krizhevsky和Ilya Sutskever提出,在2012年ImageNet竞赛中,AlexNet以top5错误率为15.3%取得了分类任务的第一名。
  2. VGGNet是2014年ImageNet竞赛中,由Karen Simonyan和Andrew Zisserman提出,在2014年ImageNet竞赛中,VGGNet以top5错误率为7.3%取得了分类任务的第二名。
  3. GoogLeNet是2014年ImageNet竞赛中,由Christian Szegedy提出,在2014年ImageNet竞赛中,GoogLeNet以top5错误率为6.6%取得了分类任务的第一名。
  4. ResNet是2015年ImageNet竞赛中,由Kaiming He、Xiangyu Zhang、Saining Xie、Trevor Darrell提出,在2015年ImageNet竞赛中,ResNet以top5错误率为3.57%取得了分类任务的第一名。
  5. DenseNet是2016年ImageNet竞赛中,由Gao Huang、Zhuang Liu、Kaiming He、Xiangyu Zhang提出,在2016年ImageNet竞赛中,DenseNet以top5错误率为3.03%取得了分类任务的第一名。
  6. SE-ResNet是2017年ImageNet竞赛中,由Xiaolong Wang、Kaiming He、Jian Sun提出,在2017年ImageNet竞赛中,SE-ResNet以top5错误率为2.97%取得了分类任务的第一名。
  7. ResNeXt是2017年ImageNet竞赛中,由Saining Xie、Zhifeng Cai、Trevor Darrell提出,在2017年ImageNet竞赛中,ResNeXt以top5错误率为2.80%取得了分类任务的第一名。

2.残差网络的由来

深度残差网络RestNet(deep residual network)是2015年ImageNet竞赛中由何凯明等提出,因为它简单与实用并存,随后很多研究都是建立在ResNet-50或者ResNet-101的基础上完成。
ResNet主要解决深度卷积网络在深度加深时候的“退化”问题。在一般的卷积神经网络中,增大网络深度后带来的第一个问题就是梯度消失、爆炸,这个问题Szegedy提出BN层后被顺利解决。BN层能对各层的输出做归一化,这样梯度在反向层层传递后仍能保持大小稳定,不会出现过小或过大的情况。但是作者发现加了BN后再加大深度仍然不容易收敛,其提到了第二个问题–准确率下降问题:层级大到一定程度时准确率就会饱和,然后迅速下降,这种下降即不是梯度消失引起的也不是过拟合造成的,而是由于网络过于复杂,以至于光靠不加约束的放养式的训练很难达到理想的错误率。
准确率下降问题不是网络结构本身的问题,而是现有的训练方式不够理想造成的。当前广泛使用的优化器,无论是SGD,还是RMSProp,或是Adam,都无法在网络深度变大后达到理论上最优的收敛结果。
作者在文中证明了只要有合适的网络结构,更深的网络肯定会比较浅的网络效果要好。证明过程也很简单:假设在一种网络A的后面添加几层形成新的网络B,如果增加的层级只是对A的输出做了个恒等映射(identity mapping),即A的输出经过新增的层级变成B的输出后没有发生变化,这样网络A和网络B的错误率就是相等的,也就证明了加深后的网络不会比加深前的网络效果差。

在这里插入图片描述

何恺明提出了一种残差结构来实现上述恒等映射(图1):整个模块除了正常的卷积层输出外,还有个分支把输入直接连到输出上,该分支输出和卷积的输出做算术相加得到最终的输出,用公式表达就是 H ( x ) = F ( x ) + x H(x)= F(x)+ x H(x)=F(x)+x $ x $是输入, $ F(x) 是卷积分支的输出, 是卷积分支的输出, 是卷积分支的输出, H(x) $是整个结构的输出。可以证明如果 $ F(x) $分支中所有参数都是0 $ H(x) $就是个恒等映射。残差结构人为制造了恒等映射,就能让整个结构朝着恒等映射的方向去收敛,确保最终的错误率不会因为深度的变大而越来越差。如果一个网络通过简单的手工设置参数值就能达到想要的结果,那这种结构就很容易通过训练来收敛到该结果,这是一条设计复杂的网络时通用的规则。

在这里插入图片描述

图2左边的单元为 ResNet 两层的残差单元,两层的残差单元包含两个相同输出的通道数的 3x3 卷积,只是用于较浅的 ResNet 网络,对较深的网络主要使用三层的残差单元。三层的残差单元又称为bottleneck 结构,先用一个1x1卷积进行降维,然后 3x3 卷积,最后用 1x1 升维恢复原有的维度。另外,如果有输入输出维度不同的情况,可以对输入做一个线性映射变换维度,再连接后面的层。层的残差单元对于相同数量的层又减少了参数量,因此可以拓展更深的模型。通过残差单元的组合有经典的 ResNet-50ResNet-101等网络结构。

二、前期工作

1.设置GPU

import tensorflow as tf
gpus = tf.config.list_physical_devices('GPU')

if gpus:
    tf.config.explicitly_set_memory_growth(gpus[0], True)
    tf.config.set_visible_devices(gpus[0], 'GPU')
    print("GPUs available")

2.导入数据

import matplotlib.pyplot as plt
# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

import os,PIL,pathlib
import numpy as np

from tensorflow import keras
from tensorflow.keras import layers,models
data_dir = pathlib.Path('F:/host/Data/bird_photos')

3.查看数据

image_count = len(list(data_dir.glob('*/*')))

print("图片总数为:",image_count)

在这里插入图片描述

三、数据预处理

文件夹数量
Bananaquit166张
Black Skimmer111张
Black Throated Bushtiti122张
Cockatoo166张

1.加载数据

使用image_dataset_from_directory方法将磁盘中的数据加载到tf.data.Dataset对象中。

batch_size = 8
img_height = 224
img_width = 224
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size,
)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size,
)
# 我们可以通过`class_names`属性查看类别名称
class_names = train_ds.class_names
print(class_names)

在这里插入图片描述

2.可视化数据

plt.figure(figsize=(10, 5)) # 图形的宽为10高为5
plt.suptitle('Bird Photos')

for images, labels in train_ds.take(1):
  for i in range(8):
    ax = plt.subplot(2, 4, i+1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")

在这里插入图片描述

plt.imshow(images[1].numpy().astype("uint8"))

在这里插入图片描述

3.再次检查数据

for image_batch, labels_batch in train_ds:
  print(image_batch.shape)
  print(labels_batch.shape)
  break

在这里插入图片描述

  • image_batch: 包含8张图像的张量,形状为(8, 224, 224, 3)。
  • labels_batch: 包含8个标签的张量,形状为(8,)。

4.配置数据集

  • shuffle: 随机打乱数据集。
  • prefetch: 预取数据集,以加速数据集的迭代。
  • cache: 缓存数据集,以加速数据集的迭代。
AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

四、残差网络(ResNet)介绍

1.残差网络解决了什么

残差网络是为了解决深度神经网络(DNN)训练过程中梯度消失和梯度爆炸的问题而提出的。它通过引入残差连接,将输入直接加到输出上,从而允许网络学习更复杂的函数。

2.ResNet-50介绍

ResNet-50有两个基本的块,分别名为Conv BlockIdentity Block

五、构建ResNet-50网络模型

from keras import layers

from keras.layers import Input, Activation, BatchNormalization, Flatten
from keras.layers import Conv2D, AveragePooling2D, Dense, MaxPooling2D, ZeroPadding2D
from keras.models import Model

def identity_block(input_tensor, kernel_size, filters, stage, block):
    filters1, filters2, filters3 = filters
    
    name_base = str(stage) + block + '_identity_block_'
    
    x = Conv2D(filters1, (1, 1), name=name_base + 'conv1')(input_tensor)
    x = BatchNormalization(name=name_base + 'bn1')(x)
    x = Activation('relu',name=name_base + 'relu1')(x)
    
    x = Conv2D(filters2, kernel_size, padding='same', name=name_base + 'conv2')(x)
    x = BatchNormalization(name=name_base + 'bn2')(x)
    x = Activation('relu',name=name_base + 'relu2')(x)
    
    x = Conv2D(filters3, (1, 1), name=name_base + 'conv3')(x)
    x = BatchNormalization(name=name_base + 'bn3')(x)
    
    x = layers.add([x, input_tensor], name=name_base + 'add')
    x = Activation('relu',name=name_base + 'relu4')(x)
    return x

# 在残差网络中,广泛地使用了BN层;但是没有使用MaxPoo1ing以便减小特征图尺寸,
# 作为替代,在每个模块的第一层,都使用了strides=(2,2)的方式进行特征图尺寸缩减,
# 与使用MaxPooling相比,毫无疑问是减少了卷积的次数,输入图像分辨率较大时比较适合
# 在残差网络的最后一级,先利用layer.add()实现H(x)=x+F(x)
def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):
    filters1, filters2, filters3 = filters
    
    res_name_base = str(stage) + block + '_conv_block_res_'
    name_base =  str(stage) + block + '_conv_block_'
    
    x = Conv2D(filters1, (1, 1), strides=strides, name=name_base + 'conv1')(input_tensor)
    x = BatchNormalization(name=name_base + 'bn1')(x)
    x = Activation('relu',name=name_base + 'relu1')(x)
    
    x = Conv2D(filters2, kernel_size, padding='same', name=name_base + 'conv2')(x)
    x = BatchNormalization(name=name_base + 'bn2')(x)
    x = Activation('relu',name=name_base + 'relu2')(x)
    
    x = Conv2D(filters3, (1, 1), name=name_base + 'conv3')(x)
    x = BatchNormalization(name=name_base + 'bn3')(x)
    
    shortcut = Conv2D(filters3, (1, 1), strides=strides, name=res_name_base + 'conv')(input_tensor)
    shortcut = BatchNormalization(name=res_name_base + 'bn')(shortcut)
    
    x = layers.add([x, shortcut],name=name_base + 'add')
    x = Activation('relu',name=name_base + 'relu4')(x)
    return x

def ResNet50(input_shape=(224, 224, 3), num_classes=1000):
    
    img_input = Input(shape=input_shape)
    x = ZeroPadding2D((3, 3))(img_input)
    
    x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1')(x)
    x = BatchNormalization(name='bn_conv1')(x)
    x = Activation('relu')(x)
    x = MaxPooling2D((3, 3), strides=(2, 2))(x)
    
    x =     conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))
    x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
    x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')
    
    x =     conv_block(x, 3, [128, 128, 512], stage=3, block='a')
    x = identity_block(x, 3, [128, 128, 512], stage=3, block='b')
    x = identity_block(x, 3, [128, 128, 512], stage=3, block='c')
    x = identity_block(x, 3, [128, 128, 512], stage=3, block='d')
    
    x =     conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='f')
    
    x =     conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
    x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
    x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')
    
    x = AveragePooling2D((7, 7), name='avg_pool')(x)
    
    x =  Flatten()(x)
    x = Dense(num_classes, activation='softmax', name='fc1000')(x)
    
    model = Model(img_input, x, name='ResNet50')
    
    # 加载预训练模型
    model.load_weights('./weights/resnet50_weights_tf_dim_ordering_tf_kernels.h5')
    
    return model

model = ResNet50()
model.summary()

在这里插入图片描述

六、编译

在准备对模型进行训练之前,还需要再对其进行一些设置。以下内容是在模型的编译步骤中添加的:

  • 损失函数(loss):用于衡量模型在训练期间预测值和实际值之间的差距。
  • 优化器(optimizer):决定模型如何根据其看到的数据和自身的损失函数进行更新。
  • 指标(metrics):用于监控训练和测试步骤。
# 设置优化器
opt = tf.keras.optimizers.Adam(learning_rate=1e-7)

model.compile(optimizer="adam",
              loss='sparse_categorical_crossentropy',
             metrics=['accuracy'])

七、训练模型

epochs = 10

history = model.fit(train_ds,
                    validation_data=val_ds,
                    epochs=epochs)

在这里插入图片描述

八、评估模型

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

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

在这里插入图片描述

九、预测

# 采用加载的模型来看预测结果

plt.figure(figsize=(10, 5))
plt.suptitle('Predictions')

for images, labels in val_ds.take(1):
  
  for i in range(8):
      ax = plt.subplot(2, 4, i + 1)
      
      # 显示图片
      plt.imshow(images[i].numpy().astype("uint8"))
      
      # 需要给图片增加一个维度
      img_array = tf.expand_dims(images[i], 0)
      
      # 使用模型预测图片
      predictions = model.predict(img_array)
      plt.title(class_names[np.argmax(predictions)])
      
      plt.axis("off")

在这里插入图片描述

十、个人小结

在这篇文章中,我深入探讨了卷积神经网络(CNN)的发展历程,特别是残差网络(ResNet)的诞生和原理。CNN在图像识别领域取得了显著的进展,但随着网络深度的增加,梯度消失和爆炸的问题逐渐显现,影响了深层网络的性能。ResNet通过引入残差学习框架,有效地解决了这一问题,使得网络能够学习到恒等映射,从而在保持性能的同时增加深度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值