使用TensorFlow进行验证码识别主要实现思路

        整个实现的代码和主要过程已经在前面使用Tensorflow进行数字(字母)验证码训练和预测中体现了,但是我没有系统总结过。这篇文章是对使用TensorFlow进行验证码识别的主要思路的梳理,以及对关键处理技术的解析。

        其实主要的思路还是传统的机器学习思路,即:准备训练集、提取特征、使用定义好的神经网络结构进行训练生成训练模型;对预测集进行特征提取,再使用模型预测。

1、准备训练集。对训练集进行人工标注(也可以通过captcha验证码库生成标注好的验证码),这些训练集的格式必须是统一的,比如都是160*60分辨率,RGB格式的。(当然正式训练时可以再进一步预处理,例如分辨率转化、灰化等)

准备验证集,可以是从训练集中提取10%的样本不加入训练,这部分样本可以在在训练过程中对模型正确率进行检验,如果没有达到预测的正确率,则继续训练。

准备预测集:主要是用训练好的模型对预测集进行预测。但是预测集的格式必须是模型能够识别的格式。

2、构建神经网络。这边就是利用卷积神经网络的方式去创建。

首先要构建卷积神经网络的结构,这是一种利用计算图的思路,先定义好训练的规则和过程,再赋予数值进行计算。一个完整的卷积神经网络主要由以下几个方面组成:

(1)输入层。整个神经网络的输入,在处理验证码的卷积神经网络中,一般代表了图片的像素矩阵。(这边就需要把图片转化为像素矩阵)

X = tf.placeholder(tf.float32, [None, IMAGE_HEIGHT * IMAGE_WIDTH]) #定义了输入层格式,把图片经过处理保存在一个IMAGE_HEIGHT * IMAGE_WIDTH长度的数组中,个数不确定,先用None表示

x = tf.reshape(X, shape=[-1, IMAGE_HEIGHT, IMAGE_WIDTH, 1]) #这边是把X维数进行了调整为四维矩阵

(2)卷积层:和传统全连接层不同,卷积层每一个节点的输入只是上一层神经网络的一小块,他可以将每一小块进行更加深入地分析从而得到更高的特征。他通过一个过滤器(内核,卷积核)将当前层神经网络上的一个子节点转化为下一层神经网络上的一个单位节点矩阵(长和宽都为1,但深度不限)。

构建卷积层的主要步骤:(可看TensorFlow教程P147)

    w_c1 = tf.Variable(w_alpha * tf.random_normal([3, 3, 1, 32])) #创建权重变量。这边是随机初始化。前两维是过滤器尺寸,第三维是当前层的深度(这边是把RGB的图片灰花了,因此深度为1),第四维是过滤器深度
    b_c1 = tf.Variable(b_alpha * tf.random_normal([32])) # 创建偏置项变量,由于卷积层当前矩阵上不同位置的偏置项也是共享的,所以偏置项个数也是过滤器节点的深度
    conv1 = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(x, w_c1, strides=[1, 1, 1, 1], padding='SAME'), b_c1)) #这边把多个步骤写在一起了。分别是:

    conv1 = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') #(3)
    conv1 = tf.nn.dropout(conv1, keep_prob) #

tf.nn.conv2d:卷积层的前向传播算法。第一个参数x为输入矩阵,这是一个四维矩阵;w_c1是权重变量;strides=[1, 1, 1, 1]代表不同维度上的步长,但是第一维和最后一维数字要求一定是1;padding='SAME'是填充,表示全0填充。

tf.nn.bias_add:可以给上面的计算结果中每一个节点加上偏置项。

tf.nn.relu:将以上结果使用ReLU函数完成去线性化。(使用了激活函数)

该代码使用了3层卷积+池化,即使用了多个隐藏层使得神经网络结构更深,可以解决更加复杂的问题。

(3)池化层:有效地缩小矩阵的尺寸,从而减少最后的全连接层中的参数,可以有效防止过拟合。这边使用了max pooling是最大池化层,于此对应的还有一种叫average pooling,平均池化层。池化层也需要人工设定过滤器尺寸,是否使用权0填充以及过滤器移动步长等,但池化层只影响一个深度上的节点(卷积层横跨整个深度),即还需要在深度维上移动。

tf.nn.max_pool:conv1是卷积层计算结果,也就是当前节点矩阵,ksize=[1, 2, 2, 1]是过滤器尺寸(一般还用[1,3,3,1]),strides是步长,padding是是否使用全0填充。

(4)全连接层:经过几轮卷积层和池化层处理后,可以认为图像中的信息被抽象成了信息含量更高的特征。即卷积层和池化层可以看成图像特征提取的过程。在特征提取完成之后,需要使用全连接层完成分类任务。

# Fully connected layer
    w_d = tf.Variable(w_alpha * tf.random_normal([8 * 32 * 40, 1024]))
    b_d = tf.Variable(b_alpha * tf.random_normal([1024]))
    dense = tf.reshape(conv3, [-1, w_d.get_shape().as_list()[0]])
    dense = tf.nn.relu(tf.add(tf.matmul(dense, w_d), b_d))
    dense = tf.nn.dropout(dense, keep_prob)

(5)输出层:定义了输出层参数

    w_out = tf.Variable(w_alpha * tf.random_normal([1024, MAX_CAPTCHA * CHAR_SET_LEN]))
    b_out = tf.Variable(b_alpha * tf.random_normal([MAX_CAPTCHA * CHAR_SET_LEN]))
    out = tf.add(tf.matmul(dense, w_out), b_out)

3、定义损失函数

神经网络模型的效果以及优化目标是通过损失函数定义的。train_crack_captcha_cnn方法下的语句:

loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=output, labels=Y))

sigmoid_cross_entropy_with_logits的作用是计算经sigmoid 函数激活之后的交叉熵。logits是神经网络输出结果,Y是标准答案,也就是标注的值。

tf.reduce_mean 函数用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的的平均值,主要用作降维或者计算tensor(图像)的平均值。以上就是可以计算出一个batch的交叉熵平均值。

经典的损失函数:

(1)分类问题:交叉熵,刻画两个概率分布之间的距离。交叉熵值越小,两个概率分布越接近。此外还有 一种softmax_cross_entropy_with_logits,即使用softmax回归之后的交叉熵损失函数。

(2)回归问题:均方误差,MSE。回归问题是对具体数值预测,一般只有一个输出节点。tf.reduce_mean(tf.square(y_ - y))

4、定义优化函数:

optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

反向传播算法是训练神经网络的核心算法,它可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络在训练数据集上的损失函数达到一个较小值。参数优化过程直接决定了模型的质量。

梯度下降算法会迭代更新参数,不断沿着梯度的反方向让参数朝着总损失最小(minimize(loss))的方向更新。还需要定义一个学习率learning_rate来定义每次参数更新的幅度

为了减少收敛所需要的迭代次数,每次计算一小部分的训练数据的损失函数,这部分数据被称为batch。即每次迭代的时候都是选用一批数据的。

5、验证集正确率函数构建:

predict = tf.reshape(output, [-1, MAX_CAPTCHA, CHAR_SET_LEN])
    max_idx_p = tf.argmax(predict, 2)
    max_idx_l = tf.argmax(tf.reshape(Y, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)
    correct_pred = tf.equal(max_idx_p, max_idx_l)
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

tf.argmax(input,axis)根据axis取值的不同返回每行或者每列最大值的索引。 axis是维度,即取该维上的最大值。tf.argmax(predict, 2),tf.argmax(tf.reshape(Y, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2)都是表示仅在CHAR_SET_LEN,即标签维度上去取最大值。输出的是一个长度为batch的一维数组,这个一维数组中的值就表示了每一个样例对应的数字结果。

tf.equal:判断两个张量每一维是否相等,它的判断方法不是整体判断,而是逐个元素进行判断,如果相等就是True,不相等,就是False。由于是逐个元素判断,所以x,y 的维度要一致。即判断输出结果和正确标注的值是否一致,每个对应位置比较的结果张量。

tf.cast:执行 tensorflow 中张量数据类型转换。也就是把布尔型数值转换为实数,tf.reduce_mean计算平均值,得出模型在这组batch上的正确率。

6、以上是定义计算图中所有的计算,第二个阶段为执行计算。也就是进入具体训练环节

(1)

saver = tf.train.Saver() #该类用于保存和还原一个神经网络模型
    with tf.Session() as sess: #创建一个会话
        sess.run(tf.global_variables_initializer()) #初始化计算图中的变量

        step = 0 #定义了训练的轮次

tf.Session() :会话拥有并管理TensorFlow程序运行时所有资源,所有计算完成后需要关闭会话来帮助系统回收资源。这边使用with语句,即上下文管理器来使用会话。然后使用 sess.run()函数还执行相应的计算。

tf.global_variables_initializer():是实现初始化所有变量的过程,无需对变量一个个地初始化了。

(2)

while True:
            batch_x, batch_y = get_next_batch('./train/', 64) #从训练集目录读取;64是一次训练的样本数,可以更改
            _, loss_ = sess.run([optimizer, loss], feed_dict={X: batch_x, Y: batch_y, keep_prob: 0.75})
            #输出当前时间,训练轮次,损失值
            #print (time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())),step, loss_)

            # 每100 step计算一次准确率
            if step % 100 == 0:
                batch_x_test, batch_y_test = get_next_batch('./valide/', 50)#从验证集目录读取50张图片特征和标签向量进行验证
                acc = sess.run(accuracy, feed_dict={X: batch_x_test, Y: batch_y_test, keep_prob: 1.})
                print (u'***************************************************************第%s次的准确率为%s'%(step, acc))
                # 如果准确率大于50%,保存模型,完成训练
                if acc > 0.95:                  ##我这里设了0.9,设得越大训练要花的时间越长,如果设得过于接近1,很难达到。如果使用cpu,花的时间很长,cpu占用很高电脑发烫。
                    saver.save(sess, "crack_capcha.model", global_step=step) #保存模型
                    print (time.time()-start_time)
                    break

            step += 1

batch_x, batch_y = get_next_batch('./train/', 64):是从训练集中抽取64个样本,batch_x是64张图片构成的2维特征向量,batch_y 是标签向量,也就是用一个二维数组,每张图片的特征和标签用保存在一行中。

_, loss_ = sess.run([optimizer, loss], feed_dict={X: batch_x, Y: batch_y, keep_prob: 0.75}) : 训练并且优化。这里的列表中optimizer和loss应该是指利用用X,Y的值,同时跑这两个函数,同时会把loss结果返回,而optimizer的值这里不关心,所以用_代替。

keep_prob: 是每个元素被保留的概率,一般在大量数据训练时,为了防止过拟合,添加Dropout层,设置一个0~1之间的小数,keep_prob:1就是所有元素全部保留的意思。也就是dropout中的参数。

同理:acc = sess.run(accuracy, feed_dict={X: batch_x_test, Y: batch_y_test, keep_prob: 1.}) 也就是用X,Y跑accuracy的值,并且返回。

预测:以上是一个通过训练和优化生成模型的过程。下面解析利用模型预测的过程:

1、加载定义好的神经网络前向传播结果和训练好的模型。

output = crack_captcha_cnn() #其实是输出了前向传播结果,但是还未给数据的值初始化,所以这是获取了一个计算图
saver = tf.train.Saver()
sess = tf.Session()
saver.restore(sess, tf.train.latest_checkpoint('.')) #从当前目录加载模型

2、获取预测集,并进行格式转换

    text, image = gen_captcha_text_and_image_local(dir_path)
    image = convert2gray(image)
    image = image.flatten() / 255  #把训练图片转成了一维数组

3、获取预测结果    

predict = tf.argmax(tf.reshape(output, [-1, MAX_CAPTCHA, CHAR_SET_LEN]), 2) #也是先定义了一个获取前向传播结果中第3维的值,也就是预测值的函数。
    text_list = sess.run(predict, feed_dict={X: [image], keep_prob: 1}) #给predict 正式赋值,并跑一下。但此处返回的是
    predict_text = text_list[0].tolist() #获取预测值

4、把预测结果保存到一个MAX_CAPTCHA * CHAR_SET_LEN的向量中,该预测值对应的位置标注为1。再通过vec2text函数转化为文本。

vector = np.zeros(MAX_CAPTCHA * CHAR_SET_LEN)
    i = 0
    for t in predict_text:
        vector[i * CHAR_SET_LEN + t] = 1
        i += 1
        # break

    print("正确值: {} 预测值:{}".format(text, vec2text(vector)))

发布了49 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/figo8875/article/details/88837838