4.3 数据读取

🏷sec0403_Datasets_DataReading

深度学习模型需要大量的数据来完成训练和评估,这些数据样本可能是图片(image)、文本(text)、语音(audio)等多种类型,而模型训练过程实际是数学计算过程,因此数据样本在送入模型前需要经过一系列处理,如转换数据格式、划分数据集、变换数据形状(shape)、构建数据迭代读取器等以备分批训练。在前面的内容和项目中,我们已经尝试过多种不同的数据读取和载入方法,其中就包括最简单的Paddle高级API等。但在工业实践中,我们可能面临的任务和数据是千差万别的。要将这些多样化的数据输入到模型中,最好是能够设计一套数据定义和读取流程,以规范数据的读取和预处理。这种做法的好处是,在设计模型的时候不需要去关心数据的基本情况,只需要按照统一的接口去调用数据就可以了。

4.3.1 数据读取方法简介

4.3.1.1 同步数据读取和异步数据读取

在从硬盘等存储设备上进行数据读取的时候,一般可以使用两种方法来进行读取,一种称之为同步数据读取,一种称之为异步数据读取。在对于数据量大、数据读取较慢的场景,建议采用异步数据读取方式进行读取。二者的对比如下图所示:

图4-6 深度学习数据读取方式对比图

图4-6 深度学习数据读取方式对比图

4.3.1.2 数据读取的基本流程

在Paddle框架中,进行数据读取一般包含四个步骤:

  1. 定义必要库及全局参数:和大多数程序一样,在进行数据读取前同样需要载入一些必要的库,例如系统库os,数据处理库numpy,图像处理库cv2及Paddle库。
  2. 定义数据集:数据集定义主要包括两个部分,一是读取数据列表文件,二是数据归约。
    • 数据列表文件的读取主要实现将磁盘中保存的原始图片、文字等样本和对应的标签映射到 Dataset 类,方便后续通过索引(index)读取数据。
    • 数据规约是确保输入数据能够被正常输入到模型中的必要步骤,这其中最重要是将输入数据按照一定尺度转换成Paddle工具包所需要的四维张量。在这个过程中还需要将图像从原始的HWC形态转换为CHW形态,并且将图像矩阵的数据类型转换为32位浮点型的numpy数组,标签矩阵的数据类型转换为64位整型。对于彩色图像通常还需要做均值消除操作,而黑白图像需要做像素归一化操作。在飞桨的 paddle.vision.datasets 库和 paddle.text 库中已经内置了一些经典数据集类,我们可以直接进行调用。当然,我们也可以全手工的方式来定义数据类,本小节中内容将采用这种自定义的方法进行数据集类的定义。
  3. 创建数据读取器:数据读取器的主要功能是将数据量类进行实例化,并返回图像数据。数据读取器可以根据类的定义分别获取四个不同的子集,同时生成图像矩阵、类别标签和样本的数量。
  4. 创建数据迭代读取器:迭代读取器能够自动将数据集的样本进行分批(batch)、打乱(shuffle)等操作,方便训练时迭代读取。在飞桨框架中内置了 paddle.io.DataLoader 迭代读取器,该库支持多进程异步读取功能,可加快数据读取速度。

4.3.2 同步数据读取

下面我们将基于同步数据读取模式来获取十二生肖数据集Zodiac的四个数据子集中的数据,并进行简单的测试。

4.3.2.1 定义必要库及全集参数

程序清单4-3 同步数据读取的参数定义
# codes04003_synchronous_initialization
import os
import numpy as np
import cv2
import json
import paddle

# 1. 定义数据集列表文件路径
dataset_name      = 'Zodiac'
dataset_path      = 'D:\\Workspace\\ExpDatasets\\'
dataset_root_path = os.path.join(dataset_path, dataset_name)

trainval_list = os.path.join(dataset_root_path, 'trainval.txt')
train_list = os.path.join(dataset_root_path, 'train.txt')
val_list = os.path.join(dataset_root_path, 'val.txt')
test_list = os.path.join(dataset_root_path, 'test.txt')

# 2. 图像基本信息
input_size = [3, 227, 227]             # 定义图像输入模型时的尺寸
mean_value = [0.485, 0.456, 0.406]     # Imagenet均值
std_value = [0.229, 0.224, 0.225]      # Imagenet标准差    

4.3.2.2 创建数据集读取器

在同步数据读取模式下,我们需要手动编写python代码来实现数据的读取。在 程序清单4-4 中,我们定义了两个函数 transformsdata_reader 分别用来实现数据预处理和数据读取。在 transforms 函数中,可以进行 第4.2小节 中关于数据规约的相关配置,例如像素归一化、均值消除、通道变换、数据类型转换等。在 data_reader 函数中,主要实现从数据列表中读取数据,并将图像和类别标签进行分割。下面是这部分内容的示例代码。

程序清单4-4 创建同步数据读取器
# codes04004_synchronous_create_reader
# 0. 在CPU多进程处理方法
from multiprocessing import cpu_count

# 1. 定义数据预处理方法
def transforms(sample):
    img, label = sample
    img = cv2.imread(img, 1)
    # 将图像尺度resize为指定尺寸
    img = cv2.resize(img, [input_size[1], input_size[2]]) 
    # 将图像数据类型转化为float32                     
    img = img.astype('float32')
    # 将像素值归一化到[0,1]之间
    img = img/255.0
    # 数据标准化(均值消除)
    img = (img - mean_value) / std_value
    # 将图像数据类型转化为float32
    img = img.reshape(1, 3, input_size[1], input_size[2])                                      
    
    return img, label

# 2. 定义数据器eader,用于从列表文件中批量获取图像
def data_reader(data_list_path):
    # 定义读取函数,从列表文件中读取
    def reader():
        with open(data_list_path, 'r') as f:
            lines = f.readlines()
            for line in lines:
                img_path, label = line.split('\t')
                yield img_path, int(label)
    # 通过用户自定义的映射器mapper来映射reader返回的样本
    # cpu_count()可以实现多线程方式运行 
    return paddle.reader.xmap_readers(transforms, reader, cpu_count(), 512) 

4.3.2.3 创建数据迭代读取器

依托数据集读取器所获取的样本是大批量的,当数据量比较大的时候,设备可能会无法一次性容纳整个数据集。因此,我们还需要将数据集划分成若干个批次来分批进行训练。因此,为了实现基于小批量的训练,我们还需要创建基于批量的数据迭代读取器。数据迭代读取器可以有效实现将数据进行小批量划分以及在数据被送入模型前的打乱操作。此处,我们需要为四个数据子集分别创建数据迭代读取器。

程序清单4-5 创建同步数据小批量迭代读取器
# codes04005_synchronous_create_iterative_reader
trainval_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(trainval_list), buf_size=256), batch_size=64, drop_last=False)
train_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(train_list), buf_size=256),  batch_size=64, drop_last=False)
val_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(val_list), buf_size=256),  batch_size=64, drop_last=False)
test_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(test_list), buf_size=256),  batch_size=64, drop_last=False)

4.3.2.4 测试数据迭代器

在数据迭代读取器创建完成后,就可以直接进行训练了。但为了保证数据送入模型的正确性,我们可以先对数据迭代器进行一定的测试,例如输出数据的形态。

程序清单4-6 在同步数据读取过程中创建数据迭代读取器
# codes04006_synchronous_print_reader
# 迭代的读取数据并打印数据的形状
for i, data in enumerate(val_reader()):
    if i < 1:
        print('验证集batch_{}的图像形态: {}'.format(i, data[0][0].shape))
    else:
        break
验证集batch_0的图像形态: (1, 3, 224, 224)

4.3.3 异步数据读取

相比同步数据读取,异步数据读取更为常用。特别是对于更喜欢大数据的深度学习来说,异步数据读取具有速度快的优势。在Paddle中,我们可以使用内置的 paddle.io 库来实现异步数据读取,这个过程主要包括两个步骤:

  1. 构建数据库类,基于数据列表完成数据的读取和基本预处理,数据读取器继承于 paddle.io.Dataset 类;
  2. 使用 paddle.io.DataLoader 创建异步数据迭代读取器。

下面我们仍然以十二生肖数据集Zodiac为例,来构建基于异步数据读取模式的数据读取框架。此处,我们同样将实现四个数据子集的获取,包括训练集、验证集、训练验证集和测试集。

4.3.3.1 定义必要库及全局参数

在异步数据读取模式下,全局参数的定义和必要库的导入基本上和同步的一致,但需要增加一个 paddle.vision.transforms 类来实现数据预处理和后续的数据增广操作。

程序清单4-7 异步数据读取的参数定义
# codes04007_asynchronous_initialization
import os
import cv2
import json
import numpy as np
import matplotlib.pyplot as plt
import sys
sys.path.append(r'D:\WorkSpace\DeepLearning\WebsiteV2')   # 定义课程自定义模块保存位置
from codes.paddle import common, datasets
import paddle
import paddle.vision.transforms as T

# 1. 定义数据集基本信息
dataset_name      = 'Zodiac'
dataset_path      = 'D:\\Workspace\\ExpDatasets\\'
dataset_root_path = os.path.join(dataset_path, dataset_name)

# 2. 图像基本信息
args = {
'input_size': [3, 227, 227],             # 定义图像输入模型时的尺寸
'mean_value': [0.485, 0.456, 0.406],     # Imagenet均值
'std_value': [0.229, 0.224, 0.225],      # Imagenet标准差
}

4.3.3.2 定义数据集类

在实际的场景中,一般需要使用自有的数据来构建和定义数据集,此时可以通过继承 paddle.io.Dataset 基类来实现自定义数据集类的创建。该类主要实现以下三个函数:

程序清单4-8 定义数据集类
  1. 通过继承 paddle.io.Dataset 类来定义数据集类。该类主要包括三个内置的函数,分别是 __init__, __getitem____len__
# codes04008_asynchronous_create_dataset
class DatasetZodiac(paddle.io.Dataset):
    # 1. 初始化数据集,并将样本和标签映射到列表中
    def __init__(self, dataset_root_path, mode='test'):
    # 2. 定义数据获取函数,返回单条数据(样本数据、对应的标签)    
    def __getitem__(self, index):
    # 3. 定义样本总数获取函数    
    def __len__(self):
  1. 初始化数据集,并将样本和标签映射到列表中。同时,根据数据子集的功能不同,分别定义数据预处理函数。此处,我们对训练集和训练验证集执行从256像素到224像素的随机裁剪,对测试集和验证集,我们直接将它们压缩成224×224像素。同时,对所有的数据集都执行均值消除和数据类型转换操作。注意,高宽尺度224×224像素只是大多数经典深度模型的典型设置,并非固定设置。

均值减除和方差(标准差)归一化是图像标准化最常见的方法,它可以通过消除相邻像素之间的相似性来突出目标的显著性。均值减除指将图像的均值变为0,标准差归一化指将标准差置为1。不过根据UFLDL的描述,对于自然图像,即使不做标准差归一化操作,图像也基本上满足1标准差的要求。下面代码给出的标准化参数是在百万级别的Imagenet数据集上计算出的均值和方差,对于自然图像,通常情况都使用该值作为默认值。在 RGB色彩体系下,我们将这两个超参数定义为:mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225] 或 mean=[124, 117, 104], std=[58,57,57]。

def __init__(self, dataset_root_path, mode='test', args=None, isTransforms=2):
    assert mode in ['train', 'val', 'test', 'trainval']
    self.data = []                                         # 定义数据序列,用于保存数据的路径和标签
    self.args = args                                       # 定义超参数列表
    self.isTransforms = isTransforms                       # 定义transforms类型
    
    # 读取数据列表文件,将每一行都按照路径和标签进行拆分成两个字段的序列,并将序列依次保存至data序列中
    # 1) 若列表信息长度为2,则表示包含路径和标签信息。
    # 2) 若列表信息长度为1,则表示只包含路径,不包含标签。一般正式的测试文件都只包含路径,不包含标签。
    with open(os.path.join(dataset_root_path, mode+'.txt')) as f:
        for line in f.readlines():
            info = line.strip().split('\t')                # 拆分从列表文件中读取到数据信息
            image_path = info[0].strip()                   # 信息的[0]位置为路径
            if len(info) == 2:                             # 判断信息的长度,若包含标签则写入image_label
                image_label = info[1].strip()
            elif len(info) == 1:                           # 判断信息的长度,若不包含标签,则用"-1"表示
                image_label = -1
            self.data.append([image_path, image_label])             # 将路径和标签写入[data]容器

    # 对训练数据和验证、测试数据采用不同的数据预处理方法
    # 1) train和trainval:执行随机裁剪,并完成标准化预处理
    # 2) train和trainval:直接执行尺度缩放,并完成标准化预处理
    inputSize = self.args['input_size'][1:3] if len(self.args['input_size'])==3 else self.args['input_size']
    if self.isTransforms == 0:
        self.transforms = T.Compose([                      # 0) 必要数据规约
            T.Resize(inputSize),             # 直接尺度缩放
            T.ToTensor(),                                  # 转换成Paddle规定的Tensor格式
        ])
    elif self.isTransforms == 1 or (self.isTransforms == 2 and mode in ['val', 'test']):
        self.transforms = T.Compose([                      # 1) 基本数据预处理,不含数据增广
            T.Resize(inputSize),             # 直接尺度缩放
            T.ToTensor(),                                  # 转换成Paddle规定的Tensor格式
            T.Normalize(mean=self.args['mean_value'],      # 均值方差归一化
                        std=self.args['std_value'])    
        ])
    elif self.isTransforms == 2 and mode in ['train', 'trainval']:
        self.transforms = T.Compose([                      # 2) 训练数据预处理,包含数据增广
            T.Resize((256, 256)),                          # 直接尺度缩放
            T.RandomResizedCrop(inputSize),  # 随机裁剪
            T.RandomHorizontalFlip(prob=0.5),              # 水平翻转
            T.RandomRotation(15),                          # 随机旋转
            T.ColorJitter(brightness=0.4,                  # 色彩扰动:亮度、对比度、饱和度和色度
                            contrast=0.4, 
  1. 定义数据获取函数,返回单条数据,包括样本数据和其对应的类别标签。
def __getitem__(self, index):
    image_path, label = self.data[index]            # 根据索引,从列表中取出指定[index]图像,并将数据拆分成路径和列表
    img = cv2.imread(image_path, 1)                 # 使用cv2进行数据读取,0为灰度模式,1为彩色模式

    img = self.transforms(img)                      # 执行数据预处理
    
    label = np.array(label, dtype='int64')          # 将标签转换为64位整型
    
    return img, label
  1. 获取数据集的样本总数
def __len__(self): 
    return len(self.data)

在上面的代码中,我们自定义了一个数据集类 DatasetZodiac,该类继承自 paddle.io.Dataset 基类,并且实现了 __init__ ,__getitem____len__ 三个函数。

4.3.3.3 创建数据读取器

创建了数据集类之后,就可以通过实例化的方法来将数据集中的数据载入到内存中。

程序清单4-9 创建异步数据读取器
# codes04009_asynchronous_create_reader
dataset_train = DatasetZodiac(dataset_root_path, args=args, mode='train')
dataset_val = DatasetZodiac(dataset_root_path, args=args, mode='val')
dataset_trainval = DatasetZodiac(dataset_root_path, args=args, mode='trainval')
dataset_test = DatasetZodiac(dataset_root_path, args=args, mode='test')

Zodiac数据集由12个类别的图像组成,每个类别由训练集(train dataset)中的600张图像、验证集(val dataset)中的54张图像和测试集中55张图像组成。因此,训练集、验证集和测试集分别包含7190、650和660张图像。测试数据集和验证数据集不会用于训练,只用于评估模型性能。注意部分数据可能存在损坏的情况,因此需要通过数据清洗操作将其进行实现排除(参考 4.2小节 )。

print('train:{}, val:{}, trainval:{}, test:{}'.format(len(dataset_train),len(dataset_val), len(dataset_test), len(dataset_trainval)))
train:7190, val:650, test:660, trainval:7840

该数据集中的每个输入图像的高度和宽度都在数据集类中被约束成224像素,这也是分类任务的一种典型配置。所有输入图像都由彩色图像组成,其通道数为3,即红绿蓝(RGB)三个通道。为了简洁起见,本书将高度h像素和宽度w像素的图像记为 h×w 或 (h,w)。

dataset_train[0][0].shape
[3, 224, 224]

Zodiac数据集中总共包含的12个类别,分别是dog(狗)、dragon(龙)、goat(羊)、horse(马)、monkey(猴)、ox(牛)、pig(猪)、rabbit(兔)、ratt(鼠)、rooster(鸡)、snake(蛇)、tiger(虎)。以下函数用于在数字标签中索引及其文本名称之间进行转换。

程序清单4-10 从自建字典中获取Zodiac数据集的标签
#@save dataasets.get_Zodiac_labelname_from_labelID @TODO:4.3
def get_Zodiac_labelname_from_labelID(label_id):
    """根据标签ID,返回Zodiac数据集的文本标签"""
    label_dict = {'0': 'dog', '1': 'dragon', '2': 'goat', '3': 'horse', 
            '4': 'monkey', '5': 'ox', '6': 'pig', '7': 'rabbit',
            '8': 'ratt', '9': 'rooster', '10': 'snake', '11': 'tiger'}
    return label_dict[str(label_id)]
dataset_info_path = os.path.join(dataset_root_path, 'dataset_info.json')
print('标签 0 的名称为:{}。'.format(datasets.get_Zodiac_labelname_from_labelID_by_json(0, dataset_info_path)))
标签 0 的名称为:dog。

此外,我们也可以通过读取 4.3节 中生成数据集信息文件 dataset_info.json 来获取标签字典。对于自定义数据集推荐使用这种方法来获取数据标签。

程序清单4-11 从数据集信息字典中获取Zodiac数据集的标签
#@save dataasets.get_Zodiac_labelname_from_labelID_by_json @TODO:4.3
def get_Zodiac_labelname_from_labelID_by_json(label_id, dataset_info_path):    
    """根据标签ID,返回Zodiac数据集的文本标签,标签信息来源于dataset_info.json"""
    dataset_info = json.load(open(dataset_info_path, 'r', encoding='utf-8'))
    label_dict = dataset_info['label_dict']
    return label_dict[str(label_id)]    
dataset_info_path = os.path.join(dataset_root_path, 'dataset_info.json')
print('标签 0 的名称为:{}。'.format(datasets.get_Zodiac_labelname_from_labelID_by_json(0, dataset_info_path)))
标签 0 的名称为:dog。

接下来,我们定义一个图片可视化函数来显示数据集中的图像。为了比较好的可视化数据集中的图像,我们需要关闭读取器的数据预处理功能(isTransforms=0),同时开启小批量迭代读取器的打乱功能(shuffle=True)。注意,对于测试集数据一般不需要进行打乱操作。

程序清单4-12 从迭代读取器中读取数据并可视化图片
#@save common.show_dataset_images @TODO:4.3
def show_dataset_images(reader, num_rows=2, num_cols=6, scale=1.5):
    "显示数据集中的图像文件"
    _, (image, label) = next(enumerate(reader))
    num_images = num_rows*num_cols
    image = np.transpose(image[0:num_images], (0,2,3,1))
    label = label[0:num_images]

    plt.figure(figsize = (num_cols*scale, num_rows*scale+1))
    for i in range(1, num_rows+1):
        for j in range(1, num_cols+1):
            n = num_cols*(i-1)+j
            ax = plt.subplot(num_rows, num_cols, n)
            ax.set_title(datasets.get_Zodiac_labelname_from_labelID(int(label[n-1])))
            img = cv2.cvtColor(image[n-1].numpy(), cv2.COLOR_BGR2RGB)
            plt.imshow(img)
dataset_test = DatasetZodiac(dataset_root_path, mode='test', args=args,isTransforms=0)
test_reader = paddle.io.DataLoader(dataset_test, batch_size=32, shuffle=True, drop_last=False)
common.show_dataset_images(test_reader, num_rows=2, num_cols=6, scale=3)

chapter04007ShowZodiacExamples

4.3.3.4 创建小批量数据迭代读取器

为了使模型在读取训练集和测试集时更容易,推荐使用内置的数据迭代器API来创建小批量数据迭代读取器,而不是从零开始创建。在每次迭代训练时,数据加载器都会读取一个小批量的数据,其大小为 batch_size。此外,通过内置数据迭代器,我们可以随机打乱所有样本,从而无偏见地读取小批量样本,并提供给模型进行迭代训练。

程序清单4-13 创建异步数据迭代读取器
# codes04013_asynchronous_create_dataLoader
train_reader = paddle.io.DataLoader(dataset_train, batch_size=64, shuffle=True,drop_last=True)
val_reader = paddle.io.DataLoader(dataset_val, batch_size=64, shuffle=False, drop_last=False)
trainval_reader = paddle.io.DataLoader(dataset_trainval, batch_size=64, shuffle=True, drop_last=True)
test_reader = paddle.io.DataLoader(dataset_test, batch_size=64, shuffle=False, drop_last=False)

在上述方法中,我们实例化了一个训练数据读取器 dataset_train。依托于该实例,我们可以使用 paddle.io.DataLoader 类来创建一个数据迭代读取器 train_reader。函数 DataLoader 可以返回一个异步的批次数据迭代器。它主要包含以下几个关键字段:

4.3.3.5 测试数据迭代器

定义好数据读取器之后,就可以使用 for 循环来迭代地读取批次数据并用于模型训练了。值得注意的是,如果使用高层API中的 paddle.Model.fit 函数来训练数据,则只需定义数据集类 DatasetZodiac,而不需要再单独定义迭代读取器。因为 paddle.Model.fit 中实际已经封装了 DataLoader 的功能。下面给出测试迭代读取器的函数。

程序清单4-14 测试异步模式下的数据迭代读取器
# codes04014_asynchronous_print_reader
for i, (image, label) in enumerate(val_reader()):
    if i < 2:
        print('验证集batch_{}的图像形态:{}, 标签形态:{}'
                        .format(i, image.shape, label.shape))
    else:
        break
验证集batch_0的图像形态:[64, 3, 224, 224], 标签形态:[64]
验证集batch_1的图像形态:[64, 3, 224, 224], 标签形态:[64]

4.3.3.6 整合所有组件

现在我们定义 load_dataset_Zodiac 函数,用于完整的获取和读取Zodiac数据集。这个函数返回数据集的验证集、训练集、测试集和训练验证集四个子集的批量数据迭代器。此外,这个函数还接受两个可选参数batch_sizetransform,前者用于设置迭代器的批量大小,后者则用于接受各种数据预处理参数。

程序清单4-15 创建Zodiac数据集读取器
# codes04015_load_dataset_Zodiac
def load_dataset_Zodiac(batch_size=64, transformArgs=args, isTransforms=2):
    """载入Zodiac数据集并对其进行基本预处理"""
    # 1. 实例化数据类
    dataset_train = datasets.DatasetZodiac(dataset_root_path, args=transformArgs, isTransforms=isTransforms, mode='train')
    dataset_val = datasets.DatasetZodiac(dataset_root_path, args=transformArgs, isTransforms=isTransforms, mode='val')
    dataset_trainval = datasets.DatasetZodiac(dataset_root_path, args=transformArgs, isTransforms=isTransforms, mode='trainval')
    dataset_test = datasets.DatasetZodiac(dataset_root_path, args=transformArgs, isTransforms=isTransforms, mode='test')

    # 2. 创建小批量数据迭代读取器
    # 使用paddle.io.DataLoader 定义DataLoader对象用于加载Python生成器产生的数据,
    # DataLoader 返回的是一个批次数据迭代器,并且是异步的。
    train_reader = paddle.io.DataLoader(dataset_train, batch_size=batch_size, shuffle=True, drop_last=False)
    val_reader = paddle.io.DataLoader(dataset_val, batch_size=batch_size, shuffle=False, drop_last=False)
    trainval_reader = paddle.io.DataLoader(dataset_trainval, batch_size=batch_size, shuffle=True, drop_last=False)
    test_reader = paddle.io.DataLoader(dataset_test, batch_size=batch_size, shuffle=False, drop_last=False)
    
    return train_reader, val_reader, trainval_reader, test_reader

下面,我们通过设置transform参数来测试load_dataset_Zodiac函数的参数调整功能。

程序清单4-16 创建Zodiac数据集小批量迭代读取器
# codes04016_Zodiac_create_dataLoader
train_reader, val_reader, train_reader, test_reader 
                    = load_dataset_Zodiac(batch_size=32, transformArgs=args, isTransforms=2)
for i, (image, label) in enumerate(val_reader):
    if i < 2:
        print('验证集batch_{}的图像形态:{}, 标签形态:{}'.format(i, image.shape, label.shape))
    else:
        break
验证集batch_0的图像形态:[32, 3, 100, 100], 标签形态:[32]
验证集batch_1的图像形态:[32, 3, 100, 100], 标签形态:[32]

4.3.4 小结

一般来说,异步数据读取模型需要在大规模的数据集上运行才能看出其明显的效果。不过,这两种模式都并不影响模型训练的精度。为了规范化,还是建议使用异步数据读取方法进行数据读取。在后面的项目中,我们都将使用异步读取模式进行数据读取。此外,数据迭代器是获得更高性能的关键组件。依靠实现良好的数据迭代读取器,可以实现深度学习训练中最重要的设计——基于小批量数据的训练。这种技术可以应对单一显卡容量不足的问题,并且可以有效利用高性能并行计算来提高训练速度。

4.3.5 练习

  1. 尝试对其他数据集进行数据集类定义和数据读取器的定义,并完成相关的测试。

  2. 以下描述符合异步数据读取的包括:()。
    A. 适合数据量较大、数据读取较慢的场景
    B. 数据的读取和模型的训练以串行方式进行
    C. 模型直接从缓存队列获取数据
    D. 在进行模型训练前需要一次性读取整个数据集的样本

  3. 以下函可以用来实现小批量数据迭代读取的是()。
    A. paddle.reader.shuffle
    B. paddle.vision.transforms
    C. paddle.io.DataLoader
    D. cv2.imread
    E. paddle.reader.xmap_reader

  4. 在对训练数据和测试数据进行处理的时候,都需要将原始样本Resize到模型要求的固定尺寸(例如224×224)。那么,对于训练集和验证集来说,它们所使用的Resize方法必须保持一致。
    A. 正确
    B. 错误

  5. 在创建小批量数据迭代读取器的时候,通常需要将批次数进行打乱操作,下列哪些数据子集必须执行打乱。()
    A. 训练集
    B. 验证集
    C. 测试集
    D. 训练验证集

  6. OpenCV中默认的色彩空间存储格式是()。
    A. RGB
    B. HSV
    C. BGR
    D. HSL

  7. 对于一个神经网络模型,规定它的输出为28×28的RGB图像,输入张量形状为[8,3,28,28],这里8表示()。
    A. batch_size
    B. channel
    C. height
    D. epoch