更复杂的体系结构能保证更好的模型吗?
使用的数据集和数据预处理
我们将使用Kaggle的狗与猫数据集。它是根据知识共享许可证授权的,这意味着你可以免费使用它:
该数据集相当大——25000张图像均匀分布在不同的类中(12500张狗图像和12500张猫图像)。它应该足够大,以训练一个像样的图像分类器。
你还应该删除train/cat/666.jpg和train/dog/11702.jpg图像,这些已经损坏,你的模型将无法使用它们进行训练。
接下来,让我们看看如何使用TensorFlow加载图像。
如何使用TensorFlow加载图像数据
今天你将看到的模型将比前几篇文章中的模型具有更多的层。
为了可读性,我们将从TensorFlow中导入单个类。如果你正在跟进,请确保有一个带有GPU的系统,或者至少使用Google Colab。
让我们把库的导入放在一边:
这是很多,但模型会因此看起来格外干净。
我们现在将像往常一样加载图像数据——使用ImageDataGenerator类。
我们将把图像矩阵转换为0–1范围,使用用三个颜色通道,将所有图像调整为224x224。出于内存方面的考虑,我们将barch大小降低到32:
以下是你应该看到的输出:
让我们鼓捣第一个模型!
向TensorFlow模型中添加层会有什么不同吗?
从头开始编写卷积模型总是一项棘手的任务。网格搜索最优架构是不可行的,因为卷积模型需要很长时间来训练,而且有太多的参数需要检查。实际上,你更有可能使用迁移学习。这是我们将在不久的将来探讨的主题。
今天,这一切都是为了理解为什么在模型架构上大刀阔斧是不值得的。我们用一个简单的模型获得了75%的准确率,所以这是我们必须超越的基线。
模型1-两个卷积块
我们将宣布第一个模型在某种程度上类似于VGG体系结构——两个卷积层,后面是一个池层。滤波器设置如下,第一个块32个,第二个块64个。
至于损失和优化器,我们将坚持基本原则——分类交叉熵和Adam。数据集中的类是完全平衡的,这意味着我们只需跟踪准确率即可:
model_1 = tf.keras.Sequential([
Conv2D(filters=32, kernel_size=(3, 3), input_shape=(224, 224, 3), activation='relu'),
Conv2D(filters=32, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Conv2D(filters=64, kernel_size=(3, 3), activation='relu'),
Conv2D(filters=64, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Flatten(),
Dense(units=128, activation='relu'),
Dense(units=2, activation='softmax')
])
model_1.compile(
loss=categorical_crossentropy,
optimizer=Adam(),
metrics=[BinaryAccuracy(name='accuracy')]
)
model_1_history = model_1.fit(
train_data,
validation_data=valid_data,
epochs=10
)
以下是经过10个epoch后的训练结果:
看起来我们的表现并没有超过基线,因为验证准确率仍然在75%左右。如果我们再加上一个卷积块会发生什么?
模型2-三个卷积块
我们将保持模型体系结构相同,唯一的区别是增加了一个包含128个滤波器的卷积块:
model_2 = Sequential([
Conv2D(filters=32, kernel_size=(3, 3), input_shape=(224, 224, 3), activation='relu'),
Conv2D(filters=32, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Conv2D(filters=64, kernel_size=(3, 3), activation='relu'),
Conv2D(filters=64, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Conv2D(filters=128, kernel_size=(3, 3), activation='relu'),
Conv2D(filters=128, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Flatten(),
Dense(units=128, activation='relu'),
Dense(units=2, activation='softmax')
])
model_2.compile(
loss=categorical_crossentropy,
optimizer=Adam(),
metrics=[BinaryAccuracy(name='accuracy')]
)
model_2_history = model_2.fit(
train_data,
validation_data=valid_data,
epochs=10
)
日志如下:
效果变差了。虽然你可以随意调整batch大小和学习率,但效果可能仍然不行。第一个架构在我们的数据集上工作得更好,所以让我们试着继续调整一下。
模型3-带Dropout的卷积块
第三个模型的架构与第一个模型相同,唯一的区别是增加了一个全连接层和一个Dropout层。让我们看看这是否会有所不同:
model_3 = tf.keras.Sequential([
Conv2D(filters=32, kernel_size=(3, 3), input_shape=(224, 224, 3), activation='relu'),
Conv2D(filters=32, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Conv2D(filters=64, kernel_size=(3, 3), activation='relu'),
Conv2D(filters=64, kernel_size=(3, 3), activation='relu'),
MaxPool2D(pool_size=(2, 2), padding='same'),
Flatten(),
Dense(units=512, activation='relu'),
Dropout(rate=0.3),
Dense(units=128),
Dense(units=2, activation='softmax')
])
model_3.compile(
loss=categorical_crossentropy,
optimizer=Adam(),
metrics=[BinaryAccuracy(name='accuracy')]
)
model_3_history = model_3.fit(
train_data,
validation_data=valid_data,
epochs=10
)
以下是训练日志:
太可怕了,现在还不到70%!上一篇文章中的简单架构非常好。反而是数据质量问题限制了模型的预测能力。
结论
这就证明了,更复杂的模型体系结构并不一定会产生性能更好的模型。也许你可以找到一个更适合猫狗数据集的架构,但这可能是徒劳的。
你应该将重点转移到提高数据集质量上。当然,有20K个训练图像,但我们仍然可以增加多样性。这就是数据增强的用武之地。
感谢阅读!