机器学习实战——2.k-近邻算法

2.1.k-近邻(kNN)算法概述
k-近邻算法采用测量不同特征值之间的距离进行分类。
优点:精度高、对异常值不敏感、无数据输入设定。
缺点:计算复杂度高、空间复杂度高。
适用范围:数值型和标称型。
kNN工作原理:存在一个样本数据集,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
k-近邻算法的一般流程:
在这里插入图片描述
2.1.1准备:使用python导入数据
下面给出一个小例子:
写一个createDataSet()函数用于生成数据集:

import numpy as np
import operator
import matplotlib.pyplot as plt
#生成数据集
def createDataSet():
    group=np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    label=[['A'],['A'],['B'],['B']]
    return group,label

if __name__=='__main__':
    group,label=createDataSet()
    figure=plt.figure()
    plt.xlim((-0.5,1.2))
    plt.ylim((-0.5,1.2))
    for i in range(len(label)):
        plt.plot(group[i][0],group[i][1],marker='o',markerfacecolor='b')
        plt.annotate(label[i][0],xy=group[i],xycoords='data',xytext=(-5,5),textcoords='offset points',fontsize=7)
    plt.show()

在这里插入图片描述
如图四组数据中,每组数据有两个已知的属性或特征值。由于人类大脑的限制,通常只能可视化处理三维以下的事务,因此为了简单地实现数据可视化,对于每个数据通常只使用两个特征。
上面的group矩阵每行包含一个不同的数据,可以把它想象为某个日志文件中不同的测量点或者入口。向量label包含了每个数据点的标签信息,label包含的元素个数等于group矩阵行数。这里将数据点(1,1.1)定义为类A,数据点(0,0.1)定义为类B。
2.1.2从文本文件中解析数据
使用k-近邻算法将每组数据划分到某个类中的伪代码:
对未知类别属性的数据集中的每个点依次执行以下操作:
(1)计算已知类别数据集中的每个点与当前点之间的距离;
(2)按照距离递增次序排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类。
k-近邻算法的程序为:

import numpy as np
#生成数据集
def createDataSet():
    group=np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    label=[['A'],['A'],['B'],['B']]
    return group,label
def classify(input,dataset,label,k):
    datasetsize=dataset.shape[0]    #数据集数量

    #计算距离
    diffmat=np.array([input]*datasetsize)-dataset
    #diffmat=tile(input,(datasize,1))-dataset
    distance=np.sqrt((diffmat**2).sum(axis=1))
    #选择距离最小的k个点排序
    sorteddistanceindex=distance.argsort()
    sortedlabel=[]
    for i in range(k):
        sortedlabel.append(label[sorteddistanceindex[i]])
    category=[]
    #比较k个点的标签,返回input最有可能的类别
    for i in sortedlabel:
        category.append(sortedlabel.count(i))
    return sortedlabel[category.index(max(category))][0]


if __name__=='__main__':
    group,label=createDataSet()
    print('目标的类别为:',classify([0,0],group,label,3))

classify函数有四个参数:用于分类的输入向量是input,输入的训练样本集是dataset,标签向量是label,最后的参数k表示用于选择最近邻的数目,其中标签向量的元素数目和矩阵dataset的行数相同。上述程序使用的是欧式距离,计算两个向量点xA和xB之间的距离:
在这里插入图片描述
例如:点(0,0)与(1,2)之间的距离计算为:
在这里插入图片描述
如果数据集存在4个特征值,则点(1,0,0,1)与(7,6,9,4)之间的距离计算为:
在这里插入图片描述
计算完所有点之间的距离后,可以对数据按照从小到大的次序排序。然后,确定前k个距离最小元素所在的主要分类,输入k总是正整数,最后返回发生频率最高的元素标签。
结果:
在这里插入图片描述
到此为止,构造了第一个分类器,使用这个分类器可以完成很多分类任务。从这个实例出发,构造使用分类算法将会更加容易。
2.1.3如何测试分类器
分类器并不会得到百分百正确的结果,可以使用多种方法检测分类器的正确率。此外分类器的性能也会受到多种因素的影响,如分类器设置和数据集等。不同的算法在不同数据集上的表现可能完全不同。
为了测试分类器效果,可以使用已知答案的数据,检验分类器给出的结果是否符合预期结果。通过大量的测试数据,可以得到分类器的错误率——分类器给出错误结果的次数除于测试执行的总数。错误率是常用的评估方法,主要用于评估分类器在某个数据集上的执行效果。完美分类器的错误率为0,最差分类器的错误率为1.0,在这种情况下,分类器根本就无法找到一个正确答案。
2.2.示例:使用k-近邻算法改进约会网站的配对效果
示例:在约会网站上使用k-近邻算法
(1)收集数据:提供文本文件;
(2)准备数据:使用python解析文本文件;
(3)分析数据:使用matplotlib画二维扩散图;
(4)训练算法:此步骤不适用于k-近邻算法
(5)测试算法:使用部分数据作为测试样本;
测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
(6)使用算法:产生简单的命令行程序,然后输入一些特征数据判断对方是否为自己喜欢的类型。
2.2.1准备数据:从文本文件中解析数据
由于书中没有提供数据集地址,所以自己随便写了几组数据保存在了‘datingSet.txt’中。每个样本数据占据一行,共4个数,前三个数为样本特征,最后一个数。样本主要包含以下3种特征:
*每年后得的飞行常客里程数
*玩视频游戏所耗时间百分比
*每周消费的冰淇淋公升数
在这里插入图片描述
将上述特征数据输入到分类器之前,必须将待处理数据的格式转变成分类器可以接受的格式。在kNN.py中创建名file2matrix的函数,以此来处理输入格式问题,该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
代码:

#创建文件处理函数
def file2matrix(filename):
    fr=open(filename)
    arrayoflines=fr.readlines()
    numoflines=len(arrayoflines)  #获得文件行数
    returnmat=np.zeros([numoflines,3])   #创建一个矩阵用于存储特征
    classlabelvector=[]  #创建一个列表用于存储标签
    index=0
    for line in arrayoflines:
        line=line.strip()
        listfromline=line.split(' ')
        returnmat[index,:]=listfromline[0:3]
        classlabelvector.append(listfromline[-1])
        index+=1
    return returnmat,classlabelvector

从上面的代码可以看出,python处理文本文件非常容易。首先需要知道文本文件包含多少行。打开文件得到文件的行数。然后创建以0填充的矩阵numpy(实际上,numpy是一个二维数组,这里暂时不用考虑其用途)。为了简化处理,将矩阵的另一维设置为固定值3,可以按照自己的实际需求增加相应的代码以适应变化的输入值。循环处理文件的每行数据,使用line.strip()去掉首尾的空白字符(包括\n,\r,\t这些),然后使用空格(‘ ’)将上一步得到的整行数据分割成一个元素列表,接着,取前3个元素,将它们存储在特征矩阵中,将列表的最后一列存储在标签向量classlabelvector中。需要注意的是,必须明确通知解释器列表中存储的元素值为整型,否则python语言会将这些元素当做字符串处理。以前我们必须自己处理这些变量值类型问题,现在这些细节问题完全可以交给numpy函数库处理。
调用函数,python的输出结果大致如下:
在这里插入图片描述
2.2.2分析数据:使用matplotlib创建散点图
一般来说,会采用图形化的方式直观地展示数据。下面就用python工具来图形化展示数据内容,以便辨识出一些数据模式。
首先使用matplotlib制作原始数据的散点图:

if __name__=='__main__':
    featuremat,label=file2matrix('./datingSet.txt')
    fig=plt.figure()
    plt.scatter(featuremat[:,1],featuremat[:,2]) #取后两维特征
    plt.rcParams['font.sans-serif'=['SimHei']]
    plt.xlabel('玩视频游戏所耗时间百分比')
    plt.ylabel('每周消耗的冰淇淋公升数')
    plt.show()

在这里插入图片描述
由于没有使用样本分类的特征值,很难从上图中看到任何有用的数据模型信息。一般来说,会采用色彩或其他的几号来标记不同样本分类,以便更好的理解数据信息。matplotlib库提供的scatter函数支持个性化标记散点图上的点。

    featuremat,label=file2matrix('./datingSet.txt')
    fig=plt.figure()
    list=[]
    for i in label:
        list.append(int(i))
    plt.scatter(featuremat[:,1],featuremat[:,2],15.0*np.array(list),15.0*np.array(list))
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.xlabel('玩视频游戏所耗时间百分比')
    plt.ylabel('每周消耗的冰淇淋公升数')
    plt.show()

在这里插入图片描述
利用上述代码在散点图上绘制了色彩不等,尺寸不同的点,基本可以从图中看到数据点所属三个样本分类的区域轮廓。
2.2.3准备数据:归一化数值
下表给出了提取的四组数据,如果想要计算样本3和样本4之间的距离,可以使用下面的方法:
在这里插入图片描述
在这里插入图片描述
我们很容易发现,上面方程中数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于表中其它两个特征——玩视频游戏和每周消耗冰淇淋公升数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数原大于其它特征值。但如果认为这三种特征是同等重要的话,作为三个等权重的特征之一,飞行常客里程数不应该如此严重的影响到计算结果。
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或-1到1之间。下面的公式可以将任意取值范围的特征值转化为0-1区间内的值:
在这里插入图片描述
其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,单为了得到准确结果,必须这样做。在kNN.py中增加一个新函数autoNorm(),用于将数字特征值转化为0-1之间:

#创建归一化函数
def autoNorm(dataset):
    minval=dataset.min(0) #每列的最小值
    maxval=dataset.max(0)  #每列的最大值
    ranges=maxval-minval
    m=dataset.shape[0]
    normaldataset=dataset-np.tile(minval,(m,1))
    normaldataset=normaldataset/np.tile(ranges,(m,1))
    return normaldataset,ranges,minval

原特征矩阵:
在这里插入图片描述
归一化后的特征矩阵:
在这里插入图片描述
2.2.4测试算法:作为完整程序验证分类器
机器学习算法一个很重要的工作就是评估算法的正确率,通常用已有数据的90%作为训练样本来训练分类器,使用其余的10%测试分类器,检测分类器的正确率。
为了测试分类器效果,在kNN.py文件中创建函数datingclasstest,该函数是自包含的,可以在任何时候在python运行环境中使用该函数测试分类器效果。
测试代码:

#创建测试函数
def datingclasstest():
    ratio=0.5   #由于样本只有6个,所以训练集测试集没有按照9:1分
    featuremat,label=file2matrix('./datingSet.txt')
    normmat,ranges,minval=autoNorm(featuremat)
    m=normmat.shape[0]
    numtestset=int(m*ratio)
    errorcount=0
    for i in range(numtestset):
        classifyresult=classify(normmat[i,:],normmat[numtestset:m,:],label,1)
        print('the classifier came back with:%d'%int(classifyresult),end=' ')
        print('the real answer is:%d' % int(label[i]))
        if classifyresult1=label[i]:
            errorcount+=1
    print('the total error is %f'%(errorcount/numtestset))

在这里插入图片描述
由于数据集较少,所以出现了100%的错误率,所以机器学习需要大数据集。改变ratio和变量k的值,检测错误了可能会改变。依赖于分类算法,数据集和程序设置,分类器的输出结果可能有很大的不同。
2.2.5使用算法:构建完整可用系统
可以用input函数得到用户输入的特征,然后采用分类器对其进行分类。
2.3.示例:手写识别系统
示例:使用k-近邻算法的手写识别系统
(1)收集数据:提供文本文件。
(2)准备数据:编写函数classify(),将图像格式转换为分类器使用的list格式。
(3)分析数据:在python命令提示符中检查数据,确保它符合要求。
(4)训练算法:此步骤不适用于k近邻算法。
(5)测试数据:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
(6)使用算法
2.3.1.准备数据:将图像转换成测试向量
由于书中没有给出数据集,所以选了100张mnist数据集中的图片(0~9各10张,尺寸为28x28)放在‘digit_dataset’文件夹里。
在这里插入图片描述
尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便,还是将图像转换为文本格式,再将图像矩阵将其转换成1x784的向量:。
代码:

#把图像转换成向量存储在digitDataSet.txt文件中
def img2vector(rootdir):
    list=os.listdir(rootdir)
    rimg=[]
    for num in range(len(list)):
        rimg.append([])
        path=os.path.join(rootdir,list[num])
        img=mimage.imread(path)
        for i in range(28):
            for j in range(28):
                 rimg[num].append(img[i][j])
        f=open('digitDataSet.txt','a')
        f.write(str(rimg[num])+'\n')
        f.close()

2.3.1.测试算法:使用k-近邻算法识别手写数字
利用2.1.2中的classify()函数,输入测试图片向量,采用k-近邻的方法预测测试图片的类别。
2.4.本章小结
k-近邻算法是分类数据最简单最有效的算法。k-近邻算法是基于实例的学习,使用算法时必须有接近实际数据的训练样本数据。k-近邻算法必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。
k-近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此也无法知晓平均实例样本和典型实例样本具有什么特征。下一章将使用概率测量方法处理分类问题,该算法可以解决这个问题。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值