如何在Keras中重复使用VGG19进行图像分类?

4

我目前正在尝试理解如何重用VGG19(或其他架构),以改进我的小型图像分类模型。 我将图像(在这种情况下是绘画)分类为3个类别(假设是15世纪、16世纪和17世纪的绘画)。 我有一个相当小的数据集,每个类别有1800个训练示例,在验证集中,每个类别有250个。

我有以下实现:

from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import backend as K
from keras.callbacks import ModelCheckpoint
from keras.regularizers import l2, l1
from keras.models import load_model

# set proper image ordering for TensorFlow
K.set_image_dim_ordering('th')

batch_size = 32

# this is the augmentation configuration we will use for training
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

# this is the augmentation configuration we will use for testing:
# only rescaling
test_datagen = ImageDataGenerator(rescale=1./255)

# this is a generator that will read pictures found in
# subfolers of 'data/train', and indefinitely generate
# batches of augmented image data
train_generator = train_datagen.flow_from_directory(
        'C://keras//train_set_paintings//',  # this is the target directory
        target_size=(150, 150),  # all images will be resized to 150x150
        batch_size=batch_size,
        class_mode='categorical')

# this is a similar generator, for validation data
validation_generator = test_datagen.flow_from_directory(
        'C://keras//validation_set_paintings//',
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='categorical')

model = Sequential()

model.add(Conv2D(16, (3, 3), input_shape=(3, 150, 150)))
model.add(Activation('relu'))  # also tried LeakyRelu, no improvments
model.add(MaxPooling2D(pool_size=(2, 3), data_format="channels_first"))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 3), data_format="channels_first"))

model.add(Flatten())
model.add(Dense(64, kernel_regularizer=l2(.01))) 
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(3))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy',
              optimizer='adam',  # also tried SGD, it doesn't perform as well as adam
              metrics=['accuracy'])

fBestModel = 'best_model_final_paintings.h5'
best_model = ModelCheckpoint(fBestModel, verbose=0, save_best_only=True)

hist = model.fit_generator(
    train_generator,
    steps_per_epoch=2000 // batch_size,
    epochs=100,
    validation_data=validation_generator,
    validation_steps=200 // batch_size,
    callbacks=[best_model],
    workers=8  # cpu generation is run in parallel to the gpu training
)

print("Maximum train accuracy:", max(hist.history["acc"]))
print("Maximum train accuracy on epoch:", hist.history["acc"].index(max(hist.history["acc"]))+1)

print("Maximum validation accuracy:", max(hist.history["val_acc"]))
print("Maximum validation accuracy on epoch:", hist.history["val_acc"].index(max(hist.history["val_acc"]))+1)

在防止过拟合方面,我已经控制得比较平衡: enter image description here enter image description here

如果我将架构变得更深,它要么会过拟合得很严重,要么会像疯子一样跳来跳去,即使我更加严格地正则化它,甚至有一次达到了100%: enter image description here

我也尝试使用批量标准化,但是模型几乎没有学习,训练集的准确率不到50%。我尝试了带和不带dropout。

我正在寻找除了过度修改架构之外改进模型的其他方法。我看到其中一个选项是重复使用具有其权重的现有体系结构并将其插入到我的模型中。但是我找不到任何真正的例子来说明如何做到这一点。我主要是按照这篇博客文章进行的: https://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html

它讨论了重复使用VGG19以提高准确性,但实际上并没有解释如何做到这一点。是否有其他示例可以遵循?我应该如何将其适应到我的当前实现中?我找到了完整的模型架构,但在我的硬件上无法运行,因此我正在寻找一种重复使用已经训练好的模型权重并将其适应于我的问题的方法。

此外,我不理解博客中VGG部分所谈论的“瓶颈特征”的概念。如果有人能解释一下就好了。

2个回答

8
你应该一定要尝试迁移学习(链接指向“transfer learning Keras”的谷歌搜索结果,有许多关于这个主题的教程)。基本上,TL是对已经在一些大型数据集(例如,最常见的是Imagenet)上进行了预训练的网络进行微调,以添加新的分类层。其背后的想法是,您希望保留网络较低层级中学到的所有好的特征(因为您的图像也很可能具有这些特征),并且只需在这些特征之上学习一个新的分类器。这通常效果很好,特别是如果您有小型数据集无法从头开始完全训练网络(它也比完全训练快得多)。
请注意,有几种方法可以实现TL(我鼓励您研究此主题以找到最适合您的方法)。在我的应用程序中,我仅使用来自Imagenet公共检查点的权重初始化网络,删除最后一层,并从那里开始训练所有内容(使用足够低的学习率,否则会破坏您实际想要保留的低级特征)。这种方法允许数据增强。
另一种方法是使用“瓶颈”。在这种情况下,“瓶颈”(在其他情况下称为嵌入)是您的一个输入样本在网络中特定深度级别上的内部表示。换句话说,您可以将第N层的瓶颈看作是网络输出停在第N层之后的结果。为什么这很有用?因为您可以使用预先训练好的网络预先计算出所有样本的瓶颈,然后模拟只训练网络的最后几层,而无需重新计算达到瓶颈点的所有昂贵的网络部分。
一个简单的例子: 假设您有一个具有以下结构的网络:
in -> A -> B -> C -> D -> E -> out

在一个神经网络中,inout 是输入层和输出层,其他层可以是任何类型的层。假设你从某个地方找到了 Imagenet 上预先训练的网络检查点,Imagenet 有 1000 个类别,你并不需要全部。因此,你将丢弃网络的最后一层(分类器)。然而,其他层包含你想要保留的特征。在本例中,假设 E 是分类器层。

从数据集中获取样本,将它们馈送到 in 并收集匹配的瓶颈值作为图层 D 的输出。针对数据集中的所有样本都进行这样的操作。瓶颈值的集合是你将用来训练新分类器的新数据集。

你需要建立一个虚拟网络,其结构如下:

bottleneck_in -> E' -> out

你现在可以像平常一样训练这个网络,但是不要使用数据集中的样本,而是使用瓶颈数据集中的匹配瓶颈。请注意,这样做可以节省从 AD 的所有层的计算量,但是在训练过程中不能应用任何数据增强技术(当然,在构建瓶颈时仍然可以这样做,但你需要大量存储空间)。
最后,为了构建最终的分类器,你的网络架构将会是:
in -> A -> B -> C -> D -> E' -> out

使用公共检查点中的权重AD,以及由您的训练产生的权重E'


2

简要概述:

  1. 加载Vgg
  2. 舍弃输出层和倒数第二层
  3. 在末尾添加一个新的、随机初始化的输出层
  4. 使用数据进行微调

我几乎可以肯定Keras中至少有一个示例代码可供参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接