百度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. 数据校验

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

扫描二维码关注公众号,回复: 9037420 查看本文章

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

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

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

机器校验

如下代码所示,如果数据集中的图片数量和标签数量不等,说明数据逻辑存在问题,可使用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]

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


猜你喜欢

转载自www.cnblogs.com/yuzaihuan/p/12286354.html