一文教你如何用神经网络识别验证码!

AI 研习社按:本文作者 Slyne_D,原载于作者个人博客,雷锋网 AI 研习社已获授权。文中相关链接详见文末“阅读原文”。

这是去年博主心血来潮实现的一个小模型,现在把它总结一下。由于楼主比较懒,网上许多方法都需要切割图片,但是楼主思索了一下感觉让模型有多个输出就可以了呀,没必要一定要切割的吧?切不好还需要损失信息啊!本文比较简单,只基于传统的验证码。

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<total): 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表示,并且末尾加上表示<EOF>的id。比如假设a-z的id为0-25,<EOF>的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 '\n———————————--------' 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 '\n———————————--------' val_acc_check_pointer = ValidateAcc(image_model,val,val_label,model_output)

记录每个epoch的模型结果

check_pointer = ModelCheckpoint(filepath=model_output + "weights.{epoch:02d}.hdf5")

训练

image_model.fit(train, train_label, shuffle=True, batch_size=16, nb_epoch=20, validation_split=0.2, callbacks=[check_pointer, val_acc_check_pointer])

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

原文发布于微信公众号 - AI研习社(okweiwu)

原文发表时间:2017-06-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python数据科学

Seaborn从零开始学习教程(四)

数据集中的数据类型有很多种,除了连续的特征变量之外,最常见的就是类目型的数据类型了,常见的比如人的性别,学历,爱好等。这些数据类型都不能用连续的变量来表示,而是...

21020
来自专栏素质云笔记

k-means+python︱scikit-learn中的KMeans聚类实现( + MiniBatchKMeans)

之前一直用R,现在开始学python之后就来尝试用Python来实现Kmeans。 之前用R来实现kmeans的博客:笔记︱多种常见聚类模型以及分群质...

3.3K90
来自专栏Hadoop数据仓库

HAWQ + MADlib 玩转数据挖掘之(十一)——分类方法之决策树

一、分类方法简介 1. 分类的概念         数据挖掘中分类的目的是学会一个分类函数或分类模型(也常常被称作分类器),该模型能把数据库中的数据项映射到给定...

347100
来自专栏祝威廉

基于Spark /Tensorflow使用CNN处理NLP的尝试

关于CNN如何和NLP结合,其实是被这篇文章指导入门的 。 我觉得使用CNN去处理一些NLP的分类问题,是非常不错的。

19120
来自专栏大数据挖掘DT机器学习

百度魅族深度学习大赛初赛冠军作品(图像识别.源码)

赛题以识别类似手写体的四则运算式为主题,参赛者需要在充满干扰信息的10万张图片中,设计算法识别图片上数学运算式并计算结果。决赛在初赛的基础上,引入分数和更加复杂...

58660
来自专栏人工智能LeadAI

图像学习-验证码识别

这是去年博主心血来潮实现的一个小模型,现在把它总结一下。由于楼主比较懒,网上许多方法都需要切割图片,但是楼主思索了一下感觉让模型有多个输出就可以了呀,没必要一定...

70140
来自专栏华章科技

与数据挖掘有关或有帮助的R包和函数的集合

rpart,party,randomForest,rpartOrdinal,tree,marginTree,

9730
来自专栏云霄雨霁

算法设计策略----回溯法和分枝限界法

25900
来自专栏游戏开发那些事

【小白学游戏常用算法】一、随机迷宫算法

  现在的很多游戏中的地图一般采用格子的方式,虽然在表面地图上无法看到实际的格子,但是在地图的结构中专门有一个逻辑层,这个层和地图大小相等,划出很多小的格子,然...

17420
来自专栏mathor

matlab—影像分析进阶

在这一章里面我们要做的事情全部都围绕两个问题,一个图像当中有多少个xxx,他们的大小是多少,举个例子

28020

扫码关注云+社区

领取腾讯云代金券