深度學(xué)習(xí)開發(fā)篇(三):在PaddlePaddle中使用自定義數(shù)據(jù)集

本篇主要內(nèi)容:
1、自定義一個簡單的數(shù)據(jù)集
2、自定義簡單的圖片數(shù)據(jù)集示例
3、設(shè)置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ù)進(jìn)行打亂、分組、多線程處理
2、更方便進(jìn)行異步處理
3、速度有提升空間
4、邏輯結(jié)構(gòu)更加清晰,后期維護(hù)更加簡便!

二、寫一個簡單的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 進(jìn)一步封裝
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)

輸出結(jié)果為

>>>
第 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進(jìn)行返回。
yield可以粗略的理解為返回當(dāng)前的變量,但不終止循環(huán)。

如果每次都把全部數(shù)據(jù)return掉,10條數(shù)據(jù)還好說,但數(shù)據(jù)量多了就不太合適了。

所以這里使用yield來進(jìn)行“吐”數(shù)據(jù)。

那么數(shù)據(jù)讀取部分寫完了,接下來該做些什么呢?

為了更好的進(jìn)行訓(xùn)練,我們還需要設(shè)置Batch_size,這也就是第三部分。

fluid.io.batch(reader = 定義Reader的函數(shù)名, batch_size = Batch_size大小)

注意reader =的是函數(shù)名reader,而不是reader()

除此之外,如果還想對數(shù)據(jù)進(jìn)行打亂,還可以在這時增加打亂操作

reader = fluid.io.shuffle(reader, buf_size=1024)
train_reader = fluid.io.batch(reader, batch_size=batch_size)

這里的buf_size是打亂時的緩沖區(qū)大小,設(shè)置為5則視為每5條數(shù)據(jù)劃分為1組,每組數(shù)據(jù)組內(nèi)順序打亂。
我們設(shè)置為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是不是可以設(shè)置很大呢?

當(dāng)然可以,即使設(shè)置為1024都沒事,并不會一次性讀取那么多的數(shù)據(jù)再進(jìn)行打亂

三、復(fù)雜的Reader--自定義圖片數(shù)據(jù)集

以簡單驗證碼數(shù)據(jù)集為例(Demo數(shù)據(jù)集在文末GitHub鏈接中可找到)

貼一下數(shù)據(jù)集大致結(jié)構(gòu):
文件夾下有2000張單個數(shù)字的圖片,每個圖片命名為索引號.jpg
而標(biāo)簽數(shù)據(jù)集在一個文本里正序排列。

圖像數(shù)據(jù)集

標(biāo)簽數(shù)據(jù)集

接下來我們就可以寫一個Reader

import numpy as np
import PIL.Image as Image

data_path = "./data"
save_model_path = "./model"

# 讀取標(biāo)簽數(shù)據(jù)
with open(data_path + "/OCR_100P.txt", 'rt') as f:
    labels = f.read()


# 構(gòu)建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轉(zhuǎn)換一下格式。(其實label也可以使用Numpy轉(zhuǎn)換一下)

除此之外,還需要為每一個Numpy對象補(bǔ)上一個“1”,代表返回的是單條數(shù)據(jù)。

舉個例子:一張三通道的32x32圖片,正常的shape應(yīng)該為3x32x32(通道數(shù)x長x寬),如果直接返回的是3x32x32,可能會被誤以為是一次返回了3條數(shù)據(jù),每一條是1x32x32。

我們補(bǔ)上1之后變成1x3x32x32,程序就能掙錢認(rèn)出這是1張3通道32高度32寬度的圖像。

同理,目標(biāo)檢測任務(wù)會輸入圖像、標(biāo)記框、標(biāo)簽等數(shù)據(jù),可以模仿上方操作進(jìn)行喂數(shù)據(jù)。

四、提速方案

1、異步讀取

文章前面些的都是同步讀取方案,如果想提升讀取速度、實現(xiàn)實時傳入數(shù)據(jù)并預(yù)測等,則需要考慮使用異步讀取方案。
可從官網(wǎng)文檔中找到相關(guān)說明
https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/user_guides/howto/prepare_data/use_py_reader.html
但需要注意的是,異步一定比同步快嗎?
異步并非比同步快,當(dāng)數(shù)據(jù)加載速度跟不上網(wǎng)絡(luò)運(yùn)算的速度時,異步才會為訓(xùn)練總進(jìn)程提速。數(shù)據(jù)讀取完畢之后,當(dāng)網(wǎng)絡(luò)部分運(yùn)算完畢,才有“位置”供數(shù)據(jù)進(jìn)入時數(shù)據(jù)才真正開始喂進(jìn)網(wǎng)絡(luò)。若網(wǎng)絡(luò)部分一直在運(yùn)算,那么即使數(shù)據(jù)提前讀取了好幾組,也是沒有“位置”進(jìn)入網(wǎng)絡(luò)的。
當(dāng)然,異步是多線程,占用的開銷不會比同步少。

2、多線程數(shù)據(jù)集處理

如果數(shù)據(jù)的預(yù)處理部分特別繁雜,嚴(yán)重拖慢了程序運(yùn)行速度,多線程數(shù)據(jù)集處理則顯得很重要。(實際上這種情況并不是很多)
https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/io_cn/xmap_readers_cn.html#xmap-readers
應(yīng)用在剛剛的自定義圖片數(shù)據(jù)集的Reader中,增加歸一化這一步預(yù)處理操作。

# 構(gòu)建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之間,提升模型訓(xùn)練速度和效果
    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%,因為部分運(yùn)算操作仍需要借助CPU,所以盡量不去大幅度占用資源,盡管這些與CPU線程數(shù)的關(guān)系不是很大。

示例代碼以及數(shù)據(jù)集

https://github.com/GT-ZhangAcer/DLExample/tree/master/easy02_Reader

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容