文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

深度学习Tensorflow 2.4 完成迁移学习和模型微调

2023-01-06 18:00

关注

前言

本文使用 cpu 的 tensorflow 2.4 完成迁移学习和模型微调,并使用训练好的模型完成猫狗图片分类任务。

预训练模型在 NLP 中最常见的可能就是 BERT 了,在 CV 中我们此次用到了 MobileNetV2 ,它也是一个轻量化预训练模型,它已经经过大量的图片分类任务的训练,里面保存了一个可以通用的去捕获图片特征的模型网络结构,其可以通用地提取出图片的有意义特征。这些特征捕获功能可以轻松迁移到其他图片任务上帮助其完成特征提取工作。

本文的工作,就是在 MobileNetV2 基础上加入我们自定义的若干网络层,通过使用大量的猫狗数据先对新添加的分类器层进行迁移学习,然后结合基础模型的最后几层与我们的自定义分类器一起进行微调训练,就可以轻松获得一个效果很好的猫狗图片分类模型,而不必基于大量数据集从头开始训练一个大型模型,这样会很耗时间。

实现过程

1. 获取数据

首先我们要使用 tensorflow 的内置函数,从网络上下载猫狗图片集,训练数据中的猫、狗图片各 1000 张,验证数据中的猫、狗图片各 500 张。每张图片的大小都是(160, 160, 3),每个 batch 中有 32 张图片。

import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
BATCH_SIZE = 32
IMG_SIZE = (160, 160)
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')
train_dataset = tf.keras.utils.image_dataset_from_directory(train_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)
validation_dataset = tf.keras.utils.image_dataset_from_directory(validation_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)
class_names = train_dataset.class_names

在这里我们挑选了部分图片和标签进行展示。

plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
    for i in range(3):
        ax = plt.subplot(3, 3, i + 1)
        plt.imshow(images[i].numpy().astype("uint8"))
        plt.title(class_names[labels[i]])
        plt.axis("off")     

2. 数据扩充与数据缩放

(1)由于我们没有测试集数据,使用 tf.data.experimental.cardinality 确定验证集中有多少个 batch 的数据,然后将其中的 20% 的 batch 编程测试集。

val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)
validation_dataset = validation_dataset.skip(val_batches // 5)

(2)为了保证在加载数据的时候不会出现 I/O 不会阻塞,我们在从磁盘加载完数据之后,使用 cache 会将数据保存在内存中,确保在训练模型过程中数据的获取不会成为训练速度的瓶颈。如果说要保存的数据量太大,可以使用 cache 创建磁盘缓存提高数据的读取效率。另外我们还使用 prefetch 在训练过程中可以并行执行数据的预获取。

AUTOTUNE = tf.data.AUTOTUNE
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)

(3)因为我们没有大量的图片数据,我们将现有的图片数据进行旋转或者翻转操作可以增加样本的多样性,这样也有助于减少过拟合现象。RandomFlip 函数将根据 mode 属性将图片进行水平或垂直翻转图像。"可选的有 "horizontal" 、 "vertical" 或 "horizontal_and_vertical" 。"horizontal" 是左右翻转, "vertical" 是上下翻转。RandomRotation 函数通过设置 factor 来讲图片进行旋转,假如 factor=0.2 会将图片在 [-20% * 2pi, 20% * 2pi] 范围内随机旋转。

data_augmentation = tf.keras.Sequential([
  tf.keras.layers.RandomFlip('horizontal'),
  tf.keras.layers.RandomRotation(0.1),
])	

我们这里将随便使用一张图片,使用数据增强的方法对其进行变化,第一张图是原图,其他的都是进行了变化的图片

for image, _ in train_dataset.take(1):
    plt.figure(figsize=(10, 10))
    first_image = image[0]
    for i in range(3):
        if  i==0:
            ax = plt.subplot(3, 3, i + 1)
            plt.imshow(first_image/ 255)
            plt.axis('off')
        else:
            ax = plt.subplot(3, 3, i + 1)
            augmented_image = data_augmentation(tf.expand_dims(first_image, 0))
            plt.imshow(augmented_image[0] / 255)
            plt.axis('off')

(4)由于 MobileNetV2 模型的输入值范围是在 [-1, 1] 范围内,但此时我们的图片数据中的像素值处于 [0, 255] 范围内,所以要重新缩放这些像素值, Rescaling 函数可以实现该操作。如果将 [0, 255] 范围的输入缩放到 [0, 1] 的范围,我们可以通过设置参数 scale=1./255 来实现。如果将 [0, 255] 范围内的输入缩放到 [-1, 1] 范围内,我们可以通过设置参数 scale=1./127.5, offset=-1 来实现。

rescale = tf.keras.layers.Rescaling(1./127.5, offset=-1)	

3. 迁移学习

(1)我们将 MobileNet V2 模型当做一个基础模型,此模型已基于 ImageNet 数据集进行完美的预训练,一般来说最后一层就是一个分类器,我们选择将在 MobileNet V2 的倒数第二个网络层上搭建自己的分类器,与最后一层相比倒数第二层能够保留更丰富的图片特征。在实际操作中我们实例化 MobileNetV2 模型,通过指定 include_top=False 参数,可以加载不包括最顶层的整个预训练网络结果。 该模型的作用就是将(160,160,3)大小的图片转换为(5,5,1280)的特征输出。

(2)第一层是将我们的训练数据都进行数据增强操作,也就是随机的翻转和旋转。

(3)第二层是接收输入为 (160,160,3)大小的图片的输入层。

(4)第三层是我们直接拿来用的 MobileNetV2 ,因为我们要直接使用基础模型的图片特征提取能力,所以为了在训练过程中其权重不发生变化,我们将基础模型中的权重参数都冻结。

(5)第四层是一个池化层,可以将每个 batch 从 (32,5,5,1280) 压缩为 (32,1280) 大小的输出。

(6)第五层是 Dropout ,防止过拟合。

(7)第六层是对该图片一个预测值,也就是 logit 。如果是正数预测标签 1 ,如果是负数预测标签 0 。

(8)我们从模型的 summary 中可以看到,此时我们的模型中的 2259265 个参数被冻结,只有 1281 个参数是可以训练的,它们是分属两个变量的可训练参数,即最后一个全连接层即权重 1280 个可训练参数和偏差 1 个可训练参数。

IMG_SHAPE = IMG_SIZE + (3,)
inputs = tf.keras.Input(shape=(160, 160, 3))
x = data_augmentation(inputs)
x = tf.keras.applications.mobilenet_v2.preprocess_input(x)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
base_model.trainable = False
x = base_model(x, training=False)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = tf.keras.layers.Dense(1)(x)
model = tf.keras.Model(inputs, outputs)
model.summary()

模型结构如下:

Model: "model_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_8 (InputLayer)        [(None, 160, 160, 3)]     0         
 sequential_2 (Sequential)   (None, 160, 160, 3)       0         
 tf.math.truediv_3 (TFOpLamb  (None, 160, 160, 3)      0         
 da)                                                             
 tf.math.subtract_3 (TFOpLam  (None, 160, 160, 3)      0         
 bda)                                                            
 mobilenetv2_1.00_160 (Funct  (None, 5, 5, 1280)       2257984   
 ional)                                                          
 global_average_pooling2d_3   (None, 1280)             0         
 (GlobalAveragePooling2D)                                        
 dropout_2 (Dropout)         (None, 1280)              0         
 dense_2 (Dense)             (None, 1)                 1281      
=================================================================
Total params: 2,259,265
Trainable params: 1,281
Non-trainable params: 2,257,984

可以看到模型中的可训练的 2 个变量:

model.trainable_variables

结果如下:

[<tf.Variable 'dense_2/kernel:0' shape=(1280, 1) dtype=float32, numpy=
 array([[ 0.08899798],
        [-0.06681276],
        [ 0.00906871],
        ...,
        [-0.00114891],
        [-0.01134416],
        [-0.02000826]], dtype=float32)>,
 <tf.Variable 'dense_2/bias:0' shape=(1,) dtype=float32, numpy=array([0.03746362], dtype=float32)>]    

(9)选择 Adam 优化器,将我们的学习率设置为 0.0003 。选择 BinaryCrossentropy 作为损失函数。选择常规的 accuracy 作为评估指标。此时我们先对基础模型训练 10 个 epoch 。

lr = 0.0003
initial_epochs = 10
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=lr),
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])
history = model.fit(train_dataset, validation_data=test_dataset, epochs=initial_epochs)

训练过程如下:

Epoch 1/10
63/63 [==============================] - 24s 352ms/step - loss: 0.5517 - accuracy: 0.6835 - val_loss: 0.2720 - val_accuracy: 0.8958
Epoch 2/10
63/63 [==============================] - 21s 327ms/step - loss: 0.2792 - accuracy: 0.8865 - val_loss: 0.1499 - val_accuracy: 0.9531
...
Epoch 9/10
63/63 [==============================] - 20s 321ms/step - loss: 0.1075 - accuracy: 0.9530 - val_loss: 0.0766 - val_accuracy: 0.9740
Epoch 10/10
63/63 [==============================] - 21s 329ms/step - loss: 0.1040 - accuracy: 0.9560 - val_loss: 0.0742 - val_accuracy: 0.9740

(10)使用测试数据对模型进行评估。

model.evaluate(validation_dataset)

结果如下:

loss: 0.0664 - accuracy: 0.9765

4. 微调

(1)在上面的操作中,我们仅仅在 MobileNetV2 基础模型的顶部添加了一层池化层、一层 Dropout、一层全连接层作为我们的自定义分类器。预训练模型 MobileNetV2 中权重在训练过程中未曾发生过更新。

(2)我们还有一种被称为微调的方法,可以在训练基础模型权重的同时,同时训练我们上面自定义添加的用于分类的分类器。这个微调的训练过程可以将基础模型的通用的图片特征提取能力调整为专门提取本任务中猫狗数据集特征的能力。这个微调的操作只能在我们进行了上面的迁移学习操作之后才能进行,否则如果一开始直接将基础模型和我们自定义的若干层分类器一起进行训练,则会由于随机初始化的分类器导致整个模型的更新梯度太大,从而使得基础模型丧失了其预训练的有效能力。其次在微调过程中我们应该选择性地去微调少量顶部的网络层而不是整个 MobileNet 模型,因为在卷积神经网络中,低层的网络层一般捕获到的是通用的图片特征,这个能力可以泛化应用到几乎所有类型的图片,但是越往顶部的网络层,越来越聚焦于捕获训练时所用到的训练数据的特征,而微调的目标正是是让这个模型更加适用于所用的专门的数据集,也就是本次进行的猫狗图片。

(3)MobileNetV2 模型一共有 154 层结构,我们将模型的前 100 层的参数进行冻结,对顶部的 54 层网络结构中的参数与我们自定义的分类器的若干层一起进行训练,我们通过打印模型的 summary 可以看到,此时共有 54 个可以训练的变量,这些变量中共有可训练的参数 1862721 个。

base_model.trainable = True
fine_tune_at = 100
for layer in base_model.layers[:fine_tune_at]:
    layer.trainable = False
print("%d trainable variables "%len(model.trainable_variables))
model.summary()

结果如下:

56 trainable variables 
Model: "model_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_8 (InputLayer)        [(None, 160, 160, 3)]     0         
 sequential_2 (Sequential)   (None, 160, 160, 3)       0         
 tf.math.truediv_3 (TFOpLamb  (None, 160, 160, 3)      0         
 da)                                                             
 tf.math.subtract_3 (TFOpLam  (None, 160, 160, 3)      0         
 bda)                                                            
 mobilenetv2_1.00_160 (Funct  (None, 5, 5, 1280)       2257984   
 ional)                                                          
 global_average_pooling2d_3   (None, 1280)             0         
 (GlobalAveragePooling2D)                                        
 dropout_2 (Dropout)         (None, 1280)              0         
 dense_2 (Dense)             (None, 1)                 1281      
=================================================================
Total params: 2,259,265
Trainable params: 1,862,721
Non-trainable params: 396,544

(4)之前我们对基础模型训练了 10 个 epoch ,现在我们进行微调过程中再对模型训练 10 个 epoch ,且我们将从上面训练结束的 epoch 开始恢复并进行现在的训练过程。

(5)这里我们改用 RMSprop 优化器,且因为此时我们是要对整体模型进行微调,所以设置的学习率比之前降低 10 倍,否则如果较大会很快产生过拟合。

fine_tune_epochs = 10
total_epochs =  initial_epochs + fine_tune_epochs
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer = tf.keras.optimizers.RMSprop(learning_rate=lr/10),
              metrics=['accuracy'])
history_fine = model.fit(train_dataset,  epochs=total_epochs,  initial_epoch=history.epoch[-1],  validation_data=validation_dataset)	

训练结果输出:

Epoch 10/20
63/63 [==============================] - 39s 561ms/step - loss: 0.1073 - accuracy: 0.9570 - val_loss: 0.1017 - val_accuracy: 0.9592
Epoch 11/20
63/63 [==============================] - 34s 538ms/step - loss: 0.0688 - accuracy: 0.9725 - val_loss: 0.0448 - val_accuracy: 0.9827
...
Epoch 19/20
63/63 [==============================] - 34s 537ms/step - loss: 0.0244 - accuracy: 0.9900 - val_loss: 0.0709 - val_accuracy: 0.9777
Epoch 20/20
63/63 [==============================] - 33s 528ms/step - loss: 0.0220 - accuracy: 0.9905 - val_loss: 0.0566 - val_accuracy: 0.9851

(6)使用测试数据对模型进行评估。

model.evaluate(test_dataset)

结果输出:

loss: 0.0544 - accuracy: 0.9792

之前模型最后的验证准确率为 0.9740 ,测试准确率为 0.9765 ,而经过微调的模型,最后饿验证准确率为 0.9851 ,测试准确率为 0.9792 ,都有所提升。

5. 预测

我们随机挑选一个 batch 进行预测,并将图片与预测标签进行显示,结果表明预测全都正确。

image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch).flatten()
predictions = tf.nn.sigmoid(predictions)
predictions = tf.where(predictions < 0.5, 0, 1)
plt.figure(figsize=(10, 10))
for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image_batch[i].astype("uint8"))
    plt.title(class_names[predictions[i]])
    plt.axis("off")

以上就是深度学习Tensorflow 2.4 完成迁移学习和模型微调的详细内容,更多关于Tensorflow 迁移学习模型微调的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯