第J2周:ResNet50V2算法实现02(Tensorflow优化版)

目标

使用tensorflow实现ResNetV50V2的网络结构。本次根据第一层的细节手动硬编码,没有任何的优化,只为了更好的理解细节。
目录结构:
image.png

网络结构图见最后

具体实现

(一)环境

语言环境:Python 3.10
编 译 器: PyCharm
框 架: Tensorflow

(二)具体步骤

1. ResNet50V2的实现
上一版第J2周:ResNet50V2算法实现01(Tensorflow硬编码版)-CSDN博客是根据网络结构一行一行写出来的。现在进行优化,代码如下:

from keras import layers  
from keras.layers import Input, Activation, BatchNormalization, Flatten, GlobalAveragePooling2D  
from keras.layers import Dense, Conv2D, MaxPooling2D, ZeroPadding2D, add  
from keras.models import Model  
  
def identity_block(input_tensor, filters, stage, block):  
    """  
    ResNetV2的恒等块(没有维度变化的残差块)  
    参数:  
    - input_tensor: 输入张量  
    - filters: 整数列表,定义卷积层的滤波器数量  
    - stage: 整数,用于命名层  
    - block: 字符串,用于命名层  
        返回:  
    - x: 输出张量  
    """    filters1, filters2, filters3 = filters  
      
    # 命名约定  
    conv_name_base = 'res' + str(stage) + block + '_branch'  
    bn_name_base = 'bn' + str(stage) + block + '_branch'  
    # 预激活部分  
    x = BatchNormalization(name=bn_name_base + '2a')(input_tensor)  
    x = Activation('relu', name=conv_name_base + '2a_relu')(x)  
      
    # 第一个卷积块  
    x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(x)  
      
    x = BatchNormalization(name=bn_name_base + '2b')(x)  
    x = Activation('relu', name=conv_name_base + '2b_relu')(x)  
    x = Conv2D(filters2, (3, 3), padding='same', name=conv_name_base + '2b')(x)  
      
    x = BatchNormalization(name=bn_name_base + '2c')(x)  
    x = Activation('relu', name=conv_name_base + '2c_relu')(x)  
    x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)  
      
    # 添加残差连接  
    x = add([x, input_tensor], name='res' + str(stage) + block + '_add')  
      
    return x  
  
def conv_block(input_tensor, filters, stage, block, strides=(2, 2)):  
    """  
    ResNetV2的卷积块(有维度变化的残差块)  
    参数:  
    - input_tensor: 输入张量  
    - filters: 整数列表,定义卷积层的滤波器数量  
    - stage: 整数,用于命名层  
    - block: 字符串,用于命名层  
    - strides: 卷积的步长  
        返回:  
    - x: 输出张量  
    """    filters1, filters2, filters3 = filters  
      
    # 命名约定  
    conv_name_base = 'res' + str(stage) + block + '_branch'  
    bn_name_base = 'bn' + str(stage) + block + '_branch'  
    # 预激活部分  
    x = BatchNormalization(name=bn_name_base + '2a')(input_tensor)  
    x = Activation('relu', name=conv_name_base + '2a_relu')(x)  
      
    # 保存预激活结果用于shortcut  
    shortcut = x  
      
    # 第一个卷积块  
    x = Conv2D(filters1, (1, 1), strides=strides, name=conv_name_base + '2a')(x)  
      
    x = BatchNormalization(name=bn_name_base + '2b')(x)  
    x = Activation('relu', name=conv_name_base + '2b_relu')(x)  
    x = Conv2D(filters2, (3, 3), padding='same', name=conv_name_base + '2b')(x)  
      
    x = BatchNormalization(name=bn_name_base + '2c')(x)  
    x = Activation('relu', name=conv_name_base + '2c_relu')(x)  
    x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)  
      
    # shortcut路径  
    shortcut = Conv2D(filters3, (1, 1), strides=strides, name=conv_name_base + '1')(shortcut)  
      
    # 添加残差连接  
    x = add([x, shortcut], name='res' + str(stage) + block + '_add')  
      
    return x  
  
def ResNet50V2(input_shape=[224, 224, 3], classes=1000):  
    """  
    ResNet50V2模型  
        参数:  
    - input_shape: 输入图像的形状  
    - classes: 分类的类别数  
        返回:  
    - model: Keras模型  
    """    img_input = Input(shape=input_shape)  
      
    # Stage 0  
    x = ZeroPadding2D(padding=(3, 3), name='conv1_pad')(img_input)  
    x = Conv2D(64, (7, 7), strides=(2, 2), padding='valid', name='conv1')(x)  
    x = ZeroPadding2D(padding=(1, 1), name='pool1_pad')(x)  
    x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x)  
      
    # Stage 1  
    x = conv_block(x, [64, 64, 256], stage=2, block='a', strides=(1, 1))  
    x = identity_block(x, [64, 64, 256], stage=2, block='b')  
    x = identity_block(x, [64, 64, 256], stage=2, block='c')  
      
    # Stage 2  
    x = conv_block(x, [128, 128, 512], stage=3, block='a')  
    x = identity_block(x, [128, 128, 512], stage=3, block='b')  
    x = identity_block(x, [128, 128, 512], stage=3, block='c')  
    x = identity_block(x, [128, 128, 512], stage=3, block='d')  
      
    # Stage 3  
    x = conv_block(x, [256, 256, 1024], stage=4, block='a')  
    x = identity_block(x, [256, 256, 1024], stage=4, block='b')  
    x = identity_block(x, [256, 256, 1024], stage=4, block='c')  
    x = identity_block(x, [256, 256, 1024], stage=4, block='d')  
    x = identity_block(x, [256, 256, 1024], stage=4, block='e')  
    x = identity_block(x, [256, 256, 1024], stage=4, block='f')  
      
    # Stage 4  
    x = conv_block(x, [512, 512, 2048], stage=5, block='a')  
    x = identity_block(x, [512, 512, 2048], stage=5, block='b')  
    x = identity_block(x, [512, 512, 2048], stage=5, block='c')  
      
    # 预激活的最后一个批归一化和激活  
    x = BatchNormalization(name='post_bn')(x)  
    x = Activation('relu', name='post_relu')(x)  
      
    # 全局平均池化和全连接层  
    x = GlobalAveragePooling2D(name='avg_pool')(x)  
    x = Dense(classes, activation='softmax', name='fc1000')(x)  
      
    # 创建模型  
    model = Model(img_input, x, name='resnet50v2')  
      
    return model  
  
if __name__ == '__main__':  
    model = ResNet50V2()  
    model.summary()

2.训练代码

import numpy as np  
import tensorflow as tf  
from models.ResNet50V2 import ResNet50V2  
from tensorflow.python.data import AUTOTUNE  
  
# 设置GPU  
# 获取当前系统中所有可用的物理GPU设备  
gpus = tf.config.list_physical_devices("GPU")  
  
# 如果系统中存在GPU设备  
if gpus:  
    # 设置第一个GPU设备的内存增长模式为动态增长,以避免一次性占用所有显存  
    tf.config.experimental.set_memory_growth(gpus[0], True)  
  
    # 设置当前可见的GPU设备为第一个GPU,确保程序仅使用该GPU进行计算  
    tf.config.set_visible_devices([gpus[0]], "GPU")  
  
  
# 导入数据  
import matplotlib.pyplot as plt  
import os, PIL, pathlib  
from tensorflow import keras  
from tensorflow.keras import layers, models  
  
# 设置matplotlib的字体为SimHei,以支持中文显示  
plt.rcParams['font.sans-serif'] = ['SimHei']  
# 设置matplotlib的负号显示为正常符号,避免显示为方块  
plt.rcParams['axes.unicode_minus'] = False  
  
# 定义数据目录路径  
data_dir = "./data/bird_photos"  
# 将路径转换为pathlib.Path对象,方便后续操作  
data_dir = pathlib.Path(data_dir)  
  
# 使用glob方法获取所有子目录下的jpg文件,并计算其数量  
image_count = len(list(data_dir.glob('*/*.jpg')))  
# 打印图片数量  
print("图片数量:",image_count)  
  
# 数据预处理  
# 定义批量大小和图像尺寸  
batch_size = 8  
img_height = 224  
img_width = 224  
  
# 使用 `tf.keras.preprocessing.image_dataset_from_directory` 从指定目录加载训练数据集  
# 参数说明:  
# - data_dir: 包含图像数据的目录路径  
# - validation_split: 用于验证集的数据比例,此处为20%  
# - subset: 指定加载的数据子集,此处为训练集  
# - seed: 随机种子,确保数据分割的可重复性  
# - image_size: 图像将被调整到的尺寸,此处为224x224  
# - batch_size: 每个批次的图像数量,此处为8  
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)  
  
# 使用 `tf.keras.preprocessing.image_dataset_from_directory` 从指定目录加载验证数据集  
# 参数说明与训练集相同,但 `subset` 参数指定为验证集  
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 = train_ds.class_names  
  
# 打印类别名称  
print("类别:", class_names)  
  
# 可视化数据  
# 可视化训练数据集中的部分图像及其对应的标签  
# 该代码块创建一个大小为10x5的图形窗口,并在窗口中展示训练数据集中的前8张图像及其标签。  
  
plt.figure(figsize=(10, 5))  # 创建一个大小为10x5的图形窗口  
plt.suptitle("训练数据集可视化")  # 设置图形的标题为"训练数据集可视化"  
  
# 从训练数据集中取出一批数据(images和labels),并展示其中的前8张图像  
for images, labels in train_ds.take(1):  
    for i in range(8):  
        ax = plt.subplot(2, 4, i+1)  # 在2行4列的网格中创建第i+1个子图  
        plt.imshow(images[i].numpy().astype("uint8"))  # 显示第i张图像,并将其转换为uint8类型  
        plt.title(class_names[labels[i]])  # 设置子图的标题为对应的类别名称  
        plt.axis("off")  # 关闭子图的坐标轴显示  
  
# 检查数据  
"""  
遍历训练数据集中的批次,并打印图像批次和标签批次的形状。  
  
该代码片段从训练数据集 `train_ds` 中获取一个批次的数据,并打印该批次中图像和标签的形状。  
`train_ds` 是一个可迭代对象,通常包含图像和标签的批次数据。  
  
代码执行流程:  
1. 从 `train_ds` 中获取一个批次的图像和标签。  
2. 打印图像批次的形状。  
3. 打印标签批次的形状。  
4. 使用 `break` 语句提前退出循环,仅处理第一个批次。  
"""  
for image_batch, labels_batch in train_ds:  
    # 打印图像批次的形状,通常为 (batch_size, height, width, channels)    print(image_batch.shape)  
  
    # 打印标签批次的形状,通常为 (batch_size,)    print(labels_batch.shape)  
  
    # 仅处理第一个批次后退出循环  
    break  
  
# 配置数据集  
# 设置自动调优参数,用于优化数据加载和预处理性能  
AUTOTUNE = tf.data.AUTOTUNE  
  
# 对训练数据集进行优化处理:  
# 1. `cache()`: 将数据集缓存到内存或磁盘,避免在每个epoch重复加载数据,提高训练效率。  
# 2. `shuffle(1000)`: 对数据集进行随机打乱,缓冲区大小为1000,确保训练数据的随机性。  
# 3. `prefetch(buffer_size=AUTOTUNE)`: 使用自动调优的缓冲区大小,预取数据以重叠数据加载和模型训练,提高整体性能。  
train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)  
  
# 对验证数据集进行优化处理:  
# 1. `cache()`: 将数据集缓存到内存或磁盘,避免在每个epoch重复加载数据,提高验证效率。  
# 2. `prefetch(buffer_size=AUTOTUNE)`: 使用自动调优的缓冲区大小,预取数据以重叠数据加载和模型验证,提高整体性能。  
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)  
  
# 初始化一个ResNet50V2模型实例  
# 参数说明:  
#   - input_shape: 输入图像的形状,格式为[height, width, channels],此处为[224, 224, 3],表示224x224像素的RGB图像  
#   - classes: 分类任务的类别数量,此处为class_names列表的长度,表示模型将输出对应类别的概率  
model = ResNet50V2(classes=4)  
  
# 打印模型的摘要信息,包括每一层的名称、输出形状和参数数量  
model.summary()  
  
model.compile(  
    # 使用Adam优化器,学习率初始值为0.001  
    optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),  
    # 设置损失函数为交叉熵损失函数  
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),  
    # 设置性能指标列表,将在模型训练时监控列表中的指标  
    metrics=['accuracy']  
)  
  
# 训练模型并记录训练过程中的历史数据  
#  
# 参数:  
#   train_ds: 训练数据集,通常是一个tf.data.Dataset对象,包含训练数据。  
#   validation_data: 验证数据集,通常是一个tf.data.Dataset对象,用于在训练过程中评估模型性能。  
#   epochs: 训练的轮数,即模型将遍历整个训练数据集的次数。  
#  
# 返回值:  
#   history: 一个History对象,包含训练过程中的损失和评估指标的历史记录。  
  
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)  
epochs_range = range(epochs)  
  
# 创建一个大小为12x4的图形窗口  
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()  
  
# 预测  
# 该函数用于展示验证数据集中的图片,并使用训练好的模型对图片进行预测,显示预测结果。  
# 函数的主要步骤包括:  
# 1. 创建一个大小为10x5的图形窗口。  
# 2. 设置图形的总标题为“图片预测”。  
# 3. 从验证数据集中取出一批图片和标签。  
# 4. 对每张图片进行预测,并在子图中显示图片和预测结果。  
# 5. 关闭子图的坐标轴显示。  
  
plt.figure(figsize=(10, 5))  # 创建一个大小为10x5的图形窗口  
plt.suptitle("图片预测")  # 设置图形的总标题为“图片预测”  
  
# 从验证数据集中取出一批图片和标签  
for images, labels in val_ds.take(1):  
    # 遍历前8张图片,并在子图中显示图片和预测结果  
    for i in range(8):  
        ax = plt.subplot(2, 4, i+1)  # 创建2行4列的子图,并选择第i+1个子图  
        plt.imshow(images[i].numpy().astype("uint8"))  # 显示第i张图片  
  
        # 对图片进行预测  
        img_array = tf.expand_dims(images[i], 0)  # 扩展图片的维度以适应模型输入  
        predictions = model.predict(img_array)  # 使用模型进行预测  
  
        # 在子图标题中显示预测结果  
        plt.title(class_names[np.argmax(predictions)])  
  
        plt.axis("off")  # 关闭子图的坐标轴显示

结果

avg_pool (GlobalAveragePooling  (None, 2048)        0           ['post_relu[0][0]']              
 2D)                                                                                              
                                                                                                  
 fc1000 (Dense)                 (None, 4)            8196        ['avg_pool[0][0]']               
                                                                                                  
==================================================================================================
Total params: 23,580,548
Trainable params: 23,535,108
Non-trainable params: 45,440
___________________________________________________________________________________57/57 [==============================] - 111s 2s/step - loss: 1.2856 - accuracy: 0.5221 - val_loss: 3329.2102 - val_accuracy: 0.2301
Epoch 2/30
57/57 [==============================] - 129s 2s/step - loss: 0.8626 - accuracy: 0.6792 - val_loss: 75.4244 - val_accuracy: 0.4071
Epoch 3/30
57/57 [==============================] - 114s 2s/step - loss: 0.7778 - accuracy: 0.7190 - val_loss: 16.8421 - val_accuracy: 0.2920
Epoch 4/30
57/57 [==============================] - 144s 3s/step - loss: 0.7563 - accuracy: 0.7146 - val_loss: 4.2231 - val_accuracy: 0.3363
Epoch 5/30
57/57 [==============================] - 142s 2s/step - loss: 0.6325 - accuracy: 0.7478 - val_loss: 16.1217 - val_accuracy: 0.2566
Epoch 6/30
57/57 [==============================] - 143s 3s/step - loss: 0.5739 - accuracy: 0.7854 - val_loss: 5.5337 - val_accuracy: 0.4248
Epoch 7/30
57/57 [==============================] - 139s 2s/step - loss: 0.5424 - accuracy: 0.8252 - val_loss: 1.6228 - val_accuracy: 0.7257
Epoch 8/30
57/57 [==============================] - 136s 2s/step - loss: 0.5588 - accuracy: 0.7987 - val_loss: 60.0301 - val_accuracy: 0.1947
Epoch 9/30
57/57 [==============================] - 138s 2s/step - loss: 0.4307 - accuracy: 0.8407 - val_loss: 2.9932 - val_accuracy: 0.6195
Epoch 10/30
57/57 [==============================] - 138s 2s/step - loss: 0.4951 - accuracy: 0.8296 - val_loss: 23.8406 - val_accuracy: 0.3274
Epoch 11/30
57/57 [==============================] - 137s 2s/step - loss: 0.4494 - accuracy: 0.8518 - val_loss: 3.7616 - val_accuracy: 0.4159
Epoch 12/30
57/57 [==============================] - 145s 3s/step - loss: 0.3192 - accuracy: 0.8783 - val_loss: 2.8697 - val_accuracy: 0.4867
Epoch 13/30
57/57 [==============================] - 146s 3s/step - loss: 0.2757 - accuracy: 0.9071 - val_loss: 11.9953 - val_accuracy: 0.5310
Epoch 14/30
57/57 [==============================] - 142s 3s/step - loss: 0.3783 - accuracy: 0.8584 - val_loss: 5.5670 - val_accuracy: 0.6106
Epoch 15/30
57/57 [==============================] - 147s 3s/step - loss: 0.2573 - accuracy: 0.8894 - val_loss: 0.9284 - val_accuracy: 0.8142
Epoch 16/30
57/57 [==============================] - 139s 2s/step - loss: 0.2313 - accuracy: 0.9226 - val_loss: 1.2852 - val_accuracy: 0.7788
Epoch 17/30
57/57 [==============================] - 143s 3s/step - loss: 0.1561 - accuracy: 0.9469 - val_loss: 3.3514 - val_accuracy: 0.5841
Epoch 18/30
57/57 [==============================] - 142s 2s/step - loss: 0.2027 - accuracy: 0.9491 - val_loss: 0.5952 - val_accuracy: 0.8319
Epoch 19/30
57/57 [==============================] - 143s 3s/step - loss: 0.1473 - accuracy: 0.9558 - val_loss: 0.5724 - val_accuracy: 0.8496
Epoch 20/30
57/57 [==============================] - 141s 2s/step - loss: 0.1186 - accuracy: 0.9602 - val_loss: 0.8773 - val_accuracy: 0.8142
Epoch 21/30
57/57 [==============================] - 138s 2s/step - loss: 0.0262 - accuracy: 0.9978 - val_loss: 0.6079 - val_accuracy: 0.8230
Epoch 22/30
57/57 [==============================] - 143s 3s/step - loss: 0.0068 - accuracy: 1.0000 - val_loss: 0.3401 - val_accuracy: 0.8938
Epoch 23/30
57/57 [==============================] - 125s 2s/step - loss: 0.0103 - accuracy: 0.9978 - val_loss: 0.4190 - val_accuracy: 0.8761
Epoch 24/30
57/57 [==============================] - 137s 2s/step - loss: 0.0027 - accuracy: 1.0000 - val_loss: 0.3727 - val_accuracy: 0.9115
Epoch 25/30
57/57 [==============================] - 141s 2s/step - loss: 0.0013 - accuracy: 1.0000 - val_loss: 0.3672 - val_accuracy: 0.9027
Epoch 26/30
57/57 [==============================] - 139s 2s/step - loss: 0.0010 - accuracy: 1.0000 - val_loss: 0.3754 - val_accuracy: 0.8938
Epoch 27/30
57/57 [==============================] - 143s 3s/step - loss: 8.6549e-04 - accuracy: 1.0000 - val_loss: 0.3825 - val_accuracy: 0.8850
Epoch 28/30
57/57 [==============================] - 141s 2s/step - loss: 7.4869e-04 - accuracy: 1.0000 - val_loss: 0.3820 - val_accuracy: 0.8850
Epoch 29/30
57/57 [==============================] - 141s 2s/step - loss: 6.6109e-04 - accuracy: 1.0000 - val_loss: 0.3830 - val_accuracy: 0.8850
Epoch 30/30
57/57 [==============================] - 136s 2s/step - loss: 5.9396e-04 - accuracy: 1.0000 - val_loss: 0.3837 - val_accuracy: 0.9027
1/1 [==============================] - 1s 1s/step
1/1 [==============================] - 0s 193ms/step
1/1 [==============================] - 0s 170ms/step
1/1 [==============================] - 0s 137ms/step
1/1 [==============================] - 0s 197ms/step
1/1 [==============================] - 0s 171ms/step
1/1 [==============================] - 0s 136ms/step
1/1 [==============================] - 0s 164ms/step

image.png

resnet50v2的详细网络结构:
resnet50v2.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值