手写数字识别(二)----SVM 实现Mnist-image 手写数字图像识别

前言

前两天利用kNN实现了手写数字的识别,数据不是很多,训练数据1934个,测试数据946个。

这两天把Mnist-image的手写数字数据down了下来,利用SVM进行识别一下。

Mnist-image的手写数字数据有7万的图像数据(6万训练数据+1万测试数据),每个图像数据为 20px * 20px。

数据长啥样?

手写字体0的数据

上面是手写数字‘0’的图像数据的一部分。

0_1.png

这是0_1.png放大的效果,20px * 20px。

怎么搞?

有了数据,我们怎么搞呢?

1.把图像数据转换成向量

用numpy向量来表示图像数据。
首先将图像数据灰度化处理,然后将其存入在numpy数组中,此时每一个元素的取值范围为0~255。接着,我们对其进行灰度变换,将其映射到0~1的范围内,并对每个元素四舍五入,让取值为0或者1,最终化为二值矩阵。最后把20 * 20的二值矩阵转换为1 * 400的向量。

# 将 20px * 20px 的图像数据转换成 1*400 的 numpy 向量
# 参数:imgFile--图像名  如:0_1.png
# 返回:1*400 的 numpy 向量
def img2vector(imgFile):
    img = Image.open(imgFile).convert('L')
    img_arr = np.array(img, 'i') # 20px * 20px 灰度图像
    img_normlization = np.round(img_arr/255) # 对灰度值进行归一化
    img_arr2 = np.reshape(img_normlization, (1,-1)) # 1 * 400 矩阵
    return img_arr2

2. 读取每个数字的所有实例,转换为矩阵

接着我们需要把每个数字的所有实例都转换成1 * 400的向量,并将它们转换成N * 400(N为实例数量)的矩阵。

# 读取一个类别的所有数据并转换成矩阵 
# 参数:
#    basePath: 图像数据所在的基本路径
#       Mnist-image/train/
#       Mnist-image/test/
#    cla:类别名称
#       0,1,2,...,9
# 返回:某一类别的所有数据----[样本数量*(图像宽x图像高)] 矩阵和标签向量
def read_and_convert(imgFileList):
    dataLabel = [] # 存放类标签
    dataNum = len(imgFileList)
    dataMat = np.zeros((dataNum, 400)) # dataNum * 400 的矩阵
    for i in range(dataNum):
        imgNameStr = imgFileList[i]
        imgName = get_img_name_str(imgNameStr)  # 得到 数字_实例编号.png
        #print("imgName: {}".format(imgName))
        classTag = imgName.split(".")[0].split("_")[0] # 得到 类标签(数字)
        #print("classTag: {}".format(classTag))
        dataLabel.append(classTag)
        dataMat[i,:] = img2vector(imgNameStr)
    return dataMat, dataLabel

3. 整合训练数据

读取所有数字的所有实例,整合成一个60000 * 400的超大矩阵(呵呵,其实没多大)。

# 读取训练数据
def read_all_data():
    cName = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
    train_data_path = "Mnist-image\\train\\0"
    flist = get_file_list(train_data_path)
    dataMat, dataLabel = read_and_convert(flist)
    for c in cName:
        train_data_path_ = "Mnist-image\\train\\" + c
        flist_ = get_file_list(train_data_path_)
        dataMat_, dataLabel_ = read_and_convert(flist_)
        dataMat = np.concatenate((dataMat, dataMat_), axis=0)
        dataLabel = np.concatenate((dataLabel, dataLabel_), axis=0)
    print(dataMat.shape)
    print(len(dataLabel))
    return dataMat, dataLabel

此时我们的训练数据就准备好了。

加载数据后,我们看看这个矩阵:

训练数据矩阵

上面的array是60000 * 400的训练数据,下面的array是1 * 60000的类标签数据。

4. 构造SVM模型

SVM既可以用来处理分类问题,也可以用来作回归任务。SVM不修改的话,只能用来进行处理二分类问题。

手写数字字体识别,显然是个多类别分类问题。对于多分类问题,解决的基本思路是“拆分法”,即将多个二分类问题拆分为若干个十分类任务进行求解。具体来讲,先对问题进行拆分,然后为拆出的每个十分类任务训练一个分类器,在测试时,对这些二分类器的结果进行集成以获得最终的多分类结果。拆分的策略主要有以下几种:

  1. OvO(one-vs-one)
    这种解决方法的思路是:对于有N个类别的分类任任务,将这N个类别两两配对,从而产生N(N-1)/2个二分类任务。在测试阶段,新样本同时提交给所有分类器,这样可以得到N(N-1)/2个分类结果,最终的结果可以通过投票产生:即把预测的最多的类别作为最终的分类结果。
  2. OvR(one-vs-rest)
    这种解决方法的思路是:每次将一个类的样例作为正例,所有其他类的样例作为负例来训练N个分类器。在测试时,若仅有一个分类器预测为正类,则对应的类别标记为最终分类结果。

两种策略对比:

明显看出,OvR只需要训练N个分类器,而OvO需要训练N(N-1)/2个分类器,因此OvO的存储开销和测试时间开销通常比OvR更大。但在训练时,OvR的每个分类器均使用全部训练样例,而OvO的每个分类器仅用到两个类的样例。因此,在类别很多的时候,OvO的训练时间开销通常比OvR更小。至于性能,取决于具体的数据分布,多数情况下两者差不多。

# create model
def create_svm(dataMat, dataLabel, decision='ovr'):
    clf = svm.SVC(decision_function_shape=decision)
    clf.fit(dataMat, dataLabel)
    return clf

SVC参数:

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

5. 分类测试

读取测试数据,整合成矩阵

# 对10个数字进行分类测试
def main():
    tbasePath = "Mnist-image\\test\\"
    tcName = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    tst = time.clock()
    allErrCount = 0
    allErrorRate = 0.0
    allScore = 0.0
    for tcn  in tcName:
        testPath = "Mnist-image\\test\\" + tcn
        #print("class " + tcn + " path is: {}.".format(testPath))
        tflist = get_file_list(testPath)
        #tflist
        tdataMat, tdataLabel = read_and_convert(tflist)
        print("test dataMat shape: {0}, test dataLabel len: {1} ".format(tdataMat.shape, len(tdataLabel)))

        #print("test dataLabel: {}".format(len(tdataLabel)))
        pre_st = time.clock()
        preResult = clf.predict(tdataMat)
        pre_et = time.clock()
        print("Recognition  " + tcn + " spent {:.4f}s.".format((pre_et-pre_st)))
        #print("predict result: {}".format(len(preResult)))
        errCount = len([x for x in preResult if x!=tcn])
        print("errorCount: {}.".format(errCount))
        allErrCount += errCount
        score_st = time.clock()
        score = clf.score(tdataMat, tdataLabel)
        score_et = time.clock()
        print("computing score spent {:.6f}s.".format(score_et-score_st))
        allScore += score
        print("score: {:.6f}.".format(score))
        print("error rate is {:.6f}.".format((1-score)))
        print("---------------------------------------------------------")


    tet = time.clock()
    print("Testing All class total spent {:.6f}s.".format(tet-tst))
    print("All error Count is: {}.".format(allErrCount))
    avgAccuracy = allScore/10.0
    print("Average accuracy is: {:.6f}.".format(avgAccuracy))
    print("Average error rate is: {:.6f}.".format(1-avgScore))

分类结果:

test dataMat shape: (980, 400), test dataLabel len: 980 
Recognition  0 spent 9.5305s.
errorCount: 11.
computing score spent 9.693493s.
score: 0.988776.
error rate is 0.011224.
---------------------------------------------------------
test dataMat shape: (1135, 400), test dataLabel len: 1135 
Recognition  1 spent 10.7278s.
errorCount: 11.
computing score spent 10.832672s.
score: 0.990308.
error rate is 0.009692.
---------------------------------------------------------
test dataMat shape: (1032, 400), test dataLabel len: 1032 
Recognition  2 spent 9.7781s.
errorCount: 57.
computing score spent 9.973572s.
score: 0.944767.
error rate is 0.055233.
---------------------------------------------------------
test dataMat shape: (1010, 400), test dataLabel len: 1010 
Recognition  3 spent 9.6072s.
errorCount: 55.
computing score spent 10.072705s.
score: 0.945545.
error rate is 0.054455.
---------------------------------------------------------
test dataMat shape: (982, 400), test dataLabel len: 982 
Recognition  4 spent 9.4975s.
errorCount: 44.
computing score spent 9.271692s.
score: 0.955193.
error rate is 0.044807.
---------------------------------------------------------
test dataMat shape: (892, 400), test dataLabel len: 892 
Recognition  5 spent 8.4760s.
errorCount: 70.
computing score spent 8.578377s.
score: 0.921525.
error rate is 0.078475.
---------------------------------------------------------
test dataMat shape: (958, 400), test dataLabel len: 958 
Recognition  6 spent 9.0267s.
errorCount: 27.
computing score spent 9.043633s.
score: 0.971816.
error rate is 0.028184.
---------------------------------------------------------
test dataMat shape: (1028, 400), test dataLabel len: 1028 
Recognition  7 spent 9.8431s.
errorCount: 66.
computing score spent 9.765103s.
score: 0.935798.
error rate is 0.064202.
---------------------------------------------------------
test dataMat shape: (974, 400), test dataLabel len: 974 
Recognition  8 spent 9.3546s.
errorCount: 75.
computing score spent 9.849029s.
score: 0.922998.
error rate is 0.077002.
---------------------------------------------------------
test dataMat shape: (1009, 400), test dataLabel len: 1009 
Recognition  9 spent 9.6555s.
errorCount: 79.
computing score spent 9.595665s.
score: 0.921705.
error rate is 0.078295.
---------------------------------------------------------
Testing All class total spent 196.587770s.
All error Count is: 495.
Average accuracy is: 0.949843.
Average error rate is: 0.050157.

6. 结果分析

从以上结果可以看出,SVM对于手写数字字体识别的准确率还是相对较高的,94.9%。在本例中,对灰度值归一化的时候,截取了小数点后一位,对并其四舍五入,归一化为0或1,得到0-1的二值矩阵。如果采取不同的处理方式,得到不同的二值矩阵,即不同的特征矩阵,则分类结果会不相同。

后续

本篇文章简要介绍了利用机器学习中的SVM模型对手写数字字体识别,手写数字字体数据集是Mnist-image。下篇我们将利用深度学习框架TensorFlow来构建神经网络对该数据集进行识别。

参考

  1. 《机器学习》,周志华
  2. 《Python计算机视觉编程》,Jan Erik Solem
  3. Mnist-image 数据,点击这里
  4. TensorFlow, 点击这里

Standing on Shoulders of Giants.

评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值