CNN識別手寫吉爾吉斯語字母以及相關(guān)

0. 前言
本專欄主要介紹了一下我做的一個小的深度學(xué)習(xí)項目,并分享一下我的感受。主要面向有一定深度學(xué)習(xí)經(jīng)驗的人,如果有任何不懂的或者我有什么錯誤的地方歡迎評論區(qū)留言。
這里分成兩部分來介紹,首先是項目具體內(nèi)容,然后是我對這個項目的一些相關(guān)討論。
1. 項目內(nèi)容
1.1 項目介紹
使用CNN(卷積神經(jīng)網(wǎng)絡(luò))實現(xiàn)識別手寫吉爾吉斯字母的分類器,即輸入一個手寫的吉爾吉斯字母的PNG圖片,輸出對應(yīng)的字母。
數(shù)據(jù)集:https://www.kaggle.com/datasets/ilgizzhumaev/database-of-36-handwritten-kyrgyz-letters
具體大概是這個樣子(共36種吉爾吉斯字母):


1.2?注意事項
數(shù)據(jù)集文件夾直接使用了吉爾吉斯字母作為文件夾名稱,在中文的windows系統(tǒng)下,這些名稱會被識別為中文字符,程序中需要注意進(jìn)行轉(zhuǎn)換。
數(shù)據(jù)集為134x134的PNG圖片,直接全部讀取會占用大量內(nèi)存,直接作為神經(jīng)網(wǎng)絡(luò)的輸入也過大,這里需要對其作預(yù)處理。
數(shù)據(jù)集內(nèi)部有做訓(xùn)練集(train)和測試集(test)的劃分,嚴(yán)格來說測試集用作超參數(shù)的確定,會部分參與訓(xùn)練,因此嚴(yán)格來說應(yīng)該要保留一個驗證集(validation),其沒有參與任何訓(xùn)練。這里直接將數(shù)據(jù)集中的測試集作為驗證集,而不再去專門劃分測試集,但是后續(xù)描述依舊稱其為測試集(test)。
神經(jīng)網(wǎng)絡(luò)的實現(xiàn)這里使用tensorflow的keras包(https://keras.io/getting_started),因此程序使用python實現(xiàn)(即便我很討厭python)。
1.3 數(shù)據(jù)預(yù)處理
這里統(tǒng)一將輸入的數(shù)據(jù)轉(zhuǎn)換成32x32的灰度圖像作為輸入,具體操作為(借助opencv包實現(xiàn)):
使用opencv讀取圖片
如果圖像有透明度通道,首先將其置于純白的背景下得到?jīng)]有透明度的BGR圖像
使用opencv的內(nèi)置函數(shù)將BGR圖像轉(zhuǎn)為單通道的灰度圖像
將整個灰度圖像做仿射變化,使得最亮顏色為255,而最暗顏色為0(最大化對比度)
裁剪周圍多余的白色背景(容差為32)
將裁剪后的圖像使用白色向周圍延申成正方形圖像
縮放圖像到28x28
將周圍填充2寬度的白色像素,得到32x32的灰度圖像
預(yù)處理前后對比:

在這里最后再將得到的圖像數(shù)組除以255歸一化并使用浮點數(shù)存儲,并且使用1減去結(jié)果(反色),使得重要的字符部分接近1而不重要的背景接近0。
1.4 CNN的結(jié)構(gòu)
最后確認(rèn)下來的結(jié)構(gòu)如下圖:

對應(yīng)代碼(model.py):
其實模型本身沒什么特別的,這里提幾個比較重要的點:
所有卷積核采用3x3的大小。
其中DepthwiseConv2D的使用主要是控制整個模型的參數(shù)數(shù)量。
除了最后分類使用softmax,其余部分統(tǒng)一使用relu作為激活函數(shù)。
圖片展示的為默認(rèn)最簡單的結(jié)構(gòu),實際可以通過dropout和BN參數(shù)來增加這兩個層,以及append_layers來增加額外的中間層。
參數(shù)數(shù)量:

1.5?CNN的訓(xùn)練
使用交叉熵作為損失函數(shù),使用keras默認(rèn)自帶的SGD優(yōu)化器,設(shè)置batch_size=32,epoch=32(or 24),初始學(xué)習(xí)率設(shè)置為0.1,策略為前4個epoch固定為0.1,后續(xù)按照0.4/epoch遞減,如下圖:

關(guān)于這些選取的解釋為:
損失函數(shù):由于是分類問題,選用交叉熵(回歸問題則選擇均方差,這是極大似然估計的結(jié)論)。
優(yōu)化器,batch_size,epoch:認(rèn)為這只是一個最優(yōu)化問題,因此只需要保證在可以接受的時間內(nèi)達(dá)到(或基本接近)最小值即可,因此(不去考慮早停的情況下)如何選擇沒有本質(zhì)區(qū)別。
學(xué)習(xí)率:同樣認(rèn)為只是一個最優(yōu)化問題,因此學(xué)習(xí)率只要保證迭代收斂即可。
1.6?訓(xùn)練結(jié)果
對于上述最簡單的模型(不增加dropout或BN),有結(jié)果:



其中top-1準(zhǔn)確率表示模型預(yù)測的概率最高的分類就是正確分類的比例,而top-5準(zhǔn)確率表示模型預(yù)測的概率最高5個分類中包含正確分類的比例
很明顯,訓(xùn)練過程出現(xiàn)了過擬合的現(xiàn)象,隨著訓(xùn)練輪次增加,雖然訓(xùn)練集上loss持續(xù)減少,但是測試集上的loss先下降后上升。
許多地方會采用早停的技術(shù)來抑制這種過擬合現(xiàn)象,即對于上述情況,會在epoch=11處停止進(jìn)一步訓(xùn)練,將其直接作為最終模型,此時測試集上的loss最小。
這里我并不推薦這種做法,首先這樣我們的測試集就一定程度參與了訓(xùn)練過程,用來確定“訓(xùn)練輪次”這個超參數(shù)(上面說過,這里還是將測試集作為驗證集來使用,不希望其參與訓(xùn)練過程)。
另一點則是,應(yīng)該認(rèn)為訓(xùn)練過程是一個最優(yōu)化的過程,“早?!睂嶋H上并沒有讓這個模型中的參數(shù)達(dá)到最優(yōu),這樣會有更多的東西影響訓(xùn)練結(jié)果:例如優(yōu)化器的選擇,batch_size,等等。
關(guān)于過擬合,不是只有這種測試集上的loss先下降后上升才可稱為過擬合,其實只要測試集上的loss明顯高于訓(xùn)練集就是過擬合,因此過擬合其實是一個相對概念。
1.7?抑制過擬合
這里通過向模型中增加dropout或BN層的方式來抑制過擬合,并且對比兩者的效果:
關(guān)于dropout和BN具體內(nèi)容請讀者自行查閱相關(guān)資料



數(shù)據(jù)都疊在一起了,將縱坐標(biāo)換成對數(shù)坐標(biāo)可以看得更加清晰一些:


可以看到無論是使用dropout還是BN層,都可以很好的抑制過擬合,并且BN可以增加訓(xùn)練的速度,而dropout則有相對更好的效果。而同時使用兩種則可以兼顧訓(xùn)練速度的提升和更好的效果(雖然一般認(rèn)為只需要選擇一種即可)。
可以看到雖然測試集下,dropout的loss比BN更小,但是top-1準(zhǔn)確率是相反的,盡管如此這里還是以loss為準(zhǔn)。
在使用dropout后,訓(xùn)練集的loss反而大于測試集的loss,并且錯誤率也有類似的結(jié)果,這是由于keras的dropout層只有在訓(xùn)練的時候會開啟(只使用部分參數(shù)),而在測試的時候會關(guān)閉(使用了所有參數(shù)),因此一般測試時得到的結(jié)果會更好。
可以發(fā)現(xiàn)無論使用dropout還是BN層,最終測試集上的效果都要好于不添加的模型,即使使用上“早?!奔夹g(shù),因此我更加推薦使用dropout或者BN層來抑制過擬合而不是"早停"。
2. 相關(guān)討論
2.1 參數(shù)對網(wǎng)絡(luò)性能的影響
這里統(tǒng)一使用效果最好的同時添加dropout和BN層的模型,訓(xùn)練epoch=32保證基本收斂,并調(diào)整每一層的卷積核數(shù)目或者增加中間層的數(shù)目,以此來擴大網(wǎng)絡(luò)的規(guī)模,看看能否進(jìn)一步提高分類的性能。
首先調(diào)整每層的卷積核數(shù)目,得到top-1和top-5準(zhǔn)確率的趨勢:


最開始的網(wǎng)絡(luò)選擇的卷積核數(shù)目為32。
然后是設(shè)定卷積核數(shù)目為16,增加神經(jīng)網(wǎng)絡(luò)的層數(shù),得到top-1和top-5準(zhǔn)確率的趨勢:


可以看到兩種方式都可以增加網(wǎng)絡(luò)性能,并且都存在邊際效應(yīng)。
這里統(tǒng)計每個的具體參數(shù)數(shù)目,得到性能隨參數(shù)增加的變化趨勢:


可以看到,增加網(wǎng)絡(luò)的層數(shù)相比增加每層的寬度,可以使用更少的參數(shù)達(dá)到相同的性能,也就是增加網(wǎng)絡(luò)的層數(shù)比增加每層的寬度“性價比”更高。
當(dāng)然也不能無限的增加網(wǎng)絡(luò)的參數(shù),當(dāng)層數(shù)過多時,會出現(xiàn)梯度消失或者梯度爆炸的問題,導(dǎo)致訓(xùn)練困難;而著名的ResNet就是克服了這個問題,從而可以將網(wǎng)絡(luò)層數(shù)增加到非常高(152層)也可以保證訓(xùn)練能收斂,得到了非常好的效果。

由于這個項目問題比較簡單,這里不考慮使用ResNet。
2.2?GUI交互實現(xiàn)
這時會想是否可以直接創(chuàng)建一個繪圖區(qū)域,實時將繪制的圖像使用上述訓(xùn)練的模型來進(jìn)行分類,可以馬上查看模型的效果。
比較可惜的是似乎沒有比較好用的現(xiàn)成的庫實現(xiàn)這個功能,這樣我就只能從tkinter來做一個簡單的gui程序(最討厭寫gui)。
實現(xiàn)過程這里略去,最后得到的界面如下:

選擇的是每層32個卷積核,不添加額外層的模型,并只使用了BN層而沒有使用dropout。
使用鼠標(biāo)左鍵繪制,右鍵擦除,左上角按鈕清空所有圖像:


試了一下才知道,我也不知道手寫吉爾吉斯字母到底是個什么寫法,所以分類到底準(zhǔn)不準(zhǔn)我也不知道,也就圖一樂
3. 源碼提供和說明
源碼已經(jīng)上傳到github,感興趣的可以直接使用:https://github.com/CHanzyLazer/ML-CNN-Kyrgyz
里面包含模型構(gòu)建,圖片預(yù)處理,訓(xùn)練和繪圖,以及gui的實現(xiàn)代碼;并且保留了我運行的結(jié)果。
倉庫中只包含源碼不包含數(shù)據(jù)集,數(shù)據(jù)集需要另外下載:https://www.kaggle.com/datasets/ilgizzhumaev/database-of-36-handwritten-kyrgyz-letters
下載完成后數(shù)據(jù)集置于dataset目錄下,文件結(jié)構(gòu)為: