一文教你如何用神經網路識別驗證碼!
這是去年博主心血來潮實現的一個小模型,現在把它總結一下。由於樓主比較懶,網上許多方法都需要切割圖片,但是樓主思索了一下感覺讓模型有多個輸出就可以了呀,沒必要一定要切割的吧?切不好還需要損失信息啊!本文比較簡單,只基於傳統的驗證碼。
Part 0 模型概覽
從圖片到序列實際上就是Image2text也就是seq2seq的一種。encoder是Image, decoder是驗證碼序列。由於keras不支持傳統的在decoder部分每個cell輸出需要作為下一個rnn的cell的輸入(見下圖),所以我們這裡把decoder部分的輸入用encoder(image)的最後一層複製N份作為decoder部分的每個cell的輸入。
典型的seq2seq
keras可以直接實現的image2text
當然利用 recurrentshop 和 seq2seq,我們也可以實現標準的seq2seq的網路結構(後文會寫)。
Part I 收集數據
網上還是有一些數據集可以用的,包括dataCastle也舉辦過驗證碼識別的比賽,都有現成的標註好了的數據集。(然而難點是各種花式驗證碼啊,填字的,滑動的,還有那個基於語義的reCaptcha~)。
因為我想弄出各種長度的驗證碼,所以我還是在github上下載了一個生成驗證碼的python包。
下載後,按照例子生成驗證碼(包含26個小寫英文字母):
#!/usr/bin/env python
# -*- coding: utf-8
from captcha.image import ImageCaptcha
from random import sample
image = ImageCaptcha() #fonts=[ "font/Xenotron.ttf"]
characters = list("abcdefghijklmnopqrstuvwxyz")
def generate_data(digits_num, output, total):
num = 0
while(num
cur_cap = sample(characters, digits_num)
cur_cap = .join(cur_cap)
_ = image.generate(cur_cap)
image.write(cur_cap, output+cur_cap+".png")
num += 1
generate_data(4, "images/four_digit/", 10000) #產生四個字元長度的驗證碼
generate_data(5, "images/five_digit/", 10000) #產生五個字元長度的驗證碼
generate_data(6, "images/six_digit/", 10000) #產生六個字元長度的驗證碼
generate_data(7, "images/seven_digit/",10000) # 產生七個字元長度的驗證碼
產生的驗證碼
(目測了一下生成驗證碼的包的代碼,發現主要是在x,y軸上做一些變換,加入一些噪音)
Part II 預處理
由於生成的圖片不是相同尺寸的,為了方便訓練我們需要轉換成相同尺寸的。另外由於驗證碼長度不同,我們需要在label上多加一個符號來表示這個序列的結束。
處理之後的結果就是圖像size全部為Height=60, Width=250, Channel=3。label全部用字元id表示,並且末尾加上表示的id。比如假設a-z的id為0-25,的id為26,那麼對於驗證碼"abdf"的label也就是[0,1,3,5,26,26,26,26],"abcdefg"的label為[0,1,2,3,4,5,6,26]。
由於我們用的是categorical_crossentropy來判斷每個輸出的結果,所以對label我們還需要把其變成one-hot的形式,那麼用Keras現成的工具to_categorical函數對上面的label做一下處理就可以了。比如abdf的label進一步轉換成:
[[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]]
Part III 構建模型
不藉助外部包可以實現的模型
def create_simpleCnnRnn(image_shape, max_caption_len,vocab_size):
image_model = Sequential()
# image_shape : C,W,H
# input: 100x100 images with 3 channels -> (3, 100, 100) tensors.
# this applies 32 convolution filters of size 3x3 each.
image_model.add(Convolution2D(32, 3, 3, border_mode= valid , input_shape=image_shape))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(Convolution2D(32, 3, 3))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(MaxPooling2D(pool_size=(2, 2)))
image_model.add(Dropout(0.25))
image_model.add(Convolution2D(64, 3, 3, border_mode= valid ))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(Convolution2D(64, 3, 3))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(MaxPooling2D(pool_size=(2, 2)))
image_model.add(Dropout(0.25))
image_model.add(Flatten())
# Note: Keras does automatic shape inference.
image_model.add(Dense(128))
image_model.add(RepeatVector(max_caption_len)) # 複製8份
image_model.add(Bidirectional(GRU(output_dim=128, return_sequences=True)))
image_model.add(TimeDistributed(Dense(vocab_size)))
image_model.add(Activation( softmax ))
sgd = SGD(lr=0.002, decay=1e-6, momentum=0.9, nesterov=True)
image_model.compile(loss= categorical_crossentropy , optimizer=sgd, metrics=[ accuracy ])
return image_model
藉助recurrentshop和seq2seq可以實現的結構
def create_imgText(image_shape, max_caption_len,vocab_size):
image_model = Sequential()
# image_shape : C,W,H
# input: 100x100 images with 3 channels -> (3, 100, 100) tensors.
# this applies 32 convolution filters of size 3x3 each.
image_model.add(Convolution2D(32, 3, 3, border_mode= valid , input_shape=image_shape))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(Convolution2D(32, 3, 3))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(MaxPooling2D(pool_size=(2, 2)))
image_model.add(Dropout(0.25))
image_model.add(Convolution2D(64, 3, 3, border_mode= valid ))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(Convolution2D(64, 3, 3))
image_model.add(BatchNormalization())
image_model.add(Activation( relu ))
image_model.add(MaxPooling2D(pool_size=(2, 2)))
image_model.add(Dropout(0.25))
image_model.add(Flatten())
# Note: Keras does automatic shape inference.
image_model.add(Dense(128))
image_model.add(RepeatVector(1)) # 為了兼容seq2seq,要多包一個[]
#model = AttentionSeq2Seq(input_dim=128, input_length=1, hidden_dim=128, output_length=max_caption_len, output_dim=128, depth=2)
model = Seq2Seq(input_dim=128, input_length=1, hidden_dim=128, output_length=max_caption_len,
output_dim=128, peek=True)
image_model.add(model)
image_model.add(TimeDistributed(Dense(vocab_size)))
image_model.add(Activation( softmax ))
image_model.compile(loss= categorical_crossentropy , optimizer= adam , metrics=[ accuracy ])
return image_model
Part IV 模型訓練
之前寫過固定長度的驗證碼的序列準確率可以達到99%,項目可以參考這裡。
另外,我們在用Keras訓練的時候會有一個acc,這個acc是指的一個字元的準確率,並不是這一串序列的準確率。也就是說在可以預期的情況下,如果你的一個字元的準確率達到了99%,那麼如果你的序列長度是5的時候,理論上你的序列準確率是0.99^5 = 0.95, 如果像我們一樣序列長度是7,則為0.99^8=0.923。
所以當你要看到實際的驗證集上的準確率的時候,應該自己寫一個callback的類來評測,只有當序列中所有的字元都和label一樣才可以算正確。
class ValidateAcc(Callback):
def __init__(self, image_model, val_data, val_label, model_output):
self.image_model = image_model
self.val = val_data
self.val_label = val_label
self.model_output = model_output
def on_epoch_end(self, epoch, logs={}): # 每個epoch結束後會調用該方法
print
———————————--------
self.image_model.load_weights(self.model_output+ weights.%02d.hdf5 % epoch)
r = self.image_model.predict(val, verbose=0)
y_predict = np.asarray([np.argmax(i, axis=1) for i in r])
val_true = np.asarray([np.argmax(i, axis = 1) for i in self.val_label])
length = len(y_predict) * 1.0
correct = 0
for (true,predict) in zip(val_true,y_predict):
print true,predict
if list(true) == list(predict):
correct += 1
print "Validation set acc is: ", correct/length
print
———————————--------
val_acc_check_pointer = ValidateAcc(image_model,val,val_label,model_output)
記錄每個epoch的模型結果
訓練
image_model.fit(train, train_label,
Part V 訓練結果
在39866張生成的驗證碼上,27906張作為訓練,11960張作為驗證集。
第一種模型:
序列訓練了大約80輪,在驗證集上最高的準確率為0.9264, 但是很容易變化比如多跑一輪就可能變成0.7,主要原因還是因為預測的時候考慮的是整個序列而不是單個字元,只要有一個字元沒有預測準確整個序列就是錯誤的。
第二種模型:
第二個模型也就是上面的create_imgText,驗證集上的最高準確率差不多是0.9655(當然我沒有很仔細的去調參,感覺調的好的話兩個模型應該是差不多的,驗證集達到0.96之後相對穩定)。
Part VI 其它
看起來還是覺得keras實現簡單的模型會比較容易,稍微變形一點的模型就很糾結了,比較好的是基礎的模型用上其他包都可以實現。keras 2.0.x開始的版本跟1.0.x還是有些差異的,而且recurrentshop現在也是支持2.0版本的。如果在建模型的時候想更flexible一點的話,還是用tensorflow會比較好,可以調整的東西也比較多,那下一篇可以寫一下img2txt的tensorflow版本。
Part VII 代碼
完整源代碼:
https://github.com/Slyne/CaptchaVariLength
Part VIII 後續
現在的這兩個模型還是需要指定最大的長度,後面有時間會在訓練集最多只有8個字元的情況下,利用rnn的最後一層進一步對於有9個以及以上字元的驗證碼效果,看看是不是可以再進一步的擴展到任意長度。(又立了一個flag~)
開發者專場 | 英偉達深度學習學院現場授課
學習形式:線下授課 + 交流答疑
時間:7 月 8 日
地點:深圳市福田區福華路大中華喜來登酒店
培訓價格:1999 元,前五十名報名者提供五折早鳥票,先到先得!
報名地址:mooc.ai
另:
經社長百般努力!從老闆手中搶下兩枚優惠碼!每枚優惠碼可優惠 200 元,一周內有效!先到先得啊~
現在兩枚優惠碼粘貼在下面,是你們拼手速的時候了!
祝大家搶碼順利,期待在大會現場見到大家!
1024MOOC48238
1024MOOC16251
註:每單只可使用一枚優惠碼,每個優惠碼只可使用一次。
點擊展開全文
※谷歌開源物體檢測系統 API
※如何實現模擬人類視覺注意力的循環神經網路?
※一文詳解如何用 R 語言繪製熱圖
※不用寫代碼就能實現深度學習?手把手教你用英偉達 DIGITS 解決圖像分類問題
※深度學習先驅 Yoshua Bengio 解讀深度學習的關鍵突破點:無監督學習
TAG:唯物 |
※如何用 Python 和深度神經網路識別圖像?
※用神經網路識別歌曲流派
※「乾貨」用神經網路識別歌曲流派(附代碼)
※用BP人工神經網路識別手寫數字——《Python也可以》
※生物神經網路與機器學習的碰撞,Nature論文提出DNA試管網路識別手寫數字
※生物神經網路與機器學習的碰撞,Nature論文DNA試管網路識別
※現今無人車「眼」多「眼」尖 感測器助認路識物
※獲英諾、紅杉中國青睞 老路識堂加碼知識付費
※MIT基於AI研發新型衛星地圖道路識別系統Roadtracer
※獲2160萬投資,老路識堂開啟知識付費MCN模式
※美國投資120萬美元開發殭屍網路識別系統
※獲2160萬投資,單營收過五千萬,老路識堂開啟知識付費MCN
※獲2160萬投資,單課程營收過5000萬,老路識堂開啟知識付費MCN