利用深度学习(CNN)进行验证码(字母+数字)识别

利⽤深度学习(CNN)进⾏验证码(字母+数字)识别本⽂⽅法针对的验证码为定长验证码,不包含中⽂。
本⽂的思路是:1. 使⽤keras中预训练好的模型,在python⽣成的验证码(5万条)上fine tune,得到python验证码模型;2. 使⽤python验证码模型,在实际验证码(500条)上fine tune;3. 增加样本,提⾼精度。
通俗的解释2个问题:
1. keras中预训练好的模型是什么东西?
答: keras上有很多已经训练好的图像识别模型,诸如Alex Net,google
net,VGG16,VGG19,ResNet50,Xception,InceptionV3,这些都是由ImageNet训练⽽来。那ImageNet⼜是什么的?ImageNet 项⽬是⼀个⽤于视觉对象识别软件研究的⼤型可视化数据库,超过1400万的图像URL被ImageNet⼿动注释,以指⽰图⽚中的对象。说⽩了,上述已经训练好的图像识别模型是⽤来识别⼤千世界的各种物品的,如⽓球、草莓、汽车、楼房等等。这些个模型都能识别这么复杂的东西了,那我现在要识别⼀个验证码数据,岂不是很简单?这就是站在巨⼈的肩膀上,再微微调整⼀下模型就可以了。这其实就是迁移学习的概念。
1. 为什么要先使⽤python⽣成得验证码进⾏fine tune?
答:虽说我们站在巨⼈的肩膀上,利⽤已有的深度神经⽹络来微调我们的模型,但是从⼤千世界迁移到验证码图⽚,这两个样本集还是有很⼤的差别的。这就要求我们⽤“⼤量“的验证码数据再去训练我们的神经⽹络参数。这个”⼤量“我为什么⽤引号呢,因为其实只需要数万张(下⽂使⽤了5万)验证码图⽚就可以了,相对于ImageNet的1400万,这个数字其实很⼩,但是如果需要我们⼿⼯标记数万张验证码,那就是不⼩的⼯作量了。所以,我们先⽤python⾃动⽣成标记好的5万张验证码图⽚,先进⾏fine tune,保存好输出的模型,注意,这个模型已经不是识别⼤千世界的模型了,是识别数字和英⽂字母的模型了,但是直接应⽤于实际的验证码,效果还是很差,因为两种验证码长得不⼀样,然后我们进⾏再⼀次的迁移学习,再在我们⼿⼯标记好的、实际要识别的验证码(500条)进⾏fine tune,这样就可以识别很好的识别我们要识别的验证码了。
1. 利⽤python⽣成你所需要的验证码
⼀般的验证码都为4位,由数字和⼤⼩写字母构成。利⽤python的captcha模块,⽣成5万张样本图⽚:
from captcha.image import ImageCaptcha
from random import randint
def gen_captcha(num,captcha_len):
# # 纯数字,如果⽬标识别是10位数字,预训练⽤纯数字效果更好
# list = [chr(i) for i in range(48, 58)]
# # 10数字+26⼤写字母+26⼩写字母
list = [chr(i) for i in range(48, 58)] + [chr(i) for i in range(65, 91)] + [chr(i) for i in range(97, 123)]
for j in range(num):
if j % 100 == 0:
print(j)
chars = ''
for i in range(captcha_len):
rand_num = randint(0, 61)
chars += list[rand_num]
image = ImageCaptcha().generate_image(chars)
image.save('./train/' + chars + '.jpg')
num = 50000
captcha_len = 4
gen_captcha(num,captcha_len)
这样我们就轻松的获取到了5万张样本数据
2. 使⽤python⽣成的样本进⾏预训练
keras_weight中Alex Net,google net,VGG16,VGG19,ResNet50,Xception,InceptionV3。都是由ImageNet训练⽽来。这⾥使⽤Xception模型
Xception模型
参考keras⽂档:keras.io/zh/applications/#xception
ption.Xception(include_top=True,
weights='imagenet', input_tensor=None,
input_shape=None, pooling=None, classes=1000)
在 ImageNet 上预训练的 Xception V1 模型。
在 ImageNet 上,该模型取得了验证集 top1 0.790 和 top5 0.945 的准确率。
注意该模型只⽀持channels_last 的维度顺序(⾼度、宽度、通道)。
模型默认输⼊尺⼨是 299x299
参数
include_top: 是否包括顶层的全连接层。
weights: None 代表随机初始化,'imagenet' 代表加载在 ImageNet 上预训练的权值。
input_tensor: 可选,Keras tensor 作为模型的输⼊(即layers.Input() 输出的 tensor)。
input_shape: 可选,输⼊尺⼨元组,仅当include_top=False 时有效(否则输⼊形状必须是(299, 299, 3),因为预训练模型是以这个⼤⼩训练的)。它必须拥有 3 个输⼊通道,且宽⾼必须不⼩于 71。例如(150, 150, 3) 是⼀个合法的输⼊尺⼨。
pooling: 可选,当include_top 为False 时,该参数指定了特征提取时的池化⽅式。
None 代表不池化,直接输出最后⼀层卷积层的输出,该输出是⼀个 4D 张量。
'avg' 代表全局平均池化(GlobalAveragePooling2D),相当于在最后⼀层卷积层后⾯再加⼀层全局平均池化层,输出是⼀个 2D 张量。
'max' 代表全局最⼤池化。
classes: 可选,图⽚分类的类别数,仅当 include_top 为 True 并且不加载预训练权值时可⽤。
3. 模型训练
模型训练的代码如下:
import numpy as np
import glob
from ption import Xception,preprocess_input
from keras.layers import Input,Dense,Dropout
dels import Model
from scipy import misc
samples = glob.glob('./train/*.jpg')  # 获取所有样本图⽚验证码自动输入
np.random.shuffle(samples)    # 将图⽚打乱
nb_train = 45000      #共有5万样本,4.5万⽤于训练,5k⽤于验证
train_samples = samples[:nb_train]
test_samples = samples[nb_train:]
letter_list = [chr(i) for i in range(48, 58)] + [chr(i) for i in range(65, 91)]  # 需要识别的36类
# CNN适合在⾼宽都是偶数的情况,否则需要在边缘补齐,把全体图⽚都resize成这个尺⼨(⾼,宽,通道)
img_size = (60, 160)
input_image = Input(shape=(img_size[0],img_size[1],3))
#直接将验证码输⼊,做⼏个卷积层提取特征,然后把这些提出来的特征连接⼏个分类器(36分类,因为不区分⼤⼩写),
#输⼊图⽚
#⽤预训练的Xception提取特征,采⽤平均池化
base_model = Xception(input_tensor=input_image, weights='imagenet', include_top=False, pooling='avg')
#⽤全连接层把图⽚特征接上softmax然后36分类,dropout为0.5,因为是多分类问题,激活函数使⽤softmax。
#ReLU - ⽤于隐层神经元输出
#Sigmoid - ⽤于隐层神经元输出
#Softmax - ⽤于多分类神经⽹络输出
#Linear - ⽤于回归神经⽹络输出(或⼆分类问题)
predicts = [Dense(36, activation='softmax')(Dropout(0.5)(base_model.output)) for i in range(4)]
model = Model(inputs=input_image, outputs=predicts)
modelpile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
#misc.imread把图⽚转化成矩阵,
#misc.imresize重塑图⽚尺⼨misc.imresize(misc.imread(img), img_size)  img_size是⾃⼰设定的尺⼨
#ord()函数主要⽤来返回对应字符的ascii码,
#chr()主要⽤来表⽰ascii码对应的字符他的输⼊时数字,可以⽤⼗进制,也可以⽤⼗六进制。
def data_generator(data, batch_size): #样本⽣成器,节省内存
while True:
#np.random.choice(x,y)⽣成⼀个从x中抽取的随机数,维度为y的向量,y为抽取次数
batch = np.random.choice(data, batch_size)
x,y = [],[]
for img in batch:
x.append(misc.imresize(misc.imread(img), img_size))  # 读取resize图⽚,再存进x列表
real_num = img[-8:-4]
y_list = []
for i in real_num:
i = i.upper()
if ord(i) - ord('A') >= 0:
y_list.append(ord(i) - ord('A') + 10)
else:
y_list.append(ord(i) - ord('0'))
y.append(y_list)
#把验证码标签添加到y列表,ord(i)-ord('a')把对应字母转化为数字a=0,b=1……z=26
x = preprocess_input(np.array(x).astype(float))
#原先是dtype=uint8转成⼀个纯数字的array
y = np.array(y)
yield x,[y[:,i] for i in range(4)]
#输出:图⽚array和四个转化成数字的字母例如:[array([6]), array([0]), array([3]), array([24])])
model.fit_generator(data_generator(train_samples, 100), steps_per_epoch=450, epochs=5, validation_data=data_generator(test_samples, 100), validation_step #参数:generator⽣成器函数,
#samples_per_epoch,每个epoch以经过模型的样本数达到samples_per_epoch时,记⼀个epoch结束
#step_per_epoch:整数,当⽣成器返回step_per_epoch次数据是记⼀个epoch结束,执⾏下⼀个epoch
#epochs:整数,数据迭代的轮数
#validation_data三种形式之⼀,⽣成器,类(inputs,targets)的元组,或者(inputs,targets,sample_weights)的元祖
#若validation_data为⽣成器,validation_steps参数代表验证集⽣成器返回次数
#class_weight:规定类别权重的字典,将类别映射为权重,常⽤于处理样本不均衡问题。
#sample_weight:权值的numpy array,⽤于在训练时调整损失函数(仅⽤于训练)。可以传递⼀个1D的与样本等长的向量⽤于对样本进⾏1对1的加权,或者在⾯对时#workers:最⼤进程数
#max_q_size:⽣成器队列的最⼤容量
#pickle_safe: 若为真,则使⽤基于进程的线程。由于该实现依赖多进程,不能传递non picklable(⽆法被pickle序列化)的参数到⽣成器中,因为⽆法轻易将它们传⼊#initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有⽤。
#保存模型
model.save('CaptchaForPython.h5')
这⾥只跑了5个epoch,本机电脑cpu跑的话,⼤概需要跑24⼩时左右。有条件的可以使⽤gpu跑。
跑的时候输出如下:Epoch 1/5表⽰跑第⼀个epoch;1/450表⽰跑第⼀个step;ETA: 6:05:12表⽰跑完这个epoch的剩余时间,这边显
⽰6⼩时,实际4个⼩时左右;接下来为总的loss值和四个预测值依次的loss值;然后为4个预测值的准确率。
Epoch 1/5
1/450 [..............................] - ETA: 6:05:12 - loss: 14.6667 - dense_1_loss: 3.6935 - dense_2_loss: 3.7089 - dense_3_loss: 3.6337 - dense_4_loss: 3.6307 - dense_1_acc: 0.0400 - dense_2_acc: 0.0100 - dense_3_acc: 0.0300 - dense_4_acc:
0.0400
2/450 [..............................] - ETA: 4:58:17 - loss: 14.6598 - dense_1_loss: 3.6988 - dense_2_loss: 3.6536 - dense_3_loss: 3.6569 - dense_4_loss: 3.6505 - dense_1_acc: 0.0300 - dense_2_acc: 0.0300 - dense_3_acc: 0.0300 - dense_4_acc:
0.0450
3/450 [..............................] - ETA: 4:30:12 - loss: 14.6189 - dense_1_loss: 3.6936 - dense_2_loss: 3.6495 - dense_3_loss: 3.6589 - dense_4_loss: 3.6168 - dense_1_acc: 0.0300 - dense_2_acc: 0.0333 - dense_3_acc: 0.0267 - dense_4_acc:
0.0500
4/450 [..............................] - ETA: 4:17:47 - loss: 14.5575 - dense_1_loss: 3.6768 - dense_2_loss: 3.6342 - dense_3_loss: 3.6384 - dense_4_loss: 3.6083 - dense_1_acc: 0.0325 - dense_2_acc: 0.0325 - dense_3_acc: 0.0300 - dense_4_acc:
0.0525
5/450 [..............................] - ETA: 4:11:03 - loss: 14.5314 - dense_1_loss: 3.6617 - dense_2_loss: 3.6261 - dense_3_loss: 3.6289 - dense_4_loss: 3.6146 - dense_1_acc: 0.0340 - dense_2_acc: 0.0300 - dense_3_acc: 0.0300 - dense_4_acc:
0.0520
6/450 [..............................] - ETA: 4:05:27 - loss: 14.4945 - dense_1_loss: 3.6480 - dense_2_loss: 3.6178 - dense_3_loss: 3.6207 - dense_4_loss: 3.6080 - dense_1_acc: 0.0350 - dense_2_acc: 0.0317 - dense_3_acc: 0.0267 - dense_4_acc:
0.0467
等跑完这5个epoch,4个预测值的准确率基本上到达95%以上了,保存下来的模型权重已经可以为我们所⽤了。
4. 获取你要识别的验证码图⽚
假设我们要识别中国电信(login.189/web/login)登录时的验证码,该验证码是4位定长,10个数字+26个⼤写字母。
获取你需要识别的验证码,需要利⽤python将图⽚从⽹站上爬取下来。下⾯以中国电信为例,简单介绍如何爬取图⽚。
打开中国电信登录页⾯,单击右键,选择“检查”,在跳出来的页⾯中,选择“Network”。
在电话号码输⼊框,随便输⼀个电话号码,输完后便会跳出验证码,点击验证码,验证码会重新加载,同时右侧”Network”也抓取到了验证码的加载地址。

本文发布于:2024-09-21 22:58:26,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/2/386316.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:验证码   模型   训练   识别   输出   样本   数字
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议