文章目录

  • 前言
  • 一、使用预训练网络
  • 二、将VGG16卷积基实例化
  • 三、使用卷积基进行特征提取
    • 1.不使用数据增强的快速特征提取
    • 2.使用数据增强的特征提取
  • 四、微调模型

前言

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

一、使用预训练网络

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

1.特征提取是使用之前网络学到的表示来从新样本中提取出有趣的特征。然后将这些特征输入一个新的分类器,从头开始训练。
用于图像分类的卷积神经网络包含两部分:首先是一系列池化层和卷积层即模型的卷积基,最后是一个密集连接分类器。对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在上面运行新数据,然后在输出上面训练一个新的分类器。
注意,某个卷积层提取的表示的通用性(以及可复用性)取决于该层在模型中的深度。模型中更靠近底部的层提取的是局部的、高度通用的特征图(比如视觉边缘、颜色和纹理),而更靠近顶部的层提取的是更加抽象的概念(比如“猫耳朵”或“狗眼睛”)。 因此,如果你的新数据集与原始模型训练的数据集有很大差异,那么最好只使用模型的前几层来做特征提取,而不是使用整个卷积基。

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

二、将VGG16卷积基实例化

文章使用 VGG16 架构,它由 Karen Simonyan 和 Andrew Zisserman 在 2014 年开发。是一个在 ImageNet 数据集(140 万张标记图像,1000 个不同的类别)上表现很好的大型卷积神经网络。ImageNet 中包含许多动物类别,其中包括不同种类的猫和狗,因此可以认为它在猫狗分类问题上也能有良好的表现。对于ImageNet,它是一种简单而又广泛使用的卷积神经网络架构。
1.使用在 ImageNet 上训练的 VGG16 网络的卷积基从猫狗图像中提取有趣的特征,然后在这些特征上训练一个猫狗分类器。
VGG16 等模型内置于 Keras 中,可以从keras.applications 模块中导入。

from tensorflow.keras.applications import VGG16conv_base = VGG16(weights='imagenet',include_top=False,input_shape=(150, 150, 3))

这里向构造函数中传入了三个参数。
weights :指定模型初始化的权重检查点。
include_top :指定模型最后是否包含密集连接分类器。默认情况下,这个密集连接分类器对应于 ImageNet 的 1000 个类别。本次只打算使用自己的密集连接分类器(只有 两个类别:cat 和 dog),所以不需要包含它。
input_shape :是输入到网络中的图像张量的形状。这个参数完全是可选的,如果不传入这个参数,那么网络能够处理任意形状的输入。
VGG16 卷积基的详细架构如下所示:

_________________________________________________________________
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),接着将在这个特征上添加一个密集连接分类器。

三、使用卷积基进行特征提取

猫狗数据集可以从这里面获取

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

在数据集上运行卷积基,将输出保存成硬盘中的 Numpy 数组,然后用这个数据作为输入,输入到独立的密集连接分类器中。这种方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是目前流程中计算代价最高的。但出于同样的原因,这种方法不允许使用数据增强。
在生成器中用卷积基提取特征作为输出,保存数据在 conv_base 中的输出。

#保存数据在 conv_base 中的输出,然后将这些输出作为输入用于新模型
import os
import numpy as np
from tensorflow.keras.preprocessing.image import ImageDataGenerator
base_dir = './data/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 = 0for inputs_batch, labels_batch in generator:features_batch = conv_base.predict(inputs_batch)features[i * batch_size : (i + 1) * batch_size] = features_batchlabels[i * batch_size : (i + 1) * batch_size] = labels_batchi += 1if i * batch_size >= sample_count:#注意,这些生成器在循环中不断生成数据,所以你必须在读取完所有图像后终止循环breakreturn 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.

目前,提取的特征形状为 (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 optimizersmodel = 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))
Epoch 28/30
2000/2000 [==============================] - 1s 400us/step - loss: 0.0885 - acc: 0.9735 - val_loss: 0.2392 - val_acc: 0.9010
Epoch 29/30
2000/2000 [==============================] - 1s 391us/step - loss: 0.0866 - acc: 0.9735 - val_loss: 0.2502 - val_acc: 0.8980
Epoch 30/30
2000/2000 [==============================] - 1s 395us/step - loss: 0.0851 - acc: 0.9755 - val_loss: 0.2483 - val_acc: 0.8980

迭代了30次,训练速度非常快,因为你只需处理两个 Dense 层。验证精度达到了约 90%,比从头开始训练的小型模型效果要好得多。
看一下训练期间的损失曲线和精度曲线

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


从图中也可以看出,虽然 dropout 比率相当大,但模型几乎从一开始就过拟合。这是因为此方法没有使用数据增强,而数据增强对防止小型图像数据集的过拟合非常重要。

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

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

from keras import models
from keras import layersmodel = 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()
Layer (type)                 Output Shape              Param #
=================================================================
vgg16 (Model)                (None, 4, 4, 512)         14714688
_________________________________________________________________
flatten_2 (Flatten)          (None, 8192)              0
_________________________________________________________________
dense_5 (Dense)              (None, 256)               2097408
_________________________________________________________________
dense_6 (Dense)              (None, 1)                 257
=================================================================
Total params: 16,812,353
Trainable params: 2,097,665
Non-trainable params: 14,714,688

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

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

如此设置之后,只有添加的两个 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')
# 注意,不能增强验证数据
test_datagen = ImageDataGenerator(rescale=1./255)train_generator = train_datagen.flow_from_directory(# (目标目录)train_dir,# (将所有图像的大小调整为 150×150)target_size=(150, 150),batch_size=20,
#         因为使用了 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)
Epoch 28/30- 19s - loss: 0.2849 - acc: 0.8805 - val_loss: 0.2399 - val_acc: 0.8960
Epoch 29/30- 19s - loss: 0.2942 - acc: 0.8750 - val_loss: 0.2387 - val_acc: 0.9010
Epoch 30/30- 19s - loss: 0.2887 - acc: 0.8765 - val_loss: 0.2366 - val_acc: 0.9010

验证精度约为 90%。这比从头开始训练的小型卷积神经网络要好得多。
绘制结果查看

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


已不再出现过拟合现象

四、微调模型

冻结 VGG16 的卷积基是为了能够在上面训练一个随机初始化的分类器。同理, 只有上面的分类器已经训练好了,才能微调卷积基的顶部几层。如果分类器没有训练好,那么训练期间通过网络传播的误差信号会特别大,微调的几层之前学到的表示都会被破坏。因此,微调网络的步骤如下:
(1) 在已经训练好的基网络(base network)上添加自定义网络。
(2) 冻结基网络。
(3) 训练所添加的部分。
(4) 解冻基网络的一些层。
(5) 联合训练解冻的这些层和添加的部分。 在做特征提取时已经完成了前三个步骤。我们继续进行第四步:先解冻 conv_base,然后冻结其中的部分层。

将微调顶部最后三个卷积层,也就是说,直到 block4_pool 的所有层都应该被冻结,而block5_conv1、block5_conv2 和 block5_conv3 三层应该是可训练的。

  • 卷积基中更靠底部的层编码的是更加通用的可复用特征,而更靠顶部的层编码的是更专业化的特征。微调这些更专业化的特征更加有用,因为它们需要在你的新问题上改变用途。微调更靠底部的层,得到的回报会更少。从上一个例子结束的地方开始,继续实现此方法:
conv_base.trainable = True
set_trainable = False
for layer in conv_base.layers:if layer.name == 'block5_conv1':set_trainable = Trueif set_trainable:layer.trainable = Trueelse: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)
Epoch 98/100
100/100 [==============================] - 19s 188ms/step - loss: 0.0233 - acc: 0.9930 - val_loss: 0.2460 - val_acc: 0.9460
Epoch 99/100
100/100 [==============================] - 20s 196ms/step - loss: 0.0155 - acc: 0.9930 - val_loss: 0.2950 - val_acc: 0.9350
Epoch 100/100
100/100 [==============================] - 19s 188ms/step - loss: 0.0160 - acc: 0.9945 - val_loss: 0.2475 - val_acc: 0.9410

可以看到,精度值提高了 4%,从约 90% 提高到 94% 以上。
绘制结果:

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


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

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


曲线变得更清楚。从损失曲线上看不出与之前相比有任何真正的提高(实际上还在变差)。很奇怪,如果损失没有降低,那么精度怎么能保持稳定或提高呢?答案很简单:图中展示的是逐点(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.938999991417

得到了 93% 的测试精度。只用2000 个样本可以训练出这个测试精度还是很不错的。

深度学习--使用预训练的卷积神经网络相关推荐

  1. 【深度学习】预训练的卷积模型比Transformer更好?

    引言 这篇文章就是当下很火的用预训练CNN刷爆Transformer的文章,LeCun对这篇文章做出了很有深意的评论:"Hmmm".本文在预训练微调范式下对基于卷积的Seq2Seq ...

  2. Python深度学习(使用预训练的卷积神经网络)--学习笔记(十一)

    5.3 使用预训练的卷积神经网络 想要将深度学习应用于小型图像数据集,一种常用且非常高效的方法是使用预训练网络.预训练网络(pretrained network)是一个保存好的网络,之前已经在大型数据 ...

  3. 【深度学习】5:CNN卷积神经网络原理

    前言:先坦白的说,深度神经网络的学习在一开始对我造成的困扰还是很大的,我也是通过不断地看相关的视频资料.文献讲解尝试去理解记忆.毕竟这些内容大多都是不可查的,我们看到的都只是输入输出的东西,里面的内部 ...

  4. 深度学习笔记其六:现代卷积神经网络和PYTORCH

    深度学习笔记其六:现代卷积神经网络和PYTORCH 1. 深度卷积神经网络(AlexNet) 1.1 学习表征 1.1 缺少的成分:数据 1.2 缺少的成分:硬件 1.2 AlexNet 1.2.1 ...

  5. 深度学习多变量时间序列预测:卷积神经网络(CNN)算法构建时间序列多变量模型预测交通流量+代码实战

    深度学习多变量时间序列预测:卷积神经网络(CNN)算法构建时间序列多变量模型预测交通流量+代码实战 卷积神经网络,听起来像是计算机科学.生物学和数学的诡异组合,但它们已经成为计算机视觉领域中最具影响力 ...

  6. 深度学习笔记(26) 卷积神经网络

    深度学习笔记(26) 卷积神经网络 1. CONV 2. POOL 3. Layer 4. FC 5. 卷积的优势 1. CONV 假设,有一张大小为32×32×3的输入图片,这是一张RGB模式的图片 ...

  7. 花书+吴恩达深度学习(十四)卷积神经网络 CNN 之经典案例(LetNet-5, AlexNet, VGG-16, ResNet, Inception Network)

    目录 0. 前言 1. LeNet-5 2. AlexNet 3. VGG-16 4. ResNet 残差网络 5. Inception Network 如果这篇文章对你有一点小小的帮助,请给个关注, ...

  8. 基于深度学习的图像分类:使用卷积神经网络实现猫狗分类器

    摘要: 深度学习在计算机视觉领域中具有广泛的应用.本文将介绍如何使用卷积神经网络(CNN)实现一个猫狗分类器.我们将使用Python和TensorFlow框架搭建一个简单的卷积神经网络模型,并利用猫狗 ...

  9. 【深度学习】基于Pytorch的卷积神经网络概念解析和API妙用(一)

    [深度学习]基于Pytorch的卷积神经网络API妙用(一) 文章目录 1 不变性 2 卷积的数学分析 3 通道 4 互相关运算 5 图像中目标的边缘检测 6 基于Pytorch的卷积核 7 特征映射 ...

最新文章

  1. css 选择器 伪元素_CSS伪元素-解释选择器之前和之后
  2. Linux学习(2)-命令基础背景+命令使用帮助获取
  3. 配置Exchange 2010 服务器(二)Exchange2010证书配置
  4. 第二阶段团队每日冲刺07
  5. scrum立会报告+燃尽图(第三周第七次)
  6. Frighting的日常:第5天
  7. [你必须知道的.NET]第三十二回,,深入.NET 4.0之,Tuple一二
  8. linux系统启动自动启动,linux系统下的自动启动
  9. 关于设计秒杀系统的几个问题
  10. cryptojs vue 使用_vue 中引入cryptoJS
  11. repeate 常用的每行显示几个共几行
  12. 于娟的忠告----生命只有一次,活着才是王道啊
  13. C++Primer 第15章 OOP
  14. 台式计算机的辐射程度,台式台式电脑辐射污染标准是多少
  15. linux如何查看服务器的型号和常用信息
  16. java第一周_从计算机基础到流程控制语句(if_else)
  17. 中了勒索病毒怎么办?怎么预防
  18. 【MATLAB实验】MATLAB图形绘制相关函数与定积分计算
  19. 谷歌旗下专业图片编辑Snapseed获重大更新
  20. java pdfreader去除水印_Java - PDF操作库 ItextPdf和PdfBox添加水印

热门文章

  1. 通过pyenv和virtualenv搭建python开发环境
  2. 4月京东生鲜水果行业数据报告:榴莲销量增长400%,市场格局剧变
  3. 用java语言解释爱情
  4. 2021-08-13讯飞翻译笔S11,学生们的新福利
  5. 假设检验:正态性检验的那些bug——为什么对同一数据,normaltest和ktest会得到完全相反的结果?
  6. 京东Android面试记录
  7. Android 电子签名/手写签名 保存到相册详解
  8. 无线蓝牙耳机性价比排行_2022最值得入手的蓝牙耳机推荐
  9. 8086汇编4位bcd码_汇编语言--在屏幕上显示名字
  10. 照片删除了怎么恢复?4个方案,这才是官方指南