深度解读KNN算法代码(Python小白呕心沥血之作)

一. KNN算法(K-近邻算法)工作机制

KNN是一种监督学习类别的算法,其工作机制是给定一个新的测试样本,基于某种距离度量找出训练集中离该新样本最近的前K个样本(训练集中样本标签已知),基于这K个样本的标签信息来预测新样本的类别,通常,在这K个样本中出现最多次数的类别标签即被预测为新样本的类别标签。

二. 准备:使用Python导入数据

首先创建一个KNN.py文件,在文件中输入以下代码:

from numpy import * #导入numpy库,这种形式不建议用,一般采用,import numpy 
import operator #导入operator模块
#以下代码定义了一个数据集,其中包含数据与对应的分类标签信息
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

保存KNN.py文件,并改变当前路径到存储KNN.py的位置,打开Python开发环境。
输入以下代码导入KNN模块:

import KNN

查看数据集:

group,labels=KNN.createDataSet()
print(group)
print(labels)
输出为:
[[1.  1.1]
 [1.  1. ]
 [0.  0. ]
 [0.  0.1]]
['A', 'A', 'B', 'B']

现在我们已经知道Python如何解析数据,如何加载数据,以及kNN算法的工作原理,接下来我们将使用这些方法完成分类任务。

三.实施KNN算法

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

def calssify0(inX,dataSet,labels,k): #inX为未知类别的新样本,datSet为训练集,labels为训练集的类别标签集合,k为选择距离最近的训练样本的数量
    dataSetSize=dataSet.shape[0] #shape[0]会返回数组的行数,shape[1]返回列数
    diffMat=numpy.tile(inX,(dataSetSize,1))-dataSet #tile解释参考https://www.jianshu.com/p/9519f1984c70,diffMat数组的每列表示新样本的各列特征值与训练集各列特征值之差 
    sqDiffMat=diffMat**2
    sqDistances=sqDiffMat.sum(axis=1) #按列求和
    distances=sqDistances**0.5 #平方求和再开方得到最后距离
    sortedDistIndicies=distances.argsort() #按照距离远近进行排序,默认为升序,返回相应距离数值对应的位置索引,argsort()函数解释参照https://blog.csdn.net/dz4543/article/details/80219115
    classCount={} #创建一个新字典
    for i in range(k):
        voteIlabel=labels[sortedDistIndicies[i]] #得到第i个训练样本的类别标签
        classCount[voteIlabel]=classCount.get(voteIlabel,0)+1 #统计离新样本距离最近的k个训练样本中的类别标签数量。get()函数解释参考https://www.runoob.com/python/att-dictionary-get.html
    sortclassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #对clssCount进行排序,按照类别标签出现次数从多到少降序排列
return sortclassCount[0][0] #返回classCount中的第一项中的类别,即为新样本的类别

为了更加具体的解释上述代码,将代入一个具体的新样本与训练集实例,如classify0([0,1],group,labels,3)(group,labels为上面导入数据)

dataSetSize=group.shape[0]==4

diffMat=numpy.tile(inX,(dataSetSize,1))-group
使用tile函数后,inX从[0,1]变成[[0,1],[0,1],[0,1],[0,1]],diffMat==[[-1.,-0.1],[-1.,0.],[0.,1.],[0.,0.9.]]

sqDiffMat=diffMat**2
sqDiffMat==[[1,0.01],[1.,0.],[0.,1.],[0.,0.81]]

sqDistances=sqDiffMat.sum(axis=1)
sqDistances==[1.01,1.,1.,0.81]

distances=sqDistances**0.5
distances==[1.00498756, 1. , 1. , 0.9 ]

sortedDistIndicies=distances.argsort()
sortedDistIndicies=[3,1,2,0]
argsort()函数将数组x中的元素从小到大排序,并且取出他们对应的索引,然后返回。

for i in range(k):
voteIlabel=labels[sortedDistIndicies[i]]
classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
根据get()函数可得,classCount=={‘A’: 1, ‘B’: 2}

sortclassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
calssCout中类别标签出现次数从多到少的类别标签为B(两次)、A(一次),故sortclassCount==[(‘B’, 2), (‘A’, 1)]

return sortclassCount[0][0]
sortclassCount[0][0]==‘B’

故新样本[0,1]的分类标签为B类

四. 示例:使用k-近邻算法改进约会网站的配对效果

海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的
人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:
不喜欢的人
魅力一般的人
极具魅力的人
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的分类。她觉得可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。

A.示例:在约会网站上使用k-近邻算法

(1) 收集数据:提供文本文件datingTestSet2.txt(文本链接:https://pan.baidu.com/s/1KScV2tXuCEKNeEBn6FPcp提取码:ldu9)机器实战原文说的是datingTestSet.txt,但是datingTestSet.txt中的最后一列并不是数字,无法读取。
(2) 准备数据:使用Python解析文本文件。
(3) 分析数据:使用Matplotlib画二维扩散图。
(4) 测试算法:使用海伦提供的部分数据作为测试样本。测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
(5) 使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。

(2). 准备数据:从文本文件中解析数据

海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每
个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
 每年获得的飞行常客里程数
 玩视频游戏所耗时间百分比
 每周消费的冰淇淋公升数
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵类别标签向量

def files2matrix(filename):
    fr=open(filename)
    arraylines=fr.readlines() #readlines()方法读取整个文件所有行,函数解释参考https://www.cnblogs.com/yun1108/p/8967334.html
    lenoflines=len(arraylines) #返回集合长度,这里的集合一行为一个元素
    matrix=np.zeros((lenoflines,3))#创建全0矩阵,行数为lenoflines,列数为3
    labelmatrix=[] #创建空集合
    index=0
    for line in arraylines:
        line=line.strip() #strip()函数,去掉字符串头尾指定的回车字符,函数解释参考https://jingyan.baidu.com/article/ed2a5d1f3f38d309f7be174f.html
        listFromline=line.split('\t') #split()函数,以'\t'为分隔符拆分字符串('\t'是Tab键所产生的水平排版字符),函数解释参考https://www.cnblogs.com/xuchunlin/p/5520399.html
        matrix[index,:]=listFromline[0:3] #将listFromline的前三个元素存入matrix的第index行
        labelmatrix.append(int(listFromline[-1])) #将listFromline的最后一个元素存入labelmatrix
        index+=1
    return matrix,labelmatrix
datingDataMat,datingLabels=files2matrix('datingTestSet2.txt')
datingDataMat,datingLabels[0:20]
out:
array([[4.0920000e+04, 8.3269760e+00, 9.5395200e-01],
        [1.4488000e+04, 7.1534690e+00, 1.6739040e+00],
        [2.6052000e+04, 1.4418710e+00, 8.0512400e-01],
        ...,
        [2.6575000e+04, 1.0650102e+01, 8.6662700e-01],
        [4.8111000e+04, 9.1345280e+00, 7.2804500e-01],
        [4.3757000e+04, 7.8826010e+00, 1.3324460e+00]]),
 [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]
1.分析数据:使用Matplotlib 创建散点图
import matplotlib
import matplotlib.pyplot as plt
import pylab
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] #使中文字符能被显示在图表中
fig=plt.figure()
ax=fig.add_subplot(111) #图表画在分成一行一列的第一个表格中
ax.scatter(datingDataMat[:,0],datingDataMat[:,1],15*np.array(datingLabels),15*np.array(datingLabels)) #取datingDataMat的第一列为横坐标,第二列为纵坐标做散点图
ax.set_xlabel('玩视频游戏所耗时间百分比')
ax.set_ylabel('每周所消费的冰淇淋公升数')
plt.show()

第七行ax.scatter(datingDataMat[:,0],datingDataMat[:,1],15np.array(datingLabels),15np.array(datingLabels))后面的两个15np.array(datingLabels)为了给不同类别的数据点赋予不同的颜色,具体的原理我也不太清楚,貌似是第一个15np.array(datingLabels)代表其R通道数值,第二个为G通道数值,从而给类别标签相同的散点赋予相同的颜色,给类别标签不同的散点赋予不同的颜色。(但是我想让图像中标注出每个颜色所属的类别,却做不到)图形如下:
在这里插入图片描述
从图中可以明显的看出三个不同类别的样本点分布情况。
下面是文档中对scatter的参数c的说明:
c : color, sequence, or sequence of color, optional, default: ‘b’
c can be a single color format string, or a sequence of color specifications of length N, or a sequence of N numbers to be mapped to colors using the cmap and norm specified via kwargs (see below). Note that c should not be a single numeric RGB or RGBA sequence because that is indistinguishable from an array of values to be colormapped. c can be a 2-D array in which the rows are RGB or RGBA, however, including the case of a single row to specify the same color for all points.
更多scatter()知识参考:
https://www.cnblogs.com/OliverQin/p/7965435.html
https://baijiahao.baidu.com/s?id=1632610735266888598&wfr=spider&for=pc
https://blog.csdn.net/u013634684/article/details/49646311
https://blog.csdn.net/w576233728/article/details/86538060

2. 准备数据:归一化数值

每年获取的飞行常客里程数对于计算结果的影响将远远大于其他两个特征——玩视频游戏的时间和每周消费冰淇淋公升数——的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。但海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
n e w V a l u e = ( o l d V a l u e − m i n ) / ( m a x − m i n ) newValue=(oldValue-min)/(max-min) newValue=(oldValuemin)/(maxmin)
其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。我们需要在文件kNN.py中增加一个新函数autoNorm(),该函数可以自动将数字特征值转化为0到1的区间。

import numpy as np
def autoNorm(dataSet):
    minVals = dataSet.min(0) #取dataSet的每一列的最小值,minVals的行数为1,列数与dataSet的列数相等
    maxVals = dataSet.max(0) #取dataSet的每一列的最大值,maxVals的行数为1,列数与dataSet的列数相等
    ranges = maxVals - minVals #计算每列数据的最大最小值之差,ranges的行数为1,列数与dataSet列数相同
    normDataSet = np.zeros(np.shape(dataSet)) #创建形状大小与dataSet一样的0矩阵
    m = dataSet.shape[0] #m为dataSet行数
    normDataSet = dataSet - np.tile(minVals, (m,1)) #tile函数解释参考https://www.jianshu.com/p/9519f1984c70 normDataSet为dataSet每列数据与其最小值之差,形状与dataSet相同
    normDataSet = normDataSet/np.tile(ranges, (m,1))   #normDataSet为normDataSet的每列数值分别与该列的最大最小值之差相除
    return normDataSet, ranges, minVals
autoNorm(datingDataMat)
out:
(array([[0.44832535, 0.39805139, 0.56233353],
        [0.15873259, 0.34195467, 0.98724416],
        [0.28542943, 0.06892523, 0.47449629],
        ...,
        [0.29115949, 0.50910294, 0.51079493],
        [0.52711097, 0.43665451, 0.4290048 ],
        [0.47940793, 0.3768091 , 0.78571804]]),
 array([9.1273000e+04, 2.0919349e+01, 1.6943610e+00]),
 array([0.      , 0.      , 0.001156]))
3.测试算法:作为完整程序验证分类器

对于分类器来说,错误率就是分类器给出错误结果的次数除以测试数据的总数,完美分类器的错误率为0,而错误率为1.0的分类器不会给出任何正确的分类结果。代码里我们定义一个计数器变量,每次分类器错误地分类数据,计数器就加1,程序执行完成之后计数器的结果除以数据点总数即是错误率。
为了测试分类器效果,在kNN.py文件中创建函数datingClassTest

def datingClassTest():
    hoRatio = 0.50      #hold out 10%
    datingDataMat,datingLabels = files2matrix('datingTestSet2.txt')       #load data setfrom file
    normMat, ranges, minVals = autoNorm(datingDataMat)#数据归一化,normMat为归一化后的结果矩阵,ranges是每列数据的最大最小值之差,minVals是每列的最小值
    m = normMat.shape[0] #获取normMat的行数
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = calssify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3) #使用KNN算法对normMat的第一行的特征进行分类
        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)))
    print (errorCount)

第九行classifierResult=calssify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
其新样本为归一化后的第i行特征,训练集为归一化后的第numTestVecs到m行,这里用的是用的归一化后数据的后一半作为训练集,K的值是3。

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

上面我们已经在数据上对分类器进行了测试,现在终于可以使用这个分类器为海伦来对人们分类。我们会给海伦一小段程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息。程序会给出她对对方喜欢程度的预测值。

def classifyperson():
    resultList=['not at all','in small doses','in large doses']
    percentTats=float(input("percentage of tiome spent playing video games?")) #python3中已经将raw_input剔除掉了,具体参考https://blog.csdn.net/Leeeoplod/article/details/89669671
    ffMiles=float(input("frequent flier miles earned per year?"))
    iceCream=float(input("liters of ice cream consumed per year?"))
    datingDatMat,datingLabels=files2matrix('datingTestSet2.txt') #读取文件
    normMat,ranges,minVals=autoNorm(datingDatMat) #数据归一化
    inArr=np.array([ffMiles,percentTats,iceCream]) #创建数组
    classifierResult=calssify0((inArr-minVals)/ranges,normMat,datingLabels,3) #对新样本运用KNN算法进行分类
    print("You will probably like this person:",resultList[classifierResult-1])
classifyperson()
out:
percentage of tiome spent playing video games?100

frequent flier miles earned per year?90

liters of ice cream consumed per year?10
You will probably like this person: in large doses

上述程序清单中的大部分代码我们在前面都见过。唯一新加入的代码是函数input()

注:以上代码来自于《机器学习实战》,所使用的Python版本为Python3.5,故有些代码已经做了修改不同于原书,Python3.5版本下以上代码全部通过。

  • 7
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值