最近一直没出文,是因为最近在写一个爬虫项目,这个项目里面,碰到了一个比较棘手的事情,那就是验证码。各种方法用尽,最后我还是决定去训练自己的模型,但是,有一个问题---我深度学习可以说是0基础,这可咋弄?想来想去,我只能靠着百度&谷歌两位大佬来写了。
1 验证码样本获取(数据集的准备)
首先给大家基本的思路,最开始我们需要的是你的破解目标---验证码,因为每一个网站的验证码是不同的,所以我们需要获取你想要破解的验证码类型。
例如:
上面这个验证码就是我这几天折腾的验证码,我们看看它有些什么样的特征,为什么要看验证码的特征呢?因为我们需要大量的已标记的验证码数据集,已标记的验证码数据集又是啥意思?我直接给大家看张图吧↓
大家可以看到,每一张验证码的名字前面四个字母或数字刚好对应的就是我们验证码中的字母数字,这个就是我们待会需要训练的数据(我这里是准备了2w张)
但是一个问题来了,我怎么获取这些图片?去网站下载?那名字岂不是要我一个个改?当然不!虽然那也是一种方法,但是未免也太折磨人了...我这里就给大家推荐两个验证码生成的库,kaptcha(JAVA里的库) 和captcha(python里的库),我这次使用的是 kaptcha,因为我这个验证码需要设置相关的属性,例如验证码图片里面字体的颜色、各种效果,captcha里面貌似是不可以设置的,所以我就选择了kaptcha。验证码生成这一块大家可以直接去百度搜索这两个关键字:kaptcha,captcha。
2 处理数据集
处理数据集:
1.色彩在验证码中并不重要,我们将彩色验证码图片转为黑白,3维转1维,减少干扰数据。
2.将黑白验证码图片及其文本内容转化为数值数据。
3.设置验证码图片组,以便让图片数据分批次进行训练。
process.py
import numpy as np from demo1.getimg import file_name from demo1.getimg import load_allimg from demo1.getimg import CAPTCHA_HEIGHT, CAPTCHA_WIDTH, CAPTCHA_LEN, CAPTCHA_LIST import random import cv2 # 图片转为黑白,3维转1维 def convert2gray(img): if len(img.shape)>2: img = np.mean(img, -1) return img # 验证码文本转为向量 def text2vec(text,captcha_len=CAPTCHA_LEN, captcha_list=CAPTCHA_LIST): text_len = len(text) if text_len > captcha_len: raise ValueError("验证码超过4位啦!") vector = np.zeros(captcha_len * len(captcha_list)) for i in range(text_len): vector[captcha_list.index(text[i]) + i * len(captcha_list)] = 1 return vector # 验证码向量转为文本 def vec2text(vec, captcha_list=CAPTCHA_LIST, size=CAPTCHA_LEN): vec_idx = vec text_list = [captcha_list[v] for v in vec_idx] return ''.join(text_list) # 返回特定shape图片 def wrap_gen_captcha_text_and_image(shape=(CAPTCHA_HEIGHT, CAPTCHA_WIDTH, 3)): t_list = [] # flie_name方法是我自己定义的,获取目录中所有验证码图片的名字 t = file_name("E://DeskTop//codeimg") # 对名字进行处理,只保留前四位 for i in t: index = i.rfind('-') name = i[:index] t_list.append(name) # print(t_list) # 这个也是我定义的,获取所有的验证码图片对象 im = load_allimg() im_list = [] for i in range(0, len(im)): if im[i].shape == shape: im_list.append(im[i]) # print(len(im_list)) # print(len(t_list)) return t_list, im_list # 获取训练图片组 def next_batch(batch_count=60, width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT): batch_x = np.zeros([batch_count, width * height]) batch_y = np.zeros([batch_count, CAPTCHA_LEN * len(CAPTCHA_LIST)]) text, image = wrap_gen_captcha_text_and_image() for i in range(batch_count): # 随机抽取一张图片 text_a = random.choice(text) image_a = image[text.index(text_a)] # print(text.index(text_a)) # print(text_a) image_a = convert2gray(image_a) # 将图片数组一维化 同时将文本也对应在两个二维组的同一行 batch_x[i, :] = image_a.flatten()/ 255 batch_y[i, :] = text2vec(text_a) # 返回该训练批次 return batch_x, batch_y if __name__ == '__main__': x,y = next_batch(batch_count=1) print(x,'\n\n',y)
运行之后返回的是如下结果:
创建模型、训练模型
创建模型:
这里用到了 5 层网络,前 3 层为卷积层,第 4、5 层为全连接层。对 4 层隐藏层都进行 dropout。网络结构如下所示: input——>conv——>pool——>dropout——>conv——>pool——>dropout——>conv——>pool——>dropout——>fully connected layer——>dropout——>fully connected layer——>output
训练数据
这里选择交叉熵损失函数。sigmod_cross适用于每个类别相互独立但不互斥,如图中可以有字母和数字。每批次采用 64 个训练样本,每训练100次测试一次样本识别的准确度,当准确度大于 95% 时保存模型,当准确度大于99%时训练结束。我们这里采用CPU来训练模型,我大概训练了6-7小时,准确度达到了99%。
train.py
import os import tensorflow as tf from demo1.process import next_batch from demo1.getimg import CAPTCHA_HEIGHT, CAPTCHA_WIDTH, CAPTCHA_LEN, CAPTCHA_LIST from datetime import datetime # 随机生成权重 def weight_variable(shape, w_alpha=0.01): initial = w_alpha * tf.random_normal(shape) return tf.Variable(initial) # 随机生成偏置项 def bias_variable(shape, b_alpha=0.1): initial = b_alpha * tf.random_normal(shape) return tf.Variable(initial) # 局部变量线性组合,步长为1,模式‘SAME’代表卷积后图片尺寸不变,即零边距 def conv2d(x, w): return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME') # max pooling,取出区域内最大值为代表特征, 2x2pool,图片尺寸变为1/2 def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') # 三层卷积神经网络计算图 def cnn_graph(x, keep_prob, size, captcha_list=CAPTCHA_LIST, captcha_len=CAPTCHA_LEN): # 图片reshape为4维向量 image_height, image_width = size x_image = tf.reshape(x, shape=[-1, image_height, image_width, 1]) # 第一层 # filter定义为3x3x1, 输出32个特征, 即32个filter w_conv1 = weight_variable([3, 3, 1, 32]) b_conv1 = bias_variable([32]) # rulu激活函数 h_conv1 = tf.nn.relu(tf.nn.bias_add(conv2d(x_image, w_conv1), b_conv1)) # 池化 h_pool1 = max_pool_2x2(h_conv1) # dropout防止过拟合 h_drop1 = tf.nn.dropout(h_pool1, keep_prob) # 第二层 w_conv2 = weight_variable([3, 3, 32, 64]) b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(tf.nn.bias_add(conv2d(h_drop1, w_conv2), b_conv2)) h_pool2 = max_pool_2x2(h_conv2) h_drop2 = tf.nn.dropout(h_pool2, keep_prob) # 第三层 w_conv3 = weight_variable([3, 3, 64, 64]) b_conv3 = bias_variable([64]) h_conv3 = tf.nn.relu(tf.nn.bias_add(conv2d(h_drop2, w_conv3), b_conv3)) h_pool3 = max_pool_2x2(h_conv3) h_drop3 = tf.nn.dropout(h_pool3, keep_prob) # 全连接层 image_height = int(h_drop3.shape[1]) image_width = int(h_drop3.shape[2]) w_fc = weight_variable([image_height * image_width * 64, 1024]) b_fc = bias_variable([1024]) h_drop3_re = tf.reshape(h_drop3, [-1, image_height * image_width * 64]) h_fc = tf.nn.relu(tf.add(tf.matmul(h_drop3_re, w_fc), b_fc)) h_drop_fc = tf.nn.dropout(h_fc, keep_prob) # 全连接层(输出层) w_out = weight_variable([1024, len(captcha_list) * captcha_len]) b_out = bias_variable([len(captcha_list) * captcha_len]) y_conv = tf.add(tf.matmul(h_drop_fc, w_out), b_out) return y_conv # 最小化loss def optimize_graph(y, y_conv): # 交叉熵计算loss # sigmod_cross适用于每个类别相互独立但不互斥,如图中可以有字母和数字 loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=y_conv, labels=y)) # 最小化loss优化 optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss) return optimizer # 偏差计算 def accuracy_graph(y, y_conv, width=len(CAPTCHA_LIST), height=CAPTCHA_LEN): # 预测值 predict = tf.reshape(y_conv, [-1, height, width]) max_predict_idx = tf.argmax(predict, 2) # 标签 label = tf.reshape(y, [-1, height, width]) max_label_idx = tf.argmax(label, 2) correct_p = tf.equal(max_predict_idx, max_label_idx) accuracy = tf.reduce_mean(tf.cast(correct_p, tf.float32)) return accuracy # 训练cnn def train(height=CAPTCHA_HEIGHT, width=CAPTCHA_WIDTH, y_size=len(CAPTCHA_LIST) * CAPTCHA_LEN): acc_rate = 0.95 # 按照图片大小申请占位符 x = tf.placeholder(tf.float32, [None, height * width]) y = tf.placeholder(tf.float32, [None, y_size]) # 防止过拟合 训练时启用 测试时不启用 keep_prob = tf.placeholder(tf.float32) # cnn模型 y_conv = cnn_graph(x, keep_prob, (height, width)) # 最优化 optimizer = optimize_graph(y, y_conv) # 偏差 accuracy = accuracy_graph(y, y_conv) # 启动会话.开始训练 saver = tf.train.Saver() sess = tf.Session() sess.run(tf.global_variables_initializer()) step = 0 while 1: # 每批次64个样本 batch_x, batch_y = next_batch(64) sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: 0.75}) print("step:", step) # 每训练一百次测试一次 if step % 100 == 0: batch_x_test, batch_y_test = next_batch(100) acc = sess.run(accuracy, feed_dict={x: batch_x_test, y: batch_y_test, keep_prob: 1.0}) print(datetime.now().strftime('%c'), ' step:', step, ' accuracy:', acc) # 偏差满足要求,保存模型 if acc > acc_rate: model_path = os.getcwd() + os.sep + str(acc_rate) + "captcha.model" saver.save(sess, model_path, global_step=step) acc_rate += 0.01 if acc_rate > 0.99: break step += 1 sess.close() if __name__ == '__main__': train()
因为我们这篇文章是针对0基础深度学习的,所以我们也不需要太多的去理解里面的意思。我们训练模型,就是来运行这一串代码。
4 测试模型
我们训练完成之后,会出现以下四个文件:
这个就是我们耗费几个小时获取的成果!
最后,我们来测试一下他们:
import tensorflow as tf from demo1.train import cnn_graph from demo1.process import vec2text,convert2gray,wrap_gen_captcha_text_and_image from demo1.getimg import CAPTCHA_HEIGHT, CAPTCHA_WIDTH, CAPTCHA_LEN, CAPTCHA_LIST import numpy as np import random # 验证码图片转化为文本 def captcha2text(image_list, height=CAPTCHA_HEIGHT, width=CAPTCHA_WIDTH): x = tf.placeholder(tf.float32, [None, height * width]) keep_prob = tf.placeholder(tf.float32) y_conv = cnn_graph(x, keep_prob, (height, width)) saver = tf.train.Saver() with tf.Session() as sess: saver.restore(sess, tf.train.latest_checkpoint('.')) predict = tf.argmax(tf.reshape(y_conv, [-1, CAPTCHA_LEN, len(CAPTCHA_LIST)]), 2) vector_list = sess.run(predict, feed_dict={x: image_list, keep_prob: 1}) vector_list = vector_list.tolist() text_list = [vec2text(vector) for vector in vector_list] return text_list if __name__ == '__main__': # 从文件中随机取出一张验证码进行测试 text, image = wrap_gen_captcha_text_and_image() text_a = random.choice(text) image_a = image[text.index(text_a)] img_array = np.array(image_a) image = convert2gray(img_array) image = image.flatten() / 255 pre_text = captcha2text([image]) if pre_text[0] == text_a: print(' 正确验证码:', text_a, "识别出来的:", pre_text," TURE") else: print(' 正确验证码:', text_a, "识别出来的:", pre_text, "FLASE")
从结果可以看到,识别成功!