用卷积神经网络识别普通验证码

最近在学习神经网络的相关知识,也谈谈自己目前对神经网络一些浅薄的认识。

实际生产生活中的很多问题都可以理解成如何建立从一个集合到另一个集合的映射关系的问题,也可以说成是,如何设计一个函数的问题。这个函数有一组权重参数,输入集合的数据经过权重参数的处理对应到不同的输出数据。而所谓的神经网络正是由一层一层这样的函数堆叠组成,所以神经网络也叫深度学习。深度学习中最重要一个环节就是通过大量的数据对各层函数的权重参数进行训练,所谓学习(训练)便是通过不断地修正,使权重参数慢慢趋于准确,尽量让所有训练数据的输入在经过权重参数的处理后都能映射到正确的输出,这样的学习(训练)过程往往需要将训练数据一轮一轮的输入我们设计的神经网络模型中,经过长时间反复拟合来实现。

神经网络中的 误差函数 即是一个能表示当前输入数据经过权重参数处理后得到的实际输出和预期输出之间误差大小的函数。我们的目的则可以表述为求取误差函数值最小时对应权重参数的值,当把误差函数用权重参数来表达时,这个问题又可以理解成求误差函数的极小值(一定区间内的最小值),即误差函数对于权重参数导数为0的那一个点。在计算机的数学优化方法中,一般采用梯度下降法来解决这个问题,而所谓梯度下降法,即从误差函数上的一点开始,沿着梯度(导数)下降的方向一点一点地逼近,最终得到一个近似的误差极小的点。梯度下降法是神经网络理论中最基础的知识。而其他如随机梯度下降、批量梯度下降和小批量梯度下降都是对上面提到的梯度下降法的优化,而其本质思想是相同的。而其他的一些相关知识也大都围绕着这一基本思想展开。如学习速度(learning rate)是对在梯度下降的过程中每一步移动大小的表述,而各种各样的优化器则是针对如何让程序自动地选取最合适的学习速率所设计的算法。

常见的神经网络模型大概分为:全连接神经网络、卷积神经网络、循环神经网络等几个大类。本文所提及的卷积神经网络借鉴了数字图像处理中使用算子对图片特征进行提取的方法,在深度学习中,也常被用于处理图像方面的问题。卷积神经网络的每一层会生成一些算子并通过这些算子来提取图像特征最后对应到一个分类中,与全连接神经网络的思路类似,程序通过比较实际输出与预期输出之间的差异得到误差函数并将误差反向传播给各层网络从而实现对生成算子的修正。卷积神经网络中的算子与全连接神经网络里权重参数的概念类似。

cnn

关于深度学习,我目前的了解基本就是这些,在这段时间的学习过程中,有一个 关于深度学习基础知识的系列教程 给了我很大的帮助,这里也推荐给大家。

有了这些关于神经网络的基础知识,我就想有个机会可以真正实践一下,突然想到前公司的验证码,正好可以拿来做做文章。有之前在项目上的知识背景,我知道项目采用的验证码是通过一款 Laravel 社区中流行的验证码生成库 Captcha 来生成的。这就为我提供了准备训练数据的可能性。

破解Captcha验证码的完整代码已经放在Github上了,代码实现的比较粗糙,细节上也可能有诸多考虑不完善的地方,一方面是为自己学习,也是提供一个思路供读者参考。不过首先,可以通过 这个Demo 简单感受一下最后实现的效果。

对于用神经网络来破解验证码的这个问题,我的思路是这样的:首先将4位的验证码图片分割成每张图片一个字符的四张图片,通过卷积神经网络对每张图片进行学习后对应到一个字符的分类。考虑到 Captcha 生成的验证码每个字符所占的宽度比较均匀,我直接采用了四等分的方式将图片分成四分,使逻辑尽量简单。

在具体的实现代码中,我用PHP将2000条验证码拆分成单个字符以字符作为目录存放在 images 目录下作为训练数据,再通过 pack_data.py 将数据打包为一个二进制文件以便批量读取,最后执行 train.py 对数据进行训练。而 model.py 中的模型正是一个最基本的卷积神经网络。

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

model = m.build(x_train.shape[1:], num_classes)

model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=epochs,
          validation_data=(x_test, y_test),
          shuffle=True)

model.save_weights('weights.hdf5')

本次实验中神经网络的模型是基于一款比较流行的深度学习框架 Keras 来编写的。通过对2000个验证码累计200轮的训练,最终该模型对新数据单个字符识别的准确率大概在92%的水平。从结果来看,这个数据的水平还是相当低的。考虑原因,我觉得主要可能有以下几个因素:

  • 训练数据而样本数量较少
  • 没有对生成的二维码图片进行任何处理导致图片中的干扰信息较多
  • 直接把验证码四等分的做法会导致有很多字符不完整,也使得训练受到影响

不过即便是如此,从实用的角度出发,这样的正确率也足以对一个投入生产的项目造成不小的威胁。当然,最终能达成这样的目的,很大程度上还是由于我个人对项目的了解,深度学习如果离开了大量的训练数据作为支撑,也无法达成令人满意的效果。所以从这个角度来说,这次实验并不具有太大的普遍性,更多的还是一次自我学习与思考的过程。

Show Comments