我目前正在尝试理解如何重用VGG19(或其他架构)来改进我的小型图像分类模型。我正在对图像(在这种情况下是绘画)进行分类,分为3个类别(比如说,15世纪、16世纪和17世纪的绘画)。我的数据集相当小,每个类别的训练样本有1800个,验证集每个类别有250个。
我有以下实现:
from keras.preprocessing.image import ImageDataGeneratorfrom keras.models import Sequentialfrom keras.layers import Conv2D, MaxPooling2Dfrom keras.layers import Activation, Dropout, Flatten, Densefrom keras import backend as Kfrom keras.callbacks import ModelCheckpointfrom keras.regularizers import l2, l1from keras.models import load_model# set proper image ordering for TensorFlowK.set_image_dim_ordering('th')batch_size = 32# this is the augmentation configuration we will use for trainingtrain_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 rescalingtest_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 datatrain_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 datavalidation_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 improvmentsmodel.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)
如果我使架构更深,它要么过拟合很多,要么如果我更严格地正则化它,就会像疯了一样跳动,甚至在某一时刻达到100%:
我也尝试使用BatchNormalization,但那样模型几乎不学习,训练集上的准确率不会超过50%。我尝试过有和没有dropout的情况。
除了过多地改变架构外,我还在寻找其他改进模型的方法。我看到的一个选项是重用现有架构及其权重,并将其插入我的模型中。但我找不到任何实际的例子来说明如何操作。我主要是按照这篇博客文章进行的:https://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html
它谈到了重用VGG19来提高准确率,但并没有真正解释如何做到这一点。还有其他我可以参考的例子吗?我如何将其适应到我当前的实现中?我找到了一个完整的模型架构,但在我的硬件上运行它是不可能的,所以我正在寻找一种重用已经训练好的模型及其权重,然后将其适应到我的问题中的方法。
此外,我不理解博客在VGG部分提到的“瓶颈特征”的概念。如果有人能解释一下,我会很高兴的。
回答:
你绝对应该尝试一下迁移学习(链接是“迁移学习 Keras”的第一个Google搜索结果,关于这个主题有很多教程)。本质上,TL是对一个在大型数据集(即最常见的是ImageNet)上预训练的网络进行微调,并添加新的分类层。其背后的想法是,你希望保留网络低层级学到的所有良好特征(因为你的图像也很可能具有这些特征),然后只在这些特征之上学习一个新的分类器。这通常效果很好,特别是如果你有小数据集,无法从头开始完全训练网络(这也比完全训练要快得多)
请注意,进行TL有几种方法(我鼓励你研究这个主题,找出最适合你的方法)。在我的应用中,我只是用从ImageNet公开检查点中获取的权重初始化网络,移除最后一层,然后从那里开始训练所有内容(使用足够低的学习率,否则你会破坏你实际上想保留的低层级特征)。这种方法允许数据增强。
另一种方法是使用瓶颈。在这种情况下,瓶颈,在其他情况下也称为嵌入,是你的输入样本在网络中某个深度层次的内部表示。换句话说,你可以将第N层的瓶颈视为在N层后停止的网络的输出。这为什么有用?因为你可以使用预训练的网络预先计算所有样本的瓶颈,然后模拟仅训练网络最后一层的训练,而无需实际重新计算网络直到瓶颈点的(昂贵的)部分。
一个简化的例子
假设你有一个网络,结构如下:
in -> A -> B -> C -> D -> E -> out
其中in
和out
是输入和输出层,其他层可能是网络中你可能有的任何类型的层。假设你找到了某个地方发布的在ImageNet上预训练的网络检查点。ImageNet有1000个类别,你不需要其中的任何一个。所以你会丢掉网络的最终层(分类器)。然而,其他层包含你想保留的特征。假设E
是我们例子中的分类器层。
你从你的数据集中取样本,输入到in
,并收集与之对应的瓶颈值作为层D
的输出。你对数据集中的所有样本执行一次这个操作。瓶颈的集合就是你用来训练新分类器的新数据集。
你构建一个虚拟网络,结构如下:
bottleneck_in -> E' -> out
你现在像平常一样训练这个网络,但不是输入你的数据集中的样本,而是输入瓶颈数据集中的相应瓶颈。注意,这样做你节省了从A
到D
的所有层的计算,但这样你就无法在训练期间应用任何数据增强(当然,你仍然可以在构建瓶颈时进行数据增强,但你将需要存储大量数据)。
最后,为了构建你的最终分类器,你的网络架构将是
in -> A -> B -> C -> D -> E' -> out
其中权重A
到D
来自公开检查点,而权重E'
来自你的训练结果。