利用少量数据来训练图像分类模型,这是一种很常见的情况。如果你从事与计算机视觉相关的职业,那么很可能会在实践中遇到这种情况。“少量”样本既可能是几百张图片,也可能是上万张图片。我们来看一个实例——猫狗图片分类,数据集包含5000张猫和狗的图片(2500张猫的图片,2500张狗的图片)。我们将2000张图片用于训练,1000张用于验证,2000张用于测试。
将介绍解决这个问题的基本方法:使用已有的少量数据从头开始训练一个新模型。首先,我们在2000个训练样本上训练一个简单的小型卷积神经网络,不做任何正则化,为模型改进设定一个基准。我们得到的分类精度约为70%。这时的主要问题是过拟合。然后,我们会使用数据增强(data augmentation),它是计算机视觉领域中非常强大的降低过拟合的方法。使用数据增强之后,模型的分类精度将提高到80%~85%。
后面介绍将深度学习应用于小型数据集的另外两个重要方法:使用预训练模型做特征提取(得到的精度为97.5%)、微调预训练模型(最终精度为98.5%)。总而言之,这三种方法——从头开始训练一个小模型、使用预训练模型做特征提取、微调预训练模型——构成了你的工具箱,可用于解决小型数据集的图像分类问题。
要训练模型,“样本足够”是相对的,也就是说,“样本足够”是相对于待训练模型的大小和深度而言的。只用几十个样本训练卷积神经网络来解决复杂问题是不可能的,但如果模型很小,并且做了很好的正则化,同时任务非常简单,那么几百个样本可能就足够了。由于卷积神经网络学到的是局部、平移不变的特征,因此它在感知问题上可以高效地利用数据。虽然数据量相对较少,但在非常小的图像数据集上从头开始训练一个卷积神经网络,仍然可以得到不错的结果,而且无须任何自定义的特征工程。你将在本节中看到这种方法的效果。此外,深度学习模型本质上具有很强的可复用性。比如,已有一个在大规模数据集上训练好的图像分类模型或语音转文本模型,只需稍作修改就能将其复用于完全不同的问题。特别是在计算机视觉领域,许多预训练模型(通常都是在ImageNet数据集上训练得到的)现在都可以公开下载,并可用于在数据量很少的情况下构建强大的视觉模型。特征复用是深度学习的一大优势,8.3节将介绍这一点。下面我们先来获取数据。
本节用到的猫狗分类数据集不包含在Keras中。它由Kaggle提供,在2013年底作为一项计算机视觉竞赛的一部分,当时卷积神经网络还不是主流算法。你可以从Kaggle网站下载原始数据集Dogs vs. Cats(如果没有Kaggle账户,你需要注册一个。别担心,注册过程很简单)。此外,你也可以在Colab中使用Kaggle API来下载数据集(详见下方文本框“在Google Colaboratory中下载Kaggle数据集”)。在Google Colaboratory中下载Kaggle数据集Kaggle提供了一个易于使用的API,可以编写代码下载Kaggle托管的数据集。你可以用它将猫狗数据集下载到Colab笔记本中。这个API包含在kaggle包中,kaggle包已经预先安装在Colab上。下载这个数据集非常简单,只需在Colab单元格中运行下面这条命令。
!kaggle competitions download -c dogs-vs-cats
然而,这个API的访问仅限于Kaggle用户,所以要想运行上述命令,首先需要进行身份验证。kaggle包会在一个JSON文件(位于~/.kaggle/kaggle.json)中查找你的登录凭证。我们来创建这个文件。首先,你需要创建一个Kaggle API密钥,并将其下载到本地计算机。只需在浏览器中访问Kaggle网站,登录,然后进入My Account页面。在账户设置中,找到API,单击Create New API Token按钮将生成一个kaggle.json密钥文件,然后将其下载到计算机中。然后,打开Colab笔记本,在笔记本单元格中运行下列代码,将API密钥的JSON文件上传到Colab会话中。
from google.colab import files
files.upload()
运行这个单元格时,会出现Choose Files按钮。单击按钮并选择刚刚下载的kaggle.json文件。这样就把文件上传到Colab本地运行时。最后,创建~/.kaggle文件夹(mkdir ~/.kaggle),并将密钥文件复制过去(cp kaggle.json ~/.kaggle/)。作为最佳安全实践,你还应该确保该文件只能由当前用户(也就是你自己)读取(chmod 600)。
!mkdir ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
现在可以下载我们要使用的数据了。
!kaggle competitions download -c dogs-vs-cats
第一次尝试下载数据时,你可能会遇到403 Forbidden错误。这是因为在下载数据集之前,你需要接受与数据集相关的条款。你需要登录Kaggle账户并访问该数据集对应的Rules页面,然后单击I Understand and Accept按钮。该操作只需完成一次即可。最后,训练数据是一个名为train.zip的压缩文件。请使用静默方式(-qq)解压缩(unzip)。
!unzip -qq train.zip
这个数据集中的图片都是中等分辨率的彩色JPEG图片。图8-8给出了一些样本示例。
图8-8 猫狗分类数据集的一些样本。图像尺寸没有调整,样本具有不同的尺寸、颜色、背景等
不出所料,2013年猫狗分类Kaggle竞赛的优胜者使用的就是卷积神经网络。最佳结果达到了95%的精度。本例中,虽然训练模型的数据量不到参赛选手所用数据量的10%,但结果与这个精度相当接近(参见8.3节)。这个数据集包含25 000张猫和狗的图像(每个类别各有12 500张),压缩后的大小为543 MB。下载数据并解压后,我们将创建一个新数据集,其中包含3个子集:训练集,每个类别各1000个样本;验证集,每个类别各500个样本;测试集,每个类别各1000个样本。之所以要这样做,是因为在你的职业生涯中,你遇到的许多图像数据集只包含几千个样本,而不是几万个。拥有更多的数据,可以让问题更容易解决,所以使用小型数据集进行学习是很好的做法。我们要使用的子数据集的目录结构如下所示。
cats_vs_dogs_small/
...train/
......cat/ ←----包含1000张猫的图像
......dog/ ←----包含1000张狗的图像
...validation/
......cat/ ←----包含500张猫的图像
......dog/ ←----包含500张狗的图像
...test/
......cat/ ←----包含1000张猫的图像
......dog/ ←----包含1000张狗的图像
我们通过调用几次shutil来创建这个子数据集,如代码清单8-6所示。代码清单8-6 将图像复制到训练目录、验证目录和测试目录
import os, shutil, pathlib
original_dir = pathlib.Path("train") ←----原始数据集的解压目录
new_base_dir = pathlib.Path("cats_vs_dogs_small") ←----保存较小数据集的目录
def make_subset(subset_name, start_index, end_index): ←----一个实用函数,将索引从start_index到end_index的猫/狗图像复制到子目录new_base_dir/{subset_name}/cat(或/dog)下。subset_name可以是"train"、"validation"或"test"
for category in ("cat", "dog"):
dir = new_base_dir / subset_name / category
os.makedirs(dir)
fnames = [f"{category}.{i}.jpg"
for i in range(start_index, end_index)]
for fname in fnames:
shutil.copyfile(src=original_dir / fname,
dst=dir / fname)
make_subset("train", start_index=0, end_index=1000) ←----用每个类别的前1000张图像创建训练子集
make_subset("validation", start_index=1000, end_index=1500) ←----用每个类别接下来的500张图像创建验证子集
make_subset("test", start_index=1500, end_index=2500) ←----用每个类别接下来的1000张图像创建测试子集
现在我们有2000张训练图像、1000张验证图像和2000张测试图像。在这3个集合中,两个类别的样本数相同,所以这是一个均衡的二分类问题,分类精度可作为衡量成功的指标。