深度学习 Keras 1.5 使用预先训练的卷积网络(convnets)模型

在小图像数据集上进行深度学习的一种常见和高效的方法是利用预先训练好的网络模型。预先训练好的网络模型只是一个先前在大型数据集上训练过后保存下来的网络模型,通常是进行了大规模的图像分类任务。

如果这个原始数据集足够大且足够普遍,那么由训练有素的网络学习到的空间特征层次可以有效的作为我们的通用模型,因此,其特征可以证明对许多不同的机器视觉问题是有用的,甚至尽管这些新问题可能涉及与原始任务完全不同的类型。

例如,可以在 ImageNet 上训练一个网络(其中图像类别主要是动物和日常用品),然后重新使用这个训练有素的网络模型来识别图像中的家具。与许多较旧的浅层学习方法相比,能把不同任务的学习特征相互移植性是深度学习的一个主要优点,它使深度学习对于小数据图像数据问题非常有效。

在我们的范例中,我们将使用在 ImageNet 数据集(140万个标记的图像和1000个不同的类别)上已经与训练好的卷积网络(convnets)模型。

我们将使用由克伦·西蒙尼(Karen Simonyan)和安德鲁·齐斯曼(Andrew Zisserman)于2014年开发的VGG16框架,这时一种简单而广泛使用的 ImageNet 框架。虽然它是一个比较旧的模型(现在又许多更先进的卷积网络模型),同时 VGG16 也可能比肥重,但是我们选择它是因为他的架构与你已经熟悉的类型,并且任意理解而且不需要引入任何新概念。

这可能是你第一次看到以下名词 VGGResNetInceptionInception-ResNetXception

但你会习惯它们,因为如果你继续深入学习机器视觉的领域,它们会较长出现在你面前。

有两种方法可以用来利用预先训练的网络模型:特征提取 (feature extraction)微调(fine-tuning)

因此,本文我们将从两个层面讲解。

我们从 特征提取 (feature extraction) 开始吧!

特征提取(feature extraction)

首先,我们来实例化一个 VGG16 模型:

from keras.applications import VGG16

conv_base = VGG16(weights='imagenet',
                  # 在这里告诉 keras我们只需要卷积基底的权重模型资讯
                  include_top=False, 
                  # 宣告我们要处理的图像大小与颜色通道数
                  input_shape=(150, 150, 3)) 

特征提取有两种方式实现:

1、在我们的数据集上进行“卷积基底”,将其输出以 Numpy 阵列的形式记录到磁盘上,然后将此数据作为独立密集分类器(densely-connected classifier)的输入。这种解决方案非常快速且简单,因为它只需要为每个输入图像进行一次“卷积基底”

2、通过在已经训练好的模型顶端继续添加 Dense 层,扩展我们拥有的模型(conv_base),并且在输入数据上端对端的运行整个数据的输入。这样的做法允许我们使用数据扩充(data augmentation),因为每次输入图像每次被模型看到时都会经过“卷积基底”。然而,这种技术比第一种技术要花更多训练时间。

下面我们逐一实现这两种方法 ~

方法1:卷积基底(提取特征) + 串接新的密集分类层(重新训练)

我们来看看设置第一种方法所需的程序:在我们的数据上基于“conv_base”的输出,并使用这些输出作为新模型的输入。

我们将简单的运行 ImageDataGenerator 的实例,已将图像提取为 Numpy 数组及其标记。我们将通过调用 conv_base 模型的 predict 方法从这些图像中提取特征。

移花(特征提取)

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

base_dir = os.path.join(os.getcwd(), "data")
base_dir = os.path.join(base_dir, "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))  # 根据VGG16(卷积基底)的最后一层的轮出张量规格
    labels = np.zeros(shape=(sample_count))  # 要处理的图像数

    # 产生一个"图像资料产生器"实例(资料是在档案目录中), 每呼叫它一次, 它会吐出特定批次数的图像资料
    generator = datagen.flow_from_directory(
        directory,
        target_size=(150, 150),  # 设定图像的高(height)与宽(width)
        batch_size=batch_size,  # 设定每次产生的图像的数据批量
        class_mode='binary')  # 因为我们的目标资料集只有两类(cat & dog)

    # 让我们把训练资料集所有的图像都跑过一次
    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

    print('extract_features complete!')
    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)  # 测试资料的图像特征淬取

提取的特征当前是(样本数,4,4,512)的形状。
我们将它们喂给一个密集连接(densely-connected)的分类器,
所以首先我们必须把它们压扁(flatten)成(样本数, 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))

接木(重新训练)

接下来,我们可以定义一个密集连接(densely-connected)的分类器(注意使用 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')) # 因为我的资料集只有两类(cat & dog)

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

# 把透过预处理的卷积基底所提取的特征做为input来进行训练
history = model.fit(train_features, train_labels,
                    epochs=30,
                    batch_size=20,
                    validation_data=(validation_features, validation_labels))

训练非常快,因为我们只需要处理两个 Dense 层。

即使在 CPU 上,每个训练循环(epoch)也不到一秒的时间。

让我们来看看训练过程中的损失(loss)和精准度(accuracy):

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, label='Training acc')
plt.plot(epochs, val_acc, label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

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

plt.show()

我们的验证准确率达到了90%左右,远远优于之前从头开始训练起的模型。

不过,我们的图表也表明:我们几乎从一开始就过度拟合(overfitting)了,尽管我们使用了相当大的 Dropout。

附完整代码

from keras.applications import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False, # 在这里告诉 keras我们只需要卷积基底的权重模型资讯
                  input_shape=(150, 150, 3)) # 宣告我们要处理的图像大小与颜色通道数

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

base_dir = os.path.join(os.getcwd(), "data")
base_dir = os.path.join(base_dir, "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))  # 根据VGG16(卷积基底)的最后一层的轮出张量规格
    labels = np.zeros(shape=(sample_count))  # 要处理的图像数

    # 产生一个"图像资料产生器"实例(资料是在档案目录中), 每呼叫它一次, 它会吐出特定批次数的图像资料
    generator = datagen.flow_from_directory(
        directory,
        target_size=(150, 150),  # 设定图像的高(height)与宽(width)
        batch_size=batch_size,  # 设定每次产生的图像的数据批量
        class_mode='binary')  # 因为我们的目标资料集只有两类(cat & dog)

    # 让我们把训练资料集所有的图像都跑过一次
    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

    print('extract_features complete!')
    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)  # 测试资料的图像特征淬取

# 提取的特征当前是(样本数,4,4,512)的形状。
# 我们将它们喂给一个密集连接(densely-connected)的分类器,
# 所以首先我们必须把它们压扁(flatten)成(样本数, 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')) # 因为我的资料集只有两类(cat & dog)

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

# 把透过预处理的卷积基底所提取的特征做为input来进行训练
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, label='Training acc')
plt.plot(epochs, val_acc, label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

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

plt.show()

方法2: 卷积基底(冻结 + 串接新的密集分类层) >> 重新训练

这种方法要慢得多,而且会花更多的时间与计算资源,但是我们可以在训练过程中利用数据扩充(data augmentation):扩展 conv_base 模型,并进行端(end)对端(end)训练。

请注意,这种方法真的是非常昂贵的,只有在你有 GPU 时才应该尝试它,在 CPU 上是非常棘手的。如果没有 GPU,那么方法1 就是你要选的方法

因为模型的行为就像堆积木,所以你可以添加一个模型(像我们的 conv_base)到 Sequential 模型,就像添加一个图层一样。所以你可以执行以下操作:

把预训练的卷积基底叠上去

model.add(conv_base) # 把预训练的卷积基底叠上去

“冻结”卷积基底

conv_base.trainable = False # “冻结”卷积基底

附完整代码

from keras.applications import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False, # 在这里告诉 keras我们只需要卷积基底的权重模型资讯
                  input_shape=(150, 150, 3)) # 宣告我们要处理的图像大小与颜色通道数

##特征提取

import os
base_dir = os.path.join(os.getcwd(), "data")
base_dir = os.path.join(base_dir, "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')

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')) # 因为我的资料集只有两类(cat & dog)

# “冻结”卷积基底
conv_base.trainable = False

from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers

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

# 请注意: 验证用的资料不要进行资料的增强
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # 图像资料的目录
        train_dir,
        # 设定图像的高(height)与宽(width)
        target_size=(150, 150),
        batch_size=20,
        # 因为我们的目标资料集只有两类(cat & dog)
        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)

model.save('cats_and_dogs_small_3.h5') # 把模型储存到档案

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

epochs = range(len(acc))

import matplotlib.pyplot as plt

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

plt.figure()

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

plt.show()

微调(fine-tuning)

这是另一种被广泛使用的模型复用技术,与特征提取相辅相成。

我将另起一片文章 深度学习 什么是微调(Fine Tune)?

总结

  • 卷积网络(convnets)是目前机器视觉算法中最好的模型。及时在非常小的数据集上,也可以从头开始训练一个效果不错的模型。
  • 在一个小数据集上,过度拟合(overfitting)将成为主要问题。数据扩充(data augmentation)是处理图像数据时抵消过度拟合的有效方法。
  • 通过特征提取(feature extraction),可以轻松的在新数据集上重新复用现有卷积网络模型。这时解决小数据集非常有价值的技术
  • 作为特征提取(feature extraction)的补充,可以使用微调(fine tune),它可以调整现有模型去学习新问题的一些表示特征。进一步推动了预测性能。

转载请注明来源。 欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。 可以在下面评论区评论,也可以邮件至 sharlot2050@foxmail.com。

文章标题:深度学习 Keras 1.5 使用预先训练的卷积网络(convnets)模型

字数:3.6k

本文作者:夏来风

发布时间:2021-02-02, 22:27:01

原始链接:http://www.demo1024.com/blog/ai-ml-keras-1.5/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。