出品 | 磐创AI团队
字符识别是一种经典的模式识别问题,字符识别在现实生活中也有着非常广泛的应用,目前对于特定环境下的拉丁字符识别已经取得了很好的效果,但是对于一些复杂场景下的字符识别依然还有很多困难,例如通过手持设备拍摄以及自然场景中的图片等,Chars74K正是针对这些困难点搜集的数据集(http://www.ee.surrey.ac.uk/CVSSP/demos/chars74k/)
Chars74K包含英语和坎那达语(Kannada)两种字符,在英文数据集中包括26个拉丁文字母和10个阿拉伯数字,整个英文数据集包括64种字符(0-9,a-z,A-Z),英文数据集根据采集方式又有三种不同数据集(三种英文数据集的样本数加在一起超过了74K,Chars74K的名字也是由此而来。):
1) 7705个自然图像中采集的字符数据集(EnglishImg.tgz);
2) 3410个在平板电脑上手写的字符数据集(EnglishHnd.tgz);
3) 62992个在从计算机字体合成的字符数据集(EnglishFnt.tgz)。
在本项目中我们使用第一个数据集,即从自然场景中采集到的字符数据集,部分数据如图1所示。
图1 Chars74K数据集示例(自然场景中采集的英文字符数据集)
数据集解压之后的目录结构如图2所示,解压之后的数据集包括“BadImg”和“GoodImg”,“BadImg”中的图片质量较差,因此我们使用“GoodImg”。数据集中每一个类别的图片单独放在一个文件夹中,如图2右所示。
图2 Chars74K数据集(自然场景中采集的英文字符数据集)
数据预处理
Chars74K数据集(自然场景中采集的英文字符数据集,本项目中后续提到的Chars74K数据集一律特指该数据集)里的图片大小不一,因此我们需要将其调整为统一大小,调整图像大小的代码可以在本书配套的GitHub项目中找到,这里不做介绍。另外需要注意,原始数据集中混杂了4张单通道的灰度图,需要删除这4张图片。这里可以直接使用作者处理好的数据集。
下载地址:https://pan.baidu.com/s/1KP5JRO-M87fN-93VOsSCag
接下来我们开始实现数据处理部分,首先导入需要的包:
1 import tensorflow as tf 2 from tensorflow.keras import layers 3 import datetime 4 import numpy as np 5 from PIL import Image 6 import os
接着我们定义一个“get_dataset”函数用来获取数据集:
7 def get_dataset(path): 8 """获取数据集""" 9 data_x = [] 10 data_y = [] 11 12 # 获取当前路径下所有文件夹(或文件) 13 folder_name = os.listdir(path) 14 15 # 循环遍历每个文件夹 16 for i in folder_name: 17 file_path = os.path.join(path, i) 18 19 # 取文件夹名后三位整数作为类标 20 label = int(i[-3:]) 21 22 # 获取当前文件夹下的所有图片文件 23 filenames = os.listdir(file_path) 24 25 for filename in filenames: 26 # 组合得到每张图片的路径 27 image_path = os.path.join(file_path, filename) 28 29 # 读取图片 30 image = Image.open(image_path) 31 # 将image对象转为numpy数组 32 width, height = image.size 33 image_matrix = np.reshape(image, [width*height*3]) 34 35 data_x.append(image_matrix) 36 data_y.append(label) 37 38 return data_x, data_y
第33行代码中我们将图片转换成了numpy数组,由于我们的图像是三通道的RGB图像,因此转换后的数组大小为“width * height * 3”。
模型搭建
在本项目中我们将使用VGG-Net网络模型。VGG-Net有多种级别,其网络层数从11层到19层不等(这里的层数是指有参数更新的层,例如卷积层或全连接层),其中比较常用的是16层(VGG-Net-16)和19层(VGG-Net-19)。如图3所示是VGG-Net-16的网络结构。
图3 VGG-Net-16网络结构
VGG-Net中全部使用大小为3X3的小卷积核,希望模拟出更大的“感受野”效果,VGG-Net中的池化层均使用的是大小为2X2的最大池化。VGG-Net的设计思想在ResNet和Inception模型中也都有被采用。图4所示是不同层数的VGG-Net。
图4 不同层数的VGG-Net
本项目中我们使用的是VGG-Net-13,具体实现如下:
39 def vgg13_model(input_shape, classes): 40 model = tf.keras.Sequential() 41 42 model.add(layers.Conv2D(64, 3, 1, input_shape=input_shape, 43 padding='same', 44 activation='relu', 45 kernel_initializer='uniform')) 46 model.add(layers.Conv2D(64, 3, 1, padding='same', 47 activation='relu', 48 kernel_initializer='uniform')) 49 model.add(layers.MaxPooling2D(pool_size=(2, 2))) 50 51 model.add(layers.Conv2D(128, 3, 1, padding='same', 52 activation='relu', 53 kernel_initializer='uniform')) 54 model.add(layers.Conv2D(128, 3, 1, padding='same', 55 activation='relu', 56 kernel_initializer='uniform')) 57 model.add(layers.MaxPooling2D(pool_size=(2, 2))) 58 59 model.add(layers.Conv2D(256, 3, 1, padding='same', 60 activation='relu', 61 kernel_initializer='uniform')) 62 model.add(layers.Conv2D(256, 3, 1, padding='same', 63 activation='relu', 64 kernel_initializer='uniform')) 65 model.add(layers.MaxPooling2D(pool_size=(2, 2))) 66 67 model.add(layers.Conv2D(512, 3, 1, padding='same', 68 activation='relu', 69 kernel_initializer='uniform')) 70 model.add(layers.Conv2D(512, 3, 1, padding='same', 71 activation='relu', 72 kernel_initializer='uniform')) 73 model.add(layers.MaxPooling2D(pool_size=(2, 2))) 74 75 model.add(layers.Conv2D(512, 3, 1, padding='same', 76 activation='relu', 77 kernel_initializer='uniform')) 78 model.add(layers.Conv2D(512, 3, 1, padding='same', 79 activation='relu', 80 kernel_initializer='uniform')) 81 model.add(layers.MaxPooling2D(pool_size=(2, 2))) 82 83 model.add(layers.Flatten()) 84 model.add(layers.Dense(4096, activation='relu')) 85 86 model.add(layers.Dropout(0.5)) 87 model.add(layers.Dense(4096, activation='relu')) 88 89 model.add(layers.Dropout(0.5)) 90 model.add(layers.Dense(classes, activation='softmax')) 91 92 # 模型编译 93 model.compile(loss='categorical_crossentropy', 94 optimizer='sgd', 95 metrics=['accuracy']) 96 return model
模型训练
定义好模型后我们加载数据集并开始训练:
97 if __name__ == '__main__': 98 path = './chars74k_data' 99 data_x, data_y = get_dataset(path) 100 101 train_x = np.array(data_x).reshape(-1, 224, 224, 3) 102 train_y = [i - 1 for i in data_y] 103 train_y = tf.keras.utils.to_categorical(train_y, 62) 104 105 # 随机打乱数据集顺序 106 np.random.seed(116) 107 np.random.shuffle(train_x) 108 np.random.seed(116) 109 np.random.shuffle(train_y) 110 111 cnn_model = vgg13_model(input_shape=(224,224,3), classes=62) 112 cnn_model.summary() 113 114 # 设置TensorBoard 115 log_dir="logs/fit/"+datetime.datetime.now().strftime("%Y%m%d-%H%M%S") 116 tensorboard_callback=tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1) 117 118 # 当验证集上的loss不再下降时就提前结束训练 119 early_stop=tf.keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0.002, patience=10, mode='auto') 120 121 callbacks = [tensorboard_callback, early_stop] 122 cnn_model.fit(train_x, train_y, 123 batch_size=100, epochs=300, 124 verbose=1, validation_split=0.2, 125 callbacks=callbacks)
在第103行代码中,由于我们之前根据目录名得到的类标是从“1”开始的,因此需要对所有类标减1,让类标从“0”开始,以便在第104代码中将类标转换为one-hot编码。
第120行代码中,我们设置了一个callback函数“EarlyStopping”,该函数可以用来设置模型自动停止训练的条件。例如这里我们设置当“val_loss”的值有10次变化不超过0.002时则提前停止训练。参数“monitor”是要监测的指标;“min_delta”是监测指标的最小变化值;“patience”是没有变化的训练回合数;“mode”有三个值,分别是“auto”、“min”和“max”,当“mode”设置为“min”时,如果监测的指标有“patience”次没有达到“min_delta”的变化量,则停止训练,“max”同理。模型训练的结果如图5所示:
图5 训练过程中的Accuracy和Loss变化(红色为训练集,蓝色为验证集)
本文分享自微信公众号 - 磐创AI(xunixs),作者:小猴锅
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2020-02-08
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句