基于 Tensorflow 2.x 使用 MobileNetV2 微调模型优化训练花卉图像识别模型

Posted 小毕超

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于 Tensorflow 2.x 使用 MobileNetV2 微调模型优化训练花卉图像识别模型相关的知识,希望对你有一定的参考价值。

一、模型微调

上篇文章通过搭建三层卷积模型,训练了花卉图像识别模型,最后经验证集验证后准确率大约为 75% ,本篇文章继续对该数据集进行处理优化,以提高识别的准确度。在本篇文章中不再过多的介绍数据集的读取和强化,大家可以参考上篇文章对该部分的介绍,下面是上篇文章的地址:

https://blog.csdn.net/qq_43692950/article/details/128518757

深度学习模型应用于小型图像数据集场景下,一般由于数据量的局限性,导致模型提取特征有限,进而影响识别的准确度,一种常用且非常高效的优化方式便是使用预训练网络模型。将一个已在大型数据集(例如ImageNet)上训练好的模型作为基础模型。在此基础上对自己的数据集再进行训练,即使新的数据集和原始数据集完全不同的情况下也可以得到很好的特诊提取。

使用预训练网络有两种方法:特征提取(feature extraction)微调模型(fine-tuning)

  • 特征提取(feature extraction):使用预训练的优秀模型和权重来从新样本中提取特征,并移除原始的分类层,给到一个新的分类器,从头开始训练,之前的权重会随着反向传播进行修改。

  • 微调模型(fine-tuning):使用预训练的优秀模型和权重来从新样本中提取特征,并移除原始的分类层,最后同样给到一个新的分类器,但不同的是预训练模型的全部或某些层的权重被冻结,不会随着反向传播进行修改,只是略微调整了模型结构,这种方式不会破坏原始预训练模型的参数。

一般我们选取模型,都是基于ImageNet 上预训练过的用于图像分类的模型,在 keras 中可以在 keras.applications 下拿到各种网络模型结构,例如 VGG16、VGG19、Xception、ResNet、ResNetV2、 ResNeXt、InceptionV3、MobileNet、MobileNetV2、DenseNet 等。这些模型都是基于ImageNet 1000 分类的模型。

下面是 keras 中对各个模型的介绍:

https://keras.io/zh/applications/

其中各个模型在 ImageNet 上的Top1Top5 的准确率如下

模型大小Top-1 准确率Top-5 准确率参数数量深度
Xception88 MB0.7900.94522,910,480126
VGG16528 MB0.7130.901138,357,54423
VGG19549 MB0.7130.900143,667,24026
ResNet5098 MB0.7490.92125,636,712-
ResNet101171 MB0.7640.92844,707,176-
ResNet152232 MB0.7660.93160,419,944-
ResNet50V298 MB0.7600.93025,613,800-
ResNet101V2171 MB0.7720.93844,675,560-
ResNet152V2232 MB0.7800.94260,380,648-
ResNeXt5096 MB0.7770.93825,097,128-
ResNeXt101170 MB0.7870.94344,315,560-
InceptionV392 MB0.7790.93723,851,784159
InceptionResNetV2215 MB0.8030.95355,873,736572
MobileNet16 MB0.7040.8954,253,86488
MobileNetV214 MB0.7130.9013,538,98488
DenseNet12133 MB0.7500.9238,062,504121
DenseNet16957 MB0.7620.93214,307,880169
DenseNet20180 MB0.7730.93620,242,984 201
NASNetMobile23 MB0.7440.9195,326,716-
NASNetLarge343 MB0.8250.96088,949,818-

例如:查看 VGG16 模型的结构,其中如果权重文件不存在则会自动下载,其中里面的参数在第二点进行了说明:

from tensorflow import keras

model = keras.applications.VGG16(weights='imagenet', include_top=True)
model.summary()

打印出下面内容,可以看出输入是 (224, 224, 3) 大小的三维图片,输出是一个 1000 分类的模型:

Model: "vgg16"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 224, 224, 3)]     0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
_________________________________________________________________
flatten (Flatten)            (None, 25088)             0         
_________________________________________________________________
fc1 (Dense)                  (None, 4096)              102764544 
_________________________________________________________________
fc2 (Dense)                  (None, 4096)              16781312  
_________________________________________________________________
predictions (Dense)          (None, 1000)              4097000   

使用 VGG16 预测图片分类,图片如下:

from tensorflow import keras
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.preprocessing.image import load_img

plt.rcParams['font.sans-serif'] = ['SimHei']

model = keras.applications.VGG16(weights='imagenet', include_top=True)

img_path = './img/dog.jpg'
image = load_img(img_path, target_size=(224, 224))
image = img_to_array(image)
# 预测
y_predictions = model.predict(tf.expand_dims(image, 0))
# 解析标签
y_lable = keras.applications.vgg16.decode_predictions(y_predictions)

plt.imshow(image / 255.0)
plt.title('预测结果:' + y_lable[0][0][1] + ',概率:' + str("%.2f" % (y_lable[0][0][2] * 100)) + '%')
plt.show()

预测结果:

二、使用 MobileNetV2 微调模型优化训练花卉图像识别

关于图像的预处理可以参考上篇文章,这里基于 MobileNetV2 模型进行微调,MobileNetV2 模型是一个轻量级的分类模型,由google团队在2018年提出的,相比MobileNet V1网络,准确率更高,模型更小,可用于移动端设置计算。

这里对 MobileNetV2 模型去除最后的分类器层,其余卷积层权重均被冻结,在最后,再加上两层全连接层和一层分类层。

keras 中,applications 下的模型结构,如果想去除最后的全链接层,只需设置 include_top=False 即可,其中 weights 参数默认为 imagenet 表示使用 imagenet 上的权重,例如:

from tensorflow import keras

model = keras.applications.VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
model.summary()

运行后,对比第一点打印的结构,可以看出最后全链接层都被移除掉了:

同样在 keras 中冻结参数也非常容易,只需设置 Model.trainable = False 即可,例如:

mobienet = keras.applications.MobileNetV2(weights='imagenet', include_top=False, input_shape=(180, 180, 3))
 # 冻结权重
mobienet.trainable = False

下面开始基于 MobileNetV2 微调训练花卉数据集,整体模型如下所示:


下面使用 keras 构建模型结构:

import tensorflow as tf
from tensorflow import keras

# 定义模型类
class Model():
    # 初始化结构
    def __init__(self, checkpoint_path, log_path, model_path, num_classes, img_width, img_height):
        # checkpoint 权重保存地址
        self.checkpoint_path = checkpoint_path
        # 训练日志保存地址
        self.log_path = log_path
        # 训练模型保存地址:
        self.model_path = model_path

        # 数据统一大小并归一处理
        resize_and_rescale = tf.keras.Sequential([
            keras.layers.Resizing(img_width, img_height),
            keras.layers.Rescaling(1. / 255)
        ])
        # 数据增强
        data_augmentation = tf.keras.Sequential([
            # 翻转
            keras.layers.RandomFlip("horizontal_and_vertical"),
            # 旋转
            keras.layers.RandomRotation(0.2),
            # 对比度
            keras.layers.RandomContrast(0.3),
            # 随机裁剪
            # tf.keras.layers.RandomCrop(IMG_SIZE, IMG_SIZE),
            # 随机缩放
            keras.layers.RandomZoom(height_factor=0.3, width_factor=0.3),
        ])
        # MobileNetV2 模型结构
        mobienet = keras.applications.MobileNetV2(weights='imagenet', include_top=False, input_shape=(180, 180, 3))
        # 冻结权重
        mobienet.trainable = False
        # 初始化模型结构
        self.model = keras.Sequential([
            resize_and_rescale,
            data_augmentation,
            mobienet,
            keras.layers.Flatten(),
            keras.layers.Dense(1024,
                               kernel_initializer=keras.initializers.truncated_normal(stddev=0.05),
                               kernel_regularizer=keras.regularizers.l2(0.001),
                               activation=tf.nn.relu),
            keras.layers.Dense(256,
                               kernel_initializer=keras.initializers.truncated_normal(stddev=0.05),
                               kernel_regularizer=keras.regularizers.l2(0.001),
                               activation=tf.nn.relu),
            keras.layers.Dense(num_classes, activation='softmax')
        ])

    # 编译模型
    def compile(self):
        # 输出模型摘要
        self.model.build(input_shape=(None, 180, 180, 3))
        self.model.summary()
        # 定义训练模式
        self.model.compile(optimizer='adam',
                           loss='sparse_categorical_crossentropy',
                           metrics=['accuracy'])

    # 训练模型
    def train(self, train_ds, val_ds):
        # tensorboard 训练日志收集
        tensorboard = keras.callbacks.TensorBoard(log_dir=self.log_path)

        # 训练过程保存 Checkpoint 权重,防止意外停止后可以继续训练
        model_checkpoint = keras.callbacks.ModelCheckpoint(self.checkpoint_path,  # 保存模型的路径
                                                           # monitor='val_loss',  # 被监测的数据。
                                                           verbose=0,  # 详细信息模式,0 或者 1
                                                           save_best_only=True,  # 如果 True, 被监测数据的最佳模型就不会被覆盖
                                                           save_weights_only=True,
                                                           # 如果 True,那么只有模型的权重会被保存 (model.save_weights(filepath)),否则的话,整个模型会被保存,(model.save(filepath))
                                                           mode='auto',
                                                           # auto, min, max的其中之一。 如果 save_best_only=True,那么是否覆盖保存文件的决定就取决于被监测数据的最大或者最小值。 对于 val_acc,模式就会是 max,而对于 val_loss,模式就需要是 min,等等。 在 auto模式中,方向会自动从被监测的数据的名字中判断出来。
                                                           period=3  # 每3个epoch保存一次权重
                                                           )
        # 填充数据,迭代训练
        self.model.fit(
            train_ds,  # 训练集
            validation_data=val_ds,  # 验证集
            epochs=30,  # 迭代周期
            verbose=2,  # 训练过程的日志信息显示,一个epoch输出一行记录
            callbacks=[tensorboard, model_checkpoint]
        )
        # 保存训练模型
        self.model.save(self.model_path)

    def evaluate(self, val_ds):
        # 评估模型
        test_loss, test_acc = self.model.evaluate(val_ds)
        return test_loss, test_acc

处理数据集,其中使用 80% 的图像进行训练,20% 的图像进行验证。:

import tensorflow as tf
import pathlib
from tensorflow import keras

def getData():
    # 加载数据集
    path = "F:/Tensorflow/datasets/flower/flower_photos"
    # 解析目录
    data_dir = pathlib.Path(path)

    # keras 加载数据集
    batch_size = 20
    img_height = 180
    img_width = 180

    # 使用 80% 的图像进行训练,20% 的图像进行验证。
    class_names = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
    train_ds = keras.utils.image_dataset_from_directory(
        data_dir,
        validation_split=0.2,
        subset="training",
        image_size=(img_height, img_width),
        batch_size=batch_size,
        shuffle=True,
        seed=123,
        interpolation='bilinear',
        crop_to_aspect_ratio=True,
        labels='inferred',
        class_names=class_names,
        color_mode='rgb'
    )

    val_ds = keras.utils.image_dataset_from_directory(
        data_dir,
        validation_split=0.2,
        subset="validation",
        image_size=(img_height, img_width),
        batch_size=batch_size,
        shuffle=True,
        seed=123,
        interpolation='bilinear',
        crop_to_aspect_ratio=True,
        labels='inferred',
        class_names=class_names,
        color_mode='rgb'
    )

    AUTOTUNE = tf.data.AUTOTUNE
    train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)
    val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
    return train_ds, val_ds, len(class_names), img_width, img_height

开始训练模型:

def main():
    # 加载数据集
    train_ds, val_ds, num_classes, img_width, img_height = getData()

    checkpoint_path = './checkout/'
    log_path = './log'
    model_path = './model/model.h5'

    # 构建模型
    model = Model(checkpoint_path, log_path, model_path, num_classes, img_width, img_height)
    # 编译模型
    model.compile()
    # 训练模型
    model.train(train_ds, val_ds)
    # 评估模型
    test_loss, test_acc = model.evaluate(val_ds)
    print(test_loss, test_acc)

if __name__ == '__main__':
    main()

运行后可以看到打印的网络结构:

从训练日志中,可以看到 loss 一直在减小:

训练结束后评估模型的结果,最终在验证集上的准确率为: 85.96% ,比上篇文章整整高出了 10%


最后看下 tensorboard 中可视化的损失及准确率:

tensorboard --logdir=log/train


使用浏览器访问:http://localhost:6006/ 查看结果:

三、模型预测

训练后会在 model 下生成 model.h5 模型,下面直接加载该模型进行预测:

import tensorflow as tf
import pathlib
from tensorflow import keras
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']


def main():
    # 加载数据集
    path = "F:/Tensorflow/datasets/flower/flower_photos"
    # 解析目录
    data_dir = pathlib.Path(path)

    # keras 加载数据集
    batch_size = 32
    img_height = 180
    img_width = 180

    class_names = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
    class_names_cn = ['雏菊', '蒲公英', '玫瑰', '向日葵', '郁金香']
    val_ds = keras.utils.image_dataset_from_directory(
        data_dir,
        validation_split=0.2,
        subset="validation",
        image_size=(img_height, img_width),
        batch_size=batch_size,
        shuffle=True,
        seed=123,
        interpolation='bilinear',
        crop_to_aspect_ratio=True,
        labels='inferred',
        class_names=class_names,
        color_mode='rgb'
    )

    model = keras.models.load_model('./model/model.h5')

    for image_batch, labels_batch in val_ds.take(3):
        plt.figure(figsize=(10, 10))
        for i in range(9):
            plt.subplot(3, 3, i + 1)
            softmax = model.predict(tf.expand_dims(image_batch[i], 0))
            y_label = tf.argmax(softmax, axis=1).numpy()[0]
            plt.imshow(image_batch[i].numpy().astype("uint8"))
            plt.title('预测结果:' + class_names_cn[y_label] + ',概率:' + str("%.2f" % softmax[0][y_label]) + ',真实结果:' +
                      class_names_cn[labels_batch[i]])
            plt.axis('off')
        plt.show()


if __name__ == '__main__':
    main()


<p以上是关于基于 Tensorflow 2.x 使用 MobileNetV2 微调模型优化训练花卉图像识别模型的主要内容,如果未能解决你的问题,请参考以下文章

基于 Tensorflow 2.x 实现多层卷积神经网络,实践 MNIST 手写数字识别

基于 Tensorflow 2.x 实现多层卷积神经网络,实践 MNIST 手写数字识别

基于 Tensorflow 2.x 从零训练花卉图像识别模型

基于 Tensorflow 2.x 从零训练花卉图像识别模型

基于 Tensorflow 2.x 从零训练花卉图像识别模型

基于 Tensorflow 2.x 实现 BP 神经网络,实践 MNIST 手写数字识别