0.引言
介绍了如何生成手写体数字的数据,提取特征,借助 sklearn 机器学习模型建模,进行识别手写体数字 1-9 模型的建立和测试。
用到的几种模型:
1. LR,Logistic Regression, (线性模型)中的逻辑斯特回归
2. Linear SVC,Support Vector Classification, (支持向量机)中的线性支持向量分类
3. MLPC,Multi-Layer Perceptron Classification, (神经网络)多层感知机分类
4. SGDC,Stochastic Gradient Descent Classification, (线性模型)随机梯度法求解
手写体的识别是一个 分类 问题,提取图像特征作为模型输入,输出到标记数字 1-9;
主要内容:
1. 生成手写体数字数据集;
2. 提取图像特征存入 CSV;
3. 利用机器学习建立和测试手写体数字识别模型;
(如果你想尝试生成自己的数据集可以参考我的另一篇博客:http://www.cnblogs.com/AdaminXie/p/8379749.html)
源码上传到了我的 GitHub: https://github.com/coneypo/ML_handwritten_number, 有问题可以留言或者联系我邮箱;
得到不同样本量训练下,几种机器学习模型精度随样本的变化关系曲线:
图 0 不同样本数目下的四种模型的测试精度( 数据集大小从 100 到 5800,间隔 100 )
1. 开发环境
python: 3.6.3
import PIL, cv2, pandas, numpy, os, csv, random
需要调用的 sklearn 库:
1 from sklearn.linear_model import LogisticRegression # 线性模型中的 Logistic 回归模型 2 from sklearn.linear_model import SGDClassifier # 线性模型中的随机梯度下降模型 3 from sklearn.svm import LinearSVC # SVM 模型中的线性 SVC 模型 4 from sklearn.neural_network import MLPClassifier # 神经网络模型中的多层网络模型
2.整体设计思路
图 1 整体的框架设计
工程的目的,是想利用机器学习模型去训练识别生成的随机验证码图像(单个数字 1-9 ),通过以下三个步骤实现:
1. 生成手写体数据集
2. 提取特征向量写入 CSV
3. sklearn 模型训练和测试
图 2 整体的设计流程
3. 编程过程
3.1 生成多张单个验证码图像 ( generate_folders.py, generate_handwritten_numbers.py )
图 3 生成的多张单个验证码图像
手写体数据集的生成在我的另一篇博客详细介绍:( Link:http://www.cnblogs.com/AdaminXie/p/8379749.html )
思路就是 random 随机生成数字 1-9,然后利用PIL的画笔工具进行画图,对图像进行扭曲,然后根据随机数的真实标记 1-9,保存到对应文件夹内,用标记+序号命名。
1 draw = ImageDraw.Draw(im) # 画笔工具
3.2 提取特征向量写入 CSV ( get_features.py )
这一步是提取图像中的特征。生成的单个图像是 30*30 即 900 个像素点的;
为了降低维度,没有选择 900 个像素点每点的灰度作为输入,而是选取了 30 行每行的黑点数,和 30 列每列的黑点数作为输入,这样降到了 60 维。
(a) 提取 900 维特征
(b) 提取 60 维特征
图 4 提取图像特征
特征的提取也比较简单,逐行逐列计算然后计数求和:
1 def get_feature(img): 2 # 提取特征 3 # 30*30的图像, 4 5 width, height = img.size 6 7 global pixel_cnt_list 8 pixel_cnt_list=[] 9 10 height = 30 11 for y in range(height): 12 pixel_cnt_x = 0 13 for x in range(width): 14 # print(img.getpixel((x,y))) 15 if img.getpixel((x, y)) == 0: # 黑点 16 pixel_cnt_x += 1 17 18 pixel_cnt_list.append(pixel_cnt_x) 19 20 for x in range(width): 21 pixel_cnt_y = 0 22 for y in range(height): 23 if img.getpixel((x, y)) == 0: # 黑点 24 pixel_cnt_y += 1 25 26 pixel_cnt_list.append(pixel_cnt_y) 27 28 return pixel_cnt_list
所以我们接下来需要做的工作是,遍历访问文件夹 num_1-9 中的所有图像文件,进行特征提取,然后写入 CSV 文件中:
1 with open(path_csv+"tmp.csv", "w", newline="") as csvfile: 2 writer = csv.writer(csvfile) 3 # 访问文件夹 1-9 4 for i in range(1, 10): 5 num_list = os.listdir(path_images + "num_" + str(i)) 6 print(path_images + "num_" + str(i)) 7 print("num_list:", num_list) 8 # 读到图像文件 9 if os.path.isdir(path_images + "num_" + str(i)): 10 print("样本个数:", len(num_list)) 11 sum_images = sum_images + len(num_list) 12 13 # Travsel every single image to generate the features 14 for j in range(0, (len(num_list))): 15 16 # 处理读取单个图像文件提取特征 17 img = Image.open(path_images + "num_" + str(i)+"/" + num_list[j]) 18 get_features_single(img) 19 pixel_cnt_list.append(num_list[j][0]) 20 21 # 写入CSV 22 writer.writerow(pixel_cnt_list)
图 5 提取出来的 CSV 文件(前 60 列为输入特征,第 61 列为输出标记)
3.3 sklearn 模型训练和测试 ( ml_ana.py, test_single_images.py )
之前的准备工作都做完之后,我们生成了存放着 60 维输入特征和 1 维输出标记的 61 列的 CSV 文件;
然后就可以利用这些数据,交给 sklearn 的机器学习模型进行建模处理。
3.3.1 特征数据加工
第一步需要对 CSV 文件中的数据进行提取,利用 pd.read_csv 进行读取。写入 CSV 时,前 60 列为 60 维的特征向量,第 61 列为输出标记 1-9;
利用前面已经提取好的特征 CSV;
1 # 从 CSV 中读取数据 2 def pre_data(): 3 # CSV61维表头名 4 column_names = [] 5 6 for i in range(0, 60): 7 column_names.append("feature_" + str(i)) 8 column_names.append("true_number") 9 10 # 读取csv 11 path_csv = "../data/data_csvs/" 12 data = pd.read_csv(path_csv + "data_10000.csv", names=column_names) 13 14 # 提取数据集 15 global X_train, X_test, y_train, y_test 16 X_train, X_test, y_train, y_test = train_test_split( 17 data[column_names[0:60]], 18 data[column_names[60]], 19 test_size=0.25, # 75% for 训练,25% for 测试 20 random_state=33 21 )
利用sklearn库的 train_test_split 函数 将数据进行分割,
得到训练集数据:X_train, y_train
得到测试集数据:X_test, y_test
3.3.2 模型训练和测试
经过前面一系列的准备工作做完,这里正式开始使用 sklearn 的机器学习模型建模;
调用 sklearn 利用训练数据对模型进行训练,然后利用测试数据进行性能测试,并且保存模型到本地 ( "/data/data_models/model_xxx.m");
ml_ana.py:
1 # created at 2018-01-29 2 # updated at 2018-09-28 3 4 # Author: coneypo 5 # Blog: http://www.cnblogs.com/AdaminXie 6 # GitHub: https://github.com/coneypo/ML_handwritten_number 7 8 9 from sklearn.model_selection import train_test_split 10 import pandas as pd 11 12 from sklearn.preprocessing import StandardScaler # 标准化 13 14 # 调用模型 15 from sklearn.linear_model import LogisticRegression # 线性模型中的 Logistic 回归模型 16 from sklearn.svm import LinearSVC # SVM 模型中的线性 SVC 模型 17 from sklearn.neural_network import MLPClassifier # 神经网络模型中的多层网络模型 18 from sklearn.linear_model import SGDClassifier # 线性模型中的随机梯度下降模型 19 20 # 保存模型 21 from sklearn.externals import joblib 22 23 24 # 从 CSV 中读取数据 25 def pre_data(): 26 # CSV61维表头名 27 column_names = [] 28 29 for i in range(0, 60): 30 column_names.append("feature_" + str(i)) 31 column_names.append("true_number") 32 33 # 读取csv 34 path_csv = "../data/data_csvs/" 35 data = pd.read_csv(path_csv + "data_10000.csv", names=column_names) 36 37 # 提取数据集 38 global X_train, X_test, y_train, y_test 39 X_train, X_test, y_train, y_test = train_test_split( 40 data[column_names[0:60]], 41 data[column_names[60]], 42 test_size=0.25, # 75% for 训练,25% for 测试 43 random_state=33 44 ) 45 46 47 path_saved_models = "../data/data_models/" 48 49 50 # LR, logistic regression, 逻辑斯特回归分类(线性模型) 51 def way_LR(): 52 X_train_LR = X_train 53 y_train_LR = y_train 54 55 X_test_LR = X_test 56 y_test_LR = y_test 57 58 # 数据预加工 59 # ss_LR = StandardScaler() 60 # X_train_LR = ss_LR.fit_transform(X_train_LR) 61 # X_test_LR = ss_LR.transform(X_test_LR) 62 63 # 初始化LogisticRegression 64 LR = LogisticRegression() 65 66 # 调用LogisticRegression中的fit()来训练模型参数 67 LR.fit(X_train_LR, y_train_LR) 68 69 # 使用训练好的模型lr对X_test进行预测 70 # 结果储存在y_predict_LR中 71 global y_predict_LR 72 y_predict_LR = LR.predict(X_test_LR) 73 74 # 评分函数 75 global score_LR 76 score_LR = LR.score(X_test_LR, y_test_LR) 77 print("The accurary of LR:", ' ', score_LR) 78 79 # 保存模型 80 joblib.dump(LR, path_saved_models + "model_LR.m") 81 82 return LR 83 84 85 # 多层感知机分类(神经网络) 86 def way_MLPC(): 87 X_train_MLPC = X_train 88 y_train_MLPC = y_train 89 90 X_test_MLPC = X_test 91 y_test_MLPC = y_test 92 93 # ss_MLPC = StandardScaler() 94 # X_train_MLPC = ss_MLPC.fit_transform(X_train_MLPC) 95 # X_test_MLPC = ss_MLPC.transform(X_test_MLPC) 96 97 MLPC = MLPClassifier(hidden_layer_sizes=(13, 13, 13), max_iter=500) 98 MLPC.fit(X_train_MLPC, y_train_MLPC) 99 100 global y_predict_MLPC 101 y_predict_MLPC = MLPC.predict(X_test_MLPC) 102 103 global score_MLPC 104 score_MLPC = MLPC.score(X_test_MLPC, y_test_MLPC) 105 print("The accurary of MLPC:", ' ', score_MLPC) 106 107 # 保存模型 108 joblib.dump(MLPC, path_saved_models + "model_MLPC.m") 109 110 return MLPC 111 112 113 # Linear SVC, Linear Supported Vector Classifier, 线性支持向量分类(SVM支持向量机) 114 def way_LSVC(): 115 X_train_LSVC = X_train 116 y_train_LSVC = y_train 117 118 X_test_LSVC = X_test 119 y_test_LSVC = y_test 120 121 # Standard Scaler 122 # ss_LSVC = StandardScaler() 123 # X_train_LSVC = ss_LSVC.fit_transform(X_train_LSVC) 124 # X_test_LSVC = ss_LSVC.transform(X_test_LSVC) 125 126 LSVC = LinearSVC() 127 LSVC.fit(X_train_LSVC, y_train_LSVC) 128 129 global y_predict_LSVC 130 y_predict_LSVC = LSVC.predict(X_test_LSVC) 131 132 global score_LSVC 133 score_LSVC = LSVC.score(X_test_LSVC, y_test_LSVC) 134 print("The accurary of LSVC:", ' ', score_LSVC) 135 136 # 保存模型 137 joblib.dump(LSVC, path_saved_models + "model_LSVC.m") 138 139 return LSVC 140 141 142 # SGDC, stochastic gradient decent 随机梯度下降法求解(线性模型) 143 def way_SGDC(): 144 X_train_SGDC = X_train 145 y_train_SGDC = y_train 146 147 X_test_SGDC = X_test 148 y_test_SGDC = y_test 149 150 # ss_SGDC = StandardScaler() 151 # X_train_SGDC = ss_SGDC.fit_transform(X_train_SGDC) 152 # X_test_SGDC = ss_SGDC.transform(X_test_SGDC) 153 154 SGDC = SGDClassifier(max_iter=5) 155 156 SGDC.fit(X_train_SGDC, y_train_SGDC) 157 158 global y_predict_SGDC 159 y_predict_SGDC = SGDC.predict(X_test_SGDC) 160 161 global score_SGDC 162 score_SGDC = SGDC.score(X_test_SGDC, y_test_SGDC) 163 print("The accurary of SGDC:", ' ', score_SGDC) 164 165 # 保存模型 166 joblib.dump(SGDC, path_saved_models + "model_SGDC.m") 167 168 return SGDC 169 170 171 pre_data() 172 way_LR() 173 way_LSVC() 174 way_MLPC() 175 way_SGDC()
3.3.3 测试 ( test_single_images.py )
对于一张手写体数字,提取特征然后利用保存的模型进行预测;
1 # created at 2018-01-29 2 # updated at 2018-09-28 3 4 # Author: coneypo 5 # Blog: http://www.cnblogs.com/AdaminXie 6 # GitHub: https://github.com/coneypo/ML_handwritten_number 7 8 # 利用保存到本地的训练好的模型,来检测单张 image 的标记 9 10 from sklearn.externals import joblib 11 from PIL import Image 12 13 img = Image.open("../test/test_1.png") 14 15 # Get features 16 from generate_datebase import get_features 17 features_test_png = get_features.get_features_single(img) 18 19 path_saved_models = "../data/data_models/" 20 21 # LR 22 LR = joblib.load(path_saved_models + "model_LR.m") 23 predict_LR = LR.predict([features_test_png]) 24 print("LR:", predict_LR[0]) 25 26 # LSVC 27 LSVC = joblib.load(path_saved_models + "model_LSVC.m") 28 predict_LSVC = LSVC.predict([features_test_png]) 29 print("LSVC:", predict_LSVC[0]) 30 31 # MLPC 32 MLPC = joblib.load(path_saved_models + "model_MLPC.m") 33 predict_MLPC = MLPC.predict([features_test_png]) 34 print("MLPC:", predict_MLPC[0]) 35 36 # SGDC 37 SGDC = joblib.load(path_saved_models + "model_SGDC.m") 38 predict_SGDC = SGDC.predict([features_test_png]) 39 print("SGDC:", predict_SGDC[0])
3.3.4 绘制样本数-精度图像
可以绘图来更加直观的精度:
1 # 2018-01-29 2 # By TimeStamp 3 # cnblogs: http://www.cnblogs.com/AdaminXie/ 4 # plot_from_csv.py 5 # 从存放样本数-精度的CSV中读取数据,绘制图形 6 7 8 import numpy as np 9 import matplotlib.pyplot as plt 10 import pandas as pd 11 12 # CSV路径 13 path_csv = "F:/***/P_ML_handwritten_number/data/score_csv/" 14 15 # 存储x轴坐标 16 x_array = [] 17 18 # 存储精度 19 LR_score_arr = [] 20 LSVC_score_arr = [] 21 MLPC_score_arr = [] 22 SGDC_score_arr = [] 23 24 # 读取CSV数据 25 column_names = ["samples", "acc_LR", "acc_LSVC", "acc_MLPC", "acc_SGDC"] 26 rd_csv = pd.read_csv(path_csv + "score_100to5800.csv", names=column_names) 27 28 print(rd_csv.shape) 29 30 for i in range(len(rd_csv)): 31 x_array.append(float(rd_csv["samples"][i])) 32 LR_score_arr.append(float(rd_csv["acc_LR"][i])) 33 LSVC_score_arr.append(float(rd_csv["acc_LSVC"][i])) 34 MLPC_score_arr.append(float(rd_csv["acc_MLPC"][i])) 35 SGDC_score_arr.append(float(rd_csv["acc_SGDC"][i])) 36 37 ################ 3次线性拟合 ################ 38 xray = np.array(x_array) 39 y_LR = np.array(LR_score_arr) 40 y_LSVC = np.array(LSVC_score_arr) 41 y_MLPC = np.array(MLPC_score_arr) 42 y_SGDC = np.array(SGDC_score_arr) 43 44 z1 = np.polyfit(xray, y_LR, 5) 45 z2 = np.polyfit(xray, y_LSVC, 5) 46 z3 = np.polyfit(xray, y_MLPC, 5) 47 z4 = np.polyfit(xray, y_SGDC, 5) 48 49 p1 = np.poly1d(z1) 50 p2 = np.poly1d(z2) 51 p3 = np.poly1d(z3) 52 p4 = np.poly1d(z4) 53 54 y_LR_vals = p1(xray) 55 y_LSVC_vals = p2(xray) 56 y_MLPC_vals = p3(xray) 57 y_SGDC_vals = p4(xray) 58 ################################# 59 60 # 标明线条说明 61 plt.annotate("— LR", xy=(5030, 0.34), color='b', size=12) 62 plt.annotate("— LSVC", xy=(5030, 0.26), color='r', size=12) 63 plt.annotate("— MLPC", xy=(5030, 0.18), color='g', size=12) 64 plt.annotate("— SGDC", xy=(5030, 0.10), color='black', size=12) 65 66 # 画拟合曲线 67 plt.plot(xray, y_LR_vals, color='b') 68 plt.plot(xray, y_LSVC_vals, color='r') 69 plt.plot(xray, y_MLPC_vals, color='g') 70 plt.plot(xray, y_SGDC_vals, color='black') 71 72 # 画离散点 73 plt.plot(xray, y_LR, color='b', linestyle='None', marker='.', label='y_test', linewidth=100) 74 plt.plot(xray, y_LSVC, color='r', linestyle='None', marker='.', label='y_test', linewidth=0.01) 75 plt.plot(xray, y_MLPC, color='g', linestyle='None', marker='.', label='y_test', linewidth=0.01) 76 plt.plot(xray, y_SGDC, color='black', linestyle='None', marker='.', label='y_test', linewidth=0.01) 77 78 # 绘制y=1参考线 79 plt.plot([0, 6000], [1, 1], 'k--') 80 81 # 设置y轴坐标范围 82 plt.ylim(0, 1.1) 83 84 # 标明xy轴 85 plt.xlabel('samples') 86 plt.ylabel('accuracy') 87 88 plt.show()
3.3.4 测试结果
在样本数 sample_num = 50 的情况下,训练 75% 数据,用 25% 的数据即 13 个样本进行测试;
几种模型的测试结果如 图 6 所示,可见除了 SVM 达到 84.7% 的精度之外,其他都在 60-70% 左右;
但是因为只有 50 个样本点,小样本的情况下测试精度的偶然性误差比较大。
图 6 手写体识别的性能分析( 在样本数为 50 的情况下 )
增加样本数到 100,即生成了 100 张单个手写体图像,75 张用来训练,25 张用来测试;
25 张的测试结果 图 6 所示,几种模型的测试精度都达到了 90% 左右。
图 7 手写体识别的性能分析(在样本数为 100 的情况下)
图 8 不同样本数目下的四种模型的测试精度( 5次拟合 )
# 如果对您有帮助,欢迎在 GitHub 上 Star 支持我: https://github.com/coneypo/ML_handwritten_number
# 请尊重他人劳动成果,转载或者使用源码请注明出处: http://www.cnblogs.com/AdaminXie
# 如有问题请联系邮箱 :coneypo@foxmail.com