Keras 面向小数据集的图像分类(vgg)笔记

本文主要是在进行利用vgg16网络进行迁移学习过程当中遇到的一些问题,以及解决办法,有很多大牛的博客给我不少的帮助和思路,在此表示感谢。以下问参考文献以及链接
keras面向小数据集的图像分类(VGG-16基础上fine-tune)实现
vgg16 fine-tune代码实现
keras系列︱图像多分类训练与利用bottleneck features进行微调(三)
Building powerful image classification models using very little data
Visualization of the filters of VGG16, via gradient ascent in input space.
How convolutional neural networks see the world
在本文中,将使用VGG-16模型提供一种面向小数据集(几百张到几千张图片)构造高效、实用的图像分类器的方法并给出试验结果。

本文将探讨如下几种方法:

  1. 从图片中直接训练一个小网络(作为基准方法)
  2. 利用预训练网络的bottleneck(瓶颈)特征
  3. fine-tune预训练网络的高层

本文需要使用的Keras模块有:

fit_generator:用于从Python生成器中训练网络

ImageDataGenerator:用于实时数据提升

层参数冻结和模型fine-tune

这份数据集来源于Kaggle,猫狗大战,可以使用百度网盘下载。原数据集有12500只猫和12500只狗,我们只取了各个类的前1000张图片。另外我们还从各个类中取了400张额外图片用于测试。
图片的数量非常少,这对于图像分类来说是个大麻烦。但现实是,很多真实世界图片获取是很困难的,我们能得到的样本数目确实很有限(比如医学图像,每张正样本都意味着一个承受痛苦的病人。对数据科学家而言,我们应该有能够榨取少量数据的全部价值的能力,而不是简单的伸手要更多的数据。

经常听到的一种说法是,深度学习只有在你拥有海量数据时才有意义。虽然这种说法并不是完全不对,但却具有较强的误导性。当然,深度学习强调从数据中自动学习特征的能力,没有足够的训练样本,这几乎是不可能的。尤其是当输入的数据维度很高(如图片)时。然而,卷积神经网络作为深度学习的支柱,被设计为针对“感知”问题最好的模型之一(如图像分类问题),即使只有很少的数据,网络也能把特征学的不错。针对小数据集的神经网络依然能够得到合理的结果,并不需要任何手工的特征工程。一言以蔽之,卷积神经网络大法好!

尤其在计算机视觉领域,许多预训练的模型现在都被公开下载,并被重用在其他问题上以提升在小数据集上的性能。另一方面,深度学习模型天然就具有可重用的特性:比方说,你可以把一个在大规模数据上训练好的图像分类或语音识别的模型重用在另一个很不一样的问题上,而只需要做有限的一点改动。
nb_train_samples = 2000
nb_val_samples = 800
batch_size = 16#之前设置的是128,但是128 不能被8002000整除。原作者设置的是16,是OK的。

Keras 面向小数据集的图像分类(vgg)笔记_第1张图片
这部分代码为保存bottleneck features的步骤。

  1. 载入图片;
  2. 灌入pre-model的权重;
  3. 得到bottleneck feature
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import keras
from keras.models import Sequential
from keras.layers import Dense,Conv2D,Activation,Flatten,Dropout
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD

from keras.utils import plot_model
from matplotlib import pyplot
import numpy as np
from keras.models import Model
import matplotlib.pyplot as plt

img_width,img_height = 150,150
batch_size = 16  # 被nb_train_samples,nb_val_samples整除
epochs = 50
nb_train_samples = 2000
nb_val_samples = 800
channel = 3
train_data_dir = 'data/train'
validation_data_dir = 'data/validation'
bottleneck_features_train_path = 'vgg_parameter/bottleneck_features_train.npy'
bottleneck_features_val_path = 'vgg_parameter/bottleneck_features_val.npy'
top_model_weights_path = 'vgg_parameter/bottleneck_fc_model.h5'
def save_bottlebeck_features():

    datagen = ImageDataGenerator(rescale=1./255,
                                shear_range=0.2,
                                zoom_range=0.2,
                                horizontal_flip=True)
    # build the VGG16 network
    model = VGG16(include_top=False,weights='imagenet')
    # print model.summary()
    plot_model(model,to_file='./vgg_parameter/vgg16_bottleneck.png',show_shapes=True)

    generator_train = datagen.flow_from_directory(train_data_dir,
                                        target_size=(img_width,img_height),
                                        batch_size=batch_size,
                                        class_mode=None,
                                        shuffle=False)
    bottleneck_features_train = model.predict_generator(generator_train,nb_train_samples//batch_size)
    np.save(open(bottleneck_features_train_path,'w'),
            bottleneck_features_train)

    generator_val = datagen.flow_from_directory(validation_data_dir,
                                            target_size=(img_width,img_height),
                                            batch_size=batch_size,
                                            class_mode=None,
                                            shuffle=False)
    bottleneck_features_validation = model.predict_generator(generator_val,
                                                             nb_val_samples//batch_size)
    # save file ,format: .npy
    np.save(open(bottleneck_features_val_path,'w'),
            bottleneck_features_validation)

这部分是 fine_tune ‘小’网络

  1. 导入bottleneck_features数据;
  2. 设置标签,并规范成Keras默认格式;
  3. 写“小网络”的网络结构 设置参数并训练
def train_top_model():
    train_data = np.load(open(bottleneck_features_train_path))
    print 'train_data shape',train_data.shape
    train_labels = np.array(
        [0]*(nb_train_samples//2)+ [1]*(nb_train_samples//2))

    validation_data = np.load(open(bottleneck_features_val_path))
    print validation_data.shape
    validation_labels = np.array(
        [0]*(nb_val_samples//2)+[1]*(nb_val_samples//2))

    model = Sequential()
    model.add(Flatten(input_shape=train_data.shape[1:]))
    model.add(Dense(256,activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1,activation='sigmoid'))

    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['accuracy'])

    model.fit(train_data,train_labels,
              epochs=epochs,
              batch_size=batch_size,
              validation_data=(validation_data,validation_labels))
    model.save_weights(top_model_weights_path)

# save_bottlebeck_features()
# train_top_model()

因为特征的size很小,模型在CPU上跑的也会很快,大概1s一个epoch。
此处所得权重为两个Dense层的权重。。。(要注意哦!!!)
1.batch_size 的设置:要被nb_train_samplesnb_val_samples整除。
不能胡乱设置一通,否则,如果设置batch_size=128,运行train_data = np.load(open('bottleneck_features_train.npy'))后,train_data.shape[0]=1920,而非2000。
2.Flatten层:
input_shape的格式形如(4,4,512)

在预训练的网络上fine-tune
为了进一步提高之前的结果,我们可以试着fine-tune网络的后面几层。Fine-tune以一个预训练好的网络为基础,在新的数据集上重新训练一小部分权重。在这个实验中,fine-tune分三个步骤:

  1. 搭建vgg-16并载入权重。
  2. 将之前定义的全连接网络加在模型的顶部此处容易出问题,在下文将具体讲解。),并载入权重。
  3. 冻结vgg16网络的一部分参数。(此处冻结的是conv block5之前的网络层参数)

注意:

  1. fine-tune,所有的层都应该以训练好的权重为初始值,例如,你不能将随机初始的全连接放在预训练的卷积层之上,这是因为由随机权重产生的大梯度将会破坏卷积层预训练的权重。
  2. 选择只fine-tune最后的卷积块,而不是整个网络,这是为了防止过拟合。整个网络具有巨大的熵容量,因此具有很高的过拟合倾向。由底层卷积模块学习到的特征更加一般,更加不具有抽象性,因此我们要保持前两个卷积块(学习一般特征)不动,只fine-tune后面的卷积块(学习特别的特征)。
  3. fine-tune应该在很低的学习率下进行,通常使用SGD优化而不是其他自适应学习率的优化算法,如RMSProp。这是为了保证更新的幅度保持在较低的程度,以免毁坏预训练的特征。

Keras 面向小数据集的图像分类(vgg)笔记_第2张图片

这部分代码如下:

input_shape = (img_width,img_height,channel)
model = VGG16(include_top=False, weights='imagenet', input_shape=input_shape)
print type(model) # Model类型
x = model.output
print x
# 将全连接层添加到卷积模型之上。
x = Flatten(name='flatten')(x)
x = Dense(256,activation='relu')(x)
x = Dropout(0.5)(x)
predictions = Dense(1,activation='sigmoid')(x)
vgg_model = Model(model.input,predictions) #此时网络结构包含了卷积层和全连接层。
vgg_model.load_weights(top_model_weights_path,by_name=True)# 将之前在全连接层上预训练好的权重加载进来。
# **此后的vgg_model已经完全加载好的预训练权重。。。。**
print len(vgg_model.layers)

dict_layers = dict([(layer.name,layer) for layer in vgg_model.layers])
print len(dict_layers) # 23
plot_model(vgg_model,to_file='vgg16_fc.png',show_shapes=True)

# 冻结了conv block5之前的所有卷积层权重,不包括输入层
for layer in vgg_model.layers[1:15]:
    # print layer.name,layer
    layer.trainable = False

注意:
将全连接网络加在模型的顶部的时候。本来是这样写的:

input_shape = (img_width,img_height,channel)
model = VGG16(include_top=False, weights='imagenet', input_shape=input_shape)
# Model型
print type(model) 
top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(1,activation='sigmoid'))
top_model.load_weights(top_model_weights_path)
# Sequential型
print type(top_model) 
model.add(top_model) #model:Model,top_model:Sequential,此处会报错。Model没有add()方法。才改成了上面代码。没有报错。

模型训练
然后以很低的学习率进行训练


sgd = SGD(lr=1e-4,momentum=0.9)
vgg_model.compile(loss='binary_crossentropy',
                  optimizer=sgd,
                  metrics=['accuracy'])

# train vgg_model
# image preprocessing
train_datagen = ImageDataGenerator(rescale=1./255,
                                   shear_range=0.2,
                                   zoom_range=0.2,
                                   horizontal_flip=True)
val_datagen = ImageDataGenerator(rescale=1./255)

# image generator.要设置class_mode=‘binary’,否则训练时候报错。
train_generator = train_datagen.flow_from_directory(train_data_dir,
                                                    target_size=(img_width,img_height),
                                                    batch_size=batch_size,
                                                    class_mode='binary')
validation_generator = val_datagen.flow_from_directory(validation_data_dir,
                                                       target_size=(img_width,img_height),
                                                       batch_size=batch_size,
                                                       class_mode='binary')
# train
history = vgg_model.fit_generator(train_generator,
                                  steps_per_epoch=nb_train_samples // batch_size,
                                  epochs=epochs,
                                  validation_data=validation_generator,
                                  validation_steps=nb_val_samples // batch_size,
                                  verbose=2)

之前的网络没有问题,这一部分也是正常的。

可视化部分

plot_model(model,to_file='model.png')#将网络结构模型以png格式保存

你可能感兴趣的:(TensorFlow)