通过特征提取进行迁移学习
通常,我们会将卷积神经网络视为端到端的图像分类器:
- 我们将图像输入到网络
- 图像通过网络向前传播
- 我们在网络末端获得最终的分类概率
但是,没有规定是必须将图像向前传播到整个网络末端。
相反,我们可以:
- 在任意但预先指定的层(例如激活层或池化层)停止传播
- 从指定图层中提取值
- 将值视为特征向量
在对待网络中的特征提取器时,我们实质上是在预先指定的层(通常在全连接层之前,但实际上取决于您的特定数据集)“切断”网络。
e.g.
如果我们要在VGG16中的全连接层之前停止传播,则网络中的最后一层是最大池化层(图2,右),其输出形状为7 x 7 x 512。将这个volume展平为特征向量,我们将获得包含 7 x 7 x 512 = 25,088个值 的一个列表—— 这个数值列表用作我们的特征向量,用于量化输入图像。假设我们的网络中总共给定N张图像,现在我们的数据集将被表示为包含N个向量的列表,每个向量25,088-dim。
一旦有了特征向量,我们就可以基于这些特征来训练机器学习模型, 例如线性SVM,逻辑回归,决策树或随机森林,以获得可以识别新类别图像的分类器。
通过特征提取进行迁移学习时,您会看到两种最常见的机器学习模型:
- 逻辑回归
- 线性支持向量机
为什么是这两种模型?
首先,因为我们的特征提取器是CNN。CNN是能够学习非线性特征的非线性模型——我们假设CNN所学习的特征已经具有鲁棒性和判别力。
第二个,也许是更重要的原因是,我们的特征向量往往非常大并且具有高维度。因此,我们需要一个可以在特征上进行快速训练的模型- 线性模型往往训练起来非常快。
例如,我们5,000张图像的数据集(每个都由25,088-dim的特征向量表示)可以使用Logistic回归模型在几秒钟内进行训练。
再重复一遍,CNN 本身是不能够识别这些新类的。我们只是使用CNN作为中间的特征提取器。下游的机器学习分类器将负责学习从CNN提取的特征的基础模式。
Foods-5K数据集
顾名思义,该数据集包含5,000张图像,分为两类:
- 食物
- 非食物
我们的目标是训练一个分类器,以便我们可以区分这两个类。
使用带有tree参数的命令,看到我们的初始目录如下
$ tree --dirsfirst --filelimit 10
.
├── Food-5K
│ ├── evaluation [1000 entries]
│ ├── training [3000 entries]
│ ├── validation [1000 entries]
│ └── Food-5K.zip
├── dataset
├── output
├── pyimagesearch
│ ├── __init__.py
│ └── config.py
├── build_dataset.py
├── extract_features.py
└── train.py
7 directories, 6 files
Food-5K / 包含 evaluation/ ,training/ 和 validation/ 子目录
我们的 dataset/ 目录虽然现在为空,但很快将以更具有组织性的形式放置Food-5K图像(将在“为特征提取构建数据集”部分中进行讨论 )。
成功执行完今天的Python脚本后, output/ 目录将包含我们提取的特征(存储在三个独立的 .csv 文件)以及我们的标签编码器和模型(两者都是 .pickle 格式)。
我们的Python脚本包括:
pyimagesearch / config.py
:我们的自定义配置文件将帮助我们管理数据集,类名和路径。它是直接用Python编写的,因此我们可以使用os.path 直接在脚本中构建特定于操作系统的格式化文件路径。
build_dataset.py
:此脚本将在磁盘上创建一个更具有组织性的数据集,从而轻松提取特征。
extract_features.py
:迁移学习将从这里开始。此脚本将使用预训练的CNN提取原始特征,并将结果存储在 .csv
文件。同时输出标签编码器 .pickle
文件。
train.py
:我们的训练脚本将在先前计算得到的特征之上训练Logistic回归模型。我们将评估结果并保存模型为 .pickle
文件。
配置文件
让我们开始查看我们的配置文件。
打开 pyimagesearch 子模块里面的 config.py
,并插入以下代码:
# import the necessary packages
import os
# initialize the path to the *original* input directory of images
ORIG_INPUT_DATASET = "Food-5K"
# initialize the base path to the *new* directory that will contain
# our images after computing the training and testing split
# 该目录将包含执行 build_dataset.py 后的更有组织性的数据集,已经划分了训练集和测试集。
BASE_PATH = "dataset"
让我们进行更多的配置以及指定我们的class labels
和 batch size
:
# define the names of the training, testing, and validation
# directories
TRAIN = "training"
TEST = "evaluation"
VAL = "validation"
# initialize the list of class label names
CLASSES = ["non_food", "food"]
# set the batch size
BATCH_SIZE = 32
在第3-5行中指定了输出 训练,评估和验证 数据的路径 。
在第8行以列表形式指定 类别 。如前所述,我们将分类 “食物” 和 “非食物” 图片。
提取特征时,我们会将数据分成 bite-sized 的块,称为batches。BATCH_SIZE
在第11行指定 。
最后,我们构建其余的路径:
# initialize the label encoder file path and the output directory to
# where the extracted features (in CSV file format) will be stored
# 初始化标签编码器的文件路径 到 存储特征csv的目录位置
LE_PATH = os.path.sep.join(["output", "le.cpickle"])
BASE_CSV_PATH = "output"
# set the path to the serialized model after training 训练后的序列化模型的路径
MODEL_PATH = os.path.sep.join(["output", "model.cpickle"])
建立我们的数据集以进行特征提取
从输入图像中提取特征之前,我更喜欢将磁盘上的数据集以如下格式组织:
dataset_name / class_label / example_of_class_label.jpg
这样不仅使我们的数据集更有组织性……
也使我们能够利用Keras的 flow_from_directory
函数(后面进行微调需要用到)。
由于Food-5K数据集预先提供了数据集的划分,因此 我们的最终目录结构将具有以下形式:
dataset_name / split_name / class_label / example_of_class_label.jpg
现在开始构建数据集和目录结构。打开 build_dataset.py
文件并插入以下代码:
# import the necessary packages
from pyimagesearch import config
from imutils import paths
import shutil
import os
# loop over the data splits
for split in (config.TRAIN, config.TEST, config.VAL):
# grab all image paths in the current split
print("[INFO] processing '{} split'...".format(split)