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

概述

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

特点

工作原理

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

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

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区间内的值:

其中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)))

猜你喜欢

转载自blog.csdn.net/qq_32651225/article/details/72609885