偶尔看到小伙伴们的探讨:具体内容还不太理解,但仿佛在说 登录界面验证码 的事儿。
验证码是测试过程中常常遇到的一个问题,于是不禁思考:如何在自动化测试的过程中对验证码进行高效的辨认和解决,以此来晋升测试效率?
首先,对于简略的拖放验证码(drag-and-drop),能够应用selenium 的拖放函数,来操作被拖放的元素并设定拖放的间隔、拖放到的指标元素。
以下的例子展现了使用拖放操作在 http://pythonscraping.com/pag…,来证实访问者不是一个机器人。
页面中的 message 元素中展现了一条信息,内容为:
Prove you are not a bot, by dragging the square from the blue area to the read area!
很快地,python 就会运行结束,并失去第二条信息:
You are definitely not a bot!
当然这是一种较为简单的验证码状况,如果是对于一个生疏的网页,实现这样的操作可能会有一点艰难,尤其是绝大多数网站的验证码远比这简单。
理论环境中,咱们常见的验证码是图形验证码,以上的操作就并不实用了。
因而上面我就谈谈本人对于使用 机器学习算法 进行图像验证码的辨认的一点浅显认识。
问题剖析
要弄清怎么让机器辨认图像验证码,先来想想,咱们作为充斥智慧的人类是怎么认出一张验证码中的字符的:
1)对图像进行浏览,找出 N 个字符在图像中的地位;
2)对找到的字符顺次进行识别,并最终失去整个验证码的后果。
其实想要让计算机辨认一张图像验证码,步骤也是相似的,比方:
1)采取某种策略对蕴含多个字符的图像进行拆分,使得每个图像中尽量只蕴含一个字符;
2)对失去的子图像顺次进行辨认,失去概率最高的后果,并将全副后果顺次组合则失去最终后果。
第一个步骤常被称之为 图像宰割。如果大家对动静布局有一点印象的话,就会更容易了解了,因为图像宰割的作用与之相似,行将大的问题拆分为若干个子问题,顺次攻破。
第二个步骤则是一个比拟规范的监督学习 (Supervised Learning) 问题中的 分类问题。监督学习次要分为回归(Regression)问题和分类(Classification)问题两类,其区别在于,回归问题所产生的后果是间断的,而分类问题的后果是离散的。
一个典型的分类问题的例子是:已知某城市若干套房子的面积与其对应的房价,以此为根据,对给出面积的房子进行房价预测。
那么已知的信息用图像表达出来就会像这样:
而一个机器学习算法给出的一条拟合函数可能会像这样:
尽管理论中的回归问题往往会有更多的维度,即可能对于预测房价而言,面积不是惟一的影响因素,还会有更多的信息能造成影响,如房子所在的区域、四周的学校、与最近警察局的间隔、楼层等等,这些因素作为“参数(Parameters)”,以不同的水平对最终的后果:房价产生着影响。
工程师们往往须要思考应用哪些,或是重视哪些 参数 来取得一个较优的后果。
而对于分类问题,置信只有你再略微往后看看,就会有一个清晰的意识了。
不知你有没有留神到,对于监督学习而言,已知的数据必须蕴含参数以及后果两方面的信息,否则如果你不晓得房子的任何价格信息,就无奈做出预测。
与之对应的另一个大类的问题叫做“非监督学习(Unsupervised Learning)”,咱们很快也谈判到。
对于两个步骤别离可能存在的难点,咱们也会在之后进行探讨。
环境筹备
对于传统算法的机器学习而言,无论是 MATLAB 还是 python 都是很罕用的。
不过说到神经网络,因为 TensorFlow,PyTorch 等开源工具被宽泛应用,python 可能就要稍逊一筹了。
Anaconda是一个开源的 Python 包管理器,它能够不便咱们对多个虚拟环境的治理,并简略地解决好不同包之间的版本依赖问题。
如果你还在应用 Python2,那么倡议更换到Python3,因为有许多包曾经不再对 Python2 提供更新。
至于其余各种相干的 Python 包,在之后用到的时候会独自进行列出。
数据集筹备
产生一个图像验证码的数据集并不是一件太简单的事件。大抵步骤如下:
1)在指定的字符汇合(如 0 -9,A-Z,甚至是汉字等)中随机选取 N 个字符;
2)将产生的字符搁置于背景图像之上;
3)在图像之上减少噪声;
4)对产生的字符进行随机的缩放、旋转、投影等变换;
5)失去验证码。
如果是为了产生验证码作为数据集,还会有额定的两个步骤:
6)将产生的验证码进行存储;
7)将产生的验证码对应的字符进行记录,写入文件。
情理说完了,不过在 python 中,也不乏存在简简单单就能够产生一个图像验证码的库。这里我应用到了captcha。
在装置后,应用如下的代码就能够产生一个验证码:
产生的验证码如下:
更进一步的,应用一个循环来批量产生验证码并写入称为文件就能够失去一个个验证码的图像了,由此能够失去训练集,它们将用来进行模型的训练。
在产生验证码的同时,对其字符内容进行记录。一个简略的思路是 应用一个 list 来存储,并应用诸如 Pandas 之类的库将其写入一个 csv 文件,便于未来读取。
采纳相似的办法,咱们再来生成一个数量小一些的测试集,用于在模型训练实现后测验训练的成绩:
数据预处理
不难看出,一张验证码的图像还是有挺多噪点的。
在机器学习中,图像经常被读取为“灰度图(Gray Scale Image)”。灰度图保留了各个像素的灰度信息,用 0 -255 示意,别离对映了从黑到白的不同深浅水平。应用灰度图并不是说彩色图像不好,而是在彩色图片上进行训练会须要更大的数据量,毕竟多出了 R,G,B 三条通道。
对于这样一个绝对较简略的问题,我感觉就没有必要应用彩色图像了。事实上,当初越来越多的绝对简单的机器学习工作是在彩色图像上进行的。另一方面,灰度图经常能够便于辨别出图像的前景和背景,所以对于降噪而言,可能会更容易一点。
对于从 RGB 图像失去灰度图的计算,
公式为:
当然,这可是 python,根本没有什么须要咱们本人解决的算法。
应用如下的代码以产生一个灰度图的例子:
这里咱们应用了 matplotlib 库 来进行图像的显示,“cmap=”gray””的参数用于指定该库以灰度图的模式进行图像的显示,否则整张图片就会被显示得绿油油的。
这段代码产生的后果如下:
通过观察咱们能够失去,大多数噪点的色彩要比字符更深,即其灰度值要更低;而背景的色彩要比字符更浅,即其灰度值更高。
通过阈值进行图像的“二值化”(仅蕴含 0 与 255,即黑白两色),能够在肯定水平上进行噪点的去除,便于之后的图像宰割与图像分类。
如下的代码进行了该步骤:
值得注意的是,这两个阈值仅是针对此数据集而选取的两个值。
如果应用的是与此不同的数据集,则须要选取别的形式进行二值化。
二值化的后果如图所示:
能够看到,大多数噪点被去除了。当然,咱们同时也失落了一部分信息。
对于剩下的离散噪点,一个解决的思路是,对整个图像进行遍历,检测每个像素其四周八个点的灰度值 ,在这个九宫格内,彩色的像素点数量小于肯定阈值的点,即视为 离散噪点进行革除。
这是一个效率不高的写法,不过很便于看懂到底做了怎么的操作。
如果你想的话,能够本人进行优化。
解决后失去的图像如下:
接下来,便是采取同样的办法对整个数据集进行这样的解决。
在解决完结后,因为数据量较大,在之后的聚类的步骤中,不仅须要的工夫较长,对内存也会有相当大的占用。
如果在这个过程中产生因为诸如内存不足而解体的状况,就须要从新将图片读取为灰度图再进行解决,这就有点麻烦了。
一个解决办法是 应用诸如 pickle 的库,将失去的train_array 与 test_array 作为二进制文件存储:
图像剖析
之前曾经提到过图像宰割对于大问题拆分的意义。
对于一个具备 4 位字符,每个字符的范畴从数字到大小写字母的分类问题,共有 624 种可能的后果,而对于拆分完的图像,分类器只须要决定它是 62 个可能中的哪一个就能够了——这样即便是瞎蒙,也能进步很多的准确率。
对于图像验证码的图像宰割问题,我大抵有以下几种思路:
1)应用若干个事后设计好的 filter,顺次对图像进行点乘。
对于点乘失去的若干个后果,选取响应的值较高的,并且间隔绝对正当的,即可实现对图像的宰割。
用图像来解说就像这样:
这一办法的难点在于,如何设计 filter,以及如何选取 filter 产生的后果。
因为字符数量较多,且可能蕴含多种变换的后果,须要经验丰富的工程师能力决定设计怎么的 filter。
如果 filter 的数量过多,不仅会增大计算量,也会在后果的选取过程中减少乐音,难以获得最优解。
人为的设计出优良 filter 不是一件容易的事,兴许须要一支相当宏大团队的致力,这就使得实现一个这样的算法很难。不过这样的思路是很可取的,之后咱们也还会再见到。
2)通过某种算法进行判断。最简略的比方,纵向切割,每检测到一条不存在点的线即进行切割。
这一办法的毛病在于,对于粘连的字符难以拆分。
如果两个字符粘连在一起,还能够通过均匀切割等办法大抵取得后果,但如果有更多的字符粘连,甚至所有的字符都粘连在一起,这一办法就可能齐全生效了。
3)通过某种聚类(Cluster)算法,失去图像宰割的后果。
聚类算法是非监督学习的一种,用于在无标签的状况下,将数据集拆分为若干个类。
在传统机器学习办法中,常见的聚类算法包含 K-Means,EM-GMM,Mean-Shift 等。
而在神经网络中,常通过 Encoder-Decoder 模型 来实现聚类工作。
接下来是这三个罕用到的传统学习中的聚类算法的介绍:
- K-means 算法
对于 K -means 而言,它的指标函数在于最小化其点与核心的总方差和:
假如聚类核心已知,则只须要选取能够最小化各点到聚类核心的方差的调配形式:
那么对于每个点而言,采取如下形式将每个点调配到离它最近的聚类核心:
同理,如果咱们已知每个点到最近聚类核心的间隔,也很容易失去聚类核心的问题。
然而实际上,对于这两个前提,咱们都没法间接晓得,这就像先有鸡还是先有蛋的问题了。
幸好工程上而言,这个问题是可解的。
罕用的办法是:
- 随机选取若干个初始的聚类核心;
-
反复:
- 通过欧氏间隔计算每个点到其到不同聚类核心的间隔,以失去其应属于的聚类;
- 对于每一个聚类,从新计算新的聚类核心为其均匀点;直到收敛。
对于 K -means 而言,最终的后果依赖于初始化的聚类核心。
因而,如果运气不好,你可能须要屡次应用 K -means 能力失去一个比拟正当的聚类后果。
工程上也经常应用屡次 K -means 算法取得聚类核心后,取使得指标方程值最小的解。
因为应用了欧式间隔,K-means 算法会失去一个圆形的聚类。
K-means 算法的图形演示如下:
- EM-GMM 算法
一个多元的高斯混合模型(GMM)能够取得椭圆状的聚类。
椭圆的形态由高斯分布的协方差矩阵所决定,而地位由均值所决定。
高斯混合模型是一个高斯分布的加权和:
每个高斯分布代表了一个椭圆的聚类。
实际中,罕用 冀望最大化(Expectation Maximization, EM)算法 来对于 GMM 进行计算。
当有隐变量(hidden/unseen variables)时,EM 算法罕用来取得最大似然预计解。
对于 GMM 的 EM 算法求解过程如下:
- E-step:计算聚类的关系,将调配给聚类 _j_
留神,这里应用的调配理论是一种软调配,即一个点能够分数化地调配给不同的聚类。
2. M-step:更新每个高斯分布聚类的均值、协方差和权重
- 计算聚类中的点(软性的):
- 权重:
- 均值:
- 协方差:
因为圆实际上是一种非凡的椭圆,所以在协方差相等时,应用 EM-GMM 算法取得的聚类后果应与 K -means 的后果雷同。
应用 EM-GMM 算法经常可能失去这样的聚类后果:
- Mean-Shift 算法
Mean-Shift 算法的理念在于,对于汇合中的每个点逐渐地计算其梯度并相应的挪动,最终挪动到同一个部分最优的点,即属于同一个聚类。
实际中,具体步骤如下:
1)从一个初始点 x 开始;
2)反复:
– 依据高斯 kernel,找到 x 在肯定半径内的最近街坊;
– 将 x 作为其邻近点的均值,直到 x 不再变动。
该步骤的图形演示如下:
更直白一些的说,就是随机初始化一个半径固定的圆,而后将其圆心始终向数据集密度最大的方向挪动。
屡次迭代后,当其不再挪动时,即达到了一个聚类核心地位,而圆的半径以内的点则为该聚类中的点。对于 mean-shift 算法而言,其变量天然就是圆的半径了。
在这一问题中,一个比拟靠近于 mean-shift 的实现(同时也是比拟罕用的)算法被称为 分水岭算法(Watershed Algorithm)。
设想有一条连绵起伏的山脉,这时忽然下起了大雨,山与山之间低的中央天然就积满了水,图像也就这样被宰割了。在实现中,“下雨”的过程本质上就是梯度降落的过程。
这样听下来,聚类算法仿佛很适宜实现这样的指标。事实上,对于较为简单的验证码(字符分隔界线较显著、较为容易分隔的状况),聚类算法可能进行分类,但对于简单的状况,或者后果就不是那么现实了。
以下为应用 K -means 算法对之前验证码进行聚类后的后果:
能够看到,C 和 9 两个字符曾经被宰割开,而 X 与 6 则齐全被混同到了一起。
起因可能是:
字符并不合乎于圆形的散布,又或是 K -means 在这种状况下难以失去一个好的初始化点等等。
更要害的问题在于,如果咱们这时候去查看聚类核心的横坐标的地位,并将其对应后果顺次从左到右显示:
能够看到,更像“6”的后果 3 呈现在更像“x”的后果 4 之前,如果间接进行标注,这样的标注就能够被认为是“脏数据”。
应用分水岭算法失去的后果也并不现实:
对于传统算法的机器学习而言,解决一个较为艰难的问题,往往须要一支数十人甚至数百人的工程师团队,破费数月甚至数年的工夫进行设计。
鉴于这样的宰割后果,间接图像分类必定是走不上来了。所以我接下来我会对常见的分类算法进行一些介绍,而后一起来看看使用神经网络是否能解决这个问题。
在图像宰割之后,有一些诸如对图像进行“归一化(normalization)”的技巧。
在这一问题中,应用归一化能够在肯定水平上实现对于图像方向向量的校对,通过一个设计好的“确定方向边界框(Oriented Bounding Box)”能够是达到这一成果的一个思路。
图像分类
针对这一问题,进行分类的次要难点在于,因为诸某些字符的先天性的难以分别,这会对分类器的收敛造成艰难。
诸如 0 与 O,I 与 l – 你真的能认出这两个谁是谁吗?!
不过对于这个问题,我也并不能想到什么好的解决形式,所以只能视若无睹了。
在真正开始训练之前,对于数据集还能够采取的一种常见策略叫做“数据加强(Data Augmentation)”。
大抵上来说,数据加强经常就是对于数据集中的数据采取某种形式的变动,来扩充数据集。
不过要留神,对于采取的具体变动要小心。对于这个问题,能够采取的简略的变动形式次要有“投影(Shearing)”,“旋转(Rotation)”,“偏移(Shifting)”和“缩放(Zooming)”。
在进行旋转时,要留神只能在一个较小的幅度内进行,如对于“7”进行 180 度的旋转,就会变成一个相似于“L”的形态,然而其标签依然为“7”,这显然就是一个乐音了。也能够通过减少一些乐音,如“高斯噪声(Gaussian Noise)”来实现数据加强,让模型更为强壮。
实现这一性能的代码如下:
其实对于这一问题而言,数据加强并不是必须的,因为取得一个新的验证码太容易了,并且咱们曾经有了一个相当大的训练集了。如果破费足够的工夫产生一个足够宏大并且散布较为平均的数据集,且与测试集遵从类似的散布,模型在测试集上的体现就不会太差。
这里应用的数据加强的形式,在生成验证码图片的过程中是经常用到的,所以并不是什么必须的,或是能显著晋升准确率的伎俩。这里提及更多的是作为一种介绍。
可能你留神到,在刚刚的代码块中用到了一个名为keras 的 Python 库。Keras 根本能够了解为一套便于开发人员开发高级神经网络的 API,它反对应用 TensorFlow,Theano 等作为后端运行。个人感觉而言,它的语法要比原生的 TensorFlow 多少敌对一些。
分类算法又可分为 “二分类算法”和“多分类算法” 两种。顾名思义,二分类算法就是对于数据集,只能将它们分为两种类别的算法。常见的二分类算法包含 逻辑回归(Logistic Regression,LR)、反对向量机(Support Vector Machine,SVM)等。而最常见的多分类算法就是 决策树(Decision Tree)了。
- 逻辑回归
分类器:
对于线性函数:
给定特征向量,一个类的概率是:
sigmoid 函数:
逻辑回归的损失函数:
训练:
最大化训练集的可能性
通过正则化(regularization)防 止过拟合——通过穿插验证选取正则化超参数 C
进行分类:
对于给定样本 x *:
- 选取概率最高的类别 p(y|x*):
- 否则,间接应用 f(x*)
对于逻辑回归这样的二分类分类器,通过一些伎俩,也可将它们用于解决多分类问题。
其一是应用诸如“1-vs-rest”策略。换言之,行将多分类问题划分为若干个二分类问题,应用多个二分类分类器进行解决。
另一个失去多分类分类器的办法时定义一个多分类指标函数,并给每个类别 C 调配一个权重向量 Wc。
通过 softmax 函数 定义概率:
- 反对向量机
对于一个分类问题而言,可能会有若干个分类器:
对于 SVM 而言,其目标就是要找【边缘(margin)最大】的那个分类器。
SVM 这样决策的长处在于,在保障所有点都被正确分类的同时,也保障了最大不确定性。
训练:
对于给定训练集:
首先定义边缘间隔:
从点 Xi 到超参数 W 的间隔:
边界间隔是到所有间隔中的最短距离:
指标函数为:
预测时,对于新的数据点
能够看到,SVM 产生的决策边界是线性的。
能够应用“核函数(kernel function)”使得 SVM 能够做出非线性的判断。
这其实是一种将数据集向高维转化,使得数据集在高维上线性可分的做法。
- K 近邻法
K 近邻法(K-Nearest Neighbors)是一种很简略的分类算法。
每次有新的样本,分类器就会找到在已知样本中距离其最近的 K 个,而后选取其中的大多数来作为新样本的分类。
这并不是一种好的分类办法,因为分类器什么都没有学到。
- 决策树
决策树也是一种较为简单的分类器,你能够将它设想成流程图。
对于一个“与”运算:
产生的一个决策树如下:
应用分类器的劣势在于,它很容易过拟合。幸好,这能够通过 Bagging 策略进行补救。因为决策树自身是一种较为简单的分类器(占用资源少,运算速度快),使得其经常与 Boosting 和 Bagging 这两种策略相结合应用,以产生更好的后果。
Boosting:训练多个分类器,每个新的分类器专一于之前的分类器分类谬误的局部。
Bagging:训练多个分类器,每个分类器只应用训练集中的随机一部分子集进行训练。在预测时,应用所有的分类器来投票,失去这个新样本的分类后果。应用决策树的 Bagging 策略又被称为“随机森林(Random Forest)”分类器。
用神经网络进行验证码辨认
应用神经网络解决该问题,能够应用两种思路。
1)多分类工作
多分类工作,也就是之间所介绍过的,最终的后果只有一个标签,然而标签会有若干种类别。
2)多标签工作
对于多标签工作而言,一条数据可能有一个或多个标签。
设想你正在听一首歌曲,如果你在外面听到说唱,那么说唱就是这首歌曲的一个标签。同样的,这首歌曲还能够有诸如风行、古典……等等标签。
只有你在其中听到了相应的特色,它就有可能作为这首歌曲的一个标签,失去该标签对应的概率。
在训练神经网络时,不同工作应用的激活函数和损失函数关系如下:
如果你想对多标签工作有更多的理解,能够查阅收录于 2014 TKDE 的张敏灵与 周志华的论文:
A review on multi-label learning algorithms。
限于工夫和篇幅的关系,对于神经网络相干的内容这里也就不做过多的探讨了,如果说起来,恐怕这篇文章就要没完没了了。以下为神经网络的一个实现:
这里应用了 卷积神经网络(Convolution Neural Network,CNN)。CNN 在计算机视觉问题中被宽泛应用。相比于传统的 DNN(Deep Neural Network,这里即 MLP,Multi-Layer Perceptron 多层感知机),它可能同时兼顾“整体”与“部分”,找到不同区域间的关联关系。
这个模型中蕴含了很多神经网络(也能够用于传统机器学习)的常见内容,比方“优化器(optimizer)”(近几年最罕用的应该就是 Adam,尽管有很多大佬并不喜爱它)、“池化(Pooling)”(最常见比方 MaxPooling,即在一个矩阵中取最大值,而后将这个矩阵进行放大,通过这个过程神经网络能够“看得更广”)、“dropout”(一部分结点不响应也做出决策,使得神经网络更加 robust,用于解决过拟合)、还有 激活函数(ReLU、sigmoid 等等……),大家有趣味能够本人多去理解。
这里应用神经网络的办法,也能够说是对于应用神经网络而言最简略的办法:定义好一个网络结构,而后把数据塞进去,看看后果怎么样。如果后果不太现实的话,就调整网络结构再试试。
这也正是应用神经网络和应用传统机器学习算法的一个显著区别:神经网络由数据驱动,人为的干涉往往也只是定向的输出数据,通过数据来校正网络;而传统机器学习算法则由工程师事后设计好各种状况,如果算法的后果不够好,经常也只是因为设计好的算法对于某些状况处理不当。因而,你也大能够批改网络的构造,或者应用别的网络来试试。
上述的代码应用的是使用多标签工作的办法的,你也能够批改为多分类工作的网络(应该只须要批改全连贯层的激活函数和损失函数即可,并且依据我的查阅,这一办法也是可行的,并且准确率能够到很高的水平),来看看后果如何。
当然,也不是所有人在使用神经网络的时候,都应用这种“端到端(end-to-end)的办法,即一个神经网络解决所有问题。你也能够对于输出网络的数据进行更多的预处理(预处理也能够由别的构造的网络来实现),来看看是否能失去更好的后果。
更进一步地说,这里所采纳的 CNN 网络是解决 CV(Computer Vision,计算机视觉)问题的一个常见解决办法,而对于图形验证码问题,也能够采取 NLP(Natural Language Processing)的思路进行解决。
应用一些 RNN(Recurrent Neural Network,循环神经网络),例如前几年比拟风行的 LSTM,也能够对这个问题进行求解。
这里的网络事实上只预测了 36 个分类。
训练过程的截图在此:
能够看到在第 5 个 epoch,模型在验证集(10000 个的训练集中,9000 个用于训练而 1000 个用于验证集——一个不参加训练,只测验模型训练成绩的数据集)上的体现曾经开始降落,这阐明模型曾经开始过拟合于训练集。当然这个过程其实不肯定要手动干涉,应用早停法(early stop)就能够实现。能够看到,我大概在准确率为 95.81% 的时候进行了训练。
模型的一个预测后果如下,能够看到这是一个正确的预测。
模型在测试集上的体现为 0.9390625,也就是大概 93.9%。
结语
当然,验证码的形式多样,越来越简单的验证码也越来越难以攻破。
例如局部网站当初所采纳的图像验证码,为给出多张图片,要求用户在找出其中具备某种物体的图片并进行抉择。这个问题的难度,就远超于单纯的字符识别问题了。对于这样的问题,就 要求算法应具备辨认相当数量物体(例如,数百种物体:轿车、大象、花朵、红绿灯等等)的能力。
综上所述,针对于测试工作而言,最好用的办法必定还是和开发哥哥姐姐们商量一下,绕过一下验证码,不仅省去了开发整个辨认验证码零碎的工夫,还能够进步通过的准确率,毕竟无论怎样的机器学习办法都不可能产出 100% 的准确率(假如训练集不可能蕴含所有可能状况)。
-END-
文 / 翟天凡