使用深度学习识别图形验证码
在 8.1 节和 8.2 节,我们了解了使用 OCR 技术和图像处理技术识别验证码的方法,但这些方法有个共同的缺点,就是正确率不够高。从本节开始,我们来学习使用深度学习识别验证码的方法,包括对基本的图形验证码的识别和对滑动验证码缺口的识别。
本节中我们先学习使用深度学习识别图形验证码的方法。
准备工作
由于本节所讲的内容涉及深度学习相关的知识,所以在开始之前,请确保已经正确安装好了一个深度学习框架 PyTorch,可以通过 pip3 工具安装:
pip3 install torch torchvision
如果安装过程出现了问题,可以参考 https://setup.scrape.center/pytorch 了解更详细的安装说明。另外,由于本节需要使用深度学习训练一个识别图形验证码的模型,因此还需要准备一些训练数据。训练数据又包含两部分,一部分是验证码图片,另一部分是验证码的真实标注。我们可以使用验证码生成器自行生成一些验证码,这样就同时有了验证码图片和标注数据。生成验证码图片需要用到一个叫作 captcha 的 Python 库,可以使用 pip3 工具安装这个库:
pip3 install captcha
另外由于本节涉及的知识点都和深度学习模型的构建、训练、验证和推理等过程相关联,同时伴有数据集的准备过程,导致代码量比较大;而我们的目标是训练出一个能够识别图形验证码的深度学习模型为爬虫所用,并不是侧重学习深度学习的原理,因此本节我们不会从零开始编写一个深度学习模型,建议大家直接下载代码跟着运行一遍即可,有兴趣的话可以深入研究其中的原理。
这部分代码见 https://github.com/Python3WebSpider/DeepLearningImageCaptcha ,先复制下来:
git clone https://github.com/Python3WebSpider/DeepLearningImageCaptcha.git
运行完毕后,本地就会出现一个 DeepLearningImageCaptcha 文件夹,证明复制成功。
数据准备
要训练一个深度学习模型,必不可少的就是训练数据。上面也提到了,训练数据分为两部分,一部分是图片数据,即一张张验证码图片,另一部分是标注数据,即验证码的内容是什么。有了这两部分数据,就可以训练一个识别图形验证码的深度学习模型,模型在训练中不断调优的过程,就是逐渐学会怎么识别一张验证码图片的过程。训练好模型后,向模型输入类似的验证码图片,模型便可以识别出这个验证码对应的文本内容。
那这些数据怎么准备呢?如果你稍微了解过深度学习相关的内容,相信并不会对数据标注这个词感到陌生,数据标注有相当一部分是需要人工参与的。假如我们有很多验证码图片,又不知道验证码图片对应的文本内容是什么,就需要用到数据标注。说白了,看一下验证码图片,然后把里面的文字记录下来,就相当于标注了一条数据。例如这里有一张验证码图片,如图 8-16 所示。
现在我们只是有这张图片,而没有图片里面内容对应的文本信息,这时候就需要标注。可以看到,图片里的内容是 EHUK,把它记录下来,这张验证码图片的标注就完成了。
为了训练一个较好的用于识别图形验证码的深度学习模型,我们可能需要上万、上十万或者数百万条训练数据。此时如果只有验证码图片而没有数据标注,就需要人工标注上万、上十万甚至数百万条数据,这是个常燥且耗时的工作。
那有没有解决办法呢?办法自然是有的,我们反其道而行之就好了。具体是先随机生成标注数据即随机生成一些4位的由字母和数字组合而成的数据,然后使用这些已经生成的标注数据生成验证码图片,这样不就省去数据标注的过程了吗?有了这个方法,就不怕准备大量的训练数据了。
上述生成验证码的过程分为两步,第一步是生成随机的文本数据,第二步是根据生成的文本数据生成验证码图片。打开项目中的 generate.py 文件,其中定义了两个方法 generate_captcha_text 和 generate_captcha_text_and_image,分别用于完成这两步:
from captcha.image import ImageCaptcha
from PIL import Image
import random
import setting
def generate_captcha_text():
captcha_text = []
for i in range(setting.MAX_CAPTCHA):
c = random.choice(setting.ALL_CHAR_SET)
captcha_text.append(c)
return ''.join(captcha_text)
def generate_captcha_text_and_image():
image = ImageCaptcha()
captcha_text = generate_captcha_text()
captcha_image = Image.open(image.generate(captcha_text))
return captcha_text,captcha_image
这里 generate_captcha_text 方法用于生成随机的文本数据,可以看到方法中先定义了一个执行 MAX_CAPTCHA 次的 for 循环,每次循环都利用 random.choice 方法随机从 ALL_CHAR_SET 里挑选一个字符并放入 captcha_text,最后将 captcha_text 中的字符拼接在一起。
其中 MAX_CAPTCHA 和 ALL_CHAR_SET 的定义在 setting.py 文件里,相应代码如下:
NUMBER = ['o','1','2','3','4','5','6','7','8','9'] ALPHABET = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X' ,'Y', 'Z'] ALL_CHAR_SET = NUMBER + ALPHABET MAX_CAPTCHA = 4
可以看到 MAX_CAPTCHA 的值是 4,所以最后拼接而成的就是 4 位验证码。ALL_CHAR_SET 的值是 10 个阿拉伯数字和 26 个英文字母构成的列表,所以拼接而成的验证码文本就是 4 位数字和字母的组合。
接下来我们修改 generate.py 文件,先生成一批训练数据,建议生成 10 万个验证码,按如下这样修改 count 变量和 path 变量:
count = 100000 path = setting.TRAIN_DATASET_PATH
其中 count 就是验证码的个数,这里直接设定为 100000,path 是验证码图片保存的路径,这在 setting.py 文件中已经定义好了,有如下三个选择。
-
TRAIN_DATASET_PATH:训练集所在的路径,数据用于模型的训练。
-
EVAL_DATASET_PATH:验证集所在的路径,一般在训练过程中或者训练完毕后用到,可用于验证模型的训练效果。
-
PREDICT_DATASET_PATH:推理集,一般在训练完毕后用到,可用于模型推理和测试。
然后运行 generate.py 文件里的代码:
python3 generate.py
输出结果类似如下这样:
这里生成了非常多的验证码数据,标注结果直接出现在文件名中,最终生成的训练集数据如图 8-17 所示。
图8-17 生成的训练集数据
利用同样的方法,可以生成验证集,用于验证模型的训练效果。还是修改 count 变量和 path 变量
count = 3000 path = Setting.EVAL_DATASET_PATH
验证集不需要像训练集那么大,count 就修改为 300O,path 修改为 EVAL_DATASET_PATH,然后重新运行 generate.py 文件里的代码:
python3 generate.py
这样就生成了验证集数据,如图 8-18 所示。
图8-18 生成的验证集数据
数据都准备好后,就可以开始训练模型了。
模拟训练
本节中我们使用的深度学习模型是一个基本的 CNN 模型,模型定义在 model.py 文件里,定义如下:
import torch.nn as nn
import setting
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.Dropout(0.5),
nn.ReLU(),
nn.MaxPool2d(2))
self.layer2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.Dropout(0.5),
nn.ReLU(),
nn.MaxPool2d(2))
self.layer3 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.Dropout(0.5),
nn.ReLU(),
nn.MaxPool2d(2))
self.fc = nn.Sequential(
nn.Linear((setting.IMAGE_WIDTH // 8) * (setting.IMAGE_HEIGHT // 8) * 64, 1024),
nn.Dropout(0.5),
nn.ReLU())
self.rfc = nn.Sequential(
nn.Linear(1024, setting.MAX_CAPTCHA * setting.ALL_CHAR_SET_LEN),
)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
out = self.rfc(out)
return out
可以看到这里定义了三层,每层都是 Conv2d (卷积)、BatchNorm2d (批标准化)、Dropout (随机失活)、ReLU (激活函数) 和 MaxPool2d (池化) 的组合,经过这三层处理后,由一个全连接网络层输出最终的结果,用于计算模型的最终损失。
模型的训练过程定义在 train.py 文件中,整个训练逻辑是这样的:
-
引入定义好的模型,即
model.py文件,对模型进行初始化; -
定义损失函数
loss; -
定义优化器
optimizer; -
加载数据,一般包括训练集数据和验证集数据;
-
执行训练,这个过程包括反向求导、模型权重更新;
-
在执行特定的训练步数后,验证和保存模型。
了解了基本的逻辑之后,就可以尝试用现有的数据训练一个深度学习模型了。怎么训练呢?运行
训练模型,即 train.py 文件即可:
python3 train.py
训练过程的输出结果如下:
epoch: 0 step: 9 loss: 0.2004123032093048
epoch: 0 step: 19 loss: 0.15169423818588257
epoch: 0 step: 29 loss: 0.14101602137088776
...
epoch: 10 step: 109 loss: 0.0204183830261237
epoch: 10 step: 119 loss: 0.0216972048472911
epoch: 10 step: 129 loss: 0.0210623586177826
可以看到,训练过程中模型的损失在不断降低,说明模型在不断地学习和优化,同时每训练完一个轮次之后都会执行一次模型验证。由于在训练模型时没有使用验证集数据,所以用验证集数据来验证是可以得到模型的真实正确率的,是更为科学的。
另外在每次的验证过程中,还会保存最优的验证结果以及最优的模型,存为 best_model.pkl 文件。
|
推荐使用 GPU 来训练模型,速度会比不用 GPU 快很多。关于如何设置用 GPU 训练模型,可以参考 PyTorch 官方教程,这里不再赘述。 |
经过一段时间的训练,模型的损失趋近于 0,训练的正确率在不断提升,验证的正确率能达到 96% 以上,最后可以在本地看到一个 best_model.pkl 文件,这便是我们想要的模型。
测试
现在我们来测试一下得到的模型,先在 PREDICT_DATASET_PATH 变量对应的路径下生成几个验证码图片,生成过程前面讲过也实践过。这里随意选取两个验证码图片放在那个路径下,如图 8-19 所示。
然后在 predict.py 文件中加载上面得到的模型 best_model.pkl,关键代码如下:
cnn.load_state_dict(torch.load('best_model.pkl'))
并根据加载的模型对定义的 CNN 模型的权重进行初始化,整个模型加载完毕后,就和刚才训练时一样强大,拥有识别图形验证码的能力。
运行 predict.py 文件:
python3 predict.py
可以看到输出结果如下:
FIQG IW6S
识别成功!这样我们就成功训练出了一个识别图形验证码的深度学习模型。
总结
本节介绍了利用深度学习模型识别图形验证码的整体流程,最终我们成功训练出模型,并得到了深度学习模型文件。往这个模型中输入一张图形验证码,它便会预测出其中的文本内容。
至此,我们还可以基于本节介绍的内容进行进一步的优化。
-
由于本节各个环节使用的验证码都是由
captcha库生成的,验证码风格也都是事先设定好的,所以模型的识别正确率会比较高。但如果输入其他类型的验证码,例如文本形状、文本数量、干扰线样式和本节的不同,模型识别的正确率可能并不理想。为了让模型能够识别更多的验证码,可以多生成一些不同风格的验证码来训练模型,这样得到的模型会更加健壮。 -
当前模型的预测过程是通过命令行执行的,这在实际使用时可能并不方便。可以考虑将预测过程对接 API 服务器,例如对接 Flask、Django、FastAPI 等,把预测过程实现为一个支持 POST 请求的 API,这个 API 可以接收一张验证码图片,并返回验证码的文本信息,这样会使模型更加方便易用。