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

来源:互联网 发布:关系数据库是什么 编辑:程序博客网 时间:2024/06/04 00:24

此文讲述了:K-近邻算法—简述基本原理与实战


首先,做一个分享:

阿里云ECS服务器,有幸运券了:

https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=5uqvqirt&productCode=vm&utm_source=5uqvqirt


此处,开始讲解正文:

工作原理



    存在一个样本数据集合,也称作训练样本集,并且样本集中的每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。当输入一个没有包含标签(也就是对应的类别)的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后用算法提取样本集中特征与新数据最相似的数据(最近邻)的分类标签。一般来说,只取样本数据集中前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)))

原创粉丝点击