上篇文章講了卷積神經網絡的基本知識,原本這篇文章準備繼續深刻講CNN的相關知識和手寫CNN,可是有不少同窗跟我發郵件或私信問我關於PaddlePaddle如何讀取數據、作數據預處理相關的內容。網上看的不少教程都是幾個常見的例子,數據集不須要本身準備,因此不須要關心,可是實際作項目的時候作數據預處理感受一頭霧水,因此我就寫一篇文章彙總一下,講講如何用PaddlePaddle作數據預處理。html
PaddlePaddle的基本數據格式python
根據官網的資料,總結出PaddlePaddle支持多種不一樣的數據格式,包括四種數據類型和三種序列格式:git
四種數據類型:github
api以下:api
paddle.v2.data_type.
dense_vector
(dim, seq_type=0)
- 說明:稠密向量,輸入特徵是一個稠密的浮點向量。舉個例子,手寫數字識別裏的輸入圖片是28*28的像素,Paddle的神經網絡的輸入應該是一個784維的稠密向量。
- 參數:
- dim(int) 向量維度
- seq_type(int)輸入的序列格式
- 返回類型:InputType
paddle.v2.data_type.
sparse_binary_vector
(dim, seq_type=0)
- 說明:稀疏的二值向量。輸入特徵是一個稀疏向量,這個向量的每一個元素要麼是0,要麼是1
- 參數:同上
- 返回類型:同上
paddle.v2.data_type.
sparse_vector
(dim, seq_type=0)
- 說明:稀疏向量,向量裏大多數元素是0,其餘的值能夠是任意的浮點值
- 參數:同上
- 返回類型:同上
paddle.v2.data_type.
integer_value
(value_range, seq_type=0)
- 說明:整型格式
- 參數:
- seq_type(int):輸入的序列格式
- value_range(int):每一個元素的範圍
- 返回類型:InputType
三種序列格式:緩存
api以下:網絡
paddle.v2.data_type.dense_vector_sequence
(dim, seq_type=0)
- 說明:稠密向量的序列格式
- 參數:dim(int):稠密向量的維度
- 返回類型:InputType
paddle.v2.data_type.sparse_binary_vector_sequence
(dim, seq_type=0)
- 說明:稀疏的二值向量序列。每一個序列裏的元素要麼是0要麼是1
- 參數:dim(int):稀疏向量的維度
- 返回類型:InputType
paddle.v2.data_type.sparse_non_value_slot
(dim, seq_type=0)
- 說明:稀疏的向量序列。每一個序列裏的元素要麼是0要麼是1
- 參數:
- dim(int):稀疏向量的維度
- seq_type(int):輸入的序列格式
- 返回類型:InputType
paddle.v2.data_type.sparse_value_slot
(dim, seq_type=0)
- 說明:稀疏的向量序列,向量裏大多數元素是0,其餘的值能夠是任意的浮點值
- 參數:
- dim(int):稀疏向量的維度
- seq_type(int):輸入的序列格式
- 返回類型:InputType
paddle.v2.data_type.integer_value_sequence
(value_range, seq_type=0)
- 說明:value_range(int):每一個元素的範圍
不一樣的數據類型和序列模式返回的格式不一樣,以下表:多線程
其中f表示浮點數,i表示整數框架
注意:對sparse_binary_vector和sparse_float_vector,PaddlePaddle存的是有值位置的索引。例如,dom
[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:從本地、網絡、分佈式文件系統HDFS等讀取數據,也可隨機生成數據,並返回一個或多個數據項。
reader creator:一個返回reader的函數。
reader decorator:裝飾器,可組合一個或多個reader。
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
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