上篇文章讲了卷积神经网络的基本知识,本来这篇文章准备继续深入讲CNN的相关知识和手写CNN,但是有很多同学跟我发邮件或私信问我关于PaddlePaddle如何读取数据、做数据预处理相关的内容。网上看的很多教程都是几个常见的例子,数据集不需要自己准备,所以不需要关心,但是实际做项目的时候做数据预处理感觉一头雾水,所以我就写一篇文章汇总一下,讲讲如何用PaddlePaddle做数据预处理。
PaddlePaddle的基本数据格式
根据官网的资料,总结出PaddlePaddle支持多种不同的数据格式,包括四种数据类型和三种序列格式:
四种数据类型:
api如下:
paddle.v2.data_type.dense_vector
(dim, seq_type=0)paddle.v2.data_type.sparse_binary_vector
(dim, seq_type=0)paddle.v2.data_type.sparse_vector
(dim, seq_type=0) paddle.v2.data_type.integer_value
(value_range, seq_type=0)三种序列格式:
api如下:
paddle.v2.data_type.dense_vector_sequence
(dim, seq_type=0)paddle.v2.data_type.sparse_binary_vector_sequence
(dim, seq_type=0)paddle.v2.data_type.sparse_non_value_slot
(dim, seq_type=0)paddle.v2.data_type.sparse_value_slot
(dim, seq_type=0)paddle.v2.data_type.integer_value_sequence
(value_range, seq_type=0) 不同的数据类型和序列模式返回的格式不同,如下表:
其中f表示浮点数,i表示整数
注意:对sparse_binary_vector和sparse_float_vector,PaddlePaddle存的是有值位置的索引。例如,
[0, 1, 1, 0, 0]
,类型是sparse_binary_vector,返回的是 [1, 2]
。(因为只有第1位和第2位有值)[0, 0.5, 0.7, 0, 0]
,类型是sparse_float_vector,返回的是 [(1, 0.5), (2, 0.7)]
。(因为只有第一位和第二位有值,分别是0.5和0.7)PaddlePaddle的数据读取方式
我们了解了上文的四种基本数据格式和三种序列模式后,在处理自己的数据时可以根据需求选择,但是处理完数据后如何把数据放到模型里去训练呢?我们知道,基本的方法一般有两种:
在PaddlePaddle中我们可以有三种模式来读取数据:分别是reader、reader creator和reader decorator,这三者有什么区别呢?
reader
我们先以reader为例,为房价数据(斯坦福吴恩达的公开课第一课举例的数据)创建一个reader:
reader = paddle.dataset.uci_housing.train()
2. 创建一个shuffle_reader,把上一步的reader放进去,配置buf_size就可以读取buf_size大小的数据自动做shuffle,让数据打乱,随机化
shuffle_reader = paddle.reader.shuffle(reader,buf_size= 100)
3.创建一个batch_reader,把上一步混洗好的shuffle_reader放进去,给定batch_size,即可创建。
batch_reader = paddle.batch(shuffle_reader,batch_size = 2)
这三种方式也可以组合起来放一块:
reader = paddle.batch(
paddle.reader.shuffle(
uci_housing.train(),
buf_size = 100),
batch_size=2)
可以以一个直观的图来表示:
从图中可以看到,我们可以直接从原始数据集里拿去数据,用reader读取,一条条灌倒shuffle_reader里,在本地随机化,把数据打乱,做shuffle,然后把shuffle后的数据,一个batch一个batch的形式,批量的放到训练器里去进行每一步的迭代和训练。 流程简单,而且只需要使用一行代码即可实现整个过程。
reader creator
如果想要生成一个简单的随机数据,以reader creator为例:
def reader_creator():
def reader():
while True:
yield numpy.random.uniform(-1,1,size=784)
return reader
源码见creator.py, 支持四种格式:np_array,text_file,RecordIO和cloud_reader
1 __all__ = ['np_array', 'text_file', "cloud_reader"]
2
3
4 def np_array(x):
5 """
6 Creates a reader that yields elements of x, if it is a
7 numpy vector. Or rows of x, if it is a numpy matrix.
8 Or any sub-hyperplane indexed by the highest dimension.
9 :param x: the numpy array to create reader from.
10 :returns: data reader created from x.
11 """
12
13 def reader():
14 if x.ndim < 1:
15 yield x
16
17 for e in x:
18 yield e
19
20 return reader
21
22
23 def text_file(path):
24 """
25 Creates a data reader that outputs text line by line from given text file.
26 Trailing new line ('\\\\n') of each line will be removed.
27 :path: path of the text file.
28 :returns: data reader of text file
29 """
30
31 def reader():
32 f = open(path, "r")
33 for l in f:
34 yield l.rstrip('\n')
35 f.close()
36
37 return reader
38
39
40 def recordio(paths, buf_size=100):
41 """
42 Creates a data reader from given RecordIO file paths separated by ",",
43 glob pattern is supported.
44 :path: path of recordio files, can be a string or a string list.
45 :returns: data reader of recordio files.
46 """
47
48 import recordio as rec
49 import paddle.v2.reader.decorator as dec
50 import cPickle as pickle
51
52 def reader():
53 if isinstance(paths, basestring):
54 path = paths
55 else:
56 path = ",".join(paths)
57 f = rec.reader(path)
58 while True:
59 r = f.read()
60 if r is None:
61 break
62 yield pickle.loads(r)
63 f.close()
64
65 return dec.buffered(reader, buf_size)
66
67
68 pass_num = 0
69
70
71 def cloud_reader(paths, etcd_endpoints, timeout_sec=5, buf_size=64):
72 """
73 Create a data reader that yield a record one by one from
74 the paths:
75 :paths: path of recordio files, can be a string or a string list.
76 :etcd_endpoints: the endpoints for etcd cluster
77 :returns: data reader of recordio files.
78 .. code-block:: python
79 from paddle.v2.reader.creator import cloud_reader
80 etcd_endpoints = "http://127.0.0.1:2379"
81 trainer.train.(
82 reader=cloud_reader(["/work/dataset/uci_housing/uci_housing*"], etcd_endpoints),
83 )
84 """
85 import os
86 import cPickle as pickle
87 import paddle.v2.master as master
88 c = master.client(etcd_endpoints, timeout_sec, buf_size)
89
90 if isinstance(paths, basestring):
91 path = [paths]
92 else:
93 path = paths
94 c.set_dataset(path)
95
96 def reader():
97 global pass_num
98 c.paddle_start_get_records(pass_num)
99 pass_num += 1
100
101 while True:
102 r, e = c.next_record()
103 if not r:
104 if e != -2:
105 print "get record error: ", e
106 break
107 yield pickle.loads(r)
108
109 return reader
View Code
reader decorator
如果想要读取同时读取两部分的数据,那么可以定义两个reader,合并后对其进行shuffle。如我想读取所有用户对比车系的数据和浏览车系的数据,可以定义两个reader,分别为contrast()和view(),然后通过预定义的reader decorator缓存并组合这些数据,在对合并后的数据进行乱序操作。源码见decorator.py
data = paddle.reader.shuffle(
paddle.reader.compose(
paddle.reader(contradt(contrast_path),buf_size = 100),
paddle.reader(view(view_path),buf_size = 200),
500)
这样有一个很大的好处,就是组合特征来训练变得更容易了!传统的跑模型的方法是,确定label和feature,尽可能多的找合适的feature扔到模型里去训练,这样我们就需要做一张大表,训练完后我们可以分析某些特征的重要性然后重新增加或减少一些feature来进行训练,这样我们有需要对原来的label-feature表进行修改,如果数据量小没啥影响,就是麻烦点,但是数据量大的话需要每一次增加feature,和主键、label来join的操作都会很耗时,如果采取这种方式的话,我们可以对某些同一类的特征做成一张表,数据存放的地址存为一个变量名,每次跑模型的时候想选取几类特征,就创建几个reader,用reader decorator 组合起来,最后再shuffle灌倒模型里去训练。这!样!是!不!是!很!方!便!
如果没理解,我举一个实例,假设我们要预测用户是否会买车,label是买车 or 不买车,feature有浏览车系、对比车系、关注车系的功能偏好等等20个,传统的思维是做成这样一张表:
如果想要减少feature_2,看看feature_2对模型的准确率影响是否很大,那么我们需要在这张表里去掉这一列,想要增加一个feature的话,也需要在feature里增加一列,如果用reador decorator的话,我们可以这样做数据集:
把相同类型的feature放在一起,不用频繁的join减少时间,一共做四个表,创建4个reador:
data = paddle.reader.shuffle(
paddle.reader.compose(
paddle.reader(table1(table1_path),buf_size = 100),
paddle.reader(table2(table2_path),buf_size = 100),
paddle.reader(table3(table3_path),buf_size = 100),
paddle.reader(table4(table4_path),buf_size = 100),
500)
如果新发现了一个特征,想尝试这个特征对模型提高准确率有没有用,可以再单独把这个特征数据提取出来,再增加一个reader,用reader decorator组合起来,shuffle后放入模型里跑就行了。
PaddlePaddle的数据预处理实例
还是以手写数字为例,对数据进行处理后并划分train和test,只需要4步即可:
1 import paddle.v2.dataset.common
2 import subprocess
3 import numpy
4 import platform
5 __all__ = ['train', 'test', 'convert']
6
7 URL_PREFIX = 'http://yann.lecun.com/exdb/mnist/'
8 TEST_IMAGE_URL = URL_PREFIX + 't10k-images-idx3-ubyte.gz'
9 TEST_IMAGE_MD5 = '9fb629c4189551a2d022fa330f9573f3'
10 TEST_LABEL_URL = URL_PREFIX + 't10k-labels-idx1-ubyte.gz'
11 TEST_LABEL_MD5 = 'ec29112dd5afa0611ce80d1b7f02629c'
12 TRAIN_IMAGE_URL = URL_PREFIX + 'train-images-idx3-ubyte.gz'
13 TRAIN_IMAGE_MD5 = 'f68b3c2dcbeaaa9fbdd348bbdeb94873'
14 TRAIN_LABEL_URL = URL_PREFIX + 'train-labels-idx1-ubyte.gz'
15 TRAIN_LABEL_MD5 = 'd53e105ee54ea40749a09fcbcd1e9432'
2.创建reader creator
1 def reader_creator(image_filename, label_filename, buffer_size):
2 # 创建一个reader
3 def reader():
4 if platform.system() == 'Darwin':
5 zcat_cmd = 'gzcat'
6 elif platform.system() == 'Linux':
7 zcat_cmd = 'zcat'
8 else:
9 raise NotImplementedError()
10
11 m = subprocess.Popen([zcat_cmd, image_filename], stdout=subprocess.PIPE)
12 m.stdout.read(16)
13
14 l = subprocess.Popen([zcat_cmd, label_filename], stdout=subprocess.PIPE)
15 l.stdout.read(8)
16
17 try: # reader could be break.
18 while True:
19 labels = numpy.fromfile(
20 l.stdout, 'ubyte', count=buffer_size).astype("int")
21
22 if labels.size != buffer_size:
23 break # numpy.fromfile returns empty slice after EOF.
24
25 images = numpy.fromfile(
26 m.stdout, 'ubyte', count=buffer_size * 28 * 28).reshape(
27 (buffer_size, 28 * 28)).astype('float32')
28
29 images = images / 255.0 * 2.0 - 1.0
30
31 for i in xrange(buffer_size):
32 yield images[i, :], int(labels[i])
33 finally:
34 m.terminate()
35 l.terminate()
36
37 return reader
3.创建训练集和测试集
1 def train():
2 """
3 创建mnsit的训练集 reader creator
4 返回一个reador creator,每个reader里的样本都是图片的像素值,在区间[0,1]内,label为0~9
5 返回:training reader creator
6 """
7 return reader_creator(
8 paddle.v2.dataset.common.download(TRAIN_IMAGE_URL, 'mnist',
9 TRAIN_IMAGE_MD5),
10 paddle.v2.dataset.common.download(TRAIN_LABEL_URL, 'mnist',
11 TRAIN_LABEL_MD5), 100)
12
13
14 def test():
15 """
16 创建mnsit的测试集 reader creator
17 返回一个reador creator,每个reader里的样本都是图片的像素值,在区间[0,1]内,label为0~9
18 返回:testreader creator
19 """
20 return reader_creator(
21 paddle.v2.dataset.common.download(TEST_IMAGE_URL, 'mnist',
22 TEST_IMAGE_MD5),
23 paddle.v2.dataset.common.download(TEST_LABEL_URL, 'mnist',
24 TEST_LABEL_MD5), 100)
4.下载数据并转换成相应格式
1 def fetch():
2 paddle.v2.dataset.common.download(TRAIN_IMAGE_URL, 'mnist', TRAIN_IMAGE_MD5)
3 paddle.v2.dataset.common.download(TRAIN_LABEL_URL, 'mnist', TRAIN_LABEL_MD5)
4 paddle.v2.dataset.common.download(TEST_IMAGE_URL, 'mnist', TEST_IMAGE_MD5)
5 paddle.v2.dataset.common.download(TEST_LABEL_URL, 'mnist', TRAIN_LABEL_MD5)
6
7
8 def convert(path):
9 """
10 将数据格式转换为 recordio format
11 """
12 paddle.v2.dataset.common.convert(path, train(), 1000, "minist_train")
13 paddle.v2.dataset.common.convert(path, test(), 1000, "minist_test")
如果想换成自己的训练数据,只需要按照步骤改成自己的数据地址,创建相应的reader creator(或者reader decorator)即可。
这是图像的例子,如果我们想训练一个文本模型,做一个情感分析,这个时候如何处理数据呢?步骤也很简单。
假设我们有一堆数据,每一行为一条样本,以 \t
分隔,第一列是类别标签,第二列是输入文本的内容,文本内容中的词语以空格分隔。以下是两条示例数据:
positive 今天终于试了自己理想的车 外观太骚气了 而且中控也很棒
negative 这台车好贵 而且还费油 性价比太低了
现在开始做数据预处理
1.创建reader
1 def train_reader(data_dir, word_dict, label_dict):
2 def reader():
3 UNK_ID = word_dict["<UNK>"]
4 word_col = 0
5 lbl_col = 1
6
7 for file_name in os.listdir(data_dir):
8 with open(os.path.join(data_dir, file_name), "r") as f:
9 for line in f:
10 line_split = line.strip().split("\t")
11 word_ids = [
12 word_dict.get(w, UNK_ID)
13 for w in line_split[word_col].split()
14 ]
15 yield word_ids, label_dict[line_split[lbl_col]]
16
17 return reader
返回类型为: paddle.data_type.integer_value_sequence
(词语在字典的序号)和 paddle.data_type.integer_value
(类别标签)
2.组合读取方式
1 train_reader = paddle.batch(
2 paddle.reader.shuffle(
3 reader.train_reader(train_data_dir, word_dict, lbl_dict),
4 buf_size=1000),
5 batch_size=batch_size)
完整的代码如下(加上了划分train和test部分):
1 import os
2
3
4 def train_reader(data_dir, word_dict, label_dict):
5 """
6 创建训练数据reader
7 :param data_dir: 数据地址.
8 :type data_dir: str
9 :param word_dict: 词典地址,
10 词典里必须有 "UNK" .
11 :type word_dict:python dict
12 :param label_dict: label 字典的地址
13 :type label_dict: Python dict
14 """
15
16 def reader():
17 UNK_ID = word_dict["<UNK>"]
18 word_col = 1
19 lbl_col = 0
20
21 for file_name in os.listdir(data_dir):
22 with open(os.path.join(data_dir, file_name), "r") as f:
23 for line in f:
24 line_split = line.strip().split("\t")
25 word_ids = [
26 word_dict.get(w, UNK_ID)
27 for w in line_split[word_col].split()
28 ]
29 yield word_ids, label_dict[line_split[lbl_col]]
30
31 return reader
32
33
34 def test_reader(data_dir, word_dict):
35 """
36 创建测试数据reader
37 :param data_dir: 数据地址.
38 :type data_dir: str
39 :param word_dict: 词典地址,
40 词典里必须有 "UNK" .
41 :type word_dict:python dict
42 """
43
44 def reader():
45 UNK_ID = word_dict["<UNK>"]
46 word_col = 1
47
48 for file_name in os.listdir(data_dir):
49 with open(os.path.join(data_dir, file_name), "r") as f:
50 for line in f:
51 line_split = line.strip().split("\t")
52 if len(line_split) < word_col: continue
53 word_ids = [
54 word_dict.get(w, UNK_ID)
55 for w in line_split[word_col].split()
56 ]
57 yield word_ids, line_split[word_col]
58
59 return reader
总结
这篇文章主要讲了在paddlepaddle里如何加载自己的数据集,转换成相应的格式,并划分train和test。我们在使用一个框架的时候通常会先去跑几个简单的demo,但是如果不用常见的demo的数据,自己做一个实际的项目,完整的跑通一个模型,这才代表我们掌握了这个框架的基本应用知识。跑一个模型第一步就是数据预处理,在paddlepaddle里,提供的方式非常简单,但是有很多优点:
而我之前使用过mxnet来训练车牌识别的模型,50w的图片数据想要一次训练是非常慢的,这样的话就有两个解决方法:一是批量训练,这一点大多数的框架都会有, 二是转换成mxnet特有的rec格式,提高读取效率,可以通过im2rec.py将图片转换,比较麻烦,如果是tesnorflow,也有相对应的特定格式tfrecord,这几种方式各有优劣,从易用性上,paddlepaddle是比较简单的。
这篇文章没有与上篇衔接起来,因为看到有好几封邮件都有问怎么自己加载数据训练,所以就决定插入一节先把这个写了。下篇文章我们接着讲CNN的进阶知识。下周见^_^!
参考文章:
1.官网说明:http://doc.paddlepaddle.org/develop/doc_cn/getstarted/concepts/use_concepts_cn.html