使用cnn識(shí)別captcha驗(yàn)證碼

寫在前面

最近練習(xí)使用cnn,訓(xùn)練了一個(gè)驗(yàn)證碼識(shí)別的神經(jīng)網(wǎng)絡(luò)。在這里記錄一下戰(zhàn)斗的心路歷程。
最開始使用cpu版的tensorflow來訓(xùn)練,訓(xùn)練集的圖片是由captcha庫(kù)自動(dòng)生成的,大小為170x80。電腦intel i5-7500的cpu,主頻3.4G,4核,按理說處理170x80的圖片應(yīng)該不會(huì)太慢。實(shí)戰(zhàn)起來的時(shí)候,慢到我懷疑人生。感覺隨便訓(xùn)練一下一周過去了。還好有塊GTX 1060的顯卡。裝上gpu版的tensorflow,一下子感覺被拯救了,51200張圖片訓(xùn)練一把只要一個(gè)小時(shí),擱cpu估計(jì)要一整天。跑了兩個(gè)小時(shí)摸了一把機(jī)箱,大冷天居然發(fā)燙。著實(shí)心疼了一把顯卡。所以如果大家是在minist數(shù)據(jù)集上玩一下用cpu可以,如果真的上實(shí)彈建議不要浪費(fèi)人生了。

實(shí)驗(yàn)過程

在網(wǎng)上找了一份captcha的識(shí)別代碼,自己改了一下,發(fā)現(xiàn)根本不收斂。。。loss一直居高不下。先貼出來我的錯(cuò)誤示范。

input_tensor = Input((height, width, 3))
x = input_tensor
x = Convolution2D(24, 6, 6, subsample=(2, 2), activation='relu')(x)#這里使用24個(gè)filter,每個(gè)大小為3x3。輸入圖片大小170x80 輸出83x38
x = MaxPooling2D((2,2))(x)#pooling為2x2,即每4個(gè)網(wǎng)格取一個(gè)最大值 pooling之前是24(filter)x83x38,pooling后是24(filter)x42x19
x = Convolution2D(138, 3, 3, activation='relu')(x)#再用24x3x3filter卷積一次,大小為138(filter)x40x17
x = MaxPooling2D((2, 2))(x)  # pooling為2x2,完成后成為138(filter)x20x9
x = Convolution2D(138*2, 3, 3, activation='relu')(x)#再用138x3x3filter卷積一次,大小為276(filter)x18x7
x = Convolution2D(138*2, 3, 3, activation='relu')(x)#再用276x3x3filter卷積一次,大小為276(filter)x16x5
x = Convolution2D(138*2, 3, 3, activation='relu')(x)#再用276x3x3filter卷積一次,大小為276(filter)x14x3
x = Flatten()(x)
x = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(n_len)]
model = Model(input=input_tensor, output=x)
model.compile(loss='categorical_crossentropy',
              optimizer='adadelta',
              metrics=['accuracy'])

花了好長(zhǎng)時(shí)間YY出來了一個(gè)自覺"完美"的結(jié)構(gòu)。本來想跑一下過把癮,結(jié)果loss一直不下來持續(xù)在64左右。整個(gè)人感覺就不好了,把別人的代碼搞過來跑一下看看,剛開始訓(xùn)練loss就只有16。怎么會(huì)差那么多!先貼出來別人的結(jié)構(gòu)

input_tensor = Input((height, width, 3))
x = input_tensor
for i in range(4):
    x = Conv2D(32*2**i, 3, 3, activation='relu')(x)
    x = Conv2D(32*2**i, 3, 3, activation='relu')(x)
    x = MaxPooling2D((2, 2))(x)

x = Flatten()(x)
x = Dropout(0.25)(x)
x = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(4)]

按這個(gè)結(jié)構(gòu)一跑,收斂的飛快。兩個(gè)小時(shí)就訓(xùn)練好了,而且精度高的不要不要的。后續(xù)會(huì)示范。

簡(jiǎn)直就是奔潰。
然后我就開始把自己的網(wǎng)絡(luò)一步一步替換成他的網(wǎng)絡(luò),替換法。這個(gè)過程真是耗時(shí)費(fèi)力。不過還好發(fā)現(xiàn)了一些規(guī)律,具體原因只能猜測(cè)一下。
總結(jié)下來有三個(gè)方面,我發(fā)現(xiàn)這三個(gè)方面任意缺一個(gè),丫的loss就不收斂。。。簡(jiǎn)直了。。。

  1. activation激活函數(shù)要選擇relu,至于為什么詳細(xì)可以參見cs231n里面的課程有詳細(xì)的解釋,大致原因就是別的函數(shù)例如sigmod會(huì)導(dǎo)致梯度消失,反向傳播的時(shí)候梯度不能一層一層傳播。更新權(quán)重w是根據(jù)梯度的負(fù)方向乘以學(xué)習(xí)率來更新權(quán)重的,梯度過小則權(quán)重的變化率太小,所以收斂的慢
    2.最多隔兩層Conv2D卷積層要加一個(gè)Pooling層。個(gè)人感覺如果pooling層個(gè)數(shù)不夠的話,網(wǎng)絡(luò)要訓(xùn)練的w權(quán)重集合會(huì)很大,而且很多節(jié)點(diǎn)權(quán)重對(duì)最終輸出并沒有太多影響,訓(xùn)練過程中修正權(quán)重的時(shí)候會(huì)過多的關(guān)注這些無(wú)效結(jié)點(diǎn),反而干擾了梯度下降的過程。當(dāng)然這是個(gè)人的猜測(cè),歡迎大家給我分享你的觀點(diǎn)。但是無(wú)論怎么樣,確實(shí)我加了一些pooling 就收斂了,神奇的玩意。大家可以訓(xùn)練一個(gè)不pooling 的網(wǎng)絡(luò),看一下是不是多花一些時(shí)間也可以收斂的。
    3.加深網(wǎng)絡(luò)的深度。當(dāng)我有6個(gè)卷積層3個(gè)pooling的時(shí)候,根本感覺不要收斂,而且loss在64左右,多加一層的時(shí)候,簡(jiǎn)直神跡一般loss一下子跌到16,而且收斂的非???。至于什么原因。。。我只能盡情想象了。想象一下網(wǎng)絡(luò)只有一個(gè)隱層,而且結(jié)點(diǎn)很少會(huì)發(fā)生什么情況?網(wǎng)絡(luò)的表達(dá)能力根本就不夠,它根本就不可能正確的識(shí)別圖像里的特征,再進(jìn)一步的把特征聚會(huì)成更高級(jí)的特征。卷積核可視化的paper里面可以看到高層的特征是低層特征的聚合。當(dāng)網(wǎng)絡(luò)深度不夠的時(shí)候它根本不足以識(shí)別出來高級(jí)的特征,更何況識(shí)別出里面的驗(yàn)證碼。所以loss會(huì)根本下不來。如果大家有更合理的解釋,歡迎分享。

下面貼出來代碼,talk is cheap, show me the code

from captcha.image import ImageCaptcha
import matplotlib.pyplot as plt
import numpy as np
import random

%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import string
characters = string.digits + string.ascii_uppercase + string.ascii_lowercase
print(characters)

width, height, n_len, n_class = 170, 80, 4, len(characters)

# generator = ImageCaptcha(width=width, height=height)
# random_str = ''.join([random.choice(characters) for j in range(4)])
# img = generator.generate_image(random_str)
#
# plt.imshow(img)
# plt.title(random_str)

def gen(batch_size=32):
    X = np.zeros((batch_size, height, width, 3), dtype=np.uint8)
    y = [np.zeros((batch_size, n_class), dtype=np.uint8) for i in range(n_len)]
    generator = ImageCaptcha(width=width, height=height)
    while True:
        for i in range(batch_size):
            random_str = ''.join([random.choice(characters) for j in range(4)])
            X[i] = generator.generate_image(random_str)
            for j, ch in enumerate(random_str):
                y[j][i, :] = 0
                y[j][i, characters.find(ch)] = 1
        yield X, y

def decode(y):
    y = np.argmax(np.array(y), axis=2)[:,0]
    return ''.join([characters[x] for x in y])

# X, y = next(gen(1))
# plt.imshow(X[0])
# plt.title(decode(y))
import keras
from keras.models import *
from keras.layers import *

input_tensor = Input((height, width, 3))
x = input_tensor
for i in range(4):
    x = Conv2D(32*2**i, 3, 3, activation='relu')(x)
    x = Conv2D(32*2**i, 3, 3, activation='relu')(x)
    x = MaxPooling2D((2, 2))(x)

x = Flatten()(x)
x = Dropout(0.25)(x)
x = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(4)]
model = Model(input=input_tensor, output=x)
model.compile(loss='categorical_crossentropy',
              optimizer='adadelta',
              metrics=['accuracy'])

#這里構(gòu)造一個(gè)callback的數(shù)組,當(dāng)作參數(shù)傳給fit
tb_cb = keras.callbacks.TensorBoard(log_dir='d:\\logs', write_graph=True, write_images=False,
                                    embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None)
es_cb = keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0.09, patience=5, verbose=0, mode='auto')
cbks = [];
cbks.append(tb_cb);
cbks.append(es_cb);


model.fit_generator(gen(), samples_per_epoch=51200, nb_epoch=5,callbacks=cbks,
                    nb_worker=1,
                    validation_data=gen(), validation_steps=32)

X, y = next(gen(1))
y_pred = model.predict(X)
plt.title('real: %s\npred:%s'%(decode(y), decode(y_pred)))
plt.imshow(X[0], cmap='gray')

這里是我的訓(xùn)練過程,我只跑了兩代,10萬(wàn)多張圖片,兩個(gè)小時(shí),發(fā)現(xiàn)分別識(shí)別四個(gè)字母的acc都達(dá)到了98%以上?。?!
這個(gè)時(shí)候我摸了一把機(jī)箱,覺得就這樣吧。


訓(xùn)練過程.png

下面給大家看一下準(zhǔn)確率有多離譜。說實(shí)話,比我識(shí)別的都準(zhǔn)確。
先貼一下驗(yàn)證的代碼。

from keras.models import load_model
from captcha.image import ImageCaptcha
import matplotlib.pyplot as plt
import numpy as np
import random

%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import string
characters = string.digits + string.ascii_uppercase + string.ascii_lowercase
print(characters)

width, height, n_len, n_class = 170, 80, 4, len(characters)

# generator = ImageCaptcha(width=width, height=height)
# random_str = ''.join([random.choice(characters) for j in range(4)])
# img = generator.generate_image(random_str)
#
# plt.imshow(img)
# plt.title(random_str)

def gen(batch_size=32):
    X = np.zeros((batch_size, height, width, 3), dtype=np.uint8)
    y = [np.zeros((batch_size, n_class), dtype=np.uint8) for i in range(n_len)]
    generator = ImageCaptcha(width=width, height=height)
    while True:
        for i in range(batch_size):
            random_str = ''.join([random.choice(characters) for j in range(4)])
            X[i] = generator.generate_image(random_str)
            for j, ch in enumerate(random_str):
                y[j][i, :] = 0
                y[j][i, characters.find(ch)] = 1
        yield X, y

def decode(y):
    y = np.argmax(np.array(y), axis=2)[:,0]
    return ''.join([characters[x] for x in y])

model = load_model('d:\\tmp\\my_model.h5')

X, y = next(gen(1))
y_pred = model.predict(X)
plt.title('real: %s\npred:%s'%(decode(y), decode(y_pred)))
plt.imshow(X[0], cmap='gray')

我的model存起來了。重新加載的。先貼出一張識(shí)別錯(cuò)誤的吧。


識(shí)別錯(cuò)誤.png

把a(bǔ)識(shí)別成5了。。。
再貼正確的吧


正確識(shí)別.png

我實(shí)驗(yàn)了幾十把,全部識(shí)別正確。
這里就不貼model了,大家需要可以找我要。下次用keras-vis 看一下這些filter的究竟。

參考資料:
https://zhuanlan.zhihu.com/p/26078299

最后編輯于
?著作權(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)容