欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

K-近邻算法—基本原理与实战

程序员文章站 2022-06-01 23:28:52
...

概述

    k-近邻算法(k-Nearest Neighbor, KNN),是一个理论上比较成熟的方法,也是最简单的机器学习算法之一,用于预测数据的类别,以及对数据进行分类。该方法的简要思路就是采用测量不同特征值之间的距离来进行分类。

特点

K-近邻算法—基本原理与实战

工作原理

    存在一个样本数据集合,也称作训练样本集,并且样本集中的每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。当输入一个没有包含标签(也就是对应的类别)的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后用算法提取样本集中特征与新数据最相似的数据(最近邻)的分类标签。一般来说,只取样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后选择k个最相似数据出现次数最多的分类,作为新数据的分类(标签)。

使用欧式距离公式,计算两个向量点xA和xB的距离:

K-近邻算法—基本原理与实战

kNN算法的一般流程

(1)收集数据:可以使用任何方法。

(2)准备数据:距离计算所需要的数值,最好是结构化的数据格式。

(3)分析数据:可以使用任何方法。

(4)训练算法:k-近邻算法不需要训练算法。

(5)测试算法:计算错误率。

(6)使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。

实施kNN分类算法

#kNN算法的伪代码
#功能:使用k-近邻算法将数据划分到某个类中
#对未知类别属性的数据集中的每个点依次执行以下操作:
(1)计算已知类别数据集中的点与当前点之间的距离;
(2)按照距离递增次序对数据集中的点进行排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类;

简单分类示例

from numpy import *
import operator

#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

函数createDataSet()初始化了四个样本数据,每个样本数据含有两个特征值,并且还初始化了标签信息,其中第一个数据的标签是A,第二个是A,第三个是B,第四个是B。产生的样本数据用于对新数据进行分类预测。

#classify0:k-近邻算法的基本实现,返回数据的类别预测信息
#inX:用于分类的输入向量
#dataSet:训练样本集
#labels:训练样本集对应的标签向量
#k:选择最近邻居的数目
from numpy import *

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]                      #获取样本数据的行数
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet     #将输入向量扩展到和样本数据同样大小,然后与样本数据相减
    sqDiffMat = diffMat ** 2;                           #对相减后的数组平方(每个元素平方);如果是矩阵,表示矩阵平方(矩阵乘法)
    sqDistances = sqDiffMat.sum(axis=1)                 #axis=0表示将矩阵或数组的每个元素相加;
                                                        #axis=1表示将矩阵或数组的每行的每个元素相加,相加的结果(数量和行数相同)组成一个一维的数组
    distances = sqDistances ** 0.5                      #对数组中的每个元素求平方根
    sortedDistIndicies = distances.argsort()            #对distances数组中的元素进行排序,返回从小到大的索引值
    classCount = {}                                     #创建一个空字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]      #根据距离从小到大或取样本数据的类别
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
                                                        #将类别作为key,出现次数作为value,类别每出现一次,key值加1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
                                                        #根据字典中value值的大小进行排序,默认从小到大,添加reverse=True表示从大到小
    return sortedClassCount[0][0]                       #返回出现次数最高的类别

运行classify0(),传入上面生成的数据,返回B。

分类器的测试

    为了检验分类器给出的答案是否符合预期,就需要对分类器进行测试。通过大量的测试数据,我们可以得到分类器的错误率——分类器给出错误结果的次数/测试执行总次数。完美的分类器错误率为0,最差的分类器错误率为1.0。

归一化特征值

    对于大多数数据来说,其每个特征的特征值可能数值差距很大。例如某个人,身高属性1.7m,而体重属性70KG,这两个属性数值差距比较大,如果直接用欧拉距离公式计算,体重对分类结果的影响会大大提升,而真正意愿则是体重和身高对分类结果的影响相同,因此需要对这些数据进行归一化。

使用下列公式可以将任意取值范围的特征值转化为0到1区间内的值:
K-近邻算法—基本原理与实战

其中oldValue是需要进行归一化的特征值,min是所有同类特征值最小的特征值,max是所有同类特征值最大的特征值。

代码示例:

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet)) #创建一个和dataSet大小相同的数组
    m = dataSet.shape[0]                #dataSet的行数
    normDataSet = dataSet - tile(minVals, (m, 1))   #minVals扩展:行*m,列*n
    normDataSet = normDataSet/tile(ranges, (m, 1))  #特征值相除
    return normDataSet, ranges, minVals

示例一:使用kNN算法改进约会网站的配对效果

这里使用的示例都来自书籍《机器学习实战》,源码下载

(1)收集数据:提供文本文件。

(2)准备数据:使用Python解析文本文件。

(3)分析数据:使用Matplotlib画二维扩散图。

(4)训练算法:此步骤不适用于k-近邻算法。

(5)测试算法:使用训练数据中10%的数据进行测试。

(6)使用算法:产生简单的命令行程序,然后输入特征值进行判断。

准备数据:将文本记录转换成NumPy的解析程序:

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #获取列数
    cols = len(arrayOLines[0].split('\t'))
    returnMat = zeros((numberOfLines, cols - 1))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:cols-1]
        classLabelVector.append(int(listFromLine[-1]))  #特征值为int型
        index += 1
    return returnMat, classLabelVector

分析数据:使用Matplotlib创建散点图

import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])
plt.show()

准备数据:归一化数值

采用上述方法进行归一化

测试算法:作为完整程序验证分类器

#测试分类器的效果
def datingClassTest():
    hoRatio = 0.10
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 10)
        print("The classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("The total error rate is: %f", errorCount/float(numTestVecs))

使用算法:构建完整可用系统

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent palying video games?"))
    ffMiles = float(input("frequent flier miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))

    datingDataMat, datingLables = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLables, 3)
    print("You will like the person: ", resultList[classifierResult - 1])

示例二:手写识别系统

    原理:将图像经过图形处理软件转为为二进制的形式,然后将二进制图片转化为一个向量,每一位二进制都是向量的一个元素,将大量向量再加上对应的类别作为训练数据集。使用算法时,使用没有类别的向量,计算其与训练数据集向量的距离,通过kNN算法预测出类别信息。

准备数据:将图像(已经是二进制格式)转换为测试向量

def img2vector(filename):
    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

测试算法:使用k-近邻算法识别手写数字

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')    #listdir需要使用    from os import listdir
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]           #每个文件的文件名,如:0_1.txt
        fileStr = fileNameStr.split('.')[0]         #获取除后缀的文件名,如:0_1
        classNumStr = int(fileStr.split('_')[0])    #获取文件的标签,如0_1表示数字0的第一个训练数据,则该数据的的标签是1
        hwLabels.append(classNumStr)
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)

    testFileList = listdir('testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        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("The total number of errors is: %d" % errorCount)
    print("The total error rate is: %f" % (errorCount / float(mTest)))
相关标签: 算法 机器学习