使用cnn识别captcha验证码

2017-11-12  本文已影响964人  golgotha
写在前面

最近练习使用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

上一篇下一篇

猜你喜欢

热点阅读