精华内容
下载资源
问答
  • 基于SVM技术的手写数字识别

    万次阅读 多人点赞 2019-07-29 22:51:39
    老师常说,在人工智能未发展起来之前,SVM技术是一统江湖的...1.手写数字识别技术的含义 2.手写数字识别技术的理论价值 3.数字识别技术的难点 二、SVM技术 1.SVM方法简介 2.线性可划分问题 3.近似线性可分问题...

    老师常说,在人工智能未发展起来之前,SVM技术是一统江湖的,SVM常常听到,但究竟是什么呢?最近研究了一下基于SVM技术的手写数字识别。你没有看错,又是手写数字识别,就是喜欢这个手写数字识别,没办法(¬∀¬)σ

    一、背景

    1.手写数字识别技术的含义

    2.手写数字识别技术的理论价值

    3.数字识别技术的难点

    二、SVM技术

    1.SVM方法简介

     2.线性可划分问题

    3.近似线性可分问题的线性划分

    4.非线性划分

    5.SVM方法

    6.SVM的性能

    三、实验

    1.数据集介绍

    2.实验步骤

    3.实验代码

    4.实验结果

    四、附录

    1.参考

    2.需要安装的包

    一、背景

    1.手写数字识别技术的含义

    手写数字识别(Handwritten Digit Recognition)是光学字符识别技术的一个分支,是模式识别学科的一个传统研究领域。主要研究如何利用电子计算机自动辨认手写在纸张上的阿拉伯数字。手写数字识别分为脱机手写数字识别和联机手写数字识别。本文主要讨论脱机手写数字的识别。 随着信息化的发展,手写数字识别的应用日益广泛,研究高识别率、零误识率和低拒识率的高速识别算法具有重要意义。

    2.手写数字识别技术的理论价值

    由于手写数字识别本身的特点,对它的研究有重要的理论价值:

     ⑴阿拉伯数字是唯一被世界各国通用的符号,对手写体数字识别的研究基本上与文化背景无关,各地的研究工作者基于同一平台开展工作,有利于研究的比较和探讨。

     ⑵手写数字识别应用广泛,如邮政编码自动识别,税表系统和银行支票自动处理等。这些工作以前需要大量的手工录入,投入的人力物力较多,劳动强度较大。手写数字识别的研究适应了无纸化办公的需要,能大大提高工作效率。

    ⑶由于数字类别只有 10 个,较其他字符识别率较高,可用于验证新的理论和做深入的分析研究。许多机器学习和模式识别领域的新理论和算法都是先用手写数字识别进行检验,验证理论的有效性,然后才应用到更复杂的领域当中。这方面的典型例子就是人工神经网络和支持向量机(Support Vector Machine)。

     ⑷手写数字的识别方法很容易推广到其它一些相关问题,如对英文之类拼音文字的识别。事实上,很多学者就是把数字和英文字母的识别放在一起研究的。

    3.数字识别技术的难点

    数字的类别只有 10 种,笔划简单,其识别问题似乎不是很困难。但事实上,一些测试结果表明,数字的正确识别率并不如印刷体汉字识别率高,甚至也不如联机手写体汉字识别率高,而只仅仅优于脱机手写体汉字识别。这其中的主要原因是:

    ⑴数字笔划简单,其笔划差别相对较小,字形相差不大,使得准确区分某些数字相当困难;

    ⑵数字虽然只有 10 种,且笔划简单,但同一数字写法千差万别,全世界各个国家各个地区的人都在用,其书写上带有明显的区域特性,很难做出可以兼顾世界各种写法的、识别率极高的通用性数字识别系统。

    虽然目前国内外对脱机手写数字识别的研究已经取得了很大的成就,但是仍然存在两大难点:

    一是识别精度需要达到更高的水平。手写数字识别没有上下文,数据中的每一个数据都至关重要。而数字识别经常涉及金融、财会领域,其严格性更是不言而喻。因此,国内外众多的学者都在为提高手写数字的识别率,降低误识率而努力。

    二是识别的速度要达到很高的水平。数字识别的输入通常是很大量的数据,而高精度与高速度是相互矛盾的,因此对识别算法提出了更高的要求。

    二、SVM技术

    1.SVM方法简介

    统计学习理论是建立在一套较坚实的理论基础之上的,为解决有限样本学习问题提供了一个统一的框架。它能将很多现有方法纳入其中,有望解决许多原来难以解决的问题比如神经网络结构选择问题、局部极小点问题等同时,在该理论基础上发展了一种新的通用学习方法——支持向量机(SupportVectorMachine,简称SVM),已初步表现出很多优于己有方法的性能。一些学者认为正在成为继神经网络研究之后新的研究热点,并将推动机器学习理论和技术有重大的发展。

    SVM方法是建立在统计学习理论的维理论和结构风险最小化原理基础上的,根据有限的样本信息在模型的复杂性即对特定训练样本的学习精度和学习能力即无误识别任意样本的能力之间寻求最佳折衷,以期获得最好的推广能力。

     2.线性可划分问题

                                                                                                       图1最大间隔法

    3.近似线性可分问题的线性划分

    4.非线性划分

    讲到这里,做个思路整理:首先,面对线性可划分问题,我们可以很容易利用最大间隔法,找到最优分类面,将数据分成两类。面对有些线性不可分问题,我们找不到这样一个最优分类面,但是在我们允许很小程度上的错分后,我们可以找到一个最优分类面。这时我们寻找的依据处理要求间隔最大,还要错分的程度最小,这个要求统称为惩罚参数。最后,面对其他线性不可分问题,我们实在找不到一个最优分类面的情况下,我们需要先做一些非线性变换,将非线性问题转化为线性问题,然后再进行处理。

    5.SVM方法

     

    SVM的基本思想可以概括为首先通过非线性变换将输入空间变换到一个高维空间,然后在这个新空间中求取最优线性分类面,而这种非线性变换是通过定义适当的内积函数实现的。

    SVM求得的分类函数形式上类似于一个神经网络,其输出的若干中间层节点的线性组合,而每一个中间层节点对应于输入样本与一个支持向量机的内积,因此也被称为支持向量网络。

    由于最终判决函数中实际只包含与支持向量的内积和求和,因此识别时的计算复杂度取决于支持向量的个数。

    另一个问题的关键是,由于变换空间的维数可能很高,在这个空间中的线性判别函数的VC维因此也可能很大,将导致分类器的效果不理想。而只要在高维空间中能够构造一个具有较小的VC维,从而得到较好的推广能力。

    进一步,关于最优分类面和广义最优分类面的推广能力,有下面的结论:

    如果一组训练样本能够被一个最优分类面或广义最优分类面分开,对于测试样本分类错误率的期望的上界是训练样本中平均的支持向量占总训练样本数的比例,即

    SVM推广性也是与变换空间的维数无关的,只要能够适当地选择一种内积定义,构造一个支持向量数相对较少的最优或广义最优分类器,则就可以得到较好的推广性。

    在这里,统计学习理论使用了与传统方法完全不同的思路,即不是像传统方法那样首先试图将原输入空间降维即特征选择和特征变换,而是设法将输入空间升维,以求在高维空间中问题变得线性可分或接近线性可分因为升维后只是改变了内积运算,并没有使算法复杂性随着维数的增加而增加,而且在高维空间中的推广能力并不受维数影响,因此这种方法才是可行的。

    6.SVM的性能

    使用SVM方法,需要做特征空间的内积运算,而核函数就是內积。SVM核函数的选择对于其性能的表现有至关重要的作用,尤其是针对那些线性不可分的数据,因此核函数的选择在SVM算法中就显得至关重要。常用如下几种常用的核函数来代替自己构造核函数:

    (1)线性核函数 

    线性核主要用于线性可分的情况,我们可以看到特征空间到输入空间的维度是一样的,其参数少速度快,对于线性可分数据,其分类效果很理想。

    (2)多项式核函数 

    多项式核函数可以实现将低维的输入空间映射到高纬的特征空间,但是多项式核函数的参数多,当多项式的阶数比较高的时候,核矩阵的元素值将趋于无穷大或者无穷小,计算复杂度会大到无法计算。

    (3)高斯(RBF)核函数

    高斯径向基函数是一种局部性强的核函数,其可以将一个样本映射到一个更高维的空间内,该核函数是应用最广的一个,无论大样本还是小样本都有比较好的性能,而且其相对于多项式核函数参数要少,因此大多数情况下在不知道用什么核函数的时候,优先使用高斯核函数。

    (4)sigmoid核函数

    采用sigmoid核函数,支持向量机实现的就是一种多层神经网络。

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

    (1)OvO(one-vs-one)

    这种解决方法的思路是:对于有N个类别的分类任任务,将这N个类别两两配对,从而产生N(N-1)/2个二分类任务。在测试阶段,新样本同时提交给所有分类器,这样可以得到N(N-1)/2个分类结果,最终的结果可以通过投票产生:即把预测的最多的类别作为最终的分类结果。

    (2)OvR(one-vs-rest)

    这种解决方法的思路是:每次将一个类的样例作为正例,所有其他类的样例作为负例来训练N个分类器。在测试时,若仅有一个分类器预测为正类,则对应的类别标记为最终分类结果。

    三、实验

    1.数据集介绍

    本文采用MNIST-image手写数字集进行训练和测试。MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个训练样本和10000个测试样本组成,每个样本都是一张20 * 20像素的灰度手写数字图片。数字已经过尺寸标准化,并以固定尺寸的图像为中心。数据如图2:

                                                                                    图2 MNIST-image数据集

    2.实验步骤

    (1)对数据格式转换
    (2)使用核函数和训练策略对训练集进行训练
    (3)使用训练后得到的模型对测试集进行测试
    (4)选择最佳核函数和训练策略为模型
    (5)用此模型进行可视化识别

    3.实验代码

    (1)训练svm模型并保存

    from PIL import Image
    import os
    import sys
    import numpy as np
    import time
    from sklearn import svm
    from sklearn.externals import joblib
    
    # 获取指定路径下的所有 .png 文件
    def get_file_list(path):
        # file_list = []
        # for filename in os.listdir(path):
        #     ele_path = os.path.join(path, filename)
        #     for imgname in os.listdir(ele_path):
        #         subele_path = os.path.join(ele_path, imgname)
        #         if (subele_path.endswith(".png")):
        #             file_list.append(subele_path)
        # return file_list
        return [os.path.join(path, f) for f in os.listdir(path) if f.endswith(".png")]
    
    
    
    # 解析出 .png 图件文件的名称
    def get_img_name_str(imgPath):
        return imgPath.split(os.path.sep)[-1]
    
    
    # 将 20px * 20px 的图像数据转换成 1*400 的 numpy 向量
    # 参数:imgFile--图像名  如:0_1.png
    # 返回:1*400 的 numpy 向量
    def img2vector(imgFile):
        # print("in img2vector func--para:{}".format(imgFile))
        img = Image.open(imgFile).convert('L')
        img_arr = np.array(img, 'i')  # 20px * 20px 灰度图像
        img_normalization = np.round(img_arr / 255)  # 对灰度值进行归一化
        img_arr2 = np.reshape(img_normalization, (1, -1))  # 1 * 400 矩阵
        return img_arr2
    
    
    # 读取一个类别的所有数据并转换成矩阵
    # 参数:
    #    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
    
    
    # 读取训练数据
    def read_all_data():
        cName = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
        path = sys.path[1]
        train_data_path = os.path.join(path, 'data\\Mnist-image\\train\\0')
        #print(train_data_path)
        #train_data_path = "Mnist-image\\train\\0"
        print('0')
        flist = get_file_list(train_data_path)
        dataMat, dataLabel = read_and_convert(flist)
        for c in cName:
            print(c)
            train_data_path = os.path.join(path, 'data\\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
    
    
    # create model
    def create_svm(dataMat, dataLabel,path,decision='ovr'):
        clf = svm.SVC(C=1.0,kernel='rbf',decision_function_shape=decision)
        rf =clf.fit(dataMat, dataLabel)
        joblib.dump(rf, path)
        return clf
    '''
    SVC参数
    svm.SVC(C=1.0,kernel='rbf',degree=3,gamma='auto',coef0=0.0,shrinking=True,probability=False,
    tol=0.001,cache_size=200,class_weight=None,verbose=False,max_iter=-1,decision_function_shape='ovr',random_state=None)
    
    C:C-SVC的惩罚参数C?默认值是1.0
    C越大,相当于惩罚松弛变量,希望松弛变量接近0,即对误分类的惩罚增大,趋向于对训练集全分对的情况,这样对训练集测试时
    准确率很高,但泛化能力弱。C值小,对误分类的惩罚减小,允许容错,将他们当成噪声点,泛化能力较强。
    kernel :核函数,默认是rbf,可以是‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’ 
           0 – 线性:u'v
        1 – 多项式:(gamma*u'*v + coef0)^degree
        2 – RBF函数:exp(-gamma|u-v|^2)
        3 –sigmoid:tanh(gamma*u'*v + coef0)
    degree :多项式poly函数的维度,默认是3,选择其他核函数时会被忽略。(没用)
    gamma : ‘rbf’,‘poly’ 和‘sigmoid’的核函数参数。默认是’auto’,则会选择1/n_features
    coef0 :核函数的常数项。对于‘poly’和 ‘sigmoid’有用。(没用)
    probability :是否采用概率估计?.默认为False
    shrinking :是否采用shrinking heuristic方法,默认为true
    tol :停止训练的误差值大小,默认为1e-3
    cache_size :核函数cache缓存大小,默认为200
    class_weight :类别的权重,字典形式传递。设置第几类的参数C为weight*C(C-SVC中的C)
    verbose :允许冗余输出?
    max_iter :最大迭代次数。-1为无限制。
    decision_function_shape :‘ovo’, ‘ovr’ or None, default=None3(选用ovr,一对多)
    random_state :数据洗牌时的种子值,int值
    主要调节的参数有:C、kernel、degree、gamma、coef0
    
    '''
    
    if __name__ == '__main__':
        # clf = svm.SVC(decision_function_shape='ovr')
        st = time.clock()
        dataMat, dataLabel = read_all_data()
        path = sys.path[1]
        model_path=os.path.join(path,'model\\svm.model')
        create_svm(dataMat, dataLabel,model_path, decision='ovr')
        et = time.clock()
        print("Training spent {:.4f}s.".format((et - st)))
    
    

    (2)测试模型效果

    import sys
    import time
    import svm
    import os
    from sklearn.externals import joblib
    import numpy as np
    import matplotlib.pyplot as plt
    
    
    def svmtest(model_path):
        path = sys.path[1]
        tbasePath = os.path.join(path, "data\\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
        ErrCount=np.zeros(10,int)
        TrueCount=np.zeros(10,int)
        #加载模型
        clf = joblib.load(model_path)
        for tcn in tcName:
            testPath = tbasePath + tcn
            # print("class " + tcn + " path is: {}.".format(testPath))
            tflist = svm.get_file_list(testPath)
            # tflist
            tdataMat, tdataLabel = svm.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])
            ErrCount[int(tcn)]=errCount
            TrueCount[int(tcn)]= len(tdataLabel)-errCount
            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)))
    
        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 - avgAccuracy))
        print("number"," TrueCount"," ErrCount")
        for tcn in tcName:
            tcn=int(tcn)
            print(tcn,"     ",TrueCount[tcn],"      ",ErrCount[tcn])
        plt.figure(figsize=(12, 6))
        x=list(range(10))
        plt.plot(x,TrueCount, color='blue', label="TrueCount")  # 将正确的数量设置为蓝色
        plt.plot(x,ErrCount, color='red', label="ErrCount")  # 将错误的数量为红色
        plt.legend(loc='best')  # 显示图例的位置,这里为右下方
        plt.title('Projects')
        plt.xlabel('number')  # x轴标签
        plt.ylabel('count')  # y轴标签
        plt.xticks(np.arange(10), ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
        plt.show()
    
    
    if __name__ == '__main__':
        path = sys.path[1]
        model_path=os.path.join(path,'model\\svm.model')
        svmtest(model_path)
    
    
    

    (3)可视化

    # -*- coding: utf-8 -*-
    
    # Form implementation generated from reading ui file 'test2.ui'
    #
    # Created by: PyQt5 UI code generator 5.11.3
    #
    # WARNING! All changes made in this file will be lost!
    
    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow,QFileDialog
    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import os
    from sklearn.externals import joblib
    import svm
    class Ui_Dialog(object):
        def setupUi(self, Dialog):
            Dialog.setObjectName("Dialog")
            Dialog.resize(645, 475)
            self.pushButton = QtWidgets.QPushButton(Dialog)
            self.pushButton.setGeometry(QtCore.QRect(230, 340, 141, 41))
            self.pushButton.setAutoDefault(False)
            self.pushButton.setObjectName("pushButton")
            self.label = QtWidgets.QLabel(Dialog)
            self.label.setGeometry(QtCore.QRect(220, 50, 191, 221))
            self.label.setWordWrap(False)
            self.label.setObjectName("label")
            self.textEdit = QtWidgets.QTextEdit(Dialog)
            self.textEdit.setGeometry(QtCore.QRect(220, 280, 191, 41))
            self.textEdit.setObjectName("textEdit")
    
            self.retranslateUi(Dialog)
            QtCore.QMetaObject.connectSlotsByName(Dialog)
    
        def retranslateUi(self, Dialog):
            _translate = QtCore.QCoreApplication.translate
            Dialog.setWindowTitle(_translate("Dialog", "手写体识别"))
            self.pushButton.setText(_translate("Dialog", "打开图片"))
            self.label.setText(_translate("Dialog", "显示图片"))
    
    class MyWindow(QMainWindow, Ui_Dialog):
        def __init__(self, parent=None):
            super(MyWindow, self).__init__(parent)
            self.setupUi(self)
            self.pushButton.clicked.connect(self.openImage)
    
        def openImage(self):
            imgName, imgType = QFileDialog.getOpenFileName(self, "打开图片", "../data/Mnist-image/test")
            png = QtGui.QPixmap(imgName).scaled(self.label.width(), self.label.height())
            self.label.setPixmap(png)
            self.textEdit.setText(imgName)
    
            path = sys.path[1]
            model_path = os.path.join(path, 'model\\svm.model')
            clf = joblib.load(model_path)
            dataMat=svm.img2vector(imgName)
            preResult = clf.predict(dataMat)
            self.textEdit.setReadOnly(True)
            self.textEdit.setStyleSheet("color:red")
            self.textEdit.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter)
            self.textEdit.setFontPointSize(9)
            self.textEdit.setText("预测的结果是:")
            self.textEdit.append(preResult[0])
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        myWin = MyWindow()
        myWin.show()
        sys.exit(app.exec_())
    

    4.实验结果

    将MNIST-image数据集运用SVM进行识别,我们选择以线性核函数、RBF核函数、Sigmoid核函数为核函数,以OVO和OVR为策略,两两搭配,共有六种组合,以这六种组合分别训练和测试数据集,并记录其训练时间、测试时间、准确率和平均准确率,得到如下两表:

    从表1可以看出,三种核函数的训练时间和测试时间排序是:线性核函数<RBF核函数<Sigmoid核函数;OVO策略的训练时间小于OVR的训练时间,而OVR的测试时间小于OVO的测试时间。

    从表2可以看出,三种核函数的准确率排序是:RBF核函数>线性核函数>Sigmoid核函数。三种核函数的平均准确率排序是:RBF核函数>线性核函数>Sigmoid核函数.OVO和OVR的准确率和错误率一样。

    造成核函数和策略组合性能不同的原因如下:

    (1)线性核函数、RBF核函数和Sigmoid核函数公式的复杂度不同,导致训练时间和测试时间出现差异。

    (2)理论上,OVR只需要训练N个分类器,而OVO需要训练N(N-1)/2个分类器,因此OVO的存储开销和测试时间开销通常比OVR更大。而在训练时,OVR的每个分类器均使用全部训练样例,而OVO的每个分类器仅用到两个类的样例。因此,在类别很多的时候,OVO的训练时间开销通常比OVR更小。

    (3)手写数字识别中,各种数字写法复杂,这明显是线性不可分的情景,所以线性核函数的准确率较低。

    可视化结果如图3所示:

    最优模型的各个数字情况如图4、表3所示:

    从表3中我们可以看出,各数字整体的错误率都不高,但数字9、8、5、4的错误率是最高的四个,错误率达3%以上,原因是以为这些数字写法相对灵活多变,书写不规范,造成识别率低,这也是今后要加强的地方。

    四、附录

    1.参考

    https://blog.csdn.net/ni_guang2010/article/details/53069579

    https://blog.csdn.net/w5688414/article/details/79343542

    https://blog.csdn.net/rozol/article/details/87705426

    基于SVM技术的手写数字识别的研究_吴琳琳

    SVM在手写数字识别中的应用研究_李雅琴

    2.需要安装的包

    博主的电脑是win10系统,python版本是3.6

    (1)pip install numpy

    (2)pip install Pillow

    (3)python -m pip install --upgrade pip

    (4)pip install scipy

    (5)pip3 install sklearn

    (6)pip install PyQt5

    (7)pip3.6 install PyQt5-tools

    (8)pip install matplotlib

    安装教程

    (1)python3.6+pyQt5+QtDesigner简易安装教程 - Rocket_J的博客 - CSDN博客

                 https://blog.csdn.net/Rocket_J/article/details/80897367

    (2)python3+PyQt5+Qt designer+pycharm安装及配置+将ui文件转py文件 - lyzwjaa的博客 - CSDN博客 

                 https://blog.csdn.net/lyzwjaa/article/details/79429901

    展开全文
  • MLP手写数字识别实现

    千次阅读 2019-05-30 11:21:53
    手写数字识别是个多分类问题,共有10个分类,每个手写数字图像的类别标签是0~9中的其中个数。例如下面这三张图片的标签分别是0,1,2。 任务:利用sklearn来训练个简单的全连接神经网络,即多层感知机...

    1 任务介绍

    手写数字识别是一个多分类问题,共有10个分类,每个手写数字图像的类别标签是0~9中的其中一个数。例如下面这三张图片的标签分别是0,1,2。

    任务:利用sklearn来训练一个简单的全连接神经网络,即多层感知机(Multilayer perceptron,MLP)用于识别数据集DBRHD的手写数字。

    2 MLP的输入

    DBRHD数据集的每个图片是一个由0或1组成的32*32的文本矩阵;
    多层感知机的输入为图片矩阵展开的1*1024个神经元。

    3 MLP的输出

    MLP输出:“one-hot vectors”
    一个one-hot向量除了某一位的数字是1以外其余各维度数字都是0。
    图片标签将表示成一个只有在第n维度(从0开始)数字为1的10维向量。比如,标签0将表示成[1,0,0,0,0,0,0,0,0,0,0]。即,MLP输出层具有10个神经元。

    4 MLP结构

    MLP的输入与输出层,中间隐藏层的层数和神经元的个数设置都将影响该MLP模型的准确率。
    在本实例中,我们只设置一层隐藏层,在后续实验中比较该隐藏层神经元个数为50、100、200时的MLP效果。

    5 MLP手写识别实例构建

    本实例的构建步骤如下:
    步骤1:建立工程并导入sklearn包
    步骤2:加载训练数据
    步骤3:训练神经网络
    步骤4:测试集评价

    5.1 步骤1:建立工程并导入sklearn包

    1)创建sklearnBP.py文件
    2)在sklearnBP.py文件中导入sklearn相关包

    import numpy as np #导入numpy工具包
    from os import listdir #使用listdir模块,用于访问本地文件
    from sklearn.neural_network import MLPClassifier
     
    • 1
    • 2
    • 3

    5.2 步骤2:加载训练数据

    1)在sklearnBP.py文件中,定义img2vector函数,将加载的32*32的图片矩阵展开成一列向量

    def img2vector(fileName):
        retMat = np.zeros([1024],int) #定义返回的矩阵,大小为1*1024
        fr = open(fileName) #打开包含32*32大小的数字文件
        lines = fr.readlines() #读取文件的所有行
        for i in range(32): #遍历文件所有行
            for j in range(32): #并将01数字存放在retMat中
                retMat[i*32+j] = lines[i][j]
        return retMat
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2)在sklearnBP.py文件中定义加载训练数据的函数readDataSet,并将样本标签转化为one-hot向量

    def readDataSet(path):    
        fileList = listdir(path)    #获取文件夹下的所有文件 
        numFiles = len(fileList)    #统计需要读取的文件的数目
        dataSet = np.zeros([numFiles,1024],int) #用于存放所有的数字文件
        hwLabels = np.zeros([numFiles,10])      #用于存放对应的one-hot标签
        for i in range(numFiles):   #遍历所有的文件
            filePath = fileList[i]  #获取文件名称/路径      
            digit = int(filePath.split('_')[0])  #通过文件名获取标签      
            hwLabels[i][digit] = 1.0        #将对应的one-hot标签置1
            dataSet[i] = img2vector(path +'/'+filePath) #读取文件内容   
        return dataSet,hwLabels
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3)在sklearnBP.py文件中,调用readDataSet和img2vector函数加载数据,将训练的图片存放在train_dataSet中,对应的标签则存在train_hwLabels中

    #read dataSet
    train_dataSet, train_hwLabels = readDataSet('trainingDigits')
     
    • 1
    • 2

    5.3 步骤3:训练神经网络

    1)在sklearnBP.py文件中,构建神经网络:设置网络的隐藏层数、各隐藏层神经元个数、激活函数、学习率、优化方法、最大迭代次数。
    设置含100个神经元的隐藏层。
    hidden_layer_sizes存放的是一个元组,表示第i层隐藏层里神经元的个数
    使用logistic激活函数和adam优化方法,并令初始学习率为0.0001

    clf = MLPClassifier(hidden_layer_sizes=(100,),
                        activation='logistic', solver='adam',
                        learning_rate_init = 0.0001, max_iter=2000)
     
    • 1
    • 2
    • 3

    2)在sklearnBP.py文件中,使用训练数据训练构建好的神经网络fit函数能够根据训练集及对应标签集自动设置多层感知机的输入与输
    出层的神经元个数。
    例如train_dataSet为n*1024的矩阵,train_hwLabels为n*10的矩阵,则fit函数将MLP的输入层神经元个数设为1024,输出层神经元个数为10:

    clf.fit(train_dataSet,train_hwLabels)
     
    • 1

    5.4 步骤4:测试集评价

    1)在sklearnBP.py文件中,加载测试集

    dataSet,hwLabels = readDataSet('testDigits')
     
    • 1

    2)使用训练好的MLP对测试集进行预测,并计算错误率:

    res = clf.predict(dataSet)   #对测试集进行预测
    error_num = 0                #统计预测错误的数目
    num = len(dataSet)           #测试集的数目
    for i in range(num):         #遍历预测结果
        #比较长度为10的数组,返回包含01的数组,0为不同,1为相同
        #若预测结果与真实结果相同,则10个数字全为1,否则不全为1
        if np.sum(res[i] == hwLabels[i]) < 10: 
            error_num += 1                     
    print("Total num:",num," Wrong num:", \
          error_num,"  WrongRate:",error_num / float(num))
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6 实验效果

    6.1 隐藏层神经元个数影响

    运行隐藏层神经元个数为50、100、200的多层感知机,对比实验效果:

    随着隐藏层神经元个数的增加,MLP的正确率持上升趋势;
    大量的隐藏层神经元带来的计算负担与对结果的提升并不对等,因此,如何选取合适的隐藏神经元个数是一个值得探讨的问题。

    6.2 迭代次数影响分析

    我们设隐藏层神经元个数为100,初始学习率为0.0001,最大迭代次数分别为500、1000、1500、2000, 结果如下:

    过小的迭代次数可能使得MLP早停,造成较低的正确率。
    当最大迭代次数>1000时,正确率基本保持不变,这说明MLP在第1000迭代时已收敛,剩余的迭代次数不再进行。

    6.3 学习率影响分析

    改用随机梯度下降优化算法即将MLPclassifer的参数( solver=‘sgd’, ),设隐藏层神经元个数为100,最大迭代次数为2000,学习率分别为:0.1、0.01、0.001、0.0001,结果如下:

    结论:较小的学习率带来了更低的正确率,这是因为较小学习率无法在2000次迭代内完成收敛,而步长较大的学习率使得MLP在2000次迭代内快速收敛到最优解。因此,较小的学习率一般要配备较大的迭代次数以保证其收敛。

    展开全文
  • 1、输入:有一张数字的A4纸的图片(如下) 2、A4纸矫正 3、数字字符切割 4、用Adaboost或SVM训练个手写数字分类器 5、识别并输出:连串数字,如“13924579693”与“02087836761”等 实现环境: Windows10 + ...

    作业要求:

            1、输入:有一张写着数字的A4纸的图片(如下) 

     

            2、A4纸矫正

            3、数字字符切割

            4、用Adaboost或SVM训练一个手写数字分类器

            5、识别并输出:连串数字,如“13924579693”与“02087836761”等


    实现环境:

            Windows10 + VS2015 + cimg库 (+ opencv (用于svm预测时特征提取) + libsvm (用于训练/测试模型与预测))


            由于此次作业量比较大,所以会分为上下两部分讲述。一部分为把字符切割出来,另一部分为SVM的训练和识别。此博文主要讲述到数字字符切割的一系列操作。

            完整代码可以到我的github上查看:https://github.com/MarkMoHR/HandwritingNumberClassification


    阶段结果:







    实现步骤(仅到数字字符切割阶段):

       1、A4纸边缘顶点提取

       2、A4纸矫正

        3、数字按顺序分割

            3.1) 图像二值化

            3.2) 基于垂直方向直方图,把原图进行行分割为多张行子图,每张子图包含一行数字(可能有多列)

            3.3) 基于水平方向直方图,把子图进行列分割为多张真子图,每张子图包含单行单列的数字

            3.4) 对每张子图,进行扩张(dilation),并进行断裂字符修复

            3.5) 对每张子图用连通区域标记方法(connected-component_labeling algorithm)从左到右分割数字

            3.6) 对每张子图,存储单个数字以及一个图像名列表文本


    具体实现:

    1、A4纸边缘顶点提取

            A4纸边缘与顶点提取在前面的博文也有写过,在这里就不再说了。

            [传送门]:[计算机视觉] A4纸边缘检测


    阶段结果:


    2、A4纸矫正:

            A4纸矫正也是之前的作业,记得貌似没写成博文。但是就一个公式的转换,没其他了,所以在这里也不打算细说。详细可到我的github上参考代码~

    阶段结果:


    3、数字按顺序分割

      3.1、图像二值化

        1) 图像二值化之前,需要先将矫正后的图像转化为灰度图。

        2) 图像二值化,就是将得到的灰度图,转化为只有灰度为0和255的图像。图像二值化有多种方法,比如全局阈值分割、局部阈值分割等。我这里使用了全局阈值分割,简单、速度快、效果不坏哈哈:) 。顾名思义,就是将小于某阈值的像素点像素设为0,其余的设为255。因此找到适合的阈值很关键,我根据几张图片的效果,选择阈值为135。可以发现上面的矫正后的图,会有边缘上的黑边影响,于是可以把靠近边缘的一些像素点都设为白色像素点。


    阶段结果:



      3.2、基于垂直方向直方图,把原图进行行分割为多张子图,每张子图包含一行数字

        1) 与上一个版本相比,现在在做扩张(dilation)之前做直方图行分割,原因是先做扩张可能使两行数字在原来相隔的地方连起来,接下来就不好做行分割了。

        2) 为什么需要做行分割,而不是直接用连通区域标记方法做分割就好了?之前我在做的时候,也是直接做下面第3.4、3.5步,分割数字字符。但是由于连通区域标记算法必须从上到下或从左到右扫描,直接做的话结果是,得到分割出来的数字的顺序是乱的!从上到下扫描的话,顺序是越高的数字排越前;从左到右扫描的话,顺序是越左的数字排越前。但是我们需要的结果是一行一行的从左到右按顺序数字,不管是什么样的扫描顺序,对整张图一次性做的话,都得不到我们想要的结果。

        3) 鉴于上述原因,我们需要先把数字一行行的先分割出来。基于垂直方向直方图的方法就是,可以想象到,如果我们做竖直方向的灰度直方图,就会出现波和谷。我们只需要在谷做一条分割线即可。找分割线的方法是,我们得先找到由黑转白和由白转黑的拐点,两拐点中间就是分割点。

        4) 这样做分割之后,会出现下面这样一种情况,即白色之中有一个黑点都视为峰,这明显是一些断裂的点造成的,而这些点也显然可以忽略(不影响大的数字),也就是其所在的子图无意义,可抛弃。显然一种处理的方法是:统计子图的黑色像素个数,只有超过整张子图大小一定比例才可视为该子图存在完整数字,同时更新割线;否则抛弃。

            (原图)  (放大图)

    void ImageSegmentation::findDividingLine() {
    	HistogramImage = CImg<int>(BinaryImg._width, BinaryImg._height, 1, 3, 0);
    	DividingImg = CImg<int>(BinaryImg._width, BinaryImg._height, 1, 3, 0);
    	int lineColor[3]{ 255, 0, 0 };
    
    	cimg_forY(HistogramImage, y) {
    		int blackPixel = 0;
    		cimg_forX(BinaryImg, x) {
    			HistogramImage(x, y, 0) = 255;
    			HistogramImage(x, y, 1) = 255;
    			HistogramImage(x, y, 2) = 255;
    			DividingImg(x, y, 0) = BinaryImg(x, y, 0);
    			DividingImg(x, y, 1) = BinaryImg(x, y, 0);
    			DividingImg(x, y, 2) = BinaryImg(x, y, 0);
    			if (BinaryImg(x, y, 0) == 0)
    				blackPixel++;
    		}
    
    		cimg_forX(HistogramImage, x) {
    			if (x < blackPixel) {
    				HistogramImage(x, y, 0) = 0;
    				HistogramImage(x, y, 1) = 0;
    				HistogramImage(x, y, 2) = 0;
    			}
    		}
    
    		//判断是否为拐点
    		if (y > 0) {
    			if (blackPixel <= HistogramValleyMaxPixelNumber 
    				&& HistogramImage(HistogramValleyMaxPixelNumber, y - 1, 0) == 0) {    //下白上黑:取下
    				inflectionPointSet.push_back(y);
    				//HistogramImage.draw_line(0, y, HistogramImage._width - 1, y, lineColor);
    			}
    			else if (blackPixel > HistogramValleyMaxPixelNumber
    				&& HistogramImage(HistogramValleyMaxPixelNumber, y - 1, 0) != 0) {    //下黑上白:取上
    				inflectionPointSet.push_back(y - 1);
    				//HistogramImage.draw_line(0, y - 1, HistogramImage._width - 1, y - 1, lineColor);
    			}
    		}
    	}
    
    	divideLinePointSet.push_back(-1);
    	//两拐点中间做分割
    	if (inflectionPointSet.size() > 2) {
    		for (int i = 1; i < inflectionPointSet.size() - 1; i = i + 2) {
    			int divideLinePoint = (inflectionPointSet[i] + inflectionPointSet[i + 1]) / 2;
    			divideLinePointSet.push_back(divideLinePoint);
    		}
    	}
    	divideLinePointSet.push_back(BinaryImg._height - 1);
    }
    
    void ImageSegmentation::divideIntoBarItemImg() {
    	vector<int> newDivideLinePointSet;
    	int lineColor[3]{ 255, 0, 0 };
    
    	for (int i = 1; i < divideLinePointSet.size(); i++) {
    		int barHright = divideLinePointSet[i] - divideLinePointSet[i - 1];
    		int blackPixel = 0;
    		CImg<int> barItemImg = CImg<int>(BinaryImg._width, barHright, 1, 1, 0);
    		cimg_forXY(barItemImg, x, y) {
    			barItemImg(x, y, 0) = BinaryImg(x, divideLinePointSet[i - 1] + 1 + y, 0);
    			if (barItemImg(x, y, 0) == 0)
    				blackPixel++;
    		}
    
    		double blackPercent = (double)blackPixel / (double)(BinaryImg._width * barHright);
    		cout << "blackPercent " << blackPercent << endl;
    		if (blackPercent > SubImgBlackPixelPercentage) {
    			subImageSet.push_back(barItemImg);
    			newDivideLinePointSet.push_back(divideLinePointSet[i - 1]);
    			//barItemImg.display("barItemImg");
    
    			if (i > 1) {
    				HistogramImage.draw_line(0, divideLinePointSet[i - 1], 
    					HistogramImage._width - 1, divideLinePointSet[i - 1], lineColor);
    				DividingImg.draw_line(0, divideLinePointSet[i - 1], 
    					HistogramImage._width - 1, divideLinePointSet[i - 1], lineColor);
    			}
    		}
    	}
    	divideLinePointSet.clear();
    	for (int i = 0; i < newDivideLinePointSet.size(); i++)
    		divideLinePointSet.push_back(newDivideLinePointSet[i]);
    }


    阶段结果:

     

        


      3.3、基于水平方向直方图,把子图进行列分割为多张真子图,每张子图包含单行单列的数字

        1) 从上面行分割得到的图像,有可能是多列的数字(如下图)。



        2) 显然,我们会想到,利用上面使用过的直方图的方法,只不过改为水平的,不就好了吗。然而做了直方图后,发现与垂直方向的直方图还是有挺大差别的(如下图)。我们能看到,水平方向直方图会出现很多峰,而这是跟我们阿拉伯数字的书写方式有关,即两个数字之间有间隔



        3) 那怎么使靠近的各组数字分隔开来呢?我一开始想了用固定间距阈值,即两个峰之间间隔大于一定阈值的时候,视为中间需要隔开。但是固定阈值不适用于所有图片,毕竟可能存在两组数字之间间隔不大,但也能明显区分为两组数字的情况。于是我想出了一种动态间距阈值的方法:计算所有峰之间的间距的均值,只有当间距大于均值的一定倍数时,才视为要隔开。这种方法的好处是把同组数字之间的间隔也考虑进去了,因为我们可以明显看到,两组数字之间的间距大小,与同组数字间的间隔有关。

    //根据X方向直方图判断真实的拐点
    vector<int> getInflectionPosXs(const CImg<int>& XHistogramImage) {
    	vector<int> resultInflectionPosXs;
    	vector<int> tempInflectionPosXs;
    	int totalDist = 0, avgDist;
    	int distNum = 0;
    	//查找拐点
    	cimg_forX(XHistogramImage, x) {
    		if (x >= 1) {
    			//白转黑
    			if (XHistogramImage(x, 0, 0) == 0 && XHistogramImage(x - 1, 0, 0) == 255) {
    				tempInflectionPosXs.push_back(x - 1);
    			}
    			//黑转白
    			else if (XHistogramImage(x, 0, 0) == 255 && XHistogramImage(x - 1, 0, 0) == 0) {
    				tempInflectionPosXs.push_back(x);
    			}
    		}
    	}
    	for (int i = 2; i < tempInflectionPosXs.size() - 1; i = i + 2) {
    		int dist = tempInflectionPosXs[i] - tempInflectionPosXs[i - 1];
    		if (dist <= 0)
    			distNum--;
    		totalDist += dist;
    	}
    
    	//计算间距平均距离
    	distNum += (tempInflectionPosXs.size() - 2) / 2;
    	avgDist = totalDist / distNum;
    	//cout << "avgDist " << avgDist << endl;
    
    	resultInflectionPosXs.push_back(tempInflectionPosXs[0]);    //头
    	//当某个间距大于平均距离的一定倍数时,视为分割点所在间距
    	for (int i = 2; i < tempInflectionPosXs.size() - 1; i = i + 2) {
    		int dist = tempInflectionPosXs[i] - tempInflectionPosXs[i - 1];
    		//cout << "dist " << dist << endl;
    		if (dist > avgDist * XHistogramValleyMaxPixelNumber) {
    			resultInflectionPosXs.push_back(tempInflectionPosXs[i - 1]);
    			resultInflectionPosXs.push_back(tempInflectionPosXs[i]);
    		}
    	}
    	resultInflectionPosXs.push_back(tempInflectionPosXs[tempInflectionPosXs.size() - 1]);  //尾
    
    	return resultInflectionPosXs;
    }
    
    //获取一行行的子图的水平分割线
    vector<int> getDivideLineXofSubImage(const CImg<int>& subImg) {
    	vector<int> InflectionPosXs;
    
    	//先绘制X方向灰度直方图
    	CImg<int> XHistogramImage = CImg<int>(subImg._width, subImg._height, 1, 3, 0);
    	cimg_forX(subImg, x) {
    		int blackPixel = 0;
    		cimg_forY(subImg, y) {
    			XHistogramImage(x, y, 0) = 255;
    			XHistogramImage(x, y, 1) = 255;
    			XHistogramImage(x, y, 2) = 255;
    			if (subImg(x, y, 0) == 0)
    				blackPixel++;
    		}
    		//对于每一列x,只有黑色像素多于一定值,才绘制在直方图上
    		if (blackPixel >= XHistogramValleyMaxPixelNumber) {
    			cimg_forY(subImg, y) {
    				if (y < blackPixel) {
    					XHistogramImage(x, y, 0) = 0;
    					XHistogramImage(x, y, 1) = 0;
    					XHistogramImage(x, y, 2) = 0;
    				}
    			}
    		}
    	}
    
    	InflectionPosXs = getInflectionPosXs(XHistogramImage);    //获取拐点
    	cout << "InflectionPosXs.size() " << InflectionPosXs.size() << endl;
    	for (int i = 0; i < InflectionPosXs.size(); i++)
    		XHistogramImage.draw_line(InflectionPosXs[i], 0, InflectionPosXs[i], XHistogramImage._height - 1, lineColor);
    	//XHistogramImage.display("XHistogramImage");
    
    	//两拐点中间做分割
    	vector<int> dividePosXs;
    	dividePosXs.push_back(-1);
    	if (InflectionPosXs.size() > 2) {
    		for (int i = 1; i < InflectionPosXs.size() - 1; i = i + 2) {
    			int divideLinePointX = (InflectionPosXs[i] + InflectionPosXs[i + 1]) / 2;
    			dividePosXs.push_back(divideLinePointX);
    		}
    	}
    	dividePosXs.push_back(XHistogramImage._width - 1);
    
    	return dividePosXs;
    }
    
    //分割行子图,得到列子图
    //@_dividePosXset 以-1起,以lineImg._width结束
    vector<CImg<int>> getRowItemImgSet(const CImg<int>& lineImg, vector<int> _dividePosXset) {
    	vector<CImg<int>> result;
    	for (int i = 1; i < _dividePosXset.size(); i++) {
    		int rowItemWidth = _dividePosXset[i] - _dividePosXset[i - 1];
    		CImg<int> rowItemImg = CImg<int>(rowItemWidth, lineImg._height, 1, 1, 0);
    		cimg_forXY(rowItemImg, x, y) {
    			rowItemImg(x, y, 0) = lineImg(x + _dividePosXset[i - 1] + 1, y, 0);
    		}
    		result.push_back(rowItemImg);
    	}
    
    	return result;
    }

    阶段结果:



      3.4、对每张子图,进行扩张(dilation),并进行断裂字符修复

        1) 利用扩张进行字符修复:做二值化的时候,阈值取太小,有些数字的像素点被视为白点,容易造成字符断裂;阈值取太大,很多由于阴影产生的噪声点又会混进来。所以取恰当的阈值很重要。但是不管取什么阈值,都有可能出现字符断裂的情况(如下图)。扩张(Dilation)就是解决字符断裂的一种方法,这是数字图像处理上学到的一种方法,解释起来也比较复杂,可以参考:https://en.wikipedia.org/wiki/Dilation_(morphology)  简单的说就是当前点是0还是255还要根据周围的像素点来判断。

        2) 我用了以下两个滤波器来进行扩张与断裂字符修复:先用filterA做2次滤波,再用filterB做1次滤波(注意使用的次数以及顺序!)

            

        3) filterB作用是:当前位置为白色像素时,检测上下左右的像素,若为黑色,则把自身设为黑色。

        4) filterA作用是:当前位置为白色像素时,检测上/下1个单位像素,与左/右2个单位像素,统计黑色像素的总统计数。1为黑色像素个数加1,-1为黑色像素个数减1,只有当最后黑色像素的总统计数大于0,才把自身设为黑色。

        5) 明显,filterB使数字往4个方向变厚,但这很可能导致的结果是,像0、6、8、9这几个数字中间的洞被填充成黑色。所以我提出filterA来解决这个问题,可以看到使用filterA,像素的灰度(黑or白)与当前位置的水平邻居关系很大,即遇到类似洞的位置能够尽可能防止被填充成黑色。


    void ImageSegmentation::doDilationForEachBarItemImg(int barItemIndex) {
    	//扩张Dilation -X-X-X-XYY方向
    	CImg<int> answerXXY = CImg<int>(subImageSet[barItemIndex]._width, subImageSet[barItemIndex]._height, 1, 1, 0);
    	cimg_forXY(subImageSet[barItemIndex], x, y) {
    		int intensity = getDilationIntensityXXY(subImageSet[barItemIndex], x, y);
    		answerXXY(x, y, 0) = intensity;
    	}
    
    	//扩张Dilation -X-X-X-XYY方向
    	CImg<int> answerXXY2 = CImg<int>(answerXXY._width, answerXXY._height, 1, 1, 0);
    	cimg_forXY(answerXXY, x, y) {
    		int intensity = getDilationIntensityXXY(answerXXY, x, y);
    		answerXXY2(x, y, 0) = intensity;
    	}
    
    	//扩张Dilation XY方向
    	CImg<int> answerXY = CImg<int>(answerXXY2._width, answerXXY2._height, 1, 1, 0);
    	cimg_forXY(answerXXY2, x, y) {
    		int intensity = getDilationIntensityXY(answerXXY2, x, y);
    		answerXY(x, y, 0) = intensity;
    	}
    
    	cimg_forXY(subImageSet[barItemIndex], x, y) {
    		subImageSet[barItemIndex](x, y, 0) = answerXY(x, y, 0);
    	}
    }

    断裂字符修复对比图(左为修复前,右为修复后):




      3.5、对每张子图,用连通区域标记方法(connected-component_labeling algorithm)从左到右分割数字

        1) 连通区域标记,从字面上很好理解,毕竟一个数字本身就是一个连通区域。而这种方法的实现也有很多种算法,比如二次扫描法、双向反复扫描法、区域增长法。

        2) 我这里使用了速度相对较快的二次扫描法。下面利用一些图结合帮助理解算法的实现:

                ① 扫描图像第一列和第一行,每个黑色点作为一类,做上标记。每一类各个点的坐标用一个链表存储起来,每个链表的首地址存储在一个容器下,容器的下标刚好对应类的标记:

                

                 ②一列一列进行遍历,遇到黑色点,即当前红点所在位置,检测其正上、左上、左前、左下4个位置,如下

                    

                 ③ 找到上述4个邻点的最小类标记(当前即是0),对其余标记的黑色点(即1和2),在链表容器里面找到对应的链表,然后接到刚找到的最小标记的链表上,接着其标记全改为刚才的最小标记。最后红点的坐标也加到最小标记的链表,红点也标记为最小标记。即变成如下:

                     

                 ④ 如果当前黑点的4个邻点都没有黑色点,则自身作为新类,加上新的标记,在链表容器里面加入新的链表,存储自己的坐标位置

                 ⑤ 最后根据链表容器,同一个链表的即为同一类,即代表为同一个数字,然后可以根据链表的坐标提取单个数字。


    void ImageSegmentation::connectedRegionsTaggingOfBarItemImg(int barItemIndex) {
    	TagImage = CImg<int>(subImageSet[barItemIndex]._width, subImageSet[barItemIndex]._height, 1, 1, 0);
    	tagAccumulate = -1;
    
    	cimg_forX(subImageSet[barItemIndex], x)
    		cimg_forY(subImageSet[barItemIndex], y) {
    		//第一行和第一列
    		if (x == 0 || y == 0) {
    			int intensity = subImageSet[barItemIndex](x, y, 0);
    			if (intensity == 0) {
    				addNewClass(x, y, barItemIndex);
    			}
    		}
    		//其余的行和列
    		else {
    			int intensity = subImageSet[barItemIndex](x, y, 0);
    			if (intensity == 0) {
    				//检查正上、左上、左中、左下这四个邻点
    
    				int minTag = Infinite;        //最小的tag
    				PointPos minTagPointPos(-1, -1);
    
    				//先找最小的标记
    				findMinTag(x, y, minTag, minTagPointPos, barItemIndex);
    
    				//当正上、左上、左中、左下这四个邻点有黑色点时,合并;
    				if (minTagPointPos.x != -1 && minTagPointPos.y != -1) {
    					mergeTagImageAndList(x, y - 1, minTag, minTagPointPos, barItemIndex);
    					for (int i = -1; i <= 1; i++) {
    						if (y + i < subImageSet[barItemIndex]._height)
    							mergeTagImageAndList(x - 1, y + i, minTag, minTagPointPos, barItemIndex);
    					}
    
    					//当前位置
    					TagImage(x, y, 0) = minTag;
    					PointPos cPoint(x, y + divideLinePointSet[barItemIndex] + 1);
    					pointPosListSet[minTag].push_back(cPoint);
    
    				}
    				//否则,作为新类
    				else {
    					addNewClass(x, y, barItemIndex);
    				}
    			}
    		}
    	}
    }
    
    void ImageSegmentation::addNewClass(int x, int y, int barItemIndex) {
    	tagAccumulate++;
    	//cout << "tagAccumulate " << tagAccumulate << endl;
    	TagImage(x, y, 0) = tagAccumulate;
    	classTagSet.push_back(tagAccumulate);
    	list<PointPos> pList;
    	PointPos cPoint(x, y + divideLinePointSet[barItemIndex] + 1);
    	pList.push_back(cPoint);
    	pointPosListSet.push_back(pList);
    }
    
    void ImageSegmentation::findMinTag(int x, int y, int &minTag, PointPos &minTagPointPos, int barItemIndex) {
    	if (subImageSet[barItemIndex](x, y - 1, 0) == 0) {     //正上
    		if (TagImage(x, y - 1, 0) < minTag) {
    			minTag = TagImage(x, y - 1, 0);
    			minTagPointPos.x = x;
    			minTagPointPos.y = y - 1;
    		}
    	}
    	for (int i = -1; i <= 1; i++) {        //左上、左中、左下
    		if (y + i < subImageSet[barItemIndex]._height) {
    			if (subImageSet[barItemIndex](x - 1, y + i, 0) == 0 && TagImage(x - 1, y + i, 0) < minTag) {
    				minTag = TagImage(x - 1, y + i, 0);
    				minTagPointPos.x = x - 1;
    				minTagPointPos.y = y + i;
    			}
    		}
    	}
    }
    
    void ImageSegmentation::mergeTagImageAndList(int x, int y, const int minTag, const PointPos minTagPointPos, int barItemIndex) {
    	//赋予最小标记,归并列表
    	if (subImageSet[barItemIndex](x, y, 0) == 0) {
    		int tagBefore = TagImage(x, y, 0);
    		if (tagBefore != minTag) {    //不是最小的tag
    			//把所有同一类的tag替换为最小tag、把list接到最小tag的list
    			list<PointPos>::iterator it = pointPosListSet[tagBefore].begin();
    			for (; it != pointPosListSet[tagBefore].end(); it++) {
    				TagImage((*it).x, (*it).y - divideLinePointSet[barItemIndex] - 1, 0) = minTag;
    			}
    			pointPosListSet[minTag].splice(pointPosListSet[minTag].end(), pointPosListSet[tagBefore]);
    		}
    	}
    }

        3) 算法的实现主要参考两个链接:

               https://segmentfault.com/a/1190000006120473   

               https://en.wikipedia.org/wiki/Connected-component_labeling  

                但是可以发现我这里对链接说的算法做一些改进。上面链接提到的是对图像一行行像素进行扫描,而这导致的结果是,上面也提到了:输出数字的顺序乱了,越高的数字排在越前输出。因此我根据这个算法的原理做出以下改进:

                ① 从一行行扫描改为一列列扫描

                ② 从取左前、左上、正上、右上4个点检测连通域,改为取正上、左上、左前、左下4个点检测连通域。


    阶段结果:

     



      3.6、对每张子图,存储单个数字以及一个图像名列表文本

            由于后面SVM预测的读入格式要求,需要将需要预测的图像的名字制造成一张列表文本:

    void ImageSegmentation::saveSingleNumberImageAndImglist(int barItemIndex) {
    	for (int i = 0; i < pointPosListSet.size(); i++) {
    		if (pointPosListSet[i].size() != 0) {
    			//先找到数字的包围盒
    			int xMin, xMax, yMin, yMax;
    			getBoundingOfSingleNum(i, xMin, xMax, yMin, yMax);
    
    			int width = xMax - xMin;
    			int height = yMax - yMin;
    
    			//将单个数字填充到新图像:扩充到正方形
    			//int imgSize = (width > height ? width : height) + SingleNumberImgBoundary * 2;
    			//CImg<int> singleNum = CImg<int>(imgSize, imgSize, 1, 1, 0);
    
    			//list<PointPos>::iterator it = pointPosListSet[i].begin();
    			//for (; it != pointPosListSet[i].end(); it++) {
    			//	int x = (*it).x;
    			//	int y = (*it).y;
    			//	int singleNumImgPosX, singleNumImgPosY;
    			//	if (height > width) {
    			//		singleNumImgPosX = (x - xMin) + (imgSize - width) / 2;
    			//		singleNumImgPosY = (y - yMin) + SingleNumberImgBoundary;
    			//	}
    			//	else {
    			//		singleNumImgPosX = (x - xMin) + SingleNumberImgBoundary;
    			//		singleNumImgPosY = (y - yMin) + (imgSize - height) / 2;
    			//	}
    			//	singleNum(singleNumImgPosX, singleNumImgPosY, 0) = 255;
    			//}
    
    			//将单个数字填充到新图像:原长宽比
    			int imgSizeH = height + SingleNumberImgBoundary * 2;
    			int imgSizeW = width + SingleNumberImgBoundary * 2;
    			CImg<int> singleNum = CImg<int>(imgSizeW, imgSizeH, 1, 1, 0);
    
    			list<PointPos>::iterator it = pointPosListSet[i].begin();
    			for (; it != pointPosListSet[i].end(); it++) {
    				int x = (*it).x;
    				int y = (*it).y;
    				int singleNumImgPosX, singleNumImgPosY;
    				singleNumImgPosX = (x - xMin) + SingleNumberImgBoundary;
    				singleNumImgPosY = (y - yMin) + SingleNumberImgBoundary;
    				singleNum(singleNumImgPosX, singleNumImgPosY, 0) = 255;
    			}
    
    			//singleNum.display("single Number");
    			string postfix = ".bmp";
    			char shortImgName[200];
    			sprintf(shortImgName, "%d_%d%s\n", barItemIndex, classTagSet[i], postfix.c_str());
    			imglisttxt += string(shortImgName);
    
    			char addr[200];
    			sprintf(addr, "%s%d_%d%s", basePath.c_str(), barItemIndex, classTagSet[i], postfix.c_str());
    			singleNum.save(addr);
    		}
    	}
    	imglisttxt += "*\n";
    
    	//把tag集、每一类链表数据集清空
    	classTagSet.clear();
    	for (int i = 0; i < pointPosListSet.size(); i++) {
    		pointPosListSetForDisplay.push_back(pointPosListSet[i]);
    		pointPosListSet[i].clear();
    	}
    	pointPosListSet.clear();
    }

    阶段结果:


    (用*号将原图上一行行数字分隔)        



    好了!大功告成,后面的SVM预测只需要读入bmp以及txt就可以了~


    剩余问题:

    1、单独数字连接了起来,需要分开:

            像下面的2 6 2连在一起了,需要再研究如何分开。

            







    展开全文
  • 利用Tensorflow实现手写数字识别(附python代码)

    万次阅读 多人点赞 2019-08-22 16:00:22
    手写识别的应用场景有很多,智能手机、掌上电脑的信息工具的普及,手写文字输入,机器识别...我们来尝试搭建下手识别中最基础的手写数字识别,与手写识别的不同是数字识别只需要识别0-9的数字,样本数据集也只需...

    手写识别的应用场景有很多,智能手机、掌上电脑的信息工具的普及,手写文字输入,机器识别感应输出;还可以用来识别银行支票,如果准确率不够高,可能会引起严重的后果。当然,手写识别也是机器学习领域的一个Hello World任务,感觉每一个初识神经网络的人,搭建的第一个项目十之八九都是它。

    我们来尝试搭建下手写识别中最基础的手写数字识别,与手写识别的不同是数字识别只需要识别0-9的数字,样本数据集也只需要覆盖到绝大部分包含数字0-9的字体类型,说白了就是简单,样本特征少,难度小很多。

    一、目标

    预期目标:传入一张数字图片给机器,机器通过识别,最后返回给用户图片上的数字

    传入图片:

    机器识别输出:

    二、搭建(全连接神经网络)

    环境:python3.6   tensorflow1.14

    工具:pycharm

    数据源:来自手写数据机器视觉数据库mnist数据集,包含7万张黑底白字手写数字图片,其中55000张为训练集,5000张为验证集,10000张为测试集。每张图片大小为28*28像素,图片纯黑色像素值为0,纯白色像素值为1。数据集的标签是长度为10的一维数组,数组中的每个元素索引号表示对应数字出现的概率。

    可通过input_data模块中的read_data_sets()函数直接加载mnist数据集(详情见mnist_backward.py):

    from tensorflow.examples.tutorials.mnist import input_data
    
     mnist = input_data.read_data_sets("./data/", one_hot=True)

    一、定义网络模型,神经网络的前向传播(mnist_forward.py)

    import tensorflow as tf
    
    INPUT_NODE=784   # 输入节点
    OUTPUT_NODE=10   # 输出节点
    LAYER1_NODE=500   # 隐藏节点
    
    def get_weight(shape,regularizer):
        w=tf.Variable(tf.truncated_normal(shape,stddev=0.1))
        if regularizer !=None:
            tf.add_to_collection('losses',tf.contrib.layers.l2_regularizer(regularizer)(w))
        return w
    
    def get_bias(shape):
        b=tf.Variable(tf.zeros(shape))
        return b
    
    def forward(x,regularizer):
        w1=get_weight([INPUT_NODE,LAYER1_NODE],regularizer)
        b1=get_bias(LAYER1_NODE)
        y1=tf.nn.relu(tf.matmul(x,w1)+b1)
    
        w2=get_weight([LAYER1_NODE,OUTPUT_NODE],regularizer)
        b2=get_bias([OUTPUT_NODE])
        y=tf.matmul(y1,w2)+b2
        return y
    

    这里定义了网络模型输入输出节点的个数、隐藏层节点数、同时定义get_weigt()函数实现对参数w的设置,包括参数的形状和是否正则化的标志,从输入层到隐藏层的参数w1形状为[784,500],由隐藏层到输出层的参数w2形状为[500,10]。定义get_bias()实现对偏置b的设置。由输入层到隐藏层的偏置b1形状长度为500的一维数组,由隐藏层到输出层的偏置b2形状长度为10的一维数组,初始化值为全0。

    二、神经网络的反向传播(mnist_backward.py)

    利用训练数据集对神经网络进行训练,通过降低损失函数值,实现网络模型参数的优化,从而得到准确率高且泛化能力强的神经网络模型。

    import tensorflow as tf
    from tensorflow.examples.tutorials.mnist import input_data
    import mnist_forward
    import os
    
    batch_size=200
    learning_rate_base=0.1    # 初始学习率
    learning_rate_decay=0.99    # 学习率衰减率
    regularizer=0.0001    # 正则化系数
    steps=50000     # 训练轮数
    moving_average_decay=0.99
    model_save_path="./model/"  # 模型保存路径
    model_name="mnist_model"
    
    def backward(mnist):
        x=tf.placeholder(tf.float32,[None,mnist_forward.INPUT_NODE])
        y_=tf.placeholder(tf.float32,[None,mnist_forward.OUTPUT_NODE])
        y=mnist_forward.forward(x,regularizer)  # 调用forward()函数,设置正则化,计算y
        global_step=tf.Variable(0,trainable=False)   # 当前轮数计数器设定为不可训练类型
    
        # 调用包含所有参数正则化损失的损失函数loss
        ce=tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_,1))
        cem=tf.reduce_mean(ce)
        loss=cem+tf.add_n(tf.get_collection('losses'))
    
        # 设定指数衰减学习率learning_rate
        learning_rate=tf.train.exponential_decay(
            learning_rate_base,
            global_step,
            mnist.train.num_examples/batch_size,
            learning_rate_decay,
            staircase=True
        )
        # 梯度衰减对模型优化,降低损失函数
        train_step=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
    
        # 定义参数的滑动平均
        ema=tf.train.ExponentialMovingAverage(moving_average_decay,global_step)
        ema_op=ema.apply(tf.trainable_variables())
        with tf.control_dependencies([train_step,ema_op]):
            train_op=tf.no_op(name='train')
    
        saver=tf.train.Saver()
    
        with tf.Session() as sess:
            init_op=tf.global_variables_initializer()   # 所有参数初始化
            sess.run(init_op)
    
            ckpt = tf.train.get_checkpoint_state(model_save_path)  # 加载指定路径下的滑动平均
            if ckpt and ckpt.model_checkpoint_path:
                saver.restore(sess, ckpt.model_checkpoint_path)
    
            for i in range(steps):   # 循环迭代steps轮
                xs,ys=mnist.train.next_batch(batch_size)
                _,loss_value,step=sess.run([train_op,loss,global_step],feed_dict={x:xs,y_:ys})
                if i %1000==0:
                    print("After %d training step(s),loss on training batch is %g."%(step,loss_value))
                    saver.save(sess,os.path.join(model_save_path,model_name),global_step=global_step)   # 当前会话加载到指定路径
    
    
    if __name__=='__main__':
        mnist = input_data.read_data_sets("./data/", one_hot=True)
        backward(mnist)

    反向传播中,首先定义了每轮喂入神经网络的图片数batch_size、初始学习率learning_rate_base、学习率衰减率learning_rate_decay、正则化系数regularizer、训练轮数steps、模型保存路径以及模型保存名称等相关信息。反向传播backward()函数中,先传入minist数据集,用tf.placeholder(dtype,shape)函数实现训练样本x和样本标签y_占位。y表示定义的前向传播函数forward;  tf.Variable(0,trainable=False)给当前轮数赋值,定义为不可训练类型。接着,loss表示定义的损失函数,一般为预测值与样本标签的交叉熵与正则化损失之和;train_step表示利用优化算法对模型参数进行优化,常用的优化算法有GradientDescentOptimizer、AdamOptimizer、MomentumOptimizer算法,这里使用的GradientDescentOptimizer梯度衰减算法。接着初始化saver对象,其中利用tf.global_variables_initializer()函数初始化所有模型参数,利用sess.run()函数实现模型的训练优化过程,并每隔一定轮数保存一次模型,模型训练好之后保存在ckpt中。

    三、测试数据集,验证模型性能(mnist_test.py)

    给神经网络模型输入测试集验证网络的准确性和泛化性(测试集和训练集是相互独立的)

    # coding:utf-8
    import time
    import tensorflow as tf
    from tensorflow.examples.tutorials.mnist import input_data
    import mnist_forward
    import mnist_backward
    
    test_interval_secs=5     # 程序循环间隔时间5秒
    
    def test(mnist):
        with tf.Graph().as_default() as g:    # 复现计算图
            x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
            y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])
            y = mnist_forward.forward(x, None)
    
            # 实例化滑动平均的saver对象
            ema = tf.train.ExponentialMovingAverage(mnist_backward.moving_average_decay)
            ema_restore = ema.variables_to_restore()
            saver = tf.train.Saver(ema_restore)
    
            # 计算准确率
            correct_prediction=tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
            accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
    
            while True:
                with tf.Session() as sess:
                    ckpt=tf.train.get_checkpoint_state(mnist_backward.model_save_path)   # 加载指定路径下的滑动平均
                    if ckpt and ckpt.model_checkpoint_path:
                        saver.restore(sess,ckpt.model_checkpoint_path)
                        global_step=ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
                        accuracy_score=sess.run(accuracy,feed_dict={x:mnist.test.images,y_:mnist.test.labels})
                        print("After %s training step(s),test accuracy= %g."%(global_step,accuracy_score))
                    else:
                        print('No checkpoint file found')
                        return
                time.sleep(test_interval_secs)
    
    
    if __name__=='__main__':
        mnist = input_data.read_data_sets("./data/", one_hot=True)
        test(mnist)

    首先,制定模型测试函数test(),通过tf.placeholder()给x,y_占位,调用mnist_forward文件中的前向传播过程forward()函数计算y,mnist_backward.moving_average_decay表示滑动衰减率。在with结构中,ckpt是加载训练好的模型,如果已有ckpt模型则恢复会话、轮数等。其次,制定main()函数,加载测试数据集,调用定义好的测试函数test()就行。

    通过对测试数据的预测得到准确率,从而判断出训练出的神经网络模型性能的好坏。当准确率低时,可能原因有模型需要改进,或者是训练数据量太少导致过拟合等。

    运行以上三个文件,运行结果如下:

    从终端显示的运行结果可以看出,随着训练轮数的增加,网络模型的损失函数值在不断降低,在测试集上的准确率也在不断提升,具有较好的泛化能力。

    四、输入真实图片,输出预测结果(mnist_app.py)

    任务分两个函数完成:

    (1)pre_pic()函数,对手写数字图片做预处理

    (2)restore_model()函数,将符合神经网络输入要求的图片喂给复现的神经网络模型,输出预测值。

    # coding:utf-8
    import tensorflow as tf
    import mnist_forward
    import mnist_backward
    from PIL import Image
    import numpy as np
    
    def restore_model(testPicArr):
        with tf.Graph().as_default() as tg:     # 创建一个默认图
            x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
            y = mnist_forward.forward(x, None)
            preValue=tf.argmax(y,1)   # 得到概率最大的预测值
            '''
             实现滑动平均模型,参数moving_average_decay用于控制模型的更新速度,训练过程会对每一个变量维护一个影子变量
             这个影子变量的初始值就是相应变量的初始值,每次变量更新时,影子变量随之更细
            '''
            variable_averages=tf.train.ExponentialMovingAverage(mnist_backward.moving_average_decay)
            variable_to_restore=variable_averages.variables_to_restore()
            saver=tf.train.Saver(variable_to_restore)
    
            with tf.Session() as sess:
                # 通过checkpoint文件定位到最新保存的模型
                ckpt=tf.train.get_checkpoint_state(mnist_backward.model_save_path)
                if ckpt and ckpt.model_checkpoint_path:
                    saver.restore(sess,ckpt.model_checkpoint_path)
    
                    preValue=sess.run(preValue,feed_dict={x:testPicArr})
                    return preValue
                else:
                    print("no checkpoint file found")
                    return -1
    
    # 预处理函数,包括resize,转变灰度图,二值化操作等
    def pre_pic(picName):
        img=Image.open(picName)
        reIm=img.resize((28,28),Image.ANTIALIAS)
        im_arr=np.array(reIm.convert('L'))
        threshold=50     # 设定合理的阈值
        for i in range(28):
            for j in range(28):
                im_arr[i][j]=255-im_arr[i][j]   # 模型要求黑底白字,输入图为白底黑字,对每个像素点的值改为255-原值=互补的反色
                if (im_arr[i][j]<threshold):
                    im_arr[i][j]=0
                else:
                    im_arr[i][j]=255
    
        nm_arr=im_arr.reshape([1,784])   # 1行784列
        nm_arr=nm_arr.astype(np.float32)
        img_ready=np.multiply(nm_arr,1.0/255.0)   # 从0-255之间的数变为0-1之间的浮点数
    
        return img_ready
    
    if __name__=='__main__':
            testNum=int(input("input the number of test pictures:"))
            for i in range(testNum):
                testPic=input("the path of test picture:")
                testPicArr=pre_pic(testPic)
                preValue=restore_model(testPicArr)
                print("the prediction number is",preValue)

    在pre_pic()函数中,网络要求输入是28*28像素点的值,先将图片尺寸resize,模型要求的是黑底白字,但输入的图是白底黑字,则每个像素点的值改为255减去原值得到互补的反色。再对图片做二值化处理,这样可以滤掉噪声。nm_arr把图片拉成1行784列,并把值变为浮点数。restore_model()函数,计算输出y,网络输出的是一个一维数组(10个可能性概率),数组中最大的那个元素所对应的索引号就是预测的结果。

    运行mnist_app.py文件,结果如下:

    先输入需要识别的图片number数,然后传入图片路径,最后返回识别结果。我们传入的图片2.jpg,5.jpg如下所示:

             

    预测结果也是2,5,说明模型还可以。但是,前面我们也提到过,如果数字识别用来识别银行支票97%的准确率不算高,然后卷积神经网络就开始大放异彩了...........................

     

    最后,本人微信公众号:

    放心,不用出钱也不用报班,只是单纯的想多两个粉丝罢了。

    展开全文
  • 设计个三层神经网络解决手写数字的识别问题。 要求: (1)三层神经网络如图:784-15-10结构 (2)使用随机梯度下降算法和MNIST训练数据。 http://yann.lecun.com/exdb/mnist/ 2. 求解原理 (1)算法模型 利用BP...
  • 手写数字的分割和识别

    千次阅读 多人点赞 2019-04-08 11:41:23
    在机器学习领域,手写数字数据集MNIST之于机器学习几乎相当于HelloWorld之于编程语言,其重要地位不言而已。但是,然后呢?给你一张如下所示的图片,你的模型能否也预测出结果?(其实下面这个应用就是OCR领域的内容...
  • Python SVM 手写数字识别 流程

    千次阅读 2019-08-29 10:16:54
    scikit-learn 自带了一些数据集,其中个是数字识别图片的数据。 %matplotlib inline import matplotlib.pyplot as plt import numpy as np from sklearn import datasets digits = datasets.load_digits() #...
  • 在上篇笔记中介绍了用softmax去识别手写数字图片,正确率只有92%。这篇笔记,仍然聚焦于同个案例,但使用个更复杂一点的模型来做训练:卷积神经网络(简称CNN)。从而正确率会到达99.2%。 关于卷积神经...
  • 神经网络实现Mnist手写数字识别笔记

    千次阅读 多人点赞 2018-10-20 21:31:24
     Mnist手写数字识别是Kaggle上个很经典的机器学习数据集,里边包括55000张训练数据和10000张图片的测试数据,每张图片大小为28*28像素的单通图片。该任务为通过机器学习来识别图片中的数字属于0,1,2,3,4,...
  • Pytorch+CNN 识别自己手写的数字

    千次阅读 多人点赞 2019-07-26 13:56:35
    看了网上很多的手写数字识别的例子,发现都没有个可以识别自己手写的例子,正好现在需要写一个例子,于是自己研究了一下,出了 个能识别自己手写的例子供大家参考。转载请注明出处。如果想看原理的话,可以...
  • 1、Sklearn.svm学习  数据集下载:trainingDigits训练集下载 , testDigits测试集下载  详细数据介绍参看:KNN算法实现手写数字识别 2、代码实现  # -*- coding: UTF-8 -*- import numpy as np from os ...
  • 基于Keras:手写数字识别

    千次阅读 2018-10-05 02:21:12
    模型的输入为: 32*32的手写字体图片,这些手写字体包含0~9数字,也就是相当于10个类别的图片 模型的输出: 分类结果,0~9之间的个数 下面通过多层感知器模型以及卷积神经网络的方式进行实现 二、基于多层感知器...
  • 基于卷积神经网络的手写数字识别(附数据集+代码)配置环境1.前言2.问题描述3.解决方案4.实现步骤4.1数据集选择4.2构建网络4.3训练网络4.4测试网络4.5图像预处理4.6传入网络进行计算5.代码实现5.1文件说明5.2使用...
  • 基于K近邻法的手写数字图像识别

    千次阅读 2017-05-29 17:53:32
     模式识别(PatternRecognition)是项借助计算机,就人类对外部世界某特定环境中的客体、过程和现象的识别功能(包括视觉、听觉、触觉、判断等)进行自动模拟的科学技术,也是指对表征事物或现象的各种形式的...
  • 神经网络——实现MNIST数据集的手写数字识别

    千次阅读 多人点赞 2019-01-03 21:49:07
    由于官网下载手写数字的数据集较慢,因此提供便捷下载地址如下 手写数字的数据集MNIST下载:https://download.csdn.net/download/gaoyu1253401563/10891997 数据集包含如下: 、使用小规模数据集进行神经网络...
  • Python3:《机器学习实战》之k近邻算法(3)识别手写数字 转载请注明作者和出处:http://blog.csdn.net/u011475210 代码地址:https://github.com/WordZzzz/ML/tree/master/Ch02 操作系统:WINDOWS 10 软件版本:...
  • MNIST是机器学习领域最有名的数据集之,被应用于简单的实验发表的论文研究等各种场合。实际上,在阅读图像识别或机器学习的论文时,MNIST数据集经常作为实验用的数据出现。 MNIST数据集是由09的数字图像构成...
  • C++:图片数字水印-基于OpenCV+LSB

    千次阅读 2019-08-26 01:20:43
    由于之前在网上看到的关于LSB的方法大都是以MATLAB||Python的,于是博主基于其思路改编后得出以下的内容: 、原理: 1.首先准备一张彩色的图片(2px*2px) 为了方便展示,我用PS准备了一张(2px * 2px)的...
  • 目录环境配置在前面:三个程序第个程序:训练第二个程序:图像预处理1.二值化2.去除小联通域(即噪点)3.roi提取4.将图片压缩为28*28格式5.完整代码第三个程序:测试 环境配置 语言:python 平台:pycharm 库: ...
  • k-近邻算法实现手写数字识别系统

    千次阅读 2017-03-26 15:48:41
    、实验介绍 1.1 实验内容 本实验将会电影题材分类的例子入手,详细讲述k-近邻算法的原理。在这之后,我们将会使用该算法实现手写数字识别系统。 1.2 课程来源 本课程源自 图灵教育 的 《机器学习实战》 ...
  • 手写数字识别问题实战

    千次阅读 2020-04-02 11:04:46
    1、何为手写数字识别问题? 手写数字识别即将如下图所示的各种各样的手写体阿拉伯数字识别出来的个过程,这个问题已经被研究的很成熟了,也有人搜集和整理了手写数字的数据集-mnist。 2、如何解决手写数字...
  • 机器学习之手写数字识别大作业报告

    千次阅读 多人点赞 2020-05-03 19:21:57
    在选择特征的时候,曾纠结过是用颜色矩、像素值还是图片卷积过后的值作为特征,我选择了后者,因为个人觉得手写数字识别相对于水质颜色识别来说,更关注图片的二维结构信息,如果用颜色矩或者像素值作为特征,就把...
  • 目录 目录 数据集的介绍 定义神经网络 开始训练模型 导入依赖包 初始化Paddle ...如题目所示,本次训练使用的是MNIST数据库的手写数字,这个数据集包含60,000个示例的训练集以及10,000个示例的测试集....
  • python 手写数字识别知识不用多说,本文只要讲解在手写数字识别开发过程中所遇到的坑。 1、训练模型 通过keras训练模型,并保存该模型 import numpy from keras.datasets import mnist from keras.models ...
  • 分为两步,第步是训练模型然后把模型保存磁盘,第二步是复现模型结构然后读取模型权重系数,输入手写数字图片验证模型识别能力。 二,模型训练 在这部分使用mnist数据集,该数据集含有七万张28*28的灰度化的...
  • CNN:利用LiR、DNN、CNN算法对MNIST手写数字图片(csv)识别数据集实现(10)分类预测 目录 输出结果 设计思路 核心代码 输出结果 数据集:Dataset之MNIST:MNIST(手写数字图片识别+csv文件)数据集简介、...
  • BP神经网络实现手写数字输入python 首先,我们需要引入下面两个库 import numpy import scipy.special 然后创建个神经网络类,类里有三个函数:初始化函数、训练函数和查询函数。 初始化函数 1、引入输入层、隐藏...
  • c++手写数字识别(贝叶斯分类器)

    千次阅读 2021-10-14 21:49:38
    大家好啊!这次的文章是上个文章的后续,与上次...09共十个数,所以分母有十项,P(Bj)(j是下标)相应的是09,则每个的概率是1/10,分子上的P(Bi)是取09中的个,所以概率也是1/10。(小伙伴如果看不...
  • 做的个小游戏,让儿童照着屏幕上的根据笔顺来写数字 0-9 学会了一点细节还有实现帧动画效果,将比较好的代码放放 首先是实现启动应用后出现启动界面2s后向主界面跳转: 实现代码如下: Timer timer = ...
  • 当你安装了tensorflow后,tensorflow自带的教程演示了如何使用卷积神经网络来识别...为了快速测试该程序,我提前将需要的mnist手写数字库下载了工程目录(我在pycharm中新建了工程,并把mnist_deep.py中的代码拷贝

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 67,241
精华内容 26,896
关键字:

从一1写到200数字图片