预训练模型是一个基于大型数据集训练过的已保存的网络。在使用预训练模型时,既可以按照原有的模型结构和参数使用预训练模型,也可以使用迁移学习针对给定任务自定义模型。
在图像分类的问题中,当一个模型基于足够大的通用数据集进行训练后,那么该模型将能够有效地充当视觉世界的通用模型。当我们想要用类似的模型去识别一些东西的时候,我们就可以利用预训练模型来搭建自己的模型,而不用从头开始。
当我们有较多的图片数据需要处理,或者只是单纯想要程序运行的更快一点,那么使用缓冲区提取从磁盘中加载图像是非常有效果的。如果你苦恼没有大量的图片,那么将随机的转换用于现有的训练图像来达到数据增强也是不错的方法,这十分有助于模型对训练数据进行充分的学习并减少过拟合。
本文的代码是根据 Google 开发的 MobileNet V2 模型来创建基础模型。此模型基于 ImageNet 数据集进行预训练,ImageNet 数据集是一个包含 140 万个图像和 1000 个类的大型数据集。首先,需要选择将 MobileNet V2 的哪一层用于特征提取。最后的分类层(在“顶部”,因为大多数机器学习模型的图表是从下到上的)不是很有用。分类识别更依赖于展平操作之前的最后一层,此层被称为“瓶颈层”。与最后一层/顶层相比,瓶颈层的特征保留了更多的通用性。
import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
from tensorflow import keras
# 数据下载
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
# extract将文件提取为存档,如zip或tar
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')
# 测试集路径
BATCH_SIZE = 32
IMG_SIZE = (160, 160)
# 从指定路径获取训练数据集
train_dataset = tf.keras.utils.image_dataset_from_directory(train_dir,
# 训练集文件夹路径
shuffle=True,
# 随机打乱
batch_size=BATCH_SIZE,
# 按照每个batch_size大小去读取
image_size=IMG_SIZE)
# 指定图片大小
# 从指定路径获取验证数据集
validation_dataset = keras.utils.image_dataset_from_directory(validation_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
class_names = train_dataset.class_names
# 类名,可以print显示训练集中的类的名字,同理验证集也可以
plt.figure(figsize=(10, 10))
# 设置输出的图片格式大小为10*10
# 显示训练集中的前九个图像将图片在输出格式上按照3*3的规格排列
for images, labels in train_dataset.take(1):
# 对于训练集中的数据,按照行获取数据,分别指定给image和label
for i in range(9):
# 指定循环次数(对于循环次数已知的用for语句)
ax = plt.subplot(3, 3, i + 1)
# 将输出的figure划分为3*3的格式,第i+1个位置的图片
plt.imshow(images[i].numpy().astype("uint8"))
# 展示iMage[i],并指定格式为uint8(无符号数)
plt.title(class_names[labels[i]])
# 当前位置展示图片的名字
plt.axis("off")
# 坐标轴是否展示
# 因为在引入的数据中不存在测试集,因此从验证集中划分出一部分作为测试集
val_batches = tf.data.experimental.cardinality(validation_dataset)
# 该函数计算验证集有多少个batch
test_dataset = validation_dataset.take(val_batches // 5)
# 测试集数据为从验证集中抓取五分之一
validation_dataset = validation_dataset.skip(val_batches // 5)
# 跳过划分的20%,剩余的仍旧留给验证集
AUTOTUNE = tf.data.AUTOTUNE
# 自动微调
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
# prefetch函数 预读取函数 buffer_size 缓冲区大小 表示每次预读取缓冲区size的数据
validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)
# 数据增强,对图片进行旋转和翻转
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip('horizontal'),
tf.keras.layers.RandomRotation(0.2),
])
for image, _ in train_dataset.take(1):
# 按行读取
plt.figure(figsize=(10, 10))
first_image = image[0]
for i in range(9):
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')
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
# 下载mobilenet作为基础模型
# 从预训练模型MobileNet V2中加载参数信息
IMG_SHAPE = IMG_SIZE + (3,)
# 在原有的像素大小上加上一列,形成一个三维的张量
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
# 输入形状
include_top=False,
# 不包括最顶层
weights='imagenet')
# 权重参数从ImageNet网络获取
image_batch, label_batch = next(iter(train_dataset))
# 迭代器,迭代读取训练集中的内容
feature_batch = base_model(image_batch)
# 将图片批量读入base_model网络
print(feature_batch.shape)
# 冻结卷积基,避免在训练期间更新给定层中的权重。
base_model.trainable = False
# 将base_model的参数设置为不可训练
# 添加分类头 设置顶部层达到分类效果
global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
# 全局平均池化 平均池化更能凸显背景
feature_batch_average = global_average_layer(feature_batch)
# 对base_model出来的数据进行平均池化
print(feature_batch_average.shape)
prediction_layer = tf.keras.layers.Dense(1)
# 顶层:预测层 全连接网络 1个神经元
prediction_batch = prediction_layer(feature_batch_average)
# 对feature_batch_average的数据处理分类
print(prediction_batch.shape)
# 执行部分
inputs = tf.keras.Input(shape=(160, 160, 3))
# 输入要求
x = data_augmentation(inputs)
# 数据增强
x = preprocess_input(x)
# 预训练基础模型引入
x = base_model(x, training=False)
# 对引入模型进行设置(不要头,不可训练)
x = global_average_layer(x)
# 平均池化
x = tf.keras.layers.Dropout(0.2)(x)
# dropout 避免过拟合
outputs = prediction_layer(x)
# 输出预测层
model = tf.keras.Model(inputs, outputs)
# model搭建输入和输出模型
base_learning_rate = 0.0001
# 基础学习速率
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=base_learning_rate),
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
# 二分类交叉熵函数
metrics=['accuracy'])
model.summary()
len(model.trainable_variables)
# 显示可训练参数
initial_epochs = 10
loss0, accuracy0 = model.evaluate(validation_dataset)
# 用evaluate测试验证集
print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))
history = model.fit(train_dataset,
epochs=initial_epochs,
validation_data=validation_dataset)
# 读取训练记录
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,1.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
运行完毕后可能会发现,验证集的指标明显优于训练指标。这是因为训练集的批正则化和Dropout层都会影响训练期间的准确率,在计算验证损失时,他们处于关闭状态。在较小程度上,这也是因为训练指标报告的是某个周期的平均值,而验证指标则在经过该周期后才进行评估,因此验证指标会看到训练时间略长一些的模型。