机器学习轻松入门——KNN算法的PYTHON语言实现

KNN算法,也称K近邻算法,是一种监督学习的分类算法。

本篇文章主要由以下几个方面构成:

  1. KNN算法的原理及伪代码
  2. KNN算法的优缺点
  3. KNN算法实现手写数字识别系统

1.KNN算法的原理及伪代码

KNN算法,即在已知训练集数据所对应标签的情况下,去预测测试集数据所对应的标签,其算法核心就是要找到其训练集数据与其标签之间的对应关系。

伪代码:

  • 计算当前测试数据(1个)与所有训练集数据(N个)的距离
  • 将有距离升序排序,筛选出与当前测试数据距离最短的前K个训练集数据(算法难点:K值如何选定,一般<20
  • 依次找到对应的标签,统计标签的出现频次
  • 返回出现频次最高的标签作为当前测试数据的预测分类结果

2.KNN算法的优缺点

优点:简单易懂,精度高,对异常值不敏感

缺点:计算复杂度高,空间复杂度高

3.KNN算法实现手写数字识别系统

3.1该项目的实现过程及架构

项目概述:

 

该项目主要由三大块组成:

  • 前期数据预处理:将32行*32列的图像数据转换成1行*1024列的向量数据
  • 构建KNN分类器:实现对当前测试集数据(单个)的分类结果预测
  • (主程序)测试KNN分类器:对所有测试集数据进行分类,并统计其错误率

3.2该项目的PYTHON代码

用到的Python模块:numpy、os模块中listdir函数、operator模块

首先导入模块:

import numpy as np
import operator
from os import listdir #从os模块中导入listdir函数,实现读取文件夹下的所有文件名功能

然后进行数据预处理:

#将32*32转换成1*1024
def img2vector(filename):
    # 创建向量
    returnVect = np.zeros((1, 1024))
    # 打开数据文件,读取每行内容
    fr = open(filename)
    for i in range(32):
        # 读取每一行
        lineStr = fr.readline()
        # 将每行前 32 字符转成 int 存入向量
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])
    return returnVect

接着构建KNN分类器:

#KNN分类器
import operator
def classify0(inX, dataSet, labels, k):

    """
    参数: 
    - inX: 需要预测分类的当前测试集数据
    - dataSet: 输入的训练集数据
    - labels: 训练集数据的标签向量
    - k: 用于选择最近邻居的数目
    """

    # 获取训练数据集的行数
    dataSetSize = dataSet.shape[0]

    # 矩阵运算,计算测试数据与每个样本数据对应数据项的差值
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet

    # sqDistances 上一步骤结果平方和
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)

    # 取平方根,得到距离向量
    distances = sqDistances**0.5

    # 按照距离从低到高排序
    sortedDistIndicies = distances.argsort()
    
    # 依次取出最近的样本数据
    for i in range(k):
        # 根据索引,找到该样本数据所属的标签
        voteIlabel = labels[sortedDistIndicies[i]]
        # 建立一个字典,用于存放标签出现的频次,统计标签出现的频次
        classCount = {}
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1

    # 对标签出现的频次进行排序,从高到低
    sortedClassCount = sorted(
        classCount.items(), key=operator.itemgetter(1), reverse=True)

    # 返回出现频次最高的类别
    return sortedClassCount[0][0]

注意:

  • shape函数是numpy.core.fromnumeric中的函数,它的功能是查看矩阵或者数组的维数

    举例说明:建立一个2×4的单位矩阵e, e.shape为(2,4),表示2行4列,第一维的长度为2,第二维的长度为4。e.shape[0]为2,e.shape[1]为4。

  • tile函数的功能是对一个矩阵进行重复,得到新的矩阵,例如:b=np.tile(a,(3,1)),得到b=[a,a,a](将a重复3行)

  • argsort函数是返回一个数组升序排列的索引值,例如:x = np.array([3, 1, 2]),np.argsort(x),返回array([1, 2, 0])

  • operator.itemgetter()返回的是一个函数

       operator.itemgetter(1)按照第二个元素的次序对元组进行排序,reverse=True是逆序,即按照从大到小的顺序排列

       所以 sorted这里的意思是:

       classCount.items()将classCount字典分解为元组列表

       即由变成

       并且按第二个元素进行从大到小的排列

       最后return sortedClassCount[0][0],就是返回sortedClassCount的第一行第一列,即频数最高的那个对应的分类标签

 

最后是该项目的主程序,对所有测试集数据进行分类预测:

def handwritingClassTest():
    # 样本数据的类标签列表
    hwLabels = []

    # 样本数据文件列表
    trainingFileList = listdir(r'C:\Users\lenovo\Desktop\PYTHON\识别手写数字\trainingDigits')
    m = len(trainingFileList)
    # 初始化样本数据矩阵(M*1024)
    trainingMat = np.zeros((m, 1024))

    # 依次读取所有样本数据到数据矩阵
    # fileNamestr里存放的是当前训练集文件名,提取文件名中的数字
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        # 将样本数据存入矩阵
        trainingMat[i, :] = img2vector(r'C:\Users\lenovo\Desktop\PYTHON\识别手写数字\trainingDigits/%s' % fileNameStr)


    # 循环读取测试数据
    testFileList = listdir(r'C:\Users\lenovo\Desktop\PYTHON\识别手写数字\testDigits')
    mTest = len(testFileList)

    # 初始化错误率
    errorCount = 0.0
    
    # 循环测试每个测试数据文件
    for i in range(mTest):
        # fileNamestr里存放的是当前测试集文件名,提取文件名中的数字
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])

        # 提取数据向量
        vectorUnderTest = img2vector(r'C:\Users\lenovo\Desktop\PYTHON\识别手写数字\testDigits/%s' % fileNameStr)

        # 对数据文件进行分类,K值选取为3
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)

        # 打印 K 近邻算法分类结果和真实的分类
        print("测试样本 %d, 分类器预测: %d, 真实类别: %d" %
              (i+1, classifierResult, classNumStr))

        # 判断K 近邻算法结果是否准确
        if (classifierResult != classNumStr):
            errorCount += 1.0

    # 打印错误率
    print("\n错误分类计数: %d" % errorCount)
    print("\n错误分类比例: %f" % (errorCount/float(mTest)))

注意:

  • split函数为根据分隔符对字符串进行切片,split(‘分隔符’,num),num默认为全分割,num=1则只分割一次。例如:a='1_2.txt',b=a.split('.')=['1_2','txt']
  • append() 方法用于在列表末尾添加新的对象

 

最后的最后不要忘了运行主程序哦:

handwritingClassTest()

修改K值,分类结果的准确率不同:

K=2,错误计数13

K=3,错误计数10

K=4,错误计数11

K=5,错误计数17

K=6,错误计数17

最佳K值为3

你可能感兴趣的:(机器学习轻松入门——KNN算法的PYTHON语言实现)