Keras学习教程八

原文地址:https://nbviewer.jupyter.org/github/fchollet/deep-learning-with-python-notebooks/blob/master/5.3-using-a-pretrained-convnet.ipynb


Using a pre-trained convnet


    在小型图像数据集上进行深度学习的常用和高效方法是利用预先训练好的网络。预先训练的网络就是以前在大型数据集上训练的保存网络,通常是在大规模图像分类任务上。如果这个原始数据集足够大且足够一般,那么由预先训练的网络学习的空间要素分层结构可以有效地作为我们视觉世界的通用模型,因此它的特征可以证明对许多不同的计算机视觉问题有用,甚至尽管这些新问题可能涉及完全不同于原始任务的课程。例如,人们可以在ImageNet上培训一个网络(其中课程主要是动物和日常用品),然后将这个训练有素的网络重新用于识别图像中的家具项目。与许多较早的浅层学习方法相比,不同问题中学习特征的这种可移植性是深度学习的关键优势,并且它使深度学习对于小数据问题非常有效。

    在我们的例子中,我们将考虑在ImageNet数据集(140万标记图像和1000个不同类别)上训练的大型隐藏网络。 ImageNet包含许多动物类,包括不同种类的猫和狗,因此我们可以期望在我们的猫与狗分类问题上表现得非常好。
    我们将使用由Karen Simonyan和Andrew Zisserman在2014年开发的VGG16架构,这是一种简单且广泛使用的用于ImageNet的convnet架构。虽然它是一个比较旧的模型,与当前的艺术水平相差甚远,而且比其他许多最新的模型稍微重一些,但我们之所以选择它,是因为它的架构与您已熟悉的架构类似,并且易于理解,而无需引入任何新概念。这可能是你第一次遇到这些可爱的模型名称之一 - VGG,ResNet,Inception,Inception-ResNet,Xception ......你将会习惯它们,因为如果你继续深入学习计算机,它们会频繁出现视力。

    有两种方式可以利用预先训练好的网络:特征提取和微调。我们将涵盖他们两个。我们从特征提取开始。

特征提取

    特征提取包括使用先前网络学习的表示从新样本中提取有趣的特征。 然后通过一个新的分类器运行这些功能,这是从零开始训练的。

    正如我们先前所看到的,用于图像分类的小圆点包括两部分:它们从一系列池和卷积层开始,最后以密集连接的分类器结束。 第一部分被称为模型的“卷积基”。 对于小网络来说,“特征提取”将简单地包括采用先前训练的网络的卷积基础,通过它运行新的数据,并在输出之上训练一个新的分类器。

    为什么只重用卷积基?我们可以重复使用密集连接的分类器吗?一般来说,应该避免。原因很简单:由卷积基所学习的表示可能更通用,因此更具可重用性:一个convnet的特征映射是图片上一般概念的存在映射,无论计算机视觉如何,这很可能是有用的问题在眼前。另一方面,分类器学到的表征对于模型所训练的一组类来说必定是非常具体的 - 它们只包含整个图片中这个或那个类存在概率的信息。此外,在密集连接层中发现的表示不再包含有关对象位于输入图像中的位置的任何信息:这些层摆脱空间的概念,而对象位置仍由卷积特征映射描述。对于物体位置很重要的问题,密集连接的特征将基本无用。
    请注意,由特定卷积层提取的表示的一般性(以及因此可重用性)级别取决于模型中层的深度。在模型中较早出现的图层会提取局部高度通用的特征图(如视觉边缘,颜色和纹理),而较高层则会提取更多抽象概念(如“猫耳朵”或“狗眼”)。因此,如果您的新数据集与原始模型所训练的数据集有很大差异,则最好仅使用模型的前几层进行特征提取,而不是使用整个卷积基础。
    在我们的例子中,由于ImageNet类集确实包含多个dog和cat类,重用原始模型的密集连接层中包含的信息可能是有益的。但是,为了覆盖新问题的类集与原始模型的类集不重叠的更一般情况,我们将不选择。
    让我们通过使用在ImageNet上训练的VGG16网络的卷积基来实现这一目的,从我们的猫和狗图像中提取有趣的特征,然后在这些特征之上训练猫与狗的分类器。
    除此之外,VGG16型号还预装Keras。您可以从keras.applications模块中导入它。以下是可以作为keras.applications的一部分提供的图像分类模型列表(均在ImageNet数据集上预先训练好):
        Xception
        InceptionV3
        ResNet50
        VGG16
        VGG19
        MobileNet

    让我们来实例化VGG16模型:

from keras.applications import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))
    我们向构造函数传递了三个参数:
         权重,以指定从哪个权重检查点初始化模型
        include_top,它指的是在网络之上是否包含密集连接的分类器。 默认情况下,这个密集连接的分类器将对应于来自ImageNet的1000个类。 由于我们打算使用我们自己的密集连接分类器(只有两个类,猫和狗),因此我们不需要包含它。
         input_shape,我们将馈送给网络的图像张量的形状。 这个论点纯粹是可选的:如果我们不通过它,网络将能够处理任何大小的输入。

    以下是VGG16卷积基础架构的详细信息:它与您已熟悉的简单折点非常相似。

conv_base.summary()
    最终的特征图具有形状(4,4,512)。这是我们将粘贴密集连接分类器的特征。
    在这一点上,我们有两种方法可以进行:
    在我们的数据集上运行卷积基,将其输出记录到磁盘上的Numpy数组,然后将此数据用作独立密集连接分类器的输入,这与您在    本书第一章中看到的类似。这种解决方案运行起来非常快速且便宜,因为它只需要为每个输入图像运行一次卷积基础,而卷积基础是管线中最昂贵的部分。但是,出于完全相同的原因,该技术不允许我们充分利用数据增强。
    通过在顶部添加密集图层来扩展我们的模型(conv_base),并在输入数据上端到端地运行整个事物。这使我们可以使用数据增强,因为每次输入图像都会通过卷积基础,每当模型看到它时。但是,出于同样的原因,这种技术比第一种技术昂贵得多。
我们将涵盖这两种技术。让我们通过设置第一个代码所需的代码:在我们的数据上记录conv_base的输出,并将这些输出用作新模型的输入。

    我们首先简单地运行先前引入的ImageDataGenerator实例,将图像提取为Numpy数组以及它们的标签。我们将简单地通过调用conv_base模型的预测方法来从这些图像中提取特征。

import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator

base_dir = '/Users/fchollet/Downloads/cats_and_dogs_small'

train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20

def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 4, 4, 512))
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(
        directory,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='binary')
    i = 0
    for inputs_batch, labels_batch in generator:
        features_batch = conv_base.predict(inputs_batch)
        features[i * batch_size : (i + 1) * batch_size] = features_batch
        labels[i * batch_size : (i + 1) * batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count:
            # Note that since generators yield data indefinitely in a loop,
            # we must `break` after every image has been seen once.
            break
    return features, labels

train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.

    提取的特征目前是形状的(样本,4,4,512)。 我们将它们喂给一个密集连接的分类器,所以首先我们必须将它们压平(样本8192):

train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

    在这一点上,我们可以定义我们密集连接的分类器(注意使用正则化丢失),并在我们刚才记录的数据和标签上进行训练:

from keras import models
from keras import layers
from keras import optimizers

model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss='binary_crossentropy',
              metrics=['acc'])

history = model.fit(train_features, train_labels,
                    epochs=30,
                    batch_size=20,
                    validation_data=(validation_features, validation_labels))

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()
    我们的验证准确率达到了90%左右,远远好于我们在前一节中通过从头开始培训的小型模型所能达到的效果。然而,我们的情节也表明,我们几乎从一开始就过度适应 - 尽管使用率相当高的退出。这是因为此技术不利用数据增强,这对防止小图像数据集过度拟合至关重要。
    现在,让我们回顾一下我们提到的用于特征提取的第二种技术,该技术速度更慢,更昂贵,但这使我们可以在培训期间利用数据增强:扩展conv_base模型并在输入端端对端地运行它。请注意,这种技术实际上非常昂贵,以至于如果您有权访问GPU,则只能尝试它:它在CPU上绝对难以处理。如果你不能在GPU上运行你的代码,那么以前的技术就是要走的路。

    由于模型的行为与层相同,因此您可以将模型(如我们的conv_base)添加到Sequential模型,就像添加图层一样。所以你可以做到以下几点:

from keras import models
from keras import layers

model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

    这就是我们的模型现在的样子:

model.summary()
    如您所见,VGG16的卷积库有14,714,688个参数,非常大。 我们在顶部添加的分类器有200万个参数。
    在我们编译和训练我们的模型之前,一个非常重要的事情就是冻结卷积基础。 “冻结”一层或一组图层意味着防止他们的权重在训练期间得到更新。 如果我们不这样做,那么先前在卷积基础上学习的表示会在训练过程中被修改。 由于顶部的密集层是随机初始化的,所以非常大的权重更新将通过网络传播,有效地破坏以前学习的表示。

    在Keras中,通过将可训练属性设置为False来冻结网络:

print('This is the number of trainable weights '
      'before freezing the conv base:', len(model.trainable_weights))

    这是在冻结conv基础之前可训练的重量数量:30

    

conv_base.trainable = False


print('This is the number of trainable weights '
      'after freezing the conv base:', len(model.trainable_weights))
    通过这种设置,只有我们添加的两个密集层的权重才会被训练。 这是总共四个权重张量:每层两个(主权矩阵和偏向量)。    请注意,为了使这些更改生效,我们必须首先编译模型。 如果您在编译之后修改了重量训练能力,则应该重新编译模型,否则这些更改将被忽略。

    现在我们可以开始对我们的模型进行训练,使用我们前面例子中使用的相同的数据增强配置:

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(
      rescale=1./255,
      rotation_range=40,
      width_shift_range=0.2,
      height_shift_range=0.2,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')

# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50,
      verbose=2)

你可能感兴趣的:(keras)