基于tensorflow2.x卷積神經(jīng)網(wǎng)絡(luò)識(shí)別字符型驗(yàn)證碼

??最近入門(mén)了下深度學(xué)習(xí),實(shí)戰(zhàn)練手寫(xiě)了個(gè)定長(zhǎng)的字符型驗(yàn)證碼識(shí)別模型。網(wǎng)上對(duì)于驗(yàn)證碼這一塊好多都是tf1的案例和解決辦法,tf2的還是比較少,git上的開(kāi)源項(xiàng)目也大都基于tf1.x。最大的感觸果然還是,紙上得來(lái)終覺(jué)淺,看書(shū)看文檔讀別人的代碼總覺(jué)得會(huì)了,自己寫(xiě)還是遇到了各種各樣的問(wèn)題,梯度更新的一個(gè)大坑,卡了我兩天多。。記錄一下踩坑的過(guò)程。
??我這邊調(diào)試開(kāi)發(fā)過(guò)程用的conda環(huán)境,對(duì)于我這種小白比較友好..tf版本是2.2.0 gpu版本,tensorboard 2.2.2,python是3.6.8。
??首先是數(shù)據(jù)集的處理,我用的數(shù)據(jù)集是之前搞過(guò)的zxgk的驗(yàn)證碼,為4位數(shù)字字母,保存的圖片名字以"標(biāo)簽名_時(shí)間戳.png"命名,在處理時(shí)需要把標(biāo)簽內(nèi)容提取出來(lái),當(dāng)時(shí)搞的后面接時(shí)間戳是為了防止圖片重復(fù)。數(shù)據(jù)集我這一共差不多6萬(wàn)張圖,應(yīng)該可以訓(xùn)練出一個(gè)比較滿意的準(zhǔn)確率。

??先讀取,獲取指定路徑讀取路徑下的所有文件名:
將所有文件路徑隨機(jī)打散,有助于后面訓(xùn)練參數(shù)收斂
為了確定我們的操作沒(méi)問(wèn)題,我們顯示幾張圖片看一看:
??OK沒(méi)問(wèn)題,然后就到了處理圖片和標(biāo)簽了。圖片的預(yù)處理相對(duì)這個(gè)案例而言比較簡(jiǎn)單,噪點(diǎn)比較少,干擾線也不是每張圖片都有,省了降噪的工序。同時(shí)針對(duì)驗(yàn)證碼而言,顏色是無(wú)所謂的,保留顏色反而會(huì)增加沒(méi)有必要的特征,加大訓(xùn)練難度,所以灰度化也是需要做的。定義一個(gè)圖片預(yù)處理的函數(shù):
??這里遇到了第一個(gè)小坑,卷積層的輸入要求是四維,單純的做歸一化處理以后,圖片會(huì)是一個(gè)三維的(比如[128,70,160],第一個(gè)代表的是圖片的數(shù)量,第二和第三個(gè)分別是圖片的高和寬)。卷積層本身也不是一個(gè)平面,而是有厚度這一概念的,正常來(lái)講第四個(gè)維度也就是深度應(yīng)該是rgb三原色,這里我們?cè)谳斎氲侥P偷臅r(shí)候需要給他擴(kuò)充一個(gè)維度。

這樣就完成了圖片的預(yù)處理,然后是標(biāo)簽。我是把標(biāo)簽處理成一個(gè)標(biāo)簽長(zhǎng)度*字符數(shù)量的張量,也就是每一行都是一個(gè)ont-hot,這里就是shape為4×36的張量。
測(cè)試一下輸出:
??字符集的順序是不能變的,預(yù)測(cè)的時(shí)候需要用到。
??圖片和標(biāo)簽預(yù)處理都做完之后,就可以建立數(shù)據(jù)集了。我選擇的是使用tf.Dataset,從指定文件路徑下創(chuàng)建數(shù)據(jù)管道。封裝成一個(gè)類(lèi)的形式:
??上面的都是讀取打亂操作,然后我按9:1對(duì)數(shù)據(jù)集進(jìn)行分割,對(duì)打亂后的前90%做訓(xùn)練集,后10%做測(cè)試集。

??這樣調(diào)用build方法我們就可以同時(shí)拿到訓(xùn)練集和驗(yàn)證集啦。
??接下來(lái)建立模型,模型選的是4層卷積+2層全連接層的結(jié)構(gòu)。自定義模型,繼承自tensorflow.keras.Model。
??卷積核還是用的比較常用的3×3,每一層卷積后面緊跟一層BN和一層最大池化層。做了四層卷積以后,將張量打平,輸入到兩個(gè)全連接層,每一個(gè)全連接層后接上50%的dropout,防止過(guò)擬合。最后將張量reshape成字符長(zhǎng)度×字符集長(zhǎng)度,和前面標(biāo)簽的處理結(jié)果對(duì)應(yīng)。輸出之后使用softmax激活,做四元分類(lèi)。
??.call方法拿到模型實(shí)例。輸出模型概覽:
??在編寫(xiě)訓(xùn)練過(guò)程前,我們需要考慮訓(xùn)練的方式及各類(lèi)指標(biāo)。很明確的一點(diǎn),這里訓(xùn)練評(píng)估一定是要自己編寫(xiě)的,傳統(tǒng)的acc主要是針對(duì)單分類(lèi)。我想在指標(biāo)中輸出字符的準(zhǔn)確率和圖片的準(zhǔn)確率(圖片準(zhǔn)確率是指一張驗(yàn)證碼圖片所有字符都正確的概率),那么就需要編寫(xiě)兩個(gè)自定義評(píng)估器,首先是字符準(zhǔn)確率:
將傳入的正確標(biāo)簽和預(yù)測(cè)標(biāo)簽逐個(gè)對(duì)比,統(tǒng)計(jì)相同的數(shù)量,算出比值就可以了。
??這兩個(gè)比較類(lèi)似,區(qū)別就在于圖片準(zhǔn)確率是四個(gè)一組的對(duì)比,一個(gè)錯(cuò)全錯(cuò),字符是逐個(gè)對(duì)比,對(duì)一個(gè)算一個(gè)。loss和優(yōu)化器就沒(méi)有必要自己寫(xiě)了,需求也不是那么復(fù)雜。
??這里loss我選擇的交叉熵,交叉熵也是常用于多分類(lèi)問(wèn)題的loss函數(shù)。優(yōu)化器是老大哥Adam,學(xué)習(xí)率后面可能需要讓他隨訓(xùn)練次數(shù)的增加而減小,在剛開(kāi)始訓(xùn)練時(shí),學(xué)習(xí)率大一些,讓loss收斂快一些,訓(xùn)練達(dá)到一定次數(shù)時(shí)降低學(xué)習(xí)率,防止過(guò)擬合。

??接下來(lái)的過(guò)程,可能就不太穩(wěn)了,我們保守點(diǎn),先寫(xiě)個(gè)簡(jiǎn)單的訓(xùn)練函數(shù),方便調(diào)試,讓他跑起來(lái)看看有沒(méi)有問(wèn)題。為了保持訓(xùn)練的靈活度,這里沒(méi)有用fit訓(xùn)練,需要編寫(xiě)梯度更新過(guò)程的代碼。
??先初始化相關(guān)參數(shù):

??然后自定義訓(xùn)練循環(huán):
??訓(xùn)練10個(gè)epoch,每訓(xùn)練一次就更新梯度,每訓(xùn)練20次輸出一次,先跑起來(lái)看看效果:
??觀察loss值是在不斷下降,代表我們優(yōu)化的方向沒(méi)問(wèn)題。
??接下來(lái)需要加入測(cè)試集、tensorboard的支持,以及對(duì)一些配置字段的提取。這時(shí)我們可以把代碼挪到Pycharm里封裝一下了。
??前面model和dataset的地方不用變,先寫(xiě)一個(gè)測(cè)試的邏輯,很簡(jiǎn)單,傳入一個(gè)step的數(shù)據(jù),預(yù)測(cè)并計(jì)算loss與準(zhǔn)確率,這里不需要計(jì)算梯度。同時(shí)在傳入特征時(shí),需要設(shè)置training為False,否則會(huì)影響到BN層與Dropout層的權(quán)重更新。
??然后編寫(xiě)提前終止訓(xùn)練的代碼,這里計(jì)算了兩個(gè)條件。第一個(gè)條件是傳入的loss差值(第二個(gè)loss與第一個(gè)loss的差),連續(xù)10輪保持在0.001以內(nèi)時(shí),代表loss已經(jīng)趨于穩(wěn)定不再更新,那么就保存模型結(jié)束訓(xùn)練。第二個(gè)條件是,訓(xùn)練時(shí)記錄10輪以內(nèi)最低的loss值,并保存對(duì)應(yīng)權(quán)重。如果loss在10輪內(nèi)沒(méi)有降低,就將保存的權(quán)重恢復(fù)到模型中并結(jié)束訓(xùn)練。其實(shí)這兩個(gè)條件對(duì)于訓(xùn)練驗(yàn)證碼識(shí)別這種簡(jiǎn)單的問(wèn)題都不見(jiàn)得會(huì)觸發(fā)。。不過(guò)有還是要有的。

然后在訓(xùn)練的地方添加上。另外再加一個(gè),訓(xùn)練集準(zhǔn)確率達(dá)到99%就結(jié)束訓(xùn)練:
最后再加入一個(gè)寫(xiě)入tensorboard日志的函數(shù):
這樣在每次輸出時(shí)都會(huì)記錄訓(xùn)練集及測(cè)試集的loss及準(zhǔn)確率。
??完成之后的訓(xùn)練過(guò)程:
??完成以上步驟,我們?cè)俅闻芷饋?lái):
??兩三分鐘就跑完退出了!看看tensorboard中的曲線怎么樣:
??訓(xùn)練集的字符準(zhǔn)確率與圖片準(zhǔn)確率曲線:
??訓(xùn)練集loss:
測(cè)試集的準(zhǔn)確率與loss:

??看起來(lái)還不錯(cuò),最后寫(xiě)個(gè)預(yù)測(cè)類(lèi)的代碼就結(jié)束了:
去網(wǎng)站上重新下一張圖片:


??還可以。結(jié)束睡覺(jué)。
??完整代碼的話,我已經(jīng)放在github上了,之后隨著學(xué)習(xí)的深入,我也會(huì)慢慢優(yōu)化代碼。隨后有空的話會(huì)補(bǔ)上個(gè)簡(jiǎn)單的文檔。
??代碼地址:https://github.com/startzm/captcha_cnn

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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