colab链接
import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
from tensorflow.keras.preprocessing import image_dataset_from_directory
在本教程中,您将使用一个包含数千张猫和狗图像的数据集。下载并解压缩包含图像的zip文件,然后tf.data.Dataset使用该tf.keras.preprocessing.image_dataset_from_directory实用程序创建一个用于训练和验证的。您可以在本教程中了解有关加载图像的更多信息。
_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)
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 = image_dataset_from_directory(train_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
Downloading data from https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip
68608000/68606236 [==============================] - 2s 0us/step
Found 2000 files belonging to 2 classes.
validation_dataset = image_dataset_from_directory(validation_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
Found 1000 files belonging to 2 classes.
显示训练集中的前九幅图像和标签:
class_names = train_dataset.class_names
plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[labels[i]])
plt.axis("off")
创建画布,plt.figure(figsize=())
take(1)
创建子图对象,subplot(3,3,i+1)
先转换成uint8,然后显示标题为class_names[labels[i]]
设置坐标plt.axis(‘off’)
由于原始数据集不包含测试集,因此您将创建一个。为此,请确定在验证集中可用的数据批次,使用tf.data.experimental.cardinality,然后将其中的20%移至测试集中。
val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)
validation_dataset = validation_dataset.skip(val_batches // 5)
cardinality 返回 基数,应该是求出有多少batch
因为validation_dataset中有1000个图片,按照batch_size是32来读取
有多少个batch呢,31.25个即32
val_batches = tf.data.experimental.cardinality(validation_dataset)
val_batches
所以val_batches // 5 是6
validation_dataset最后还剩26个items
print('Number of validation batches: %d' % tf.data.experimental.cardinality(validation_dataset))
print('Number of test batches: %d' % tf.data.experimental.cardinality(test_dataset))
Number of validation batches: 26
Number of test batches: 6
使用缓冲的预取从磁盘加载映像,而不会阻塞I / O。要了解有关此方法的更多信息,请参见数据性能指南。
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)
当您没有大型图像数据集时,通过对训练图像进行随机但逼真的变换(例如旋转和水平翻转)来人为引入样本多样性是一种很好的做法。这有助于使模型暴露于训练数据的不同方面,并减少过度拟合。您可以在本教程中了解有关数据增强的更多信息。
data_augmentation = tf.keras.Sequential([
tf.keras.layers.experimental.preprocessing.RandomFlip('horizontal'),
tf.keras.layers.experimental.preprocessing.RandomRotation(0.2),
])
这些层仅在training期间(当您调用model.fit时)处于活动状态。当模型在model.evaulate或model.fit中以推理inference mode模式使用时,它们将处于非活动状态
让我们将这些图层重复应用到同一张图片上,然后查看结果。
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')
稍后,您将下载tf.keras.applications.MobileNetV2用作基本模型。该模型期望像素值为[-1,1],但此时,图像中的像素值为[0-255]。要重新缩放它们,请使用模型随附的预处理方法。
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
注意:或者,您可以使用“重新缩放”图层将像素值从缩放[0,255]到[-1, 1]
rescale = tf.keras.layers.experimental.preprocessing.Rescaling(1./127.5, offset= -1)
注意:如果使用other tf.keras.applications,请确保检查API文档以确定他们是否期望[-1,1]或中的像素[0,1],或者使用附带的preprocess_input功能。
您将根据Google开发的MobileNet V2模型创建基本模型。这在ImageNet数据集上进行了预训练,ImageNet数据集是一个由140万张图像和1000个类别组成的大型数据集。ImageNet是研究训练数据集,具有诸如jackfruit和的多种类别syringe。这些知识将帮助我们从特定数据集中对猫和狗进行分类。
首先,您需要选择将用于功能提取的MobileNet V2的哪一层。最后的分类层(在“顶部”,因为大多数机器学习模型的图是从底部到顶部)不是很有用。取而代之的是,您将遵循通常的做法来依赖于展平操作之前的最后一层。该层称为“瓶颈层”。与最终/顶层相比,瓶颈层的特征保留了更多的通用性。
首先,实例化一个预加载了ImageNet训练权重的MobileNet V2模型。通过指定include_top = False参数,可以加载不在顶部包括分类层的网络,这对于特征提取是理想的。
# Create the base model from the pre-trained model MobileNet V2
IMG_SHAPE = IMG_SIZE + (3,)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
include_top=False,
weights='imagenet')
该特征提取器将每个160x160x3图像转换为一个5x5x1280特征块。让我们看看它对一批示例图像有什么作用:
image_batch, label_batch = next(iter(train_dataset))
feature_batch = base_model(image_batch)
print(feature_batch.shape)
(32, 5, 5, 1280)
在此步骤中,您将冻结在上一步中创建的卷积基础并将其用作特征提取器。此外,您可以在其顶部添加分类器并训练顶级分类器。
在编译和训练模型之前,冻结卷积基础很重要。冻结(通过设置layer.trainable = False)可防止在训练期间更新给定层中的权重。MobileNet V2具有许多层,因此将整个模型的trainable标志设置为False将冻结所有这些层。
base_model.trainable = False
许多模型包含tf.keras.layers.BatchNormalization图层。该层是一种特殊情况,应在微调的上下文中采取预防措施,如本教程后面所示。
设置后layer.trainable = False,该BatchNormalization图层将以推理模式运行,并且不会更新其均值和方差统计信息。
当解冻包含BatchNormalization图层的模型以进行微调时,应training = False在调用基本模型时通过传递,使BatchNormalization图层保持推理模式。否则,应用于不可训练权重的更新将破坏模型所学。
有关详细信息,请参阅《转移学习指南》。
# Let's take a look at the base model architecture
base_model.summary()
为了从特征块生成预测,请对空间5x5空间位置进行平均,并使用tf.keras.layers.GlobalAveragePooling2D图层将特征转换为每个图像单个1280个元素的向量。
global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
feature_batch_average = global_average_layer(feature_batch)
print(feature_batch_average.shape)
(32, 1280)
应用tf.keras.layers.Dense一层将这些特征转换为每个图像的单个预测。您在这里不需要激活函数,因为此预测将被视为logit或原始预测值。正数表示1类,负数表示0类。
prediction_layer = tf.keras.layers.Dense(1)
prediction_batch = prediction_layer(feature_batch_average)
print(prediction_batch.shape)
(32, 1)
通过使用Keras Functional API将数据扩充,重新缩放,base_model和特征提取器层链接在一起来构建模型。如前所述,请使用training = False,因为我们的模型包含一个BatchNormalization层。
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)
outputs = prediction_layer(x)
model = tf.keras.Model(inputs, outputs)
在训练模型之前对其进行编译。由于有两类,因此使用二进制交叉熵损失,from_logits=True因为该模型提供了线性输出。
base_learning_rate = 0.0001
model.compile(optimizer=tf.keras.optimizers.Adam(lr=base_learning_rate),
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
model.summary()
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) [(None, 160, 160, 3)] 0
_________________________________________________________________
sequential (Sequential) (None, 160, 160, 3) 0
_________________________________________________________________
tf.math.truediv (TFOpLambda) (None, 160, 160, 3) 0
_________________________________________________________________
tf.math.subtract (TFOpLambda (None, 160, 160, 3) 0
_________________________________________________________________
mobilenetv2_1.00_160 (Functi (None, 5, 5, 1280) 2257984
_________________________________________________________________
global_average_pooling2d (Gl (None, 1280) 0
_________________________________________________________________
dropout (Dropout) (None, 1280) 0
_________________________________________________________________
dense (Dense) (None, 1) 1281
=================================================================
Total params: 2,259,265
Trainable params: 1,281
Non-trainable params: 2,257,984
_________________________________________________________________
MobileNet中的2.5M参数被冻结,但是在Dense层中有1.2K的可训练参数。这些分为tf.Variable权重和偏差两个对象。
len(model.trainable_variables)
2
训练10个epoch后,您应该在验证集上看到〜94%的准确性。
initial_epochs = 10
loss0, accuracy0 = model.evaluate(validation_dataset)
print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))
initial loss: 0.88
initial accuracy: 0.42
history = model.fit(train_dataset,
epochs=initial_epochs,
validation_data=validation_dataset)
让我们看一下使用MobileNet V2基本模型作为固定功能提取器时训练和验证准确性/损失的学习曲线。
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()
注意:如果您想知道为什么验证指标明显优于训练指标,则主要因素是因为在训练过程中,层 像 tf.keras.layers.BatchNormalization和tf.keras.layers.Dropout 在训练的过程中影响精度。在计算验证损失时将其关闭。
一定程度上,这也是因为训练指标报告的是某个时期的平均值,而验证指标则是在该时期之后进行评估的,因此验证指标会看到训练时间更长的模型。
在功能提取实验中,您仅在MobileNet V2基本模型的顶部训练了几层。训练过程中未更新预训练网络的权重。
进一步提高性能的一种方法是,在训练(或“微调”)预训练模型顶层的权重的同时,对您添加的分类器进行训练。训练过程将迫使权重从通用特征图调整为专门与数据集相关联的特征。
注意:只有在您将顶级分类器训练为将预训练模型设置为不可训练之后,才能尝试进行此操作。如果您在预训练模型的顶部添加随机初始化的分类器,并尝试共同训练所有层,则梯度更新的幅度将太大(由于分类器的随机权重),因此您的预训练模型将忘记它学到的东西。
另外,您应该尝试微调少量顶层而不是整个MobileNet模型。在大多数卷积网络中,高层越高,它的专业性就越强。前几层学习非常简单且通用的功能,这些功能可以推广到几乎所有类型的图像。随着您的上移,这些功能也越来越多地针对训练模型的数据集。微调的目的是使这些专用功能适应新数据集,而不是覆盖常规学习。
(在有些迁移学习中,在step2 fine-tuning中会将base_model全部解冻,但是这里,我们微调的时候,也只是解冻模型的顶层)
您需要做的就是解冻冻结,base_model并将底层设置为不可训练。然后,您应该重新编译模型(这些更改才能生效),然后继续训练。
冻结154层的前100层
base_model.trainable = True
# Let's take a look to see how many layers are in the base model
print("Number of layers in the base model: ", len(base_model.layers))
# Fine-tune from this layer onwards
fine_tune_at = 100
# Freeze all the layers before the `fine_tune_at` layer
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
Number of layers in the base model: 154
由于您正在训练一个更大的模型并希望重新适应预训练的权重,因此在此阶段使用较低的学习率很重要。否则,您的模型可能会很快过拟合。
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate/10),
metrics=['accuracy'])
由于您正在训练一个更大的模型并希望重新适应预训练的权重,因此在此阶段使用较低的学习率很重要。否则,您的模型可能会很快过拟合。
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate/10),
metrics=['accuracy'])
这里lr =0.00001万分之一的学习率
model.summary()
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) [(None, 160, 160, 3)] 0
_________________________________________________________________
sequential (Sequential) (None, 160, 160, 3) 0
_________________________________________________________________
tf.math.truediv (TFOpLambda) (None, 160, 160, 3) 0
_________________________________________________________________
tf.math.subtract (TFOpLambda (None, 160, 160, 3) 0
_________________________________________________________________
mobilenetv2_1.00_160 (Functi (None, 5, 5, 1280) 2257984
_________________________________________________________________
global_average_pooling2d (Gl (None, 1280) 0
_________________________________________________________________
dropout (Dropout) (None, 1280) 0
_________________________________________________________________
dense (Dense) (None, 1) 1281
=================================================================
Total params: 2,259,265
Trainable params: 1,862,721
Non-trainable params: 396,544
_________________________________________________________________
len(model.trainable_variables)
56
如果您早先进行了收敛训练,那么此步骤将使您的准确性提高几个百分点。
fine_tune_epochs = 10
total_epochs = initial_epochs + fine_tune_epochs
history_fine = model.fit(train_dataset,
epochs=total_epochs,
initial_epoch=history.epoch[-1],
validation_data=validation_dataset)
注意:initial_epoch之前设置了为10
在微调MobileNet V2基本模型的最后几层并在其之上训练分类器时,让我们看一下训练和验证准确性/损失的学习曲线。验证损失比训练损失高得多,因此您可能会感到过拟合。
由于新的训练集相对较小,并且与原始MobileNet V2数据集相似,因此您可能还会感到过拟合。
经过微调后,模型在验证集上的准确性几乎达到98%。
acc += history_fine.history['accuracy']
val_acc += history_fine.history['val_accuracy']
loss += history_fine.history['loss']
val_loss += history_fine.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.ylim([0.8, 1])
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='lower right')
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.ylim([0, 1.0])
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
最后,您可以使用测试集在新数据上验证模型的性能。
loss, accuracy = model.evaluate(test_dataset)
print('Test accuracy :', accuracy)
6/6 [==============================] - 0s 13ms/step - loss: 0.0650 - accuracy: 0.9740
Test accuracy : 0.9739583134651184
现在,您都可以使用此模型来预测您的宠物是猫还是狗。
#Retrieve a batch of images from the test set
image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch).flatten()
# Apply a sigmoid since our model returns logits
predictions = tf.nn.sigmoid(predictions)
predictions = tf.where(predictions < 0.5, 0, 1)
print('Predictions:\n', predictions.numpy())
print('Labels:\n', label_batch)
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")
Predictions:
[1 0 1 1 1 0 0 1 1 0 1 1 1 0 0 1 1 0 0 0 1 0 1 1 0 0 1 1 1 0 1 1]
Labels:
[1 0 1 1 1 0 0 1 0 0 1 1 1 0 0 1 1 0 0 0 1 0 1 1 0 0 1 1 1 0 1 1]
使用预先训练的模型进行特征提取:使用小型数据集时,通常的做法是利用在相同域中的较大数据集上训练的模型中学习的特征。这是通过实例化预训练的模型并在顶部添加完全连接的分类器来完成的。预先训练的模型是“冻结的”,训练过程中仅更新分类器的权重。在这种情况下,卷积基础提取了与每个图像关联的所有特征,而您刚刚训练了一个分类器,该分类器根据给定的提取特征集确定图像类。
对预训练模型进行微调:为了进一步提高性能,可能需要通过微调将预训练模型的顶层重新用于新的数据集。在这种情况下,您需要调整权重,以使模型学习到特定于数据集的高级功能。通常在训练数据集很大并且与训练前的模型非常相似的原始数据集非常相似时,建议使用此技术。
要了解更多信息,请访问转移学习指南。
主要是将valid_dataset划分为test_dataset那里,使用cardinality基数划分不太懂
model.fit(train_dataset,
epochs=total_epochs,
initial_epoch=history.epoch[-1],
validation_data=validation_dataset)
initial_epoch
fine-tuning过程中
这是把之前的训练历史acc和loss的列表拼接到一起,
acc += history_fine.history[‘accuracy’]
val_acc += history_fine.history[‘val_accuracy’]
oss += history_fine.history[‘loss’]
val_loss += history_fine.history[‘val_loss’]
成为一个整体list然后画图的时候显示
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
这是画图中的竖线,绿色分割线,分割fine-tuning前后