kNN算法与手写数字识别

kNN算法简介

kNN算法采用测量不同特征值之间的距离来进行分类。
工作原理:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。存在一个训练样本集,并且样本集中每个数据都存在标签,知道对应关系。输入没有标签的新数据之后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似的数据的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

模型三要素:距离度量、k值的选取、分类决策规则

  • 常用的距离度量是欧氏距离,或者更一般的Lp距离。值得注意的是,由不同距离度量所确定的最近邻点是不同的。
  • k值的选取反映了近似误差和估计误差之间的权衡,通常由交叉验证选择最优的k。
  • 常用的分类决策规则是多数表决,对应于经验风险最小化。

优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
使用范围:数值型和标称型

kNN伪代码

1)计算测试数据与各个训练数据之间的距离;

2)按照距离的递增关系进行排序;

3)选取距离最小的K个点;

4)确定前K个点所在类别的出现频率;

5)返回前K个点中出现频率最高的类别作为测试数据的预测分类。

代码实战

  • kNN算法
from numpy import * # 科学计算包numpy,将numpy函数库中的所有模块引入当前的命名空间
import operator # 运算符模块,k近邻算法执行排序操作时将使用这个模块提供的函数


def classify0(inX, dataSet, labels, k):
    """
    k近邻算法
    :param inX: 用于分类的输入向量
    :param dataSet:  输入的训练样本集
    :param labels:  标签向量
    :param k:  用于选择最近邻居的数目
    :return: 返回发生频率最高的元素标签
    """

    # 首先获取输入训练级的行数
    dataSetSize = dataSet.shape[0]
    # 将输入向量inX扩展,使其与训练向量具有相同的维数,便于计算度量距离
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    # 计算距离
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    # 将得到的距离按照从小到大顺序进行排序,argsort()函数返回的时索引值,其并不会改变原来的列表元素顺序
    sortedDistIndicies = distances.argsort()
    # 定义一个classCount字典,记录类别与相应的次数
    classCount={}
    # 下面的循环实现了统计类别与相应次数的功能
    for i in range(k):
        # 用voteIlabel来记录sortedDistIndicies[i]的标签
        voteIlabel = labels[sortedDistIndicies[i]]
        # 将对应的键的值+1
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 按照字典的值的大小顺序对字典classCount进行排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]
  • 测试上述kNN算法
    首先我们先定义一个createDataSet()函数,用于生成一个数据集。
def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1])
    labels = ['A','A','B','B']
    return group, labels

然后测试:

group, labels = kNN.createDataSet()
result = kNN.classify0([1,0], group, labels, 3)
print(result)

输出结果

B

改变k值,结果也会改变,所以kNN算法中k值的选择非常重要。
一般而言,k值越大,模型越简单,近似误差越大,估计误差越小;k值越小,模型越复杂,近似误差越小,但估计误差很大,容易受到噪声的影响。

  • 使用kNN算法识别手写数字

首先编写一段函数img2vector,目的是将图像转换为向量:该函数创建1×1024的Numpy数组,然后打开给定的文件,循环读出文件的前32行(后面可以得知,训练集和测试集的行数一共为32),并将每行的头32个字符值存储在Numpy数组中,最后返回数组。

def img2vector(filename):
    """
    将图片转换为向量
    :param filename: 文件名
    :return:
    """
    # 首先创建一个1✖1024的零向量
    returnVect = zeros((1,1024))
    fr = open(filename)
    # 打开文件,逐行读取字符,存储在数组中
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

接下来编写手写数字识别系统的测试代码。

def handwritingClassTest():
    """手写数字识别系统的测试代码"""

    # hwLabels是一个用于存储类的列表
    hwLabels = []
    # 使用listdir方法读取文件的名称
    trainingFileList = listdir('trainingDigits')  # load the training set
    # 获取文件的个数
    m = len(trainingFileList)
    # 创建一个 m×1024 的零矩阵,用于存储图像数据,其中矩阵的每一行代表一个图片
    trainingMat = zeros((m, 1024))
    # 从文件名中解析分类的数字,如文件名为0_0.txt
    for i in range(m):
        # fileNameStr存储文件的名字和后缀,如0_0.txt
        fileNameStr = trainingFileList[i]
        # fileStr存储文件的名字
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        # classNumStr存储文件代表的数字
        classNumStr = int(fileStr.split('_')[0])
        # 把类别加在hwLabels列表中
        hwLabels.append(classNumStr)
        # 调用img2vector()函数,将训练集中的图片转换成矩阵形式
        trainingMat[i] = img2vector('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('testDigits')  # iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        # 调用classify0()函数,即kNN算法,进行分类
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))

测试代码:
注:handwritingClassTest()方法在kNN.py中

import kNN
kNN.handwritingClassTest()
  • 测试结果

当k=1时,错误个数为13,错误率为0.013742kNN算法与手写数字识别_第1张图片

当k=2时,错误个数为13,错误率为0.013742
kNN算法与手写数字识别_第2张图片

当k=3时,错误个数为10,错误率为0.010571

kNN算法与手写数字识别_第3张图片
k=4时,错误个数为11,错误率为0.011628
kNN算法与手写数字识别_第4张图片
k=5时,错误个数为17,错误率为0.017970
kNN算法与手写数字识别_第5张图片
k=6时,错误个数为17,错误率为0.017970
kNN算法与手写数字识别_第6张图片
k=7时,错误个数为21,错误率为0.022199
kNN算法与手写数字识别_第7张图片

  • 结论
    k值的选取对错误率有很大影响,因此要选取合适的k值。

参考资料

《机器学习实战》
《统计学习方法》 李航 著

你可能感兴趣的:(机器学习,人工智能,python)