本篇主要內(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