天天看点

keras_卷积神经网络_猫狗分类案例(二)

keras_卷积神经网络_猫狗分类案例(二)

Python深度学习

参考:https://blog.csdn.net/xiewenrui1996/article/details/104032476/

**5.3 使用预训练的卷积神经网络

5.3.1 特征提取

代码清单 5-16 将 VGG16 卷积基实例化

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

    代码清单 5-17 使用预训练的卷积基提取特征

    代码清单 5-18 定义并训练密集连接分类器

    代码清单 5-19 绘制结果

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

    代码清单 5-20 在卷积基上添加一个密集连接分类器

    代码清单 5-21 利用冻结的卷积基端到端地训练模型

    5.3.2 微调模型**

微调网络的步骤如下。

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

(2) 冻结基网络。

(3) 训练所添加的部分。

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

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

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

后冻结其中的部分层。

代码清单 5-22 冻结直到某一层的所有层

代码清单 5-23 微调模型

代码清单 5-24 使曲线变得平滑

下面是你应该从以上两节的练习中学到的要点。

‰ 卷积神经网络是用于计算机视觉任务的最佳机器学习模型。即使在非常小的数据集上也

可以从头开始训练一个卷积神经网络,而且得到的结果还不错。

‰ 在小型数据集上的主要问题是过拟合。在处理图像数据时,数据增强是一种降低过拟合

的强大方法。

‰ 利用特征提取,可以很容易将现有的卷积神经网络复用于新的数据集。对于小型图像数

据集,这是一种很有价值的方法。

‰ 作为特征提取的补充,你还可以使用微调,将现有模型之前学到的一些数据表示应用于

新问题。这种方法可以进一步提高模型性能。

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

import keras
keras.__version__

# 将 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 是输入到网络中的图像张量的形状。这个参数完全是可选的,如果不传入这个参数,那么网络能够处理任意形状的输入。 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    


注意,这些生成器在循环中不断
# 生成数据,所以你必须在读取完
# 所有图像后终止循环
# 最后的特征图形状为 (4, 4, 512)。我们将在这个特征上添加一个密集连接分类器。
# 接下来,下一步有两种方法可供选择。
# • 在你的数据集上运行卷积基,将输出保存成硬盘中的 Numpy 数组,然后用这个数据作
# 为输入,输入到独立的密集连接分类器中(与本书第一部分介绍的分类器类似)。这种
# 方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是目
# 前流程中计算代价最高的。但出于同样的原因,这种方法不允许你使用数据增强。
# • 在顶部添加 Dense 层来扩展已有模型(即 conv_base),并在输入数据上端到端地运行
# 整个模型。这样你可以使用数据增强,因为每个输入图像进入模型时都会经过卷积基。
# 但出于同样的原因,这种方法的计算代价比第一种要高很多。5.3 使用预训练的卷积神经网络  119
# 1 2 3 4 5 6 7 8 9
# 这两种方法我们都会介绍。首先来看第一种方法的代码:保存你的数据在 conv_base 中的
# 输出,然后将这些输出作为输入用于新模型
​
# 1. 不使用数据增强的快速特征提取
# 首先,运行 ImageDataGenerator 实例,将图像及其标签提取为 Numpy 数组。我们需要
# 调用 conv_base 模型的 predict 方法来从这些图像中提取特征。
# 代码清单 5-17 使用预训练的卷积基提取特征
​
​
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.
The extracted features are currently of shape (samples, 4, 4, 512). We will feed them to a densely-connected classifier, so first we must flatten them to (samples, 8192):

现在你可以定义你的密集连接分类器(注意要使用 dropout 正则化),并在刚刚保存的数据
# 和标签上训练这个分类器
# 目前,提取的特征形状为 (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 正则化),并在刚刚保存的数据
# 和标签上训练这个分类器
At this point, we can define our densely-connected classifier (note the use of dropout for regularization), and train it on the data and labels that we just recorded:

训练速度非常快,因为你只需处理两个 Dense 层。即使在 CPU 上运行,每轮的时间也不
# 到一秒钟。
# 我们来看一下训练期间的损失曲线和精度曲线
# 定义并训练密集连接分类器
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))
​
# 训练速度非常快,因为你只需处理两个 Dense 层。即使在 CPU 上运行,每轮的时间也不
# 到一秒钟。
# 我们来看一下训练期间的损失曲线和精度曲线
Train on 2000 samples, validate on 1000 samples
Epoch 1/30
2000/2000 [==============================] - 1s - loss: 0.6253 - acc: 0.6455 - val_loss: 0.4526 - val_acc: 0.8300
Epoch 2/30
2000/2000 [==============================] - 0s - loss: 0.4490 - acc: 0.7965 - val_loss: 0.3784 - val_acc: 0.8450
Epoch 3/30
2000/2000 [==============================] - 0s - loss: 0.3670 - acc: 0.8490 - val_loss: 0.3327 - val_acc: 0.8660
Epoch 4/30
2000/2000 [==============================] - 0s - loss: 0.3176 - acc: 0.8705 - val_loss: 0.3115 - val_acc: 0.88


# 代码清单 5-19 绘制结果
​
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()


# 2. 使用数据增强的特征提取
# 下面我们来看一下特征提取的第二种方法,它的速度更慢,计算代价更高,但在训练期间
# 可以使用数据增强。这种方法就是:扩展 conv_base 模型,然后在输入数据上端到端地运行模型。
# 注意 本方法计算代价很高,只在有 GPU 的情况下才能尝试运行。它在 CPU 上是绝对难以运
# 行的。如果你无法在 GPU 上运行代码,那么就采用第一种方法
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'))
This is what our model looks like now:

model.summary()
_________________________________________________________________
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
_________________________________________________________________
As you can see, the convolutional base of VGG16 has 14,714,688 parameters, which is very large. The classifier we are adding on top has 2 million parameters.

Before we compile and train our model, a very important thing to do is to freeze the convolutional base. "Freezing" a layer or set of layers means preventing their weights from getting updated during training. If we don't do this, then the representations that were previously learned by the convolutional base would get modified during training. Since the Dense layers on top are randomly initialized, very large weight updates would be propagated through the network, effectively destroying the representations previously learned.

In Keras, freezing a network is done by setting its trainable attribute to False:

如你所见, VGG16 的卷积基有 14 714 688 个参数,非常多。在其上添加的分类器有 200 万
# 个参数。
# 在编译和训练模型之前,一定要“冻结”卷积基。 冻结(freeze)一个或多个层是指在训练
# 过程中保持其权重不变。如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。
# 因为其上添加的 Dense 层是随机初始化的,所以非常大的权重更新将会在网络中传播,对之前
# 学到的表示造成很大破坏。
# 在 Keras 中,冻结网络的方法是将其 trainable 属性设为 False
# 如你所见, VGG16 的卷积基有 14 714 688 个参数,非常多。在其上添加的分类器有 200 万
# 个参数。
# 在编译和训练模型之前,一定要“冻结”卷积基。 冻结(freeze)一个或多个层是指在训练
# 过程中保持其权重不变。如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。
# 因为其上添加的 Dense 层是随机初始化的,所以非常大的权重更新将会在网络中传播,对之前
# 学到的表示造成很大破坏。
# 在 Keras 中,冻结网络的方法是将其 trainable 属性设为 False
print('This is the number of trainable weights '
      'before freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights before freezing the conv base: 30
conv_base.trainable = False
print('This is the number of trainable weights '
      'after freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights after freezing the conv base: 4
With this setup, only the weights from the two Dense layers that we added will be trained. That's a total of four weight tensors: two per layer (the main weight matrix and the bias vector). Note that in order for these changes to take effect, we must first compile the model. If you ever modify weight trainability after compilation, you should then re-compile the model, or these changes would be ignored.

Now we can start training our model, with the same data augmentation configuration that we used in our previous example:

# 如此设置之后,只有添加的两个 Dense 层的权重才会被训练。总共有 4 个权重张量,每层
# 2 个(主权重矩阵和偏置向量)。注意,为了让这些修改生效,你必须先编译模型。如果在编译
# 之后修改了权重的 trainable 属性,那么应该重新编译模型,否则这些修改将被忽略。5.3 使用预训练的卷积神经网络  123
# 1 2 3 4 5 6 7 8 9
# 现在你可以开始训练模型了,使用和前一个例子相同的数据增强设置。
# 代码清单 5-21 利用冻结的卷积基端到端地训练模型
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
#     因为使用了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)
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/30
74s - loss: 0.4465 - acc: 0.7810 - val_loss: 0.2056 - val_acc: 0.9120
Epoch 2/30
72s - loss: 0.2738 - acc: 0.8905 - val_loss: 0.1239 - val_acc: 0.9550
Epoch 3/30
72s - loss: 0.2088 - acc: 0.9145 - val_loss: 0.1194 - val_acc: 0.9560



我们来再次绘制结果(见图 5-17 和图 5-18)。如你所见,验证精度约为 96%。这比从头开
# 始训练的小型卷积神经网络要好得多。
# 我们来再次绘制结果(见图 5-17 和图 5-18)。如你所见,验证精度约为 96%。这比从头开
# 始训练的小型卷积神经网络要好得多。
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()

As a reminder, this is what our convolutional base looks like:

# 前面说过,冻结 VGG16 的卷积基是为了能够在上面训练一个随机初始化的分类器。同理,
# 只有上面的分类器已经训练好了,才能微调卷积基的顶部几层。如果分类器没有训练好,那么126  第 5 章 深度学习用于计算机视觉
# 训练期间通过网络传播的误差信号会特别大,微调的几层之前学到的表示都会被破坏。因此,
# 微调网络的步骤如下。
# (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    
_________________________________________________________________


# 我们将微调最后三个卷积层,也就是说,直到 block4_pool 的所有层都应该被冻结,而
# block5_conv1、 block5_conv2 和 block5_conv3 三层应该是可训练的。
# 为什么不微调更多层?为什么不微调整个卷积基?你当然可以这么做,但需要考虑以下几点。
# • 卷积基中更靠底部的层编码的是更加通用的可复用特征,而更靠顶部的层编码的是更专
# 业化的特征。微调这些更专业化的特征更加有用,因为它们需要在你的新问题上改变用
# 途。微调更靠底部的层,得到的回报会更少。
# • 训练的参数越多,过拟合的风险越大。卷积基有 1500 万个参数,所以在你的小型数据
# 集上训练这么多参数是有风险的。
# 因此,在这种情况下,一个好策略是仅微调卷积基最后的两三层。我们从上一个例子结束
# 的地方开始,继续实现此方法。
# 代码清单 5-22 冻结直到某一层的所有层
​
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
Now we can start fine-tuning our network. We will do this with the RMSprop optimizer, using a very low learning rate. The reason for using a low learning rate is that we want to limit the magnitude of the modifications we make to the representations of the 3 layers that we are fine-tuning. Updates that are too large may harm these representations.

Now let's proceed with fine-tuning:

现在你可以开始微调网络。我们将使用学习率非常小的 RMSProp 优化器来实现。之所以让
# 学习率很小,是因为对于微调的三层表示,我们希望其变化范围不要太大。太大的权重更新可
# 能会破坏这些表示。
# 代码清单 5-23 微调模型
# 现在你可以开始微调网络。我们将使用学习率非常小的 RMSProp 优化器来实现。之所以让
# 学习率很小,是因为对于微调的三层表示,我们希望其变化范围不要太大。太大的权重更新可
# 能会破坏这些表示。
# 代码清单 5-23 微调模型
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)
Epoch 1/100
100/100 [==============================] - 32s - loss: 0.0215 - acc: 0.9935 - val_loss: 0.0980 - val_acc: 0.9720
Epoch 2/100
100/100 [==============================] - 32s - loss: 0.0131 - acc: 0.9960 - val_loss: 0.1247 - val_acc: 0.9700
Epoch 3/100
100/100 [==============================] - 32s - loss: 0.0140 - acc: 0.9940 - val_loss: 0.1044 - val_acc: 0.9790


model.save('cats_and_dogs_small_4.h5')
Let's plot our results using the same plotting code as before:

# 和前面一样的绘图代码来绘制结果
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()


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


我们得到了 97% 的测试精度。在关于这个数据集的原始 Kaggle 竞赛中,这个结果是最佳
# 结果之一。但利用现代深度学习技术,你只用一小部分训练数据(约 10%)就得到了这个结果。
# 训练 20 000 个样本与训练 2000 个样本是有很大差别的!
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)
​
# 验证精度曲线变得更清楚。可以看到,精度值提高了 1%,从约 96% 提高到 97% 以上。
# 注意,从损失曲线上看不出与之前相比有任何真正的提高(实际上还在变差)。你可能感到
# 奇怪,如果损失没有降低,那么精度怎么能保持稳定或提高呢?答案很简单:图中展示的是逐
# 点(pointwise)损失值的平均值,但影响精度的是损失值的分布,而不是平均值,因为精度是
# 模型预测的类别概率的二进制阈值。即使从平均损失中无法看出,但模型也仍然可能在改进。
# 现在,你可以在测试数据上最终评估这个模型。
​
# 我们得到了 97% 的测试精度。在关于这个数据集的原始 Kaggle 竞赛中,这个结果是最佳
# 结果之一。但利用现代深度学习技术,你只用一小部分训练数据(约 10%)就得到了这个结果。
# 训练 20 000 个样本与训练 2000 个样本是有很大差别的!
Found 1000 images belonging to 2 classes.
test acc: 0.967999992371
Here we get a test accuracy of 97%. In the original Kaggle competition around this dataset, this would have been one of the top results. However, using modern deep learning techniques, we managed to reach this result using only a very small fraction of the training data available (about 10%). There is a huge difference between being able to train on 20,000 samples compared to 2,000 samples!

           

继续阅读