特征提取步骤后的模型测试
我们将使用一些样板代码来计算使用.predict()获得的模型预测。请记住,predIdxs将类似于[0.8,0.2],即两个类M 和 O的softmax值,所以确保你使用np.argmax选择这两个的最大值。我们使用testGen.class_indices来检查从类名到类索引的映射。
testGen.reset()
predIdxs = model.predict(
x=testGen,
steps=(totalTest // BATCH_SIZE) + 1
)
predIdxs = np.argmax(predIdxs, axis = 1)
print("No. of test images", len(predIdxs))
print(testGen.class_indices)
cm = confusion_matrix(testGen.classes, predIdxs)
heatmap = sns.heatmap(cm, annot=True)
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.show()
********* OUTPUT********
No. of test images 46
{'M': 0, 'O': 1}
微调模型
我们将首先解冻当前模型的最后几层,但是,不应该随意打开或关闭层。微调模型有很多技术和技巧(请参见此示例和此示例),但发现其中一些最有用:但发现其中一些最有用:
在这一步编译模型时,使用比特征提取步骤更小的学习率。较小的学习速率意味着需要更多的epoch,因为每次更新时对网络权重的更改较小。
· BathcNormalization层需要保持冻结。
· 在网络体系结构中,卷积块需要整体打开或关闭。
· 例如:考虑model.summary()输出的最后几行。正如你所看到的,图被整齐地组织成块,block7da是最后一个块。
作为一个起点,我们将解冻block7d中的所有层(BathcNormalization层将保持原样)加上下面的7个层(其中大多数是我们在构建一个新的分类器头时定义的)。
总的来说,网络的最后20层将是解冻的候选者。
________________________________________
Layer (type) Output Shape Param # ====================================================================
.
.
.
block6d_project_conv (Conv2D) (None, 7, 7, 192) 221184 ______________________________________________
block6d_project_bn (BatchNormal (None, 7, 7, 192) 768 ________________________________________________
block6d_drop (Dropout) (None, 7, 7, 192) 0 _________________________________________________
block6d_add (Add) (None, 7, 7, 192) 0 __________________________________________________
block7a_expand_conv (Conv2D) (None, 7, 7, 1152) 221184
__________________________________________
block7a_expand_bn (BatchNormali (None, 7, 7, 1152) 4608 ______________________________________________
block7a_expand_activation (Acti (None, 7, 7, 1152) 0 ________________________________________________
block7a_dwconv (DepthwiseConv2D (None, 7, 7, 1152) 10368
__________________________________________
.
.
.
已经将用于微调的代码捆绑到一个名为fine_tune_model的函数中。大多数代码从特征提取步骤重复。
def fine_tune_model(model):
# unfreeze last conv block i.e. block7a
for layer in model.layers[-20:]:
if not isinstance(layer, BatchNormalization):
layer.trainable = True
# check which of these are trainable and which aren't
for layer in model.layers:
print("{}: {}".format(layer, layer.trainable))
# compile (with an even smaller learning rate)
opt = Adam(learning_rate=1e-5)
model.compile(
optimizer=opt,
loss='binary_crossentropy',
metrics=[tf.keras.metrics.AUC()]
)
return model
model_fine_tuned = fine_tune_model(model)
由于微调还将使用相同的数据生成器,即trainGen、valGen和testGen,因此必须重置它们,以便它们从数据集中的第一个样本开始。
trainGen.reset()
valGen.reset()
testGen.reset()
最后,让我们设置早停和模型检查点(注意,我们已经将耐心增加到20,因为我们现在将进行更长时间的训练,即50个epoch),并开始训练。
# implementing early stopping
es_tune = EarlyStopping(
monitor='val_loss',
mode='min',
patience=20,
verbose=1
)
# implementing model checkpoint
mc_tune = ModelCheckpoint(
'fine_tuned_house.h5',
monitor='val_loss',
mode='min',
verbose=1,
save_best_only=True
)
hist = model_fine_tuned.fit(
x=trainGen,
steps_per_epoch=totalTrain // BATCH_SIZE,
validation_data=valGen,
epochs=50,
verbose=2,
callbacks=[es_tune, mc_tune]
)
特征提取步骤后的模型测试
在将其与之前的混淆矩阵进行比较后,我们只成功地将正确预测的图像数量增加了2。作为最后的理智检查,看看这个微调步骤是否显示出任何过拟合。
验证损失稳定在0.55左右,表明模型没有过拟合。总的来说,验证集预测的AUC确实会随着时间的推移而变得更好,但回报会逐渐减少。(简单地说,长时间的训练似乎对我们的案例没有实质性的帮助)。
起初,认为训练曲线的波动是由于批量大小造成的,因为它们在网络学习中起着作用。同样,过大的学习速率会阻碍收敛,导致损失函数波动,陷入局部极小值。然而,无论是增加批量大小还是降低学习率都无助于平滑梯度。
另一种可能的解释是,网络已经达到了给定数据集的容量,也就是说,它无法从中学习更多内容。这是可能的,因为我们正在尝试使用344个样本来训练一个相对较大的网络(记住,我们已经解冻了一些额外的层,这意味着存在更多可训练的参数),这些样本无法提供足够的信息来了解问题(进一步)。
注意:为了改进模型,在将更多图像推入训练过程之前,可能需要对模型超参数、train:val split、预训练权重的选择以及网络体系结构本身进行修补。
未来的工作
在这篇最近的论文中已经确定,使用未标记和标记数据集的联合训练优于我们首先使用未标记数据进行预训练,然后对标记数据进行微调。这就是所谓的半监督学习,将是我们下一个教程的重点。这将允许我们充分利用数据集中难以获得标签的剩余图像。
原文标题 : 端到端深度学习项目:第1部分