到目前为止,我们使用的数据集都能够全部加载到内存中。对于小数据集,我们可以加载全部图像数据到内存中,进行预处理,并进行前向传播处理。然而,对于大规模数据集(比如ImageNet),我们需要创建数据生成器,每次只访问一小部分数据集(比如mini-batch),然后对batch数据进行预处理和前向传播。
Keras模块很方便进行数据加载,可以使用磁盘上的原始文件路径作为训练过程的输入。你不需要将整个数据集存储在内存中——只需为Keras数据生成器提供图像路径,生成器会自动从路径中加载数据并进行前向传播。
然而,这种方法非常低效。读取磁盘上的每张图像都需要一个I/O操作,这样会造成一定的延迟。训练深度学习网络本身已经够慢了,所以我们应该尽可能避免I/O瓶颈。
一个比较合理的解决方案是将原始图像生成HDF5数据集,就像我们第3章 中所做的那样,只是这一次我们存储的是原始图像,而不是提取的特征。HDF5不仅可以存储大量的数据集,而且还可以用于I/O操作,特别是用于从文件中提取batch(称为“片”)。我们将在磁盘上的原始图像保存到HDF5文件中,这可以让模型快速的遍历数据集并在其上训练深度学习网络。
在本章的其余部分,将演示如何为Kaggle上的猫狗比赛[3]构建一个HDF5数据集。然后,在下一章中,我们将使用这个HDF5数据集来训练具有开创性的AlexNet架构[6],并在排行榜中排名前25。
Kaggle: 猫狗比赛
首先,从kaggle官方网页地址 上下载狗和猫的数据集.
下载完数据之后,按照下面目录结构解压数据:
1 2 3 kaggle_dogs_vs_cats/train/cat.11866.jpg ... kaggle_dogs_vs_cats/train/dog.11046.jpg
这个项目中将使用以下数据结构:
1 2 3 4 5 6 7 8 |--- datasets | |--- kaggle_dogs_vs_cats | | |--- hdf5 | | |--- train |--- dogs_vs_cats | |--- config | |--- build_dogs_vs_cats.py | |--- ...
接下来,我们创建配置文件。
配置文件
现在我们开始构建更高级的项目和深度学习算法,为了好管理,将使用python为每个项目创建一个特殊的配置模块。例如,以下是kagge猫狗项目的目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --- dogs_vs_cats | |--- config | | |--- __init__.py | | |--- dogs_vs_cats_config.py | |--- build_dogs_vs_cats.py | |--- crop_accuracy.py | |--- extract_features.py | |--- train_alexnet.py | |--- train_model.py | |--- output/ | | |--- __init__.py | | |--- alexnet_dogs_vs_cats.model | | |--- dogs_vs_cats_features.hdf5 | | |--- dogs_vs_cats_mean.json | | |--- dogs_vs_cats.pickle
在config目录中新建一个名为dogs_vs_cats_config.py文件,该文件的主要目的是存储项目的所有相关配置,包括:
1.输入图像的路径。
2.类标签的总数。
3.训练、验证和测试数据划分信息。
4.HDF5数据集的路径。
5.模型、图表和日志等保存路径。
配置文件使用Python格式而不是JSON格式,主要是为了使配置文件更有效地使用(可以使用os.path模块进行操作文件路径)。我建议您养成使用python脚本作为自己的深度学习项目的配置文件的习惯,因为它将极大地提高您的工作效率,并允许您通过单个文件控制项目中的大部分参数。
配置信息
打开dogs_vs_cats_config.py,并写入以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 IMAGES_PATH = "../datasets/kaggle_dogs_vs_cats/train" NUM_CLASSES = 2 NUM_VAL_IMAGES = 1250 * NUM_CLASSES NUM_TEST_IMAGES = 1250 * NUM_CLASSES TRAIN_HDF5 = "../datasets/kaggle_dogs_vs_cats/hdf5/train.hdf5" VAL_HDF5 = "../datasets/kaggle_dogs_vs_cats/hdf5/val.hdf5" TEST_HDF5 = "../datasets/kaggle_dogs_vs_cats/hdf5/test.hdf5"
下面定义输出序列化权重的路径、数据集平均值和图、分类报告、日志等的输出保存路径:
1 2 3 4 5 6 7 8 MODEL_PATH = "output/alexnet_dogs_vs_cats.model" DATASET_MEAN = "output/dogs_vs_cats_mean.json" OUTPUT_PATH = "output"
DATASET_MEAN文件主要是存储整个(training)数据集中三个颜色通道(红、绿、蓝)的像素平均值。在训练网络之前,我们需要将图像中的每个像素值减去平均值(验证集和测试集也需要进行操作处理)。这种方法称为零均值化,是一种数据归一化技术,它将像素强度归一化到[0,1]范围中,这对大型数据集和更深层次的神经网络的训练很有效。
保存数据
下面,构建HDF5数据集。打开一个新文件,命名为build_dogs_vs_cats.py,写入以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from config import dogs_vs_cats_config as configfrom sklearn.preprocessing import LabelEncoderfrom sklearn.model_selection import train_test_splitfrom pyimagesearch.preprocessing import AspectAwarePreprocessor as AAPfrom pyimagesearch.io import hdf5datasetwriter as HDFfrom imutils import pathsimport numpy as npimport progressbarimport jsonimport cv2import os
获取Kaggle猫狗数据集中的图像路径:
1 2 3 4 5 6 7 trainPaths = list (paths.list_images(config.IMAGES_PATH)) trainLabels = [p.split(os.path.sep)[2 ].split("." )[0 ] for p in trainPaths] le = LabelEncoder() trainLabels = le.fit_transform(trainLabels)
原始的猫狗数据集的目录结构如下:
1 2 3 kaggle_dogs_vs_cats/train/cat.11866.jpg ... kaggle_dogs_vs_cats/train/dog.11046.jpg
注意 :类的名称是从实际的文件名中获取到的。
接下来,我们将原始数据分成三部分:训练集、验证集和测试集。
1 2 3 4 5 6 split = train_test_split(trainPaths, trainLabels,test_size=config.NUM_TEST_IMAGES, stratify=trainLabels,random_state=42 ) (trainPaths, testPaths, trainLabels, testLabels) = split split = train_test_split(trainPaths, trainLabels,test_size=config.NUM_VAL_IMAGES, stratify=trainLabels,random_state=42 ) (trainPaths, valPaths, trainLabels, valLabels) = split
划分完数据集之后,我们将数据集写入HDF5文件中:
1 2 3 4 5 6 7 8 9 datasets = [ ("train" , trainPaths, trainLabels, config.TRAIN_HDF5), ("val" , valPaths, valLabels, config.VAL_HDF5), ("test" , testPaths, testLabels, config.TEST_HDF5)] aap = AAP.AspectAwarePreprocessor(256 , 256 ) (R, G, B) = ([], [], [])
首先,我们定义一个数据集列表,其中包括训练数据集、验证数据集和测试数据集以及各自相关变量。列表中的每个元素都是一个4元组,包括:
1.数据类型名称。
2.数据路径。
3.数据标签。
4.保存HDF5数据的路径。
然后,我们初始化AspectAwarePreprocessor,在写入HDF5之前将图像大小调整为256x256像素(注意,图像的纵横比)。最后,初始化三个列表——R、G和B(图像的三个颜色通道),用于存储每个通道的平均像素值。
最后,我们准备构建HDF5数据集:
1 2 3 4 5 6 7 8 9 10 11 for (dType, paths, labels, outputPath) in datasets: print ("[INFO] building {}..." .format (outputPath)) writer = HDF.HDF5DatasetWriter((len (paths), 256 , 256 , 3 ), outputPath) widgets = ["Building Dataset: " , progressbar.Percentage(), " " , progressbar.Bar(), " " , progressbar.ETA()] pbar = progressbar.ProgressBar(maxval=len (paths), widgets=widgets).start()
其中HDF5DatasetWriter的输出数据集的shape是(len(paths),256,256,3),这意味着有len(paths)张图像且每一个shape都是(256,256,3)
然后初始化进度条模块,这样我们就可以很容易地监视数据集生成的过程。
接下来,写入数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for (i, (path, label)) in enumerate (zip (paths, labels)): image = cv2.imread(path) image = aap.preprocess(image) if dType == "train" : (b, g, r) = cv2.mean(image)[:3 ] R.append(r) G.append(g) B.append(b) writer.add([image], [label]) pbar.update(i) pbar.finish() writer.close()
首先,对图像路径数据进行遍历,读取每一张图像,并进行预处理,将图像的大小调整为256x256。
然后,如果是train数据集,则我们计算RGB的平均值(需要注意的是:我们只在train数据中计算RGB平均值,而验证集和测试集直接减去平均值)。
最后,我们将经过处理的图像写入HDF5文件中。
将RGB平均值序列化到磁盘:
1 2 3 4 5 6 print ("[INFO] serializing means..." )D = {"R" : np.mean(R), "G" : np.mean(G), "B" : np.mean(B)} f = open (config.DATASET_MEAN, "w" ) f.write(json.dumps(D)) f.close()
打开终端,直接以下命令:
1 2 3 4 5 6 7 $ python build_dogs_vs_cats.py [INFO] building kaggle_dogs_vs_cats/hdf5/train.hdf5... Building Dataset: 100% |####################################| Time: 0:02:39 [INFO] building kaggle_dogs_vs_cats/hdf5/val.hdf5... Building Dataset: 100% |####################################| Time: 0:00:20 [INFO] building kaggle_dogs_vs_cats/hdf5/test.hdf5... Building Dataset: 100% |####################################| Time: 0:00:19
从输出结果中可以看到,训练数据集、测试数据集和验证数据集分别创建了一个HDF5文件。
查看这些文件大小
1 2 3 4 5 $ ls -l ../datasets/kaggle_dogs_vs_cats/hdf5/total 38400220 -rw-rw-r-- 1 adrian adrian 3932182144 Apr 7 18:00 test.hdf5 -rw-rw-r-- 1 adrian adrian 31457442144 Apr 7 17:59 train.hdf5 -rw-rw-r-- 1 adrian adrian 3932182144 Apr 7 18:00 val.hdf5
磁盘上的猫狗数据集的大小只有595MB,为什么.hdf5文件这么大?train.hdf5文件是31.45GB,val.hdf5和test.hdf5文件几乎是4GB的.
原始的图像文件格式如JPEG和PNG应用了数据压缩算法来压缩了图像文件的大小。但是,实际上我们读取图像数据时,没有对数据进行任何压缩,保持了原本图像的大小,并将图像存储为原始的NumPy数组(比如,bitmaps),极大地增加了我们的存储成本,但也将加快我们的训练速度,因为我们可以直接从HDF5数据集读取图像,并进行预处理和前向传播。
查看RGB均值文件:
1 2 $ cat output/dogs_vs_cats_mean.json{"B": 106.13178224639893, "R": 124.96761639328003, "G": 115.97504255599975}
其中,红色通道在数据集中的所有图像中平均像素强度为124.96。蓝色通道的平均值是106.13,绿色通道的平均值是115.97。下一章中,我们将会对输入图像的像素值减去这些RGB平均值,即对图像数据做归一化。均值归一化有助于将数据集中在0均值附近。通常,这种规范化使我们的网络能够更快地学习,这也是为什么我们在更大、更有挑战性的数据集中使用这种类型的规范化(而不是[0,1]缩放)。
总结
在本章中,我们学习了如何将原始图像序列化为适合训练深度神经网络的HDF5数据集。我们将原始图像序列化为HDF5文件,而不是简单地访问磁盘上的一小批图像路径,由于会造成I/O延迟——对于磁盘上的每张图像,我们必须执行一次I/O操作来读取图像。在深度训练工程中,I/O延迟是一个大问题——训练过程已经足够缓慢,如果我们的网络很难访问数据,那么就是搬起石头砸自己的脚。
相反,如果我们将所有图像序列化到一个高效的HDF5文件中,我们可以利用非常快速的数组切片来提取我们的mini-batch数据,从而显著减少I/O延迟,并且加快训练过程。无论何时,当您使用Keras库并处理一个太大而无法装入内存的数据集时,您首先应该考虑将数据集序列化为HDF5格式——我们将在下一章中发现,它使训练您的网络成为一项更容易(也更有效)的任务。
本章代码下载地址:github