深度學(xué)習(xí)開(kāi)發(fā)篇(四):圖像分類任務(wù)

本篇主要內(nèi)容:
1、在PaddlePaddle中訓(xùn)練、預(yù)測(cè)自定義單字驗(yàn)證碼數(shù)據(jù)集

一、準(zhǔn)備工作

如果對(duì)PaddlePaddle不熟悉,在實(shí)踐本篇時(shí)遇到了問(wèn)題,不妨查看一下本專題下的內(nèi)容。
點(diǎn)擊查看本專題所有文章

驗(yàn)證碼數(shù)據(jù)集下載:https://github.com/GT-ZhangAcer/DLExample/tree/master/easy02_Reader

二、導(dǎo)入關(guān)鍵模塊

import paddle.fluid as fluid
import numpy as np
import PIL.Image as Image

三、構(gòu)建Reader

在深度學(xué)習(xí)中,訓(xùn)練并不是越多越好,因?yàn)橛?xùn)練只是對(duì)當(dāng)前送給神經(jīng)網(wǎng)絡(luò)的那一部分負(fù)責(zé)。

舉個(gè)考試的例子,平常老師給我們發(fā)測(cè)試題,我們做一遍可能還有不會(huì)的,再做一遍可能會(huì)好一些,當(dāng)做到第N遍的時(shí)候...

你看到題了,就能回想起答案。如果這種情況持續(xù)了10年,你可能會(huì)漸漸記下這個(gè)答案,而不是這種題型的做題技巧!

當(dāng)遇到了同一類型的新題時(shí),還記得當(dāng)年那個(gè)聰明的你嗎?

所以,我們需要找到一個(gè)平衡點(diǎn),當(dāng)模型對(duì)新的數(shù)據(jù)表現(xiàn)不好時(shí)立刻停止訓(xùn)練,保證學(xué)到的是“做題技巧”而不是答案。這樣的新數(shù)據(jù),可以是測(cè)試集或交叉驗(yàn)證集(兩者實(shí)際還是有一定差別,本專題深度學(xué)習(xí)開(kāi)發(fā)篇(二)有簡(jiǎn)略介紹)

Reader部分
為了劃分一部分?jǐn)?shù)據(jù)作為交叉驗(yàn)證集,在這個(gè)reader中,我們?cè)?code>reader函數(shù)外面再套上一個(gè)函數(shù)switch_reader,方便選擇reader返回的是哪一部分的數(shù)據(jù),同時(shí)設(shè)置一個(gè)布爾類型的參數(shù)作為開(kāi)關(guān)。

def switch_reader(is_val: bool = False):
    def reader():
        # 讀取標(biāo)簽數(shù)據(jù)
        with open(data_path + "/OCR_100P.txt", 'r') as f:
            labels = f.read()
        # 判斷是否是驗(yàn)證集
        if is_val:
            index_range = range(1501, 2000)
        else:
            index_range = range(1, 1500)
        # 抽取數(shù)據(jù)使用迭代器返回
        for index in index_range:
            im = Image.open(data_path + "/" + str(index) + ".jpg").convert('L')  # 使用Pillow讀取圖片
            im = np.array(im).reshape(1, 1, 30, 15).astype(np.float32)  # NCHW格式
            im /= 255  # 歸一化以提升訓(xùn)練效果
            lab = labels[index - 1]  # 因?yàn)檠h(huán)中i是從1開(kāi)始迭代的,所有這里需要減去1
            yield im, int(lab)

    return reader   # 注意!此處不需要帶括號(hào)

這里需要注意的是,return reader后不能加括號(hào),因?yàn)槲覀円祷氐氖?code>reader這個(gè)函數(shù)對(duì)象,而不是調(diào)用這個(gè)函數(shù),等劃分mini batch時(shí)再進(jìn)行調(diào)用它更為合適。

開(kāi)始制作mini_batch

# 劃分mini_batch
batch_size = 128
train_reader = fluid.io.batch(reader=switch_reader(), batch_size=batch_size)
val_reader = fluid.io.batch(reader=switch_reader(is_val=True), batch_size=batch_size)

定義輸入層
該怎樣塞進(jìn)神經(jīng)網(wǎng)絡(luò)呢?我們需要給它制定一個(gè)規(guī)范對(duì)不對(duì)?

# 定義網(wǎng)絡(luò)輸入格式
img = fluid.data(name="img", shape=[-1, 1, 30, 15], dtype="float32")
# 把標(biāo)簽也順便定義了吧
label = fluid.data(name='label', shape=[-1, 1], dtype='int64')

這里的-1, 1, 30, 15分別代表Batch_size、C、H、W。

為什么Batch_size指定為-1呢?
因?yàn)槲覀儫o(wú)法保證xxx_reader每次返回的就是我們?cè)O(shè)置的128組數(shù)據(jù),也就是說(shuō)當(dāng)我們只有200條數(shù)據(jù)時(shí),第一組有128條,剩下的72條單獨(dú)一組。

所以我們?cè)谶@里把Batch_size位置設(shè)置為-1,這樣就可以自適應(yīng)Batch_size的大小了。

四、使用PaddlePaddle搭建網(wǎng)絡(luò)層

因?yàn)閿?shù)據(jù)集特別簡(jiǎn)單,所以用簡(jiǎn)單的全連接層組成的網(wǎng)絡(luò)就足以滿足要求。

這里我們使用3層全連接層作為主要網(wǎng)絡(luò),因?yàn)閯倓偽覀円呀?jīng)定義好了輸入層,所有現(xiàn)在可以直接定義隱藏層了~

# 定義第一個(gè)隱藏層,激活函數(shù)為ReLU
hidden = fluid.layers.fc(input=img, size=200, act='relu')
# 第二個(gè),激活函數(shù)仍為ReLU
hidden = fluid.layers.fc(input=hidden, size=200, act='relu')
# 以softmax為激活函數(shù)的全連接層為輸出層,輸出層的大小必須為L(zhǎng)abel的總數(shù)10
net_out = fluid.layers.fc(input=hidden, size=10, act='softmax')

如果對(duì)激活函數(shù)不熟悉的話,可以先無(wú)腦Relu,因?yàn)镽elu在大部分情況下表現(xiàn)很好。
用他不能保證一定是最優(yōu)秀的,但至少能保證不會(huì)很輕松的掛掉。

五、訓(xùn)練開(kāi)始前的配置

使用API計(jì)算正確率

acc = fluid.layers.accuracy(input=net_out, label=label)

這里的input傳入是輸出層的結(jié)果,label則為剛剛所定義的標(biāo)簽。

克隆一個(gè)程序給驗(yàn)證集使用

eval_prog = fluid.default_main_program().clone(for_test=True)

查看驗(yàn)證集的結(jié)果也需要將數(shù)據(jù)喂到神經(jīng)網(wǎng)絡(luò)里。
是不是還需要再把上面給訓(xùn)練集定義的部分,單獨(dú)再定義一遍呢?
直接用API克隆出來(lái)一個(gè)吧,特別方便!

定義損失函數(shù)

loss = fluid.layers.cross_entropy(input=net_out, label=label)
avg_loss = fluid.layers.mean(loss)

如果對(duì)損失函數(shù)的作用不了解,可以參考之前的文章。
這里我們使用交叉熵?fù)p失函數(shù),差不多就是計(jì)算網(wǎng)絡(luò)層的輸出與標(biāo)簽直接的差距還有多大。

定義優(yōu)化方法

sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.01)
sgd_optimizer.minimize(avg_loss)     # 定義參數(shù)更新(反向傳播也包含在內(nèi))

這里我們使用SGD隨機(jī)梯度下降法作為優(yōu)化方案。
但對(duì)于較復(fù)雜的數(shù)據(jù)集還是建議使用Adam來(lái)優(yōu)化,效率可能會(huì)更高一些。

之所以把克隆驗(yàn)證程序放在定義優(yōu)化方法之前,因?yàn)槲覀冃枰WC在驗(yàn)證時(shí)模型學(xué)到的全部是訓(xùn)練集的參數(shù),驗(yàn)證集不可參與“學(xué)習(xí)”(參數(shù)更新),這樣才知道到底學(xué)習(xí)的如何。

定義執(zhí)行器

place = fluid.CPUPlace()    #使用CPU訓(xùn)練,此處也可以換成GPU
exe = fluid.Executor(place)

數(shù)據(jù)傳入順序設(shè)置

feeder = fluid.DataFeeder(place=place, feed_list=[img, label])

這里的feed_list的順序?qū)τ赗eader里yield返回的順序。

六、開(kāi)始訓(xùn)練

# 對(duì)網(wǎng)絡(luò)層進(jìn)行初始化
prog = fluid.default_startup_program()
exe.run(prog)

Epoch = 10    # 訓(xùn)練10輪
for i in range(Epoch):
    batch_loss = None
    batch_acc = None
    # 訓(xùn)練集 只看loss來(lái)判斷模型收斂情況
    for batch_id, data in enumerate(train_reader()):
        outs = exe.run(
            feed=feeder.feed(data),
            fetch_list=[loss])
        batch_loss = np.average(outs[0])
    # 驗(yàn)證集 只看準(zhǔn)確率來(lái)判斷收斂情況
    for batch_id, data in enumerate(val_reader()):
        outs = exe.run(program=eval_prog,
                       feed=feeder.feed(data),
                       fetch_list=[acc])
        batch_acc = np.average(outs[0])
    print("Epoch:", i, "\tLoss:{:3f}".format(batch_loss), "\tAcc:{:2f} %".format(batch_acc * 100))

為了輸出看起來(lái)好看一些,對(duì)于訓(xùn)練集我們只要求返回loss的值,驗(yàn)證集只返回正確率的值。
這些都定義在fetch_list=[xxx]中。

七、訓(xùn)練效果


可以看到驗(yàn)證集上效果非常棒,在第8個(gè)Epoch上已經(jīng)達(dá)到了100%!(相信你知道為什么是第8個(gè)Epoch)

關(guān)于模型保存,下一節(jié)將對(duì)模型保存進(jìn)行詳細(xì)介紹。

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

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

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

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

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