本篇主要內(nèi)容:
1、自定義一個簡單的數(shù)據(jù)集
2、自定義簡單的圖片數(shù)據(jù)集示例
3月幌、設置Batch_size以及數(shù)據(jù)集打亂
4、數(shù)據(jù)集讀取提速操作
一悬蔽、為什么要寫Reader飞醉?
簡單來說,Reader是一種數(shù)據(jù)讀取器,具有常用數(shù)據(jù)處理操作缅帘,可以快速定義自己的數(shù)據(jù)集轴术。
在上一篇中,在定義數(shù)據(jù)和數(shù)據(jù)讀取方面的代碼非常短钦无,5行不到逗栽。
既然可以做到如此的短,那么為什么還要把這些封裝成Reader(數(shù)據(jù)讀取器)呢失暂?
原因也很多彼宠,看看有沒有你想要的。
1弟塞、快速對數(shù)據(jù)進行打亂凭峡、分組、多線程處理
2决记、更方便進行異步處理
3摧冀、速度有提升空間
4、邏輯結構更加清晰系宫,后期維護更加簡便索昂!
二、寫一個簡單的Reader
先直接上代碼扩借!
import paddle.fluid as fluid
# 1 初始化環(huán)境
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 2 寫一個簡單的Reader
def reader():
data = [i for i in range(10)]
for sample in data:
yield sample
# 3 進一步封裝
batch_size = 8
train_reader = fluid.io.batch(reader, batch_size=batch_size)
# 4 遍歷一下封裝后的數(shù)據(jù)
for i, data in enumerate(train_reader()):
print("第", i, "輪\t", len(data), "\t", data)
輸出結果為
>>>
第 0 輪 8 [0, 1, 2, 3, 4, 5, 6, 7]
第 1 輪 2 [8, 9]
其中第一部分不必多說椒惨,正常的初始化操作。
第二部分就是一個簡單的生成數(shù)據(jù)data = [i for i in range(10)]
然后再遍歷這個數(shù)據(jù)潮罪,每遍歷一條數(shù)據(jù)就使用yield
進行返回康谆。
yield
可以粗略的理解為返回當前的變量,但不終止循環(huán)嫉到。
如果每次都把全部數(shù)據(jù)return
掉秉宿,10條數(shù)據(jù)還好說,但數(shù)據(jù)量多了就不太合適了屯碴。
所以這里使用yield來進行“吐”數(shù)據(jù)描睦。
那么數(shù)據(jù)讀取部分寫完了,接下來該做些什么呢导而?
為了更好的進行訓練忱叭,我們還需要設置Batch_size,這也就是第三部分今艺。
fluid.io.batch(reader = 定義Reader的函數(shù)名, batch_size = Batch_size大小)
注意reader =
的是函數(shù)名reader
韵丑,而不是reader()
!
除此之外虚缎,如果還想對數(shù)據(jù)進行打亂撵彻,還可以在這時增加打亂操作
reader = fluid.io.shuffle(reader, buf_size=1024)
train_reader = fluid.io.batch(reader, batch_size=batch_size)
這里的buf_size
是打亂時的緩沖區(qū)大小钓株,設置為5則視為每5條數(shù)據(jù)劃分為1組,每組數(shù)據(jù)組內(nèi)順序打亂陌僵。
我們設置為buf_size
為5轴合,batch_size
為10,看看會輸出什么碗短。
>>>
第 0 輪 10 [4, 0, 2, 3, 1, 9, 6, 8, 5, 7]
>>>
第 0 輪 10 [2, 0, 1, 3, 4, 5, 8, 9, 6, 7]
>>>
第 0 輪 10 [0, 4, 1, 2, 3, 6, 9, 5, 8, 7]
可以看到前5條數(shù)據(jù)總是0-4之間的數(shù)字受葛,而后5條總是5-10。
那么buf_size
是不是可以設置很大呢偎谁?
當然可以总滩,即使設置為1024都沒事,并不會一次性讀取那么多的數(shù)據(jù)再進行打亂
三巡雨、復雜的Reader--自定義圖片數(shù)據(jù)集
以簡單驗證碼數(shù)據(jù)集為例(Demo數(shù)據(jù)集在文末GitHub鏈接中可找到)
貼一下數(shù)據(jù)集大致結構:
文件夾下有2000張單個數(shù)字的圖片闰渔,每個圖片命名為索引號.jpg
而標簽數(shù)據(jù)集在一個文本里正序排列。
接下來我們就可以寫一個Reader
import numpy as np
import PIL.Image as Image
data_path = "./data"
save_model_path = "./model"
# 讀取標簽數(shù)據(jù)
with open(data_path + "/OCR_100P.txt", 'rt') as f:
labels = f.read()
# 構建Reader
def reader():
for i in range(1, 2000):
im = Image.open(data_path + "/" + str(i) + ".jpg").convert('L') # 使用Pillow讀取圖片
im = np.array(im).reshape(1, 1, 30, 15).astype(np.float32) # NCHW格式
label = labels[i - 1] # 因為循環(huán)中i是從1開始迭代的铐望,所有這里需要減去1
yield im, label
這里需要注意的是冈涧,PaddlePaddle接收的是Numpy的數(shù)組對象,而不是Python內(nèi)置的列表對象蝌以。所以此處使用Numpy轉換一下格式。(其實label
也可以使用Numpy轉換一下)
除此之外何之,還需要為每一個Numpy對象補上一個“1”跟畅,代表返回的是單條數(shù)據(jù)。
舉個例子:一張三通道的32x32圖片溶推,正常的shape應該為3x32x32(通道數(shù)x長x寬)徊件,如果直接返回的是3x32x32,可能會被誤以為是一次返回了3條數(shù)據(jù)蒜危,每一條是1x32x32虱痕。
我們補上1之后變成1x3x32x32,程序就能掙錢認出這是1張3通道32高度32寬度的圖像辐赞。
同理部翘,目標檢測任務會輸入圖像、標記框响委、標簽等數(shù)據(jù)新思,可以模仿上方操作進行喂數(shù)據(jù)。
四赘风、提速方案
1夹囚、異步讀取
文章前面些的都是同步讀取方案,如果想提升讀取速度邀窃、實現(xiàn)實時傳入數(shù)據(jù)并預測等荸哟,則需要考慮使用異步讀取方案。
可從官網(wǎng)文檔中找到相關說明
https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/user_guides/howto/prepare_data/use_py_reader.html
但需要注意的是,異步一定比同步快嗎鞍历?
異步并非比同步快舵抹,當數(shù)據(jù)加載速度跟不上網(wǎng)絡運算的速度時,異步才會為訓練總進程提速堰燎。數(shù)據(jù)讀取完畢之后掏父,當網(wǎng)絡部分運算完畢,才有“位置”供數(shù)據(jù)進入時數(shù)據(jù)才真正開始喂進網(wǎng)絡秆剪。若網(wǎng)絡部分一直在運算赊淑,那么即使數(shù)據(jù)提前讀取了好幾組,也是沒有“位置”進入網(wǎng)絡的仅讽。
當然陶缺,異步是多線程,占用的開銷不會比同步少洁灵。
2饱岸、多線程數(shù)據(jù)集處理
如果數(shù)據(jù)的預處理部分特別繁雜,嚴重拖慢了程序運行速度徽千,多線程數(shù)據(jù)集處理則顯得很重要苫费。(實際上這種情況并不是很多)
https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/io_cn/xmap_readers_cn.html#xmap-readers
應用在剛剛的自定義圖片數(shù)據(jù)集的Reader中,增加歸一化這一步預處理操作双抽。
# 構建Reader
def reader():
for i in range(1, 2000):
im = Image.open(data_path + "/" + str(i) + ".jpg").convert('L') # 使用Pillow讀取圖片
im = np.array(im).reshape(1, 1, 30, 15).astype(np.float32) # NCHW格式
label = labels[i - 1] # 因為循環(huán)中i是從1開始迭代的百框,所有這里需要減去1
yield im, label
def normalized(sample):
im, label = sample
im /= 255 # 每個數(shù)字除以255, 這樣可以保證每個數(shù)字都在0-1之間牍汹,提升模型訓練速度和效果
return im, label
reader = fluid.io.xmap_readers(normalized, reader, process_num=8, buffer_size=10)
這里同樣需要注意傳入的normalized
铐维、reader
均為函數(shù)名,并非調(diào)用函數(shù)(在函數(shù)名后加括號)慎菲,這里的' process_num'為線程數(shù)嫁蛇,推薦為CPU線程數(shù)的60%,因為部分運算操作仍需要借助CPU露该,所以盡量不去大幅度占用資源睬棚,盡管這些與CPU線程數(shù)的關系不是很大。
示例代碼以及數(shù)據(jù)集
https://github.com/GT-ZhangAcer/DLExample/tree/master/easy02_Reader