机器学习——k邻近算法(kNN)

机器学习——k近邻算法(kNN)

参考教材:机器学习实战(人民邮电出版社)Peter Harrington【美】著 

语言:python 

软件:VS code

1.k近邻算法的概述

k近邻算法采用不测量不同特征值之间的距离方法进行分类。

算法的距离计算公式为欧式距离公式

 d= \sqrt{(xA_0-xB_0)^{2}+(xA_1-xB_1)^{2}}

 k近邻算法的优缺点:

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

 k近邻算法原理:

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

k近邻算法的一般流程:

  1. 收集数据:可以使用任何放大。
  2. 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
  3. 分析数据:可以使用任何方法。
  4. 训练算法:此步骤不适用于k近邻算法。
  5. 测试算法:计算错误率。
  6. 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k近邻算法判定输入数据分别属于哪一个分类,最后应用对计算出的分类执行后续的处理 

 2.k近邻算法实战

1.使用Python导入数据

在VScode中创建kNN.py文件,在此文件中写入数据,为了方便使用createDateSet()函数创建数据集group和标签labels 

import numpy
from numpy import*
import operator


def createDataSet():
    group = numpy.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group, labels

上述代码中,导入了两个模块:一个是科学计算包Numpy;第二个是运算符模块,两个模块提供k近邻算法需要的函数。数据写好后,在终端进行测试检查导入是否成功:

 (这里在检查数据的时候labels打成了lacles,所以有了一次出错)可以看到kNN.py文件里的数据成功读出。 

2.从文本文件中解析数据

对未知类别属性的数据集中的每个点以此执行一下操作:
(1)计算已知类别数据集中的点与当前点之间的距离;
(2)按照距离依次递增次序排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类。

Python函数classify()如下

def classify0(inX,dataSet,labels,k):
    dataSetSize = dataSet.shape[0]
    diffMat = numpy.tile(inX,(dataSetSize,1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortesDistIndicies = distances.argsort()
    classCount ={}
    for i in range(k):
        voteIlabel = labels[sortesDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

classify()函数有四个输入 参数:用于分类的输出向量inX,输入的训练样本集为dataSet,标签向量为labels,最后的参数k表示用于选择最近邻居的数目,其中标签向量的元素数目和矩阵dataSet的行数相同。

计算两个点之间的距离:

diffMat = numpy.tile(inX,(dataSetSize,1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortesDistIndicies = distances.argsort()

计算完所有点的距离后,按从小到大排序。确定前k个距离最小元素所在的主要分类:

classCount ={}
    for i in range(k):
        voteIlabel = labels[sortesDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1

输入k总是正整数。最后将classCount字典分解为元组列表,然后使用导入运算符模块的itemgetter方法,按照第二个元素的次序对元组进行排序,此处按从大到小排序,最后返回发生频率最高的元素标签:

 sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

3.预测数据所在分类 

 输出结果为“B”。至此一个分类器构造完成。


十月十日添


实战:使用k近邻算法改进约会网站的配对效果

具体要求在课本的P20.

样本主要包含以下三个特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消费的冰淇淋公升数

第一步  将文本记录读到转化NumPy的解析程序

这里的文本素材用的是教材自带的一千行文本。

def file2matrix(filename):
    fr = open(filename)
    arrayOlines = fr.readlines()
    numberOfLines = len(arrayOlines)  #得到文件行数
    returnMat = numpy.zeros((numberOfLines,3))  #创建返回的numpy矩阵
    classLabelVector = []
    index = 0
    for line in arrayOlines:    #解析文件数据到列表
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

这里由于是在原本的kNN文件里面新加入的函数,所以在运行的时候会出现

AttributeError: module ‘kNN’ has no attribute ‘file2matrix’

这时需要把kNN文件reload就鞥解决,否则运行的还是原来未更新的文件。

之后就是检查是否成功读入:

<<<datingDataMat,datingLabels=file2matrix('datingTestSet.txt')
<<<datingDataMat
<<<datingLabels[0:20]

结果如图:

 结果显示成功读入。

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

在python命令行中输入如下代码:

 这里的d1即为上面的datingDataMat,只是我在测试数据时刚开始测试很久都没成功,一直重新开启终端,用d1比较方便,后来成功了就直接用d1,不影响后续实验。

 

 这个是没有染色过的图片,图上的点都是一个颜色,不好区分。上色的话只需要把上面的代码倒数第二步改为

ax.scatter(d2[:,1],d1[:,2],15.0*array(datingLabels),15.0*array(datingLabels))

 这里有用到numpy里的array,在运行时,出现array未定义的情况,需要在终端导入

from numpy import array

之后看上了色的图

第三步  准备数据:归一化数值

 在对数据分类的过程中,因为文本数据有的差值很大,对欧氏距离的计算公式的计算结果影响很大,所以需要进行归一化处理,下面公式可以将任意取值范围的特征值转化为0—1区间的值:

newValue = (oldValue-min)/(max-min)

其中min和max分别是数据集中的最小特征值和最大特征值。以下为归一化代码:

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = numpy.zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - numpy.tile(minVals, (m,1))
    normDataSet = normDataSet/numpy.tile(ranges, (m,1)) 
    return normDataSet, ranges, minVals

在命令行模式下,重载kNN执行该函数

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

测试代码如下:

def datingClassTest():
    hoRatio = 0.50      
    datingDataMat,datingLabels = file2matrix('datingTestSet.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],3)
        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)))

之后重载kNN运行该函数:

 可以看到这里测试显示错误率为6.6%。这个例子表明我们可以输入一个未知的对象来属性信息,由该分类器来预测可交往程度:讨厌、一般喜欢、非常喜欢。

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

以下是预测函数代码:

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input(
        'percentage of time spent playing video games? : '))
    ffMiles = float(input('frequent flier miles earned per year? : '))
    iceCream = float(input('liters of ice cream consumed per year? : '))
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = numpy.array([ffMiles, percentTats, iceCream])
    classifierReault = classify0((inArr-minVals)/ranges, normMat, datingLabels, 3)
    print('You will probably like this person: ',resultList[classifierReault - 1])

执行后结果:


实战:手写识别系统

实验素材为课本自带素材。

第一步  准备数据:将图像转换为测试向量

将图像转换为向量需要将32*32的二进制图像矩阵转换成1*1024的向量。代码如下:

def img2vector(filename):
    returnVect = numpy.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近邻算法识别手写数字

这里有用到os模块中的listdir函数来列出给定目录的文件名,所以要在kNN文件头部导入。测试代码如下:

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')          
    m = len(trainingFileList)
    trainingMat = numpy.zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]     
        classNumStr = int(fileStr.split('_')[0])
        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 ("\nthe total number of errors is: %d" % errorCount)
    print ("\nthe total error rate is: %f" % (errorCount/float(mTest)))

测试结果如图:

 可以看到这里的错误率为1.05%

kNN总结


优点

  1.         可以处理分类问题,算法简单易懂
  2.         可以免去训练过程
  3.         kNN还可以处理回归问题,也就是预测

缺点

  1.         效率低,每一次分类都要对训练数据进行计算
  2.         对训练数据依赖度特别大,过拟合、欠拟合问题难以权衡
  3.         存在维数灾难问题
  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值