百度PaddlePaddle入门-10
Posted yuzaihuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了百度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]
从异步数据读取的训练结果来看,损失函数下降与同步数据读取训练结果基本一致。
以上是关于百度PaddlePaddle入门-10的主要内容,如果未能解决你的问题,请参考以下文章
百度PaddlePaddle入门-14(多个CPU加速训练)
百度paddlepaddle深度学习平台全套入门教程 ‖ 资源