美文网首页
使用cnn识别captcha验证码

使用cnn识别captcha验证码

作者: golgotha | 来源:发表于2017-11-12 23:03 被阅读964次
    写在前面

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

    相关文章

      网友评论

          本文标题:使用cnn识别captcha验证码

          本文链接:https://www.haomeiwen.com/subject/rfnomxtx.html