Deep learning 五

5.数据增强

        过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型。如果拥有无限的数据,那么模型能够观察到数据分布的所有内容,这样就永远不会过拟合。数据增强是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加augment)样本。其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察到数据的更多内容,从而具有更好的泛化能力。

"""利用 ImageDataGenerator 来设置数据增强"""

datagen = ImageDataGenerator(
      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')

这里只选择了几个参数(想了解更多参数,请查阅 Kera 文)。我们来快速介绍一下这些参数的含义。
rotation_range是角度值(在0~180范围内)表示图像随机转的角度范围。

width_shift 和height shift 是图像在水平或垂直方向上平移的范围(相对于总宽度或总高度的比例)。
shear_range是随机错切变换的角度
zoomrange是图像随机缩放的范围。
horizontal_flip 是随机将一半图像水平翻转。如果没有水平不对称的假设(比如真实世界的图像 ),这种做法是有意义的。
fill_mode是用于填充新创建像素的方法这些新像素可能来自于旋转或宽度/高度平移

看一下增强后的图像

# This is module with image preprocessing utilities
"""图像预处理工具的模块"""
from keras.preprocessing import image

fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]

# We pick one image to "augment"
"""选择一张图像进行增强"""
img_path = fnames[3]

# Read the image and resize it
"""读取图像并调整大小"""
img = image.load_img(img_path, target_size=(150, 150))

# Convert it to a Numpy array with shape (150, 150, 3)
"""将其转换为形状(150, 150, 3)的Numpy数组"""
x = image.img_to_array(img)

# Reshape it to (1, 150, 150, 3)
"""将其形状转化为(1, 150, 150, 3)"""
x = x.reshape((1,) + x.shape)

# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
"""生成随机变换后的图像批量。循环是无限的,因此你需要在某个时刻终止循环"""
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break

plt.show()

 数据增强后的图像

        如果你使用这种数据增强来训练一个新网络,那么网络将不会两次看到同样的输入。但网络看到的输入仍然是高度相关的,因为这些输人都来自于少量的原始图像。你无法生成新信息而只能混合现有信息。因此,这种方法可能不足以完全消除过拟合。为了进一步降低过拟合你还需要向模型中添加一个 Dropout 层,添加到密集连接分类器之前。

"""定义一个包含 dropout 的新卷积神经网络"""

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

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


"""利用数据增强生成器训练卷积神经网络"""
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,)

# 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
        """将所有图像大小调整为150*150"""
        target_size=(150, 150),
        batch_size=32,
        # Since we use binary_crossentropy loss, we need binary labels
        """因为使用了binary_crossentropy损失,所以需要用二进制标签"""
        class_mode='binary')

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

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


"""保存模型"""
model.save('cats_and_dogs_small_2.h5')

我们再次绘制结果(见图5-12 和图 5-13)。使用了数据增强和 dropout 之后,模型不再过拟合训练曲线紧紧跟随着验证曲线。现在的精度为 82%,比未正则化的模型提高了 15%(相对比例) 

 

                                                采取数据增强后的训练精度和验证精度 

                                                数据增强后的训练损失和验证损失

通过进一步使用正则化方法以及调节网络参数(比如每个卷积层的过滤器个数或网络中的层数),你可以得到更高的精度,可以达到86%或87%。但只靠从头开始训练自己的卷积神经网络再想提高精度就十分困难,因为可用的数据太少。想要在这个问题上进一步提高精度,下一步需要使用预训练的模型

三、使用预训练的卷积神经网络

       想要将深度学习应用于小型图像数据集,一种常用且非常高效的方法是使用预训练网络。预训练网络(pretrained network)是一个保存好的网络,之前已在大型数据集(通常是大规模图像分类任务)上训练好。如果这个原始数据集足够大且足够通用,那么预训练网络学到的特征的空间层次结构可以有效地作为视觉世界的通用模型,因此这些特征可用于各种不同的计算机视觉问题,即使这些新问题涉及的类别和原始任务完全不同。举个例子,你在ImageNet 上训练了一个网络(其类别主要是动物和日常用品 ),然后将这个训练好的网络应用于某个不相干的任务,比如在图像中识别家具。这种学到的特征在不同问题之间的可移植性,是深度学习与许多
早期浅层学习方法相比的重要优势,它使得深度学习对小数据问题非常有效。

        本例中,假设有一个在ImageNet 数据集(140万张标记图像,1000个不同的类别)上训练好的大型卷积神经网络。ImageNet 中包含许多动物类别,其中包括不同种类的猫和狗,因此可以认为它在猫狗分类问题上也能有良好的表现。
        我们将使用VGG16架构,它由Karen Simonyan和Andrew Zisserman 在2014年开发。对于ImageNet,它是一种简单而又广泛使用的卷积神经网络架构。虽然VGG16是一个比较旧的模型,性能远比不了当前最先进的模型,而且还比许多新模型更为复杂,但我之所以选择它,是因为它的架构与你已经熟悉的架构很相似,因此无须引人新概念就可以很好地理解。这可能是你第一次遇到这种奇怪的模型名称-VGG、ResNet、Inception、Inception-ResNet、Xception等你会习惯这些名称的,因为如果你一直用深度学习做计算机视觉的话,它们会频繁出现。

        使用预训练网络有两种方法:特征提取(feature extraction)和微调模型(fine-tuning)。

1.特征提取

        特征提取是使用之前网络学到的表示来从新样本中提取出有趣的特征。然后将这些特征输人一个新的分类器,从头开始训练。
        如前所述,用于图像分类的卷积神经网络包含两部分:首先是一系列池化层和卷积层,最后是一个密集连接分类器。第一部分叫作模型的卷积基(convolutional base)。对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在上面运行新数据,然后在输出上面训练一个新的分类器(见图 5-14)。

为什么仅重复使用卷积基?我们能否也重复使用密集连接分类器?一般来说,应该避免这么做。原因在于卷积基学到的表示可能更加通用,因此更适合重复使用。卷积神经网络的特征图表示通用概念在图像中是否存在,无论面对什么样的计算机视觉问题,这种特征图都可能很有用。但是,分类器学到的表示必然是针对于模型训练的类别,其中仅包含某个类别出现在整张图像中的概率信息。此外,密集连接层的表示不再包含物体在输入图像中的位置信息。密集连接层舍弃了空间的概念,而物体位置信息仍然由卷积特征图所描述。如果物体位置对于问题很重要,那么密集连接层的特征在很大程度上是无用的。
注意,某个卷积层提取的表示的通用性(以及可复用性)取决于该层在模型中的深度。模型中更靠近底部的层提取的是局部的、高度通用的特征图(比如视觉边缘、颜色和纹理),而更靠近顶部的层提取的是更加抽象的概念(比如“猫耳朵”或“狗眼睛”)。因此,如果你的新数据集与原始模型训练的数据集有很大差异,那么最好只使用模型的前几层来做特征提取,而不是使用整个卷积基。
本例中,由于ImageNet 的类别中包含多种狗和猫的类别,所以重复使用原始模型密集连接层中所包含的信息可能很有用。但我们选择不这么做,以便涵盖新问题的类别与原始模型的类别不一致的更一般情况。我们来实践一下,使用在ImageNet 上训练的VGG16 网络的卷积基从猫狗图像中提取有趣的特征,然后在这些特征上训练一个猫狗分类器。
VGG16等模型内置于 Keras 中。你可以从 keras.applications 模块中导人。下面是keras.applications 中的一部分图像分类模型(都是在ImageNet数据集上预训练得到的)
Xception

Inception V3

ResNet50

VGG16
VGG19
MobileNet

"""将VGG16卷积基实例化"""
from keras.applications import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))

 这里向构造函数中传人了三个参数。
weights 指定模型初始化的权重检查点。

include_top 指定模型最后是否包含密集连接分类器。默认情况下,这个密集连接分类器对应于ImageNet 的1000个类别。因为我们打算使用自己的密集连接分类器(只有两个类别:cat 和 dog),所以不需要包含它。

input_shape 是输入到网络中的图像张量的形状。这个参数完全是可选的,如果不传0入这个参数,那么网络能够处理任意形状的输入。

VGG16卷积基的详细架构如下所示。它和已经熟悉的简单卷积神经网络很相似。

"""VGG16卷积基的详细架构"""

>>> conv_base.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 150, 150, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________

最后的特征图形状为(4,4,512)。我们将在这个特征上添加一个密集连接分类器。
接下来,下一步有两种方法可供选择。
口在你的数据集上运行卷积基,将输出保存成硬盘中的Numpy 数组,然后用这个数据作为输入,输人到独立的密集连接分类器中(与本书第一部分介绍的分类器类似)。这种方法速度快,计算代价低,因为对于每个输人图像只需运行一次卷积基,而卷积基是目前流程中计算代价最高的。但出于同样的原因,这种方法不允许你使用数据增强。

口在顶部添加 Dense层来扩展已有模型(即conv_base),并在输人数据上端到端地运行整个模型。这样你可以使用数据增强,因为每个输入图像进入模型时都会经过卷积基但出于同样的原因,这种方法的计算代价比第一种要高很多。

(1).不使用数据增强的快速特征提取


首先,运行ImageDataGenerator 实例,将图像及其标签提取为Numpy 数组。我们需要调用conv_base模型的 predict 方法来从这些图像中提取特征。

"""使用预训练的卷积基提取特征"""

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)


"""目前,提取的特征形状为(samples,4,4,512)。我们要将其输入到密集连接分类器中所以首先必须将其形状展平为(samples,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))


"""和标签上训练这个分类器
现在你可以定义你的密集连接分类器(注意要使用 dropout 正则化),并在刚刚保存的数据"""

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%,比上一节从头开始训练的小型模型效果要好得多。但从图中也可以看出,虽然 dropout 比率相当大,但模型几乎从一开始就过拟合。这是因为本方法没有使用数据增强,而数据增强对防止小型图像数据集的过拟合非常重要。

(2).使用数据增强的特征提取


下面我们来看一下特征提取的第二种方法,它的速度更慢,计算代价更高,但在训练期间可以使用数据增强。这种方法就是:扩展 conv_base 模型,然后在输人数据上端到端地运行模型

注意 本方法计算代价很高,只在有 GPU的情况下才能尝试运行。它在 CPU上是绝对难以运行的。如果你无法在 GPU 上运行代码,那么就采用第一种方法。

模型的行为和层类似,所以你可以向 Sequential模型中添加一个模型(比如conv_base),
就像添加一个层一样。

"""在卷积基上添加一个密集连接分类器"""

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'))

"""模型架构如下"""
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Model)                (None, 4, 4, 512)         14714688  
_________________________________________________________________
flatten_1 (Flatten)          (None, 8192)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 256)               2097408   
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 257       
=================================================================
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0
_________________________________________________________________

在编译和训练模型之前,一定要“冻结”卷积基。冻结( freeze)个或多个层是指在训练过程中保持其权重不变。如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改因为其上添加的 Dense层是随机初始化的,所以非常大的权重更新将会在网络中传播,对之前学到的表示造成很大破坏。
在Keras 中,冻结网络的方法是将其trainable 属性设为False。

如此设置之后,只有添加的两个 Dense 层的权重才会被训练。总共有 4 个权重张量,每层2个(主权重矩阵和偏置向量 )。注意,为了让这些修改生效,你必须先编译模型。如果在编译之后修改了权重的 trainable 属性,那么应该重新编译模型,否则这些修改将被忽略

"""利用冻结的卷积基端到端地训练模型"""

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
        """将所有图像大小调整为150*150"""
        target_size=(150, 150),
        batch_size=20,
        # Since we use binary_crossentropy loss, we need binary labels
        """因为使用了binary_crossentropy损失,所以需要用二进制标签"""
        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)

 

                                        带数据增强的特征提取的训练精度和验证精度 

                                        带数据增强的特征提取的训练损失和验证损失 

2.微调模型

另一种广泛使用的模型复用方法是模型微调(fie-tuning),与特征提取互为补充。对于用于特征提取的冻结的模型基,微调是指将其顶部的几层“解冻”,并将这解冻的几层和新增加的部分(本例中是全连接分器)联合训练(见图 -19)。之所以叫作微调,是因为它只是略微调整了所复用模型中更加抽象的表示,以便让这些表示与手头的问题更加相关。

前面说过,冻结 VGG16 的卷积基是为了能够在上面训练一个随机初始化的分类器。同理,只有上面的分类器已经训练好了,才能微调卷积基的顶部几层。如果分类器没有训练好,那么训练期间通过网络传播的误差信号会特别大,微调的几层之前学到的表示都会被破坏。因此,微调网络的步骤如下。


(1)在已经训练好的基网络(base network)上添加自定义网络


(2)冻结基网络。


(3)练所添加的部分。


(4)解冻基网络的一些层。


(5)联合训练解冻的这些层和添加的部分。


你在做特征提取时已经完成了前三个步骤。我们继续进行第四步:先解冻 conv_base,然后冻结其中的部分层。

"""卷积基的架构如下"""
>>> conv_base.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 150, 150, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 0
Non-trainable params: 14,714,688
_________________________________________________________________

 我们将微调最后三个卷积层,也就是说,直到 block4_poo1 的所有层都应该被冻结,而block5_conv1、block5_conv2 和block5_conv3 三层应该是可训练的。为什么不微调更多层?为什么不微调整个卷积基?你当然可以这么做,但需要考虑以下几点

口 卷积基中更靠底部的层编码的是更加通用的可复用特征,而更靠顶部的层编码的是更专业化的特征。微调这些更专业化的特征更加有用,因为它们需要在你的新问题上改变用途。微调更靠底部的层,得到的回报会更少。

口 训练的参数越多,过拟合的风险越大。卷积基有 1500 万个参数,所以在你的小型数据
集上训练这么多参数是有风险的。因此,在这种情况下,一个好策略是仅微调卷积基最后的两三层。我们从上一个例子结束的地方开始,继续实现此方法。

"""冻结直到某一层的所有层"""

conv_base.trainable = True

set_trainable = False
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False


"""现在你可以开始微调网络。我们将使用学习率非常小的 RMSProp 优化器来实现。之所以让学习率很小,是因为对于微调的三层表示,我们希望其变化范围不要太大。太大的权重更新可能会破坏这些表示。"""

"""微调模型"""

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

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

                                                        微调模型的训练精度和验证精度

                                                        微调模型的训练损失和验证损失

 这些曲线看起来包含噪声。为了让图像更具可读性,你可以将每个损失和精度都替换为指数移动平均值,从而让曲线变得平滑。下面用一个简单的实用函数来实现(见图 5-22和图 5-23)。

"""使曲线变得平滑"""

def smooth_curve(points, factor=0.8):
  smoothed_points = []
  for point in points:
    if smoothed_points:
      previous = smoothed_points[-1]
      smoothed_points.append(previous * factor + point * (1 - factor))
    else:
      smoothed_points.append(point)
  return smoothed_points

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

plt.figure()

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

plt.show()

                                        微调模型的训练精度和验证精度的平滑后曲线 

                                        微调模型的训练损失和验证损失的平滑后曲线 

验证精度曲线变得更清楚。可以看到,精度值提高了 1%,从约 96% 提高到97%以上。注意,从损失曲线上看不出与之前相比有任何真正的提高(实际上还在变差)。如果损失没有降低,那么精度怎么能保持稳定或提高呢?答案很简单,图中展示的是逐点(pointwise) 损失值的平均值,但影响精度的是损失值的分布,而不是平均值,因为精度是模型预测的类别概率的二进制闽值。即使从平均损失中无法看出,但模型也仍然可能在改进。现在,可以在测试数据上最终评估这个模型。 

test_generator = test_datagen.flow_from_directory(
        test_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)


Found 1000 images belonging to 2 classes.
test acc: 0.967999992371

 我们得到了 97%的测试精度。在关于这个数据集的原始 Kaggle 竞赛中,这个结果是最佳结果之一。但利用现代深度学习技术,你只用一小部分训练数据(约 10%)就得到了这个结果训练20000个样本与训练 2000个样本是有很大差别的!

小结

        卷积神经网络是用于计算机视觉任务的最佳机器学习模型。即使在非常小的数据集上也可以从头开始训练一个卷积神经网络,而且得到的结果还不错
        在小型数据集上的主要问题是过拟合。在处理图像数据时,数据增强是一种降低过拟合0的强大方法。
        利用特征提取,可以很容易将现有的券积神经网络复用于新的数据集。对于小型图像数据集,这是一种很有价值的方法。
        作为特征提取的补充,你还可以使用微调,将现有模型之前学到的一些数据表示应用于新问题。这种方法可以进一步提高模型性能。

现在你已经拥有一套可靠的工具来处理图像分类问题,特别是对于小型数据集

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值