使用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阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容