zoukankan      html  css  js  c++  java
  • 百度PaddlePaddle入门-10(数据处理)

    在“手写数字识别”案例的快速入门中,我们调用飞桨提供的API(paddle.dataset.mnist)加载MNIST数据集。但在工业实践中,我们面临的任务和数据环境千差万别,需要编写适合当前任务的数据处理程序。

    但是编写自定义的数据加载函数,一般会涉及以下四个部分:

    • 数据读取与数据集划分
    • 定义数据读取器
    • 校验数据的有效性
    • 异步数据读取

    在数据读取与处理前,首先要加载飞桨平台和数据处理库,可能使用的库都需要加载进来:

    1 #数据处理部分之前的代码,加入部分数据处理的库
    2 import paddle
    3 import paddle.fluid as fluid
    4 from paddle.fluid.dygraph.nn import FC
    5 import numpy as np
    6 import os
    7 import gzip
    8 import json
    9 import random

    1. 数据读取与数据集划分

    实际保存到的数据存储格式多种多样,本节使用的mnist数据集以json格式存储在本地。

    在'./work/'目录下读取文件名称为'mnist.json.gz'的MINST手写数字识别数据,文件格式是压缩后的json文件。文件内容包括:训练数据、验证数据、测试数据三部分,分别包含50000、10000、10000条手写数字数据和两个元素列表。

    以训练集数据为例,它为两个元素的列表为[traim_imgs, train_labels]。

    • train_imgs:一个维度为[50000, 784]的二维列表,包含50000张图片。每张图片用一个长度为784的向量表示,内容是28*28尺寸的像素灰度值(黑白图片)。
    • train_labels:一个维度为[50000, ]的列表,表示这些图片对应的分类标签,即0-9之间的一个数字。接下来我们将数据读取出来。
     1 # 声明数据集文件位置
     2 datafile = './work/mnist.json.gz'
     3 print('loading mnist dataset from {} ......'.format(datafile))
     4 # 加载json数据文件
     5 data = json.load(gzip.open(datafile))
     6 print('mnist dataset load done')
     7 # 读取到的数据区分训练集,验证集,测试集
     8 train_set, val_set, eval_set = data
     9 
    10 # 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
    11 IMG_ROWS = 28
    12 IMG_COLS = 28
    13 
    14 # 打印数据信息
    15 imgs, labels = train_set[0], train_set[1]
    16 print("训练数据集数量: ", len(imgs),len(labels))
    17 
    18 # 观察验证集数量
    19 imgs, labels = val_set[0], val_set[1]
    20 print("验证数据集数量: ", len(imgs),len(labels))
    21 
    22 # 观察测试集数量
    23 imgs, labels = val= eval_set[0], eval_set[1]
    24 print("测试数据集数量: ", len(imgs),len(labels))
    loading mnist dataset from ./work/mnist.json.gz ......
    mnist dataset load done
    训练数据集数量:  50000 50000
    验证数据集数量:  10000 10000
    测试数据集数量:  10000 10000

    2. 定义数据读取函数

    飞桨提供分批次读取数据函数paddle.batch,该接口是一个reader的装饰器,返回的reader将输入的reader的数据打包成指定的batch_size大小的批处理数据(batched.data)

    在定义数据读取函数中,我们需要做很多事情,包括但不限于:

    • 打乱数据,保证每轮训练读取的数据顺序不同。
    • 数据类型转换。
     1 def load_data(mode='train'):
     2     
     3     datafile = './work/mnist.json.gz'
     4     print('loading mnist dataset from {} ......'.format(datafile))
     5     # 加载json数据文件
     6     data = json.load(gzip.open(datafile))
     7     print('mnist dataset load done')
     8     # 读取到的数据区分训练集,验证集,测试集
     9     train_set, val_set, eval_set = data
    10     if mode=='train':
    11         # 获得训练数据集
    12         imgs, labels = train_set[0], train_set[1]
    13     elif mode=='valid':
    14         # 获得验证数据集
    15         imgs, labels = val_set[0], val_set[1]
    16     elif mode=='eval':
    17         # 获得测试数据集
    18         imgs, labels = eval_set[0], eval_set[1]
    19     else:
    20         raise Exception("mode can only be one of ['train', 'valid', 'eval']")
    21     print("训练数据集数量: ", len(imgs))
    22     # 获得数据集长度
    23     imgs_length = len(imgs)
    24     # 定义数据集每个数据的序号,根据序号读取数据
    25     index_list = list(range(imgs_length))
    26     # 读入数据时用到的批次大小
    27     BATCHSIZE = 100
    28     
    29     # 定义数据生成器
    30     def data_generator():
    31         if mode == 'train':
    32             # 训练模式下打乱数据
    33             random.shuffle(index_list)
    34         imgs_list = []
    35         labels_list = []
    36         for i in index_list:
    37             # 将数据处理成希望的格式,比如类型为float32,shape为[1, 28, 28]
    38             img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
    39             label = np.reshape(labels[i], [1]).astype('float32')
    40             imgs_list.append(img) 
    41             labels_list.append(label)
    42             if len(imgs_list) == BATCHSIZE:
    43                 # 获得一个batchsize的数据,并返回
    44                 yield np.array(imgs_list), np.array(labels_list)
    45                 # 清空数据读取列表
    46                 imgs_list = []
    47                 labels_list = []
    48     
    49         # 如果剩余数据的数目小于BATCHSIZE,
    50         # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
    51         if len(imgs_list) > 0:
    52             yield np.array(imgs_list), np.array(labels_list)
    53     return data_generator

    上面代码中mode参数可以取三个值中的一个,分别是train、valid、eval,选择的模式不同,读取的数据集也不同,为了兼容后面的代码,读取后的变量都相同,都是imgs、labels;

    在数据生成器中,只有在mode为train的情况下我们才考虑把读取的数据打乱;接下来是数据格式处理,目标类型是shape[1,28,28],1表示灰度图,数据类型为float32; 通过yield关键字返回一个batch的数据;在最后一个index_list中,如果imgs_list长度不满足一个batch,这时imgs_list长度不为零,会直接跳出for循环,被后面的len(imgs_list)拦截。


     3. 数据校验

    实际任务原始的数据可能存在数据很“脏”的情况,这里的“脏”多指数据标注不准确,或者是数据杂乱,格式不统一等等。

    因此,在完成数据处理函数时,我们需要执行数据校验和清理的操作。

    数据校验一般有两种方式:

    • 机器校验:加入一些校验和清理数据的操作。
    • 人工校验:先打印数据输出结果,观察是否是设置的格式。再从训练的结果验证数据处理和读取的有效性。

    机器校验

    如下代码所示,如果数据集中的图片数量和标签数量不等,说明数据逻辑存在问题,可使用assert语句校验图像数量和标签数据是否一致。

    1 imgs_length = len(imgs)
    2 
    3     assert len(imgs) == len(labels), 
    4           "length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(label))

    人工校验

    人工校验分两步,首先打印数据输出结果,观察是否是设置的格式。再从训练的结果验证数据处理和读取的有效性。

    实现数据处理和加载函数后,我们可以调用它读取一次数据,观察数据的shape和类型是否与函数中设置的一致。

    1 # 声明数据读取函数,从训练集中读取数据
    2 train_loader = load_data('train')
    3 # 以迭代的形式读取数据
    4 for batch_id, data in enumerate(train_loader()):
    5     image_data, label_data = data
    6     if batch_id == 0:
    7         # 打印数据shape和类型
    8         print(image_data.shape, label_data.shape, type(image_data), type(label_data))
    9     break
    loading mnist dataset from ./work/mnist.json.gz ......
    mnist dataset load done
    训练数据集数量:  50000
    (100, 1, 28, 28) (100, 1) <class 'numpy.ndarray'> <class 'numpy.ndarray'>

    观察训练结果

    数据处理部分后的代码多数保持不变,仅在读取数据时候调用新编写的load_data函数。由于数据格式的转换工作在load_data函数中做了一部分,所以向模型输入数据的代码变得更加简洁。下面我们使用自己实现的数据加载函数重新训练我们的神经网络。

     1 #数据处理部分之后的代码,数据读取的部分调用Load_data函数
     2 # 定义网络结构,同上一节所使用的网络结构
     3 class MNIST(fluid.dygraph.Layer):
     4     def __init__(self, name_scope):
     5         super(MNIST, self).__init__(name_scope)
     6         name_scope = self.full_name()
     7         self.fc = FC(name_scope, size=1, act=None)
     8 
     9     def forward(self, inputs):
    10         outputs = self.fc(inputs)
    11         return outputs
    12 
    13 # 训练配置,并启动训练过程
    14 with fluid.dygraph.guard():
    15     model = MNIST("mnist")
    16     model.train()
    17     #调用加载数据的函数
    18     train_loader = load_data('train')
    19     optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)
    20     EPOCH_NUM = 10
    21     for epoch_id in range(EPOCH_NUM):
    22         for batch_id, data in enumerate(train_loader()):
    23             #准备数据,变得更加简洁
    24             image_data, label_data = data
    25             image = fluid.dygraph.to_variable(image_data)
    26             label = fluid.dygraph.to_variable(label_data)
    27             
    28             #前向计算的过程
    29             predict = model(image)
    30             
    31             #计算损失,取一个批次样本损失的平均值
    32             loss = fluid.layers.square_error_cost(predict, label)
    33             avg_loss = fluid.layers.mean(loss)
    34             
    35             #每训练了100批次的数据,打印下当前Loss的情况
    36             if batch_id % 100 == 0:
    37                 print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
    38             
    39             #后向传播,更新参数的过程
    40             avg_loss.backward()
    41             optimizer.minimize(avg_loss)
    42             model.clear_gradients()
    43 
    44     #保存模型参数
    45     fluid.save_dygraph(model.state_dict(), 'mnist')
    loading mnist dataset from ./work/mnist.json.gz ......
    mnist dataset load done
    训练数据集数量:  50000
    epoch: 0, batch: 0, loss is: [24.632648]
    epoch: 0, batch: 100, loss is: [4.4261494]
    epoch: 0, batch: 200, loss is: [5.5177183]
    epoch: 0, batch: 300, loss is: [3.5427954]
    epoch: 0, batch: 400, loss is: [2.7455132]
    epoch: 1, batch: 0, loss is: [3.4030478]
    epoch: 1, batch: 100, loss is: [3.3895369]
    epoch: 1, batch: 200, loss is: [4.0297785]
    epoch: 1, batch: 300, loss is: [3.658723]
    epoch: 1, batch: 400, loss is: [3.7493572]
    epoch: 2, batch: 0, loss is: [3.4815173]
    epoch: 2, batch: 100, loss is: [3.566256]
    epoch: 2, batch: 200, loss is: [4.150691]
    epoch: 2, batch: 300, loss is: [3.3143735]
    epoch: 2, batch: 400, loss is: [2.8981738]
    epoch: 3, batch: 0, loss is: [2.9376304]
    epoch: 3, batch: 100, loss is: [3.322153]
    epoch: 3, batch: 200, loss is: [4.5626388]
    epoch: 3, batch: 300, loss is: [3.1342642]
    epoch: 3, batch: 400, loss is: [3.2983096]
    epoch: 4, batch: 0, loss is: [4.223956]
    epoch: 4, batch: 100, loss is: [2.982598]
    epoch: 4, batch: 200, loss is: [2.719622]
    epoch: 4, batch: 300, loss is: [3.712464]
    epoch: 4, batch: 400, loss is: [4.1207376]
    epoch: 5, batch: 0, loss is: [2.5053217]
    epoch: 5, batch: 100, loss is: [2.8577585]
    epoch: 5, batch: 200, loss is: [2.9564447]
    epoch: 5, batch: 300, loss is: [3.4296014]
    epoch: 5, batch: 400, loss is: [4.3093677]
    epoch: 6, batch: 0, loss is: [4.5576763]
    epoch: 6, batch: 100, loss is: [3.20943]
    epoch: 6, batch: 200, loss is: [3.327529]
    epoch: 6, batch: 300, loss is: [2.5192072]
    epoch: 6, batch: 400, loss is: [3.4901175]
    epoch: 7, batch: 0, loss is: [3.998215]
    epoch: 7, batch: 100, loss is: [4.351076]
    epoch: 7, batch: 200, loss is: [3.8231916]
    epoch: 7, batch: 300, loss is: [2.151733]
    epoch: 7, batch: 400, loss is: [2.995807]
    epoch: 8, batch: 0, loss is: [3.6070685]
    epoch: 8, batch: 100, loss is: [4.0988545]
    epoch: 8, batch: 200, loss is: [3.0984952]
    epoch: 8, batch: 300, loss is: [3.0793695]
    epoch: 8, batch: 400, loss is: [2.7344913]
    epoch: 9, batch: 0, loss is: [3.7788324]
    epoch: 9, batch: 100, loss is: [3.706921]
    epoch: 9, batch: 200, loss is: [2.7320113]
    epoch: 9, batch: 300, loss is: [3.2809222]
    epoch: 9, batch: 400, loss is: [3.8385432]
    batch size=100,数据总量为50000,所以有500个batch(0,100,200,300,400);epoch num=10,所以有10次循环(0,1,2,3,4,5,6,7,8,9)。
    最后,将上述几部分操作合并到load_data函数,方便后续调用。下面代码为完整的数据读取函数,可以通过数据加载函数load_data的输入参数mode为'train', 'valid', 'eval'选择返回的数据是训练集,验证集,测试集。
     1 #数据处理部分的展开代码
     2 # 定义数据集读取器
     3 def load_data(mode='train'):
     4 
     5     # 数据文件
     6     datafile = './work/mnist.json.gz'
     7     print('loading mnist dataset from {} ......'.format(datafile))
     8     data = json.load(gzip.open(datafile))
     9     # 读取到的数据可以直接区分训练集,验证集,测试集
    10     train_set, val_set, eval_set = data
    11 
    12     # 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
    13     IMG_ROWS = 28
    14     IMG_COLS = 28
    15     # 获得数据
    16     if mode == 'train':
    17         imgs = train_set[0]
    18         labels = train_set[1]
    19     elif mode == 'valid':
    20         imgs = val_set[0]
    21         labels = val_set[1]
    22     elif mode == 'eval':
    23         imgs = eval_set[0]
    24         labels = eval_set[1]
    25     else:
    26         raise Exception("mode can only be one of ['train', 'valid', 'eval']")
    27 
    28     imgs_length = len(imgs)
    29 
    30     assert len(imgs) == len(labels), 
    31           "length of train_imgs({}) should be the same as train_labels({})".format(
    32                   len(imgs), len(labels))
    33 
    34     index_list = list(range(imgs_length))
    35 
    36     # 读入数据时用到的batchsize
    37     BATCHSIZE = 100
    38 
    39     # 定义数据生成器
    40     def data_generator():
    41         if mode == 'train':
    42             # 训练模式下,将训练数据打乱
    43             random.shuffle(index_list)
    44         imgs_list = []
    45         labels_list = []
    46         
    47         for i in index_list:
    48             img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
    49             label = np.reshape(labels[i], [1]).astype('float32')
    50             imgs_list.append(img) 
    51             labels_list.append(label)
    52             if len(imgs_list) == BATCHSIZE:
    53                 # 产生一个batch的数据并返回
    54                 yield np.array(imgs_list), np.array(labels_list)
    55                 # 清空数据读取列表
    56                 imgs_list = []
    57                 labels_list = []
    58 
    59         # 如果剩余数据的数目小于BATCHSIZE,
    60         # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
    61         if len(imgs_list) > 0:
    62             yield np.array(imgs_list), np.array(labels_list)
    63     return data_generator

    4. 异步数据读取

    上面提到的数据读取是同步数据读取方式,针对于样本量较大、数据读取较慢的场景,建议采用异步数据读取方式,可以让数据读取和模型训练并行化,加快数据读取速度,牺牲一小部分内存换取数据读取效率的提升。

    说明:

    • 同步数据读取:每当模型需要数据的时候,运行数据读取函数获得当前批次的数据。在读取数据期间,模型一直在等待数据读取结束,获得数据后才会进行计算。
    • 异步数据读取数据读取和模型训练过程异步进行,读取到的数据先放入缓存区。模型训练完一个批次后,不用等待数据读取过程,直接从缓存区获得下一批次数据进行训练。

    使用飞桨实现异步数据读取非常简单,代码如下所示。

     1 # 定义数据读取后存放的位置,CPU或者GPU,这里使用CPU
     2 #place = fluid.CUDAPlace(0) 时,数据读到GPU上
     3 place = fluid.CPUPlace()
     4 with fluid.dygraph.guard(place):
     5     # 声明数据加载函数,使用训练模式
     6     train_loader = load_data(mode='train')
     7     # 定义DataLoader对象用于加载Python生成器产生的数据
     8     data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
     9     # 设置数据生成器
    10     data_loader.set_batch_generator(train_loader, places=place)
    11     # 迭代的读取数据并打印数据的形状
    12     for i, data in enumerate(data_loader):
    13         image_data, label_data = data
    14         print(i, image_data.shape, label_data.shape)
    15         if i>=5:
    16             break

    上面的capacity=5,表示异步list的最大长度。

    loading mnist dataset from ./work/mnist.json.gz ......
    0 [100, 1, 28, 28] [100, 1]
    1 [100, 1, 28, 28] [100, 1]
    2 [100, 1, 28, 28] [100, 1]
    3 [100, 1, 28, 28] [100, 1]
    4 [100, 1, 28, 28] [100, 1]
    5 [100, 1, 28, 28] [100, 1]
    与同步数据读取相比,异步数据读取仅增加了三行代码,如下所示。
    1 place = fluid.CPUPlace() 
    2 data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
    3 data_loader.set_batch_generator(train_loader, place)

    我们展开解读一下:

    • 第一行代码: 设置读取的数据是放在CPU还是GPU上。
    • 第二行代码: 创建一个DataLoader对象用于加载Python生成器产生的数据。数据会由Python线程预先读取,并异步送入一个队列中。fluid.io.DataLoader.from_generator参数名称、参数含义、默认值如下:

    参数名和默认值如下:

    • feed_list=None,
    • capacity=None,
    • use_double_buffer=True,
    • iterable=True,
    • return_list=False

    参数含义如下:

    • feed_list 仅在paddle静态图中使用,动态图中设置为None,本教程默认使用动态图的建模方式。
    • capacity 表示在DataLoader中维护的队列容量,如果读取数据的速度很快,建议设置为更大的值
    • use_double_buffer 是一个布尔型的参数,设置为True时Dataloader会预先异步读取下一个batch的数据放到缓存区
    • iterable 表示创建的Dataloader对象是否是可迭代的,一般设置为True。
    • return_list 在动态图下需要设置为True
    • 第三行代码: 用创建的DataLoader对象设置一个数据生成器set_batch_generator,输入的参数是一个Python数据生成器train_loader和服务器资源类型place(标明CPU还是GPU)。

    异步数据读取并训练的完整案例代码如下:

     1 with fluid.dygraph.guard():
     2     model = MNIST("mnist")
     3     model.train()
     4     #调用加载数据的函数
     5     train_loader = load_data('train')
     6     # 创建异步数据读取器
     7     place = fluid.CPUPlace()
     8     data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
     9     data_loader.set_batch_generator(train_loader, places=place)
    10     
    11     optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)
    12     EPOCH_NUM = 3
    13     for epoch_id in range(EPOCH_NUM):
    14         for batch_id, data in enumerate(data_loader):
    15             image_data, label_data = data
    16             image = fluid.dygraph.to_variable(image_data)
    17             label = fluid.dygraph.to_variable(label_data)
    18             
    19             predict = model(image)
    20             
    21             loss = fluid.layers.square_error_cost(predict, label)
    22             avg_loss = fluid.layers.mean(loss)
    23             
    24             if batch_id % 200 == 0:
    25                 print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
    26             
    27             avg_loss.backward()
    28             optimizer.minimize(avg_loss)
    29             model.clear_gradients()
    30 
    31     fluid.save_dygraph(model.state_dict(), 'mnist')
    loading mnist dataset from ./work/mnist.json.gz ......
    epoch: 0, batch: 0, loss is: [41.8419]
    epoch: 0, batch: 200, loss is: [4.8599553]
    epoch: 0, batch: 400, loss is: [3.949173]
    epoch: 1, batch: 0, loss is: [3.6606312]
    epoch: 1, batch: 200, loss is: [3.593772]
    epoch: 1, batch: 400, loss is: [3.3966932]
    epoch: 2, batch: 0, loss is: [3.3882492]
    epoch: 2, batch: 200, loss is: [3.512473]
    epoch: 2, batch: 400, loss is: [3.8485198]

    从异步数据读取的训练结果来看,损失函数下降与同步数据读取训练结果基本一致。


  • 相关阅读:
    scrapy-redis使用以及剖析
    完全理解 Python 迭代对象、迭代器、生成器
    Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
    HTTP状态码
    Py西游攻关之RabbitMQ、Memcache、Redis
    Django contenttypes 应用
    cookie和session 以及Django中应用
    RESTful规范
    rest_framework框架的认识
    vue 总结
  • 原文地址:https://www.cnblogs.com/yuzaihuan/p/12286354.html
Copyright © 2011-2022 走看看