微调预训练模型
另一种常用的模型复用方法是微调,如图所示,它与特征提取互为补充。微调是指,对于用于特征提取的已冻结模型基,将其顶部几层“解冻”,并对这解冻的几层与新增加的部分(本例中为全连接分类器)共同训练。之所以叫作微调,是因为它只略微调整了复用模型中更加抽象的表示,以便让这些表示与手头的问题更加相关。
图 微调VGG16网络的最后一个卷积块
前面说过,冻结VGG16卷积基是为了能够在上面训练一个随机初始化的分类器。出于同样的原因,只有该分类器已经训练好,才能对卷积基的顶部几层进行微调。如果分类器没有训练好,那么训练过程中通过神经网络传播的误差信号将会非常大,微调的几层之前学到的表示将被破坏。因此,微调的步骤如下。(1)在已经训练好的基网络(base network)上添加自定义网络。(2)冻结基网络。(3)训练新添加的部分。(4)解冻基网络的一些层。(注意,你不应该解冻“批量规范化”层。VGG16中没有这样的层,所以这里无须过多关注。)(5)共同训练解冻的这些层和新添加的部分。你在做特征提取时已经完成了前3个步骤。我们来完成第4步:先解冻conv_base,然后冻结其中的部分层。提醒一下,卷积基的架构如下所示。
>>> conv_base.summary()
Model: "vgg16"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_19 (InputLayer) [(None, 180, 180, 3)] 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 180, 180, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 180, 180, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 90, 90, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 90, 90, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 90, 90, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 45, 45, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 45, 45, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 45, 45, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 45, 45, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 22, 22, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 22, 22, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 22, 22, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 22, 22, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 11, 11, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 11, 11, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 11, 11, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 11, 11, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 5, 5, 512) 0
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________
我们将微调最后3个卷积层,也就是说,直到block4_pool的所有层都应该被冻结,而block5_conv1、block5_conv2和block5_conv3这3层应该是可训练的。为什么不对更多的层进行微调?为什么不对整个卷积基进行微调?你当然可以这样做,但需要考虑以下两点。卷积基中较早添加的层编码的是更通用的可复用特征,较晚添加的层编码的则是针对性更强的特征。微调这些针对性更强的特征更有用,因为它们需要在你的新问题上改变用途。微调较早添加的层,得到的回报会更小。训练的参数越多,出现过拟合的风险就越大。卷积基有1500万个参数,在小型数据集上训练这么多参数是有风险的。因此,针对这种情况,一个好的策略是仅微调卷积基最后添加的两三层。我们从上一个例子结束的地方开始,继续实现这种策略,如代码清单所示。
代码清单 冻结除最后4层外的所有层
conv_base.trainable = True
for layer in conv_base.layers[:-4]:layer.trainable = False
下面我们开始微调模型,如代码清单所示。我们将使用学习率很小的RMSprop优化器。之所以将学习率设置得很小,是因为对于微调的3层表示,我们希望其变化幅度不要太大。太大的权重更新可能会破坏这些表示。
代码清单 微调模型
model.compile(loss="binary_crossentropy",optimizer=keras.optimizers.RMSprop(learning_rate=1e-5),metrics=["accuracy"])callbacks = [keras.callbacks.ModelCheckpoint(filepath="fine_tuning.keras",save_best_only=True,monitor="val_loss")
]
history = model.fit(train_dataset,epochs=30,validation_data=validation_dataset,callbacks=callbacks)
最后,我们在测试数据上评估这个模型。
model = keras.models.load_model("fine_tuning.keras")
test_loss, test_acc = model.evaluate(test_dataset)
print(f"Test accuracy: {test_acc:.3f}")
测试精度为98.5%(同样,你得到的结果可能会有不到1%的差距)。在围绕这个数据集的原始Kaggle竞赛中,这个结果是最佳结果之一。但这样比较并不公平,因为我们使用了预训练特征,这些特征已经包含了关于猫和狗的先验知识,而当时的参赛者无法使用这些特征。从积极的一面来看,利用现代深度学习技术,我们只用了一小部分比赛训练数据(约10%)就得到了这个结果。训练20 000个样本与训练2000个样本是有很大差别的!至此,你已经拥有一套可靠的工具来处理图像分类问题,特别是对于小型数据集。
总结
卷积神经网络是用于计算机视觉任务的最佳机器学习模型。即使在非常小的数据集上从头开始训练一个卷积神经网络,也可以得到不错的结果。卷积神经网络通过学习模块化模式和概念的层次结构来表示视觉世界。模型在小型数据集上的主要问题是过拟合。在处理图像数据时**,数据增强是降低过拟合的强大方法。利用特征提取**,可以很容易地将现有的卷积神经网络复用于新的数据集。对于小型图像数据集,这是一种很有用的方法。作为特征提取的补充,你还可以使用微调技术,将现有模型之前学到的一些数据表示应用于新问题。这种方法可以进一步提高模型性能。
三项基本的计算机视觉任务
到目前为止,我们一直专注于图像分类模型:输入一张图片,输出一个标签。“这张图片里可能有一只猫;那张图片里可能有一只狗。”但图像分类只是深度学习在计算机视觉领域的诸多应用之一。总体来说,你需要了解以下三项基本的计算机视觉任务。图像分类(image classification)的目的是为图像指定一个或多个标签。它既可以是单标签分类(一张图像只能属于一个类别,不属于其他类别),也可以是多标签分类(找出一张图像所属的所有类别)。如果你在谷歌照片应用程序中搜索一个关键词,后台会查询一个非常大的多标签分类模型。这个模型包含20 000多个类别,是在数百万张图像上训练出来的。图像分割(image segmentation)的目的是将图像“分割”或“划分”成不同的区域,每个区域通常对应一个类别。例如,使用软件进行视频通话时,你可以在身后设置自定义背景,它就是用图像分割模型将你的脸和身后的物体区分开,并且可以达到像素级的区分效果。目标检测(object detection)的目的是在图像中感兴趣的目标周围绘制矩形(称为边界框),并给出每个矩形对应的类别。例如,自动驾驶汽车可以使用目标检测模型监控摄像头中的汽车、行人和交通标志