第二周作业2 Transfer Learning with MobileNetV2
Create the Dataset and Split it into Training and Validation Sets
在Keras中训练和预估深度学习模型时通常会从你的硬盘中简单快速的得到图像文件,而image_data_set_from_directory()
函数可以从目录中读取并创建训练和验证数据集。如果要指定验证分割,还需要指定每个部分的子集。将训练集设为子集='training’将验证集设为子集=‘validation’
BATCH_SIZE = 32
IMG_SIZE = (160, 160)
directory = "dataset/"
# 使用image_dataset_from_directory函数最后返回的是tf.data.Dataset对象
train_dataset = image_dataset_from_directory(directory,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE,
validation_split=0.2,
subset='training',
seed=42)
validation_dataset = image_dataset_from_directory(directory,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE,
validation_split=0.2,
subset='validation',
seed=42)
利用下面的代码我们可以看到训练集中的部分图像
# 得到的是训练集中的名称,试图去打印出来是 “羊驼”和“无羊驼” 两种分类
class_names = train_dataset.class_names
# 指定figure的宽和高为(10,10)
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")
Preprocess and Augment Training Data
dataset.prefetch是作为预处理一个额外的一步,使用prefetch()
可以防止从磁盘读取时可能发生的内存瓶颈,通过从输入数据创建源数据集,应用转换对其进行预处理,然后一次迭代一个元素,它会留出一些数据,并使其在需要时准备就绪。因为迭代是流的,所以数据不需要放入内存。可以手动设置预取元素个数,也可以使用tf.data.experimental.AUTOTUNE自动选择参数。
通过转换图像(即随机翻转和旋转图像)来增强图像可以增加训练集的多样性并帮助您的模型更好地学习数据。
AUTOTUNE = tf.data.experimental.AUTOTUNE
# 对于tf.data.Dataset对象也就是训练集数据使用prefetch()函数去设置预处理参数
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
Exercise 1 - data_augmenter
实现一个数据增强,使用Sequential模型
def data_augmenter():
### START CODE HERE
# 先创建一个Sequential顺序模型
data_augmentation = tf.keras.Sequential()
#通过add函数添加两个处理的方式,可以看到第一个是对文件水平方向进行随机处理
#也就是说通过下面两个预处理对数据进行增强
data_augmentation.add(RandomFlip('horizontal'))
data_augmentation.add(RandomRotation(0.2))
### END CODE HERE
# 最后返回的数据类型是tf.keras.Sequential
return data_augmentation
通过下面的代码可以看出训练集是怎样通过简单的旋转来实现数据增强的
# 此时data_augmentation 就是一个数据增强的顺序模型
data_augmentation = data_augmenter()
# 打印出来
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.mobilenet_v2.preprocess_input
来标准化之前预处理后的模型。
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
需要记住的几点:
- 在使用image_data_set_from_directory()的时候,要设置train/val的数据集以防止数据叠加
- 使用prefetch()来防止从内存读取时出现的瓶颈
- 使用旋转等简单的操作让你的模型学习更多的内容实现数据增强
- 当使用一个预先训练过的模型时,最好重复使用训练过的权重。
Using MobileNetV2 for Transfer Learning
MobileNetV2在ImageNet网络上接受了训练,并经过优化以在移动和其他低功耗应用程序上运行。
Inside a MobileNetV2 Convolutional Building Block
MobileNetV2使用深度可分离的卷积作为有效的构建块,传统的卷积通常是资源密集型的,而深度可分离卷积可以减少可训练参数和操作的数量,并且可以分两步加快卷积速度:
- 第一步通过对每个通道独立卷积来计算中间结果。这是深度卷积。
- 在第二步中,另一个卷积将前一步的输出合并为一个。这一次从单个特征获得单个结果,然后应用于输出层中的所有过滤器。这是点向卷积,或:深度卷积的形状X滤波器的数量。
每个块由两端有Bottleneck的反向残差结构组成。这些Bottleneck将中间输入和输出编码在低维空间中,并防止非线性破坏重要信息。
与传统残差网络中的快捷连接相似,具有加快训练和改进预测的目的。这些连接跳过中间的卷积,连接瓶颈层。
下面我们从预处理层得到需要训练的基础模型
IMG_SHAPE = IMG_SIZE + (3,)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
include_top=True,
weights='imagenet')
需要注意的几点对于MobileNetV2模型独有特征:
- 深度可分离卷积,提供轻量级的特征过滤和创建
- 输入和输出Bottleneck保存了块两端的重要信息
# 得到train_dataset的数据
# image_batch.shape=(32, 160, 160, 3),label_batch.shape=(32,)
image_batch, label_batch = next(iter(train_dataset))
base_model.trainable = False
# image_batch.shape=(32, 160, 160, 3),类型从张量变为array
image_var = tf.Variable(image_batch)
# 在base_model中得到pred
pred = base_model(image_var)
tf.keras.applications.mobilenet_v2.decode_predictions(pred.numpy(), top=2)
在这些输出数据中的标签中并没有我们想要的"alpaca."
原因是在MobileNet通过预先训练的ImageNet上没有正确的羊驼标签,所以当你使用完整的模型时,你得到的只是一堆分类错误的图片。
我们可以删除带有分类标签的顶层,创造一个新的分类层
Exercise 2 - alpaca_model
需要做以下几步:
- 删除顶层(分类层):设置
include_top
和base_model
为False - 添加新的分类层:通过冻结余下的网络只训练一层,一个神经元就可以解决一个二值分类问题
- 冻结基础模型并训练新创建的分类器层:设置
base model.trainable=False
避免改变权重,只训练新层。设置训练模型base_model
为False避免在批处理标准层跟踪统计信息
def alpaca_model(image_shape=IMG_SIZE, data_augmentation=data_augmenter()):
''' Define a tf.keras model for binary classification out of the MobileNetV2 model
Arguments:
image_shape -- Image width and height
data_augmentation -- data augmentation function 数据增强功能
Returns:
Returns:
tf.keras.model
'''
input_shape = image_shape + (3,)
### START CODE HERE
# 基础的MobileNetV2模型
base_model = tf.keras.applications.MobileNetV2(input_shape=input_shape,
include_top=False, # <== Important!!!!
weights='imagenet') # From imageNet
# freeze the base model by making it non trainable
# 通过non trainable 冻结模型
base_model.trainable = False
# create the input layer (Same as the imageNetv2 input size)
# 创造输入层大小和imageNetv2输入一致
inputs = tf.keras.Input(shape=input_shape)
# apply data augmentation to the inputs
# 数据增强,使用到上面的自己写的数据增强模型
x = data_augmentation(inputs)
# data preprocessing using the same weights the model was trained on
# 数据预处理使用到了相同的权值
x = preprocess_input(x)
# set training to False to avoid keeping track of statistics in the batch norm layer
# 设置 training=False
#x = base_model(None, training=None)
x = base_model(x, training=False)
# add the new Binary classification layers
# use global avg pooling to summarize the info in each channel
#x = None()(x)
# 使用全局平均池化
x = tf.keras.layers.GlobalAveragePooling2D()(x)
# include dropout with probability of 0.2 to avoid overfitting
# 使用Dropout()避免过拟合
x = tf.keras.layers.Dropout(0.2)(x)
# use a prediction layer with one neuron (as a binary classifier only needs one)
#outputs = None
outputs = tf.keras.layers.Dense(1,activation = 'linear')(x)
### END CODE HERE
model = tf.keras.Model(inputs, outputs)
return model
后面使用compile对模型进行编译fit进行训练
使用图标对模型训练进行打印
acc = [0.] + history.history['accuracy']
val_acc = [0.] + 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()
Exercise 3
Fine-tuning the Model
可以通过在最后一层中重新运行优化器(optimizer)来尝试微调模型,以提高精度。当使用较小的学习速率时,采取更小的步骤来让它更接近新数据。在迁移学习中,实现这一点的方法是解冻网络末端的各层,然后在最后一层以非常低的学习率重新训练模型。调整你的学习速度,以更小的步骤越过这些层可以产生更多的细节和更高的准确性。
base_model = model2.layers[4]
# 解冻所有层的网络
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 120层微调
fine_tune_at = 120
### START CODE HERE
# Freeze all the layers before the `fine_tune_at` layer
# 冻结fine_tune_at层之前的网络
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
# Define a BinaryCrossentropy loss function. Use from_logits=True
# 定义一个BinaryCrossentropy损失函数
loss_function=tf.keras.losses.BinaryCrossentropy(from_logits=True)
# Define an Adam optimizer with a learning rate of 0.1 * base_learning_rate
# 定义一个Adam优化器
optimizer = tf.keras.optimizers.Adam(lr=(0.1 * base_learning_rate))
# Use accuracy as evaluation metric
# 使用accuracy为评价指标
metrics=['accuracy']
### END CODE HERE
model2.compile(loss=loss_function,
optimizer = optimizer,
metrics=metrics)