大家好,欢迎来到IT知识分享网。
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、理论知识储备
1.CNN算法发展
- AlexNet是2012年ImageNet竞赛中,由Alex Krizhevsky和Ilya Sutskever提出,在2012年ImageNet竞赛中,AlexNet以top5错误率为15.3%取得了分类任务的第一名。
- VGGNet是2014年ImageNet竞赛中,由Karen Simonyan和Andrew Zisserman提出,在2014年ImageNet竞赛中,VGGNet以top5错误率为7.3%取得了分类任务的第二名。
- GoogLeNet是2014年ImageNet竞赛中,由Christian Szegedy提出,在2014年ImageNet竞赛中,GoogLeNet以top5错误率为6.6%取得了分类任务的第一名。
- ResNet是2015年ImageNet竞赛中,由Kaiming He、Xiangyu Zhang、Saining Xie、Trevor Darrell提出,在2015年ImageNet竞赛中,ResNet以top5错误率为3.57%取得了分类任务的第一名。
- DenseNet是2016年ImageNet竞赛中,由Gao Huang、Zhuang Liu、Kaiming He、Xiangyu Zhang提出,在2016年ImageNet竞赛中,DenseNet以top5错误率为3.03%取得了分类任务的第一名。
- SE-ResNet是2017年ImageNet竞赛中,由Xiaolong Wang、Kaiming He、Jian Sun提出,在2017年ImageNet竞赛中,SE-ResNet以top5错误率为2.97%取得了分类任务的第一名。
- 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-50,ResNet-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)
三、数据预处理
| 文件夹 | 数量 |
|---|---|
| Bananaquit | 166张 |
| Black Skimmer | 111张 |
| Black Throated Bushtiti | 122张 |
| Cockatoo | 166张 |
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 Block和Identity 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通过引入残差学习框架,有效地解决了这一问题,使得网络能够学习到恒等映射,从而在保持性能的同时增加深度。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/112643.html










