ResNet网络识别验证码实战练习——高准确率的机器学习模型

原文链接:https://finthon.com/resnet-captcha/

简介

使用Python+Tensorflow的CNN技术快速识别验证码一文中,我们使用了3层简单的CNN卷积神经网络完成了验证码的识别,准确率不是特别高。虽然ResNet网络经常使用,但是一直没有时间来测试之前的验证码识别的项目。于是,我花了一下午的时间从头到尾梳理了这个项目:ResNet网络识别验证码实战练习——高准确率的机器学习模型。在本文中,我将会详细地介绍如何处理图片制作tfrecord格式文件以及搭建训练、预测模型,一路实现高准确率的机器学习模型。

运行环境:

  • Python 3.6.5
  • TensorFlow 1.8.0(GPU版本)

图片处理

我直接使用先前的验证码做测试,毕竟花了6个小时码出来的验证码(3430张)。原始的验证码是这样的:

《ResNet网络识别验证码实战练习——高准确率的机器学习模型》

二值化处理后,得到如下的图形:

《ResNet网络识别验证码实战练习——高准确率的机器学习模型》

处理过程见前文。

图片resize

我们今天的项目就是基于处理后的验证码开始的。由于使用的是slim框架中的ResNet50网络,要求输入大小为 224×224×3,需要事先对图片大小进行统一修改,方便后面的操作。

import os
import numpy as np
from PIL import Image
 
 
# 验证码路径
captcha_path = r'F:\resnet_for_captcha\captcha4'
# 修改后图片存放路径
save_path = r'F:\resnet_for_captcha\1resize\resize_path'
for i in os.listdir(captcha_path):
    img = Image.open(os.path.join(captcha_path, i))
    arr = np.array(img) * 255       # 注意这里的np.array(img)是布尔值,之前二值化遗留下来的问题
    im = Image.fromarray(arr)
    im = im.resize((224, 224))
    arr = np.array(im)
    x = np.zeros([224, 224, 3])    # 创建一个224*224*3的矩阵
    for ii in range(224):
        for jj in range(224):
            x[ii, jj, :] = arr[ii, jj]
    im = Image.fromarray(x.astype('uint8'))     # 图片矩阵使用该格式
    im.save(os.path.join(save_path, i))

在这里需要注意几点:原来的图片是 114×450 大小的,要变成 224×224×3 大小,相当于多加了一个维度,在第三个维度上的值都是一样的;图片矩阵一定要改成 uint8 格式,表示像素点范围0-255之间。转换完成后就变成下图这样:

《ResNet网络识别验证码实战练习——高准确率的机器学习模型》

虽然部分拉伸了图片,但是对机器学习来说可以接受。

tfrecord格式

为了提高机器学习的效率,需要构建队列模式来减少机器读取数据的等待时间。TensorFlow提供了tfrecord格式文件,结合队列模式能够方便数据的读取操作。在resize验证码图片之后,再将其转换成tfrecord格式文件。

import os
import tensorflow as tf
from PIL import Image
import numpy as np
 
 
# 将验证码的名字转换成数组,one hot编码
def name2vec(name):
    vector = np.zeros(6 * 26)
    for i, c in enumerate(name):
        idx = i * 26 + ord(c) - 97
        vector[idx] = 1
    return vector
 
 
# 图片路径
cwd = r'F:\resnet_for_captcha\1resize\resize_path'
 
# 文件路径
file_path = r'F:\resnet_for_captcha\2to_tfrecord\tfrecord'
 
# 存放图片个数
bestnum = 1000
 
# 第几个图片
num = 0
 
# 第几个TFRecord文件
recordfilenum = 0
 
# tfrecords格式文件名
ftrecordfilename = ("train.tfrecords-%.3d" % recordfilenum)
writer = tf.python_io.TFRecordWriter(os.path.join(file_path, ftrecordfilename))
 
for i in os.listdir(cwd):
    num += 1
    print(num)
    if num > bestnum:
        num = 1
        recordfilenum += 1
        ftrecordfilename = ("train.tfrecords-%.3d" % recordfilenum)
        writer = tf.python_io.TFRecordWriter(os.path.join(file_path, ftrecordfilename))
 
    name = os.path.splitext(i)[0]
    name_vec = name2vec(name).tobytes()         # 转成二进制格式
    img = Image.open(os.path.join(cwd, i), 'r')
    img_raw = img.tobytes()
    example = tf.train.Example(
        features=tf.train.Features(feature={
            'label': tf.train.Feature(bytes_list=tf.train.BytesList(value=[name_vec])),
            'img_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
        }))
    writer.write(example.SerializeToString())
writer.close()

我们以每1000张图片制作一个tfrecord文件,我们把图片的像素矩阵和图片的标签都做成了二进制的数据流,存入tfrecord文件中。最后得到的tfrecord文件如下所示:

《ResNet网络识别验证码实战练习——高准确率的机器学习模型》

训练模型

做好了tfrecord文件后,接下来的任务就是搭建训练模型,其中包括读取tfrecord文件、构建队列模式、构建ResNet网络等。

首先定义读取tfrecord文件的函数:

def read_and_decode_tfrecord(filename):
    filename_deque = tf.train.string_input_producer(filename)
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_deque)
    features = tf.parse_single_example(serialized_example, features={
        'label': tf.FixedLenFeature([], tf.string),
        'img_raw': tf.FixedLenFeature([], tf.string)})
    label = tf.decode_raw(features['label'], tf.float64)
    label = tf.reshape(label, [6 * 26])
    label = tf.cast(label, tf.float32)
    img = tf.decode_raw(features['img_raw'], tf.uint8)
    img = tf.reshape(img, [224, 224, 3])
    img = tf.cast(img, tf.float32) / 255.0    # 归一化
    return img, label

filename就是每个tfrecord的地址,因为我们是以二进制流存入tfrecord文件的,当从文件中读取原来的数据的时候,需要指定数据格式和大小。在这里我们对图片矩阵进行归一化。

接下来我们在主函数中定义队列和模型部分:

def main():
    save_dir = r"F:\resnet_for_captcha\3train\model\train.model"
    batch_size_ = 2
    lr = tf.Variable(0.0001, dtype=tf.float32)
    x = tf.placeholder(tf.float32, [None, 224, 224, 3])
    y_ = tf.placeholder(tf.float32, [None, 6 * 26])
 
    tfrecord_path = r'F:\resnet_for_captcha\2to_tfrecord\tfrecord'
    train_list = []
    for file in os.listdir(tfrecord_path):
        train_list.append(os.path.join(tfrecord_path, file))
 
    min_after_dequeue = 1000
    # 随机打乱顺序
    img, label = read_and_decode_tfrecord(train_list)
    img_batch, label_batch = tf.train.shuffle_batch([img, label], num_threads=2, batch_size=batch_size_,
                                                    capacity=min_after_dequeue + 3 * batch_size_,
                                                    min_after_dequeue=min_after_dequeue)
 
    pred, end_points = nets.resnet_v2.resnet_v2_50(x, num_classes=6 * 26, is_training=True)
    pred = tf.reshape(pred, shape=[-1, 6 * 26])
    # 定义损失函数
    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=pred, labels=y_))
    optimizer = tf.train.AdamOptimizer(learning_rate=lr).minimize(loss)
    # 准确度
    predict = tf.reshape(pred, [-1, 6, 26])
    max_idx_p = tf.argmax(predict, 2)
    max_idx_l = tf.argmax(tf.reshape(y_, [-1, 6, 26]), 2)
    correct_pred = tf.equal(max_idx_p, max_idx_l)
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
 
    saver = tf.train.Saver()
    with tf.Session(config=config) as sess:
        sess.run(tf.global_variables_initializer())
        # saver.restore(sess, r'F:\resnet_for_captcha\3train\model\train.model-60000')
        # 创建一个协调器,管理线程
        coord = tf.train.Coordinator()
        # 启动QueueRunner,此时文件名队列已经进队
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        i = 60000
        cycle_num = 0
        while True:
            i += 1
            b_image, b_label = sess.run([img_batch, label_batch])
            _, loss_ = sess.run([optimizer, loss], feed_dict={x: b_image, y_: b_label})
            if i % 20 == 0:
                print('step: {}, loss: {}'.format(i, loss_))
            if i % 100 == 0:
                _loss, acc_train = sess.run([loss, accuracy], feed_dict={x: b_image, y_: b_label})
                print('--------------------------------------------------------')
                print('step: {}  train_acc: {}  loss: {}'.format(i, acc_train, _loss))
                print('--------------------------------------------------------')
            if i % 20000 == 0:
                saver.save(sess, save_dir, global_step=i)
                cycle_num += 1
            if cycle_num == 10:
                break
        coord.request_stop()
        # 其他所有线程关闭之后,这一函数才能返回
        coord.join(threads)

构建队列使用的是随机顺序队列,使用 resnet_v2_50 模型。在这里使用了线程来管理队列,每20步输出一次loss值,每100步输出一次准确率,每20000步保存一次模型。这里选择 batch_size_ = 2 ,个人测试后觉得不错的经验值。最后,按照整个流程训练了14万步停止。

这部分完整代码如下:

import os
import tensorflow as tf
import tensorflow.contrib.slim.nets as nets
import datetime
 
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
 
 
def read_and_decode_tfrecord(filename):
    filename_deque = tf.train.string_input_producer(filename)
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_deque)
    features = tf.parse_single_example(serialized_example, features={
        'label': tf.FixedLenFeature([], tf.string),
        'img_raw': tf.FixedLenFeature([], tf.string)})
    label = tf.decode_raw(features['label'], tf.float64)
    label = tf.reshape(label, [6 * 26])
    label = tf.cast(label, tf.float32)
    img = tf.decode_raw(features['img_raw'], tf.uint8)
    img = tf.reshape(img, [224, 224, 3])
    img = tf.cast(img, tf.float32) / 255.0    # 归一化
    return img, label
 
 
def main():
    save_dir = r"F:\resnet_for_captcha\3train\model\train.model"
    batch_size_ = 2
    lr = tf.Variable(0.0001, dtype=tf.float32)
    x = tf.placeholder(tf.float32, [None, 224, 224, 3])
    y_ = tf.placeholder(tf.float32, [None, 6 * 26])
 
    tfrecord_path = r'F:\resnet_for_captcha\2to_tfrecord\tfrecord'
    train_list = []
    for file in os.listdir(tfrecord_path):
        train_list.append(os.path.join(tfrecord_path, file))
 
    min_after_dequeue = 1000
    # 随机打乱顺序
    img, label = read_and_decode_tfrecord(train_list)
    img_batch, label_batch = tf.train.shuffle_batch([img, label], num_threads=2, batch_size=batch_size_,
                                                    capacity=min_after_dequeue + 3 * batch_size_,
                                                    min_after_dequeue=min_after_dequeue)
 
    pred, end_points = nets.resnet_v2.resnet_v2_50(x, num_classes=6 * 26, is_training=True)
    pred = tf.reshape(pred, shape=[-1, 6 * 26])
    # 定义损失函数
    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=pred, labels=y_))
    optimizer = tf.train.AdamOptimizer(learning_rate=lr).minimize(loss)
    # 准确度
    predict = tf.reshape(pred, [-1, 6, 26])
    max_idx_p = tf.argmax(predict, 2)
    max_idx_l = tf.argmax(tf.reshape(y_, [-1, 6, 26]), 2)
    correct_pred = tf.equal(max_idx_p, max_idx_l)
    accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
 
    saver = tf.train.Saver()
    with tf.Session(config=config) as sess:
        sess.run(tf.global_variables_initializer())
        # saver.restore(sess, r'F:\resnet_for_captcha\3train\model\train.model-60000')
        # 创建一个协调器,管理线程
        coord = tf.train.Coordinator()
        # 启动QueueRunner,此时文件名队列已经进队
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        i = 60000
        cycle_num = 0
        while True:
            i += 1
            b_image, b_label = sess.run([img_batch, label_batch])
            _, loss_ = sess.run([optimizer, loss], feed_dict={x: b_image, y_: b_label})
            if i % 20 == 0:
                print('step: {}, loss: {}'.format(i, loss_))
            if i % 100 == 0:
                _loss, acc_train = sess.run([loss, accuracy], feed_dict={x: b_image, y_: b_label})
                print('--------------------------------------------------------')
                print('step: {}  train_acc: {}  loss: {}'.format(i, acc_train, _loss))
                print('--------------------------------------------------------')
            if i % 20000 == 0:
                saver.save(sess, save_dir, global_step=i)
                cycle_num += 1
            if cycle_num == 10:
                break
        coord.request_stop()
        # 其他所有线程关闭之后,这一函数才能返回
        coord.join(threads)
 
 
if __name__ == '__main__':
    # 运行时间
    starttime = datetime.datetime.now().timestamp()
    main()
    endtime = datetime.datetime.now().timestamp()
    print(starttime)
    print(endtime)
    run_hour = (endtime - starttime) / 3600
    print('共运行{}小时!'.format(run_hour))

注:加上 config = tf.ConfigProto() 和 config.gpu_options.allow_growth = True 后能够提高GPU的利用率,最后我们计算程序运行的时间。

预测模型

我们分别搭建两个预测模型来展开这部分:一个模型来预测咱们训练的3430张图片,看模型学习后的准确率如何;另一个模型来预测样本外的10张验证码图片,来检测我们模型的泛化能力。

预测训练集

import tensorflow as tf
import tensorflow.contrib.slim.nets as nets
import os
import numpy as np
 
 
def read_and_decode_tfrecord(filename):
    filename_deque = tf.train.string_input_producer(filename)
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_deque)
    features = tf.parse_single_example(serialized_example, features={
        'label': tf.FixedLenFeature([], tf.string),
        'img_raw': tf.FixedLenFeature([], tf.string)})
    label = tf.decode_raw(features['label'], tf.float64)
    label = tf.reshape(label, [6 * 26])
    label = tf.cast(label, tf.float32)
    img = tf.decode_raw(features['img_raw'], tf.uint8)
    img = tf.reshape(img, [224, 224, 3])
    img = tf.cast(img, tf.float32) / 255.0      # 归一化
    return img, label
 
 
def vec2name(vec):
    name = []
    for i in vec:
        a = chr(i + 97)
        name.append(a)
    return "".join(name)
 
 
model_dir = r'F:\resnet_for_captcha\3train\model\train.model-140000'
tfrecord_path = r'F:\resnet_for_captcha\2to_tfrecord\tfrecord'
 
train_list = []
for file in os.listdir(tfrecord_path):
    train_list.append(os.path.join(tfrecord_path, file))
 
x = tf.placeholder(tf.float32, [None, 224, 224, 3])
y_ = tf.placeholder(tf.float32, [None, 6 * 26])
batch_size_ = 1
min_after_dequeue = 1000
# 顺序读取
img, label = read_and_decode_tfrecord(train_list)
img_batch, label_batch = tf.train.batch([img, label], num_threads=2, batch_size=batch_size_,
                                                capacity=min_after_dequeue + 3 * batch_size_)
 
pred, end_points = nets.resnet_v2.resnet_v2_50(x, num_classes=6 * 26, is_training=True)
predict = tf.reshape(pred, [-1, 6, 26])
max_idx_p = tf.argmax(predict, 2)
 
saver = tf.train.Saver()
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver.restore(sess, model_dir)
    coord = tf.train.Coordinator()
    # 启动QueueRunner,此时文件名队列已经进队
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    nn = 0
    count_true_num = 0
    count_false_num = 0
    while True:
        nn += 1
        b_image, b_label = sess.run([img_batch, label_batch])
        pre_index = sess.run(max_idx_p, feed_dict={x: b_image})
        vec = pre_index[0].tolist()
        predict_text = vec2name(vec)
        max_idx_l = np.argmax(np.reshape(b_label, [-1, 6, 26]), 2)
        vec1 = max_idx_l[0].tolist()
        true_text = vec2name(vec1)
        print('{}  真实值:{}   预测值:{}'.format(nn, true_text, predict_text))
        if true_text == predict_text:
            count_true_num += 1
        else:
            count_false_num += 1
 
        if nn == 3430:
            break
    print('正确:{}  错误:{} 准确率:{}'.format(count_true_num, count_false_num,
                                       count_true_num / (count_true_num + count_false_num)))
    coord.request_stop()
    # 其他所有线程关闭之后,这一函数才能返回
    coord.join(threads)

因为是预测训练集,所以这里的队列不再是随机打乱顺序,而是使用了顺序读取 tf.train.batch ,最终按照14万步模型,训练模型的准确率为0.9915,如果进一步训练,准确率还会更高。

预测测试集

对于测试集,我提供了10张样本外的验证码图片,由于测试集数量比较少,就不需要构建tfrecord文件。

import tensorflow as tf
import tensorflow.contrib.slim.nets as nets
from PIL import Image
import os
import numpy as np
 
 
# 向量转成标签名字
def vec2name(vec):
    name = []
    for i in vec:
        a = chr(i + 97)
        name.append(a)
    return "".join(name)
 
 
model_dir = r'F:\resnet_for_captcha\3train\model\train.model-140000'
x = tf.placeholder(tf.float32, [None, 224, 224, 3])
 
pred, end_points = nets.resnet_v2.resnet_v2_50(x, num_classes=6 * 26, is_training=True)
predict = tf.reshape(pred, [-1, 6, 26])
max_idx_p = tf.argmax(predict, 2)
saver = tf.train.Saver()
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver.restore(sess, model_dir)
    test_dir = r'F:\resnet_for_captcha\test'
    for pic in os.listdir(test_dir):
        pic_path = os.path.join(test_dir, pic)
        img = Image.open(pic_path)
        arr = np.array(img) * 255
        im = Image.fromarray(arr)
        im = im.resize((224, 224))
        arr = np.array(im)
        xx = np.zeros([224, 224, 3])
        for ii in range(224):
            for jj in range(224):
                xx[ii, jj, :] = arr[ii, jj]
        img1 = Image.fromarray(xx.astype('uint8'))
        img2 = tf.reshape(img1, [1, 224, 224, 3])
        img3 = tf.cast(img2, tf.float32) / 255.0
 
        name = os.path.splitext(pic)[0]
 
        b_image = sess.run(img3)
        t_label = sess.run(max_idx_p, feed_dict={x: b_image})
        vec = t_label[0].tolist()
        predict_text = vec2name(vec)
        print('真实值:{}   预测值:{}'.format(name, predict_text))

得到的结果如下:

《ResNet网络识别验证码实战练习——高准确率的机器学习模型》

咱们实现了100%的准确率!!!比之前的预测结果高了很多。

总结

本文介绍了如何处理验证码图片制作tfrecord格式文件构建训练和预测模型。使用ResNet网络识别验证码图片,实现了非常高的准确率。可见ResNet网络的普适性和效果都是非常好的。本文代码放在我的github,所有代码和模型全部放到这里,密码:f3k7。涉及到细节的部分本文并没有作过多的介绍,默认你有一定的TensorFlow基础,如有问题请在下面留言。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容