使用cnn識別captcha驗證碼

寫在前面

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

實驗過程

在網上找了一份captcha的識別代碼,自己改了一下,發現根本不收斂。。。loss一直居高不下。先貼出來我的錯誤示范。

input_tensor = Input((height, width, 3))
x = input_tensor
x = Convolution2D(24, 6, 6, subsample=(2, 2), activation='relu')(x)#這里使用24個filter,每個大小為3x3。輸入圖片大小170x80 輸出83x38
x = MaxPooling2D((2,2))(x)#pooling為2x2,即每4個網格取一個最大值 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'])

花了好長時間YY出來了一個自覺"完美"的結構。本來想跑一下過把癮,結果loss一直不下來持續在64左右。整個人感覺就不好了,把別人的代碼搞過來跑一下看看,剛開始訓練loss就只有16。怎么會差那么多!先貼出來別人的結構

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)]

按這個結構一跑,收斂的飛快。兩個小時就訓練好了,而且精度高的不要不要的。后續會示范。

簡直就是奔潰。
然后我就開始把自己的網絡一步一步替換成他的網絡,替換法。這個過程真是耗時費力。不過還好發現了一些規律,具體原因只能猜測一下。
總結下來有三個方面,我發現這三個方面任意缺一個,丫的loss就不收斂。。。簡直了。。。

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

下面貼出來代碼,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'])

#這里構造一個callback的數組,當作參數傳給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')

這里是我的訓練過程,我只跑了兩代,10萬多張圖片,兩個小時,發現分別識別四個字母的acc都達到了98%以上!!!
這個時候我摸了一把機箱,覺得就這樣吧。


訓練過程.png

下面給大家看一下準確率有多離譜。說實話,比我識別的都準確。
先貼一下驗證的代碼。

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存起來了。重新加載的。先貼出一張識別錯誤的吧。


識別錯誤.png

把a識別成5了。。。
再貼正確的吧


正確識別.png

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容