KNN
Posted 十二Zh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KNN相关的知识,希望对你有一定的参考价值。
学习《机器学习实战》第2章
参考博客:http://blog.csdn.net/c406495762/article/details/75172850#三-k-近邻算法实战之sklearn手写数字识别
K近邻法(k-nearest neighbor,K-NN)采用测量不同特征值之间的距离方法进行分类。
工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似数据,这就是k-近邻算法中k的出处,通常k是不大于20的证书。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
K近邻算法:
对未知类别属性的数据集中的每个点依次执行以下操作
(1)计算已知类别数据集中的点与当前点之间的距离;
(2)按照距离递增次序排序;
(3)选取与当前点距离最小的k个点;
(4)确定前k个点所在类别的出现频率;
(5)返回前k个点出现频率最高的类别作为当前点的预测分类。
K-近邻算法的python实现:
1 """ 2 函数说明:kNN算法,分类器 3 Parameters: 4 inX - 用于分类的数据(测试集) 5 dataSet - 用于训练的数据(训练集) 6 labes - 分类标签 7 k - kNN算法参数,选择距离最小的k个点 8 Returns: 9 sortedClassCount[0][0] - 分类结果 10 """ 11 def classify0(inX, dataSet, labels, k): 12 #如dataSet为n*m的矩阵,.shape[0]返回n,.shape[1]返回m,.shape返回n,m 13 dataSetSize = dataSet.shape[0] #返回训练集的行数 14 #tile是numpy中的函数,功能是重复:tile(inx,(n,m))按行重复n遍,按列重复m遍。 15 diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #执行待分类数据与训练数组相减操作,(xi-xj),(yi-yj) 16 #二维特征相减后平方 17 sqDiffMat = diffMat**2 18 #sum()所有元素相加,sum(0)列相加,sum(1)行相加 19 sqDistances = sqDiffMat.sum(axis=1) #(axis=1)是行相加,(axis=0)是列相加 20 #开方,计算出距离,距离是欧氏距离 21 distances = sqDistances**0.5 22 #argsort()是numpy中的函数 23 sortedDistIndices = distances.argsort() #返回distances中元素从小到大排序后的索引值 24 #定一个记录类别次数的字典 25 classCount = {} 26 for i in range(k): 27 #取出前k个元素的类别 28 voteIlabel = labels[sortedDistIndices[i]] 29 #dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值,该处设置默认值为0。 30 #计算类别次数 31 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 32 #字典的items方法返回可遍历的元组数组 33 #key=operator.itemgetter(1)根据字典的值进行排序 34 #key=operator.itemgetter(0)根据字典的键进行排序 35 #reverse降序排序字典,设置为True是降序,False为升序 36 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) 37 #返回次数最多的类别,即所要分类的类别 38 return sortedClassCount[0][0]
按照书上,定义训练集为(1.0, 1.1) (1.0, 1.0) (0.0, 0.0) (0.0, 0.1)
1 def creatDataset(): 2 group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) 3 lables = [\'A\',\'A\',\'B\',\'B\'] 4 return group,lables
用knn对一个未知点进行分类的完整python实现
from numpy import * from matplotlib import * import operator
import numpy as np import matplotlib.pyplot as plt """ 实现:有类A和类B的点,对一个未知点进行分类 """ def creatDataset(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) lables = [\'A\',\'A\',\'B\',\'B\'] return group,lables """ 函数说明:kNN算法,分类器 Parameters: inX - 用于分类的数据(测试集) dataSet - 用于训练的数据(训练集) labes - 分类标签 k - kNN算法参数,选择距离最小的k个点 Returns: sortedClassCount[0][0] - 分类结果 """ def classify0(inX, dataSet, labels, k): #如dataSet为n*m的矩阵,.shape[0]返回n,.shape[1]返回m,.shape返回n,m dataSetSize = dataSet.shape[0] #返回训练集的行数 #tile是numpy中的函数,功能是重复:tile(inx,(n,m))按行重复n遍,按列重复m遍。 diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet #执行待分类数据与训练数组相减操作,(xi-xj),(yi-yj) #二维特征相减后平方 sqDiffMat = diffMat**2 #sum()所有元素相加,sum(0)列相加,sum(1)行相加 sqDistances = sqDiffMat.sum(axis=1) #(axis=1)是行相加,(axis=0)是列相加 #开方,计算出距离 distances = sqDistances**0.5 #argsort()是numpy中的函数 sortedDistIndices = distances.argsort() #返回distances中元素从小到大排序后的索引值 #定一个记录类别次数的字典 classCount = {} for i in range(k): #取出前k个元素的类别 voteIlabel = labels[sortedDistIndices[i]] #dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值,该处设置默认值为0。 #计算类别次数 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #字典的items方法返回可遍历的元组数组 #key=operator.itemgetter(1)根据字典的值进行排序 #key=operator.itemgetter(0)根据字典的键进行排序 #reverse降序排序字典,设置为True是降序,False为升序 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #返回次数最多的类别,即所要分类的类别 return sortedClassCount[0][0] group,lables=creatDataset() #画出点的分布 plt.plot(group[:,0],group[:,1],\'ro\',label="point") plt.ylim(-0.2,1.2) plt.xlim(-0.2,1.2) plt.show() #测试[0,0]所属类别 print(classify0([0,0],group,lables,3))
分类结果为:B
示例:使用k-近邻算法改进约会网站的配对效果
海伦女士一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人。经过一番总结,她发现自己交往过的人可以进行如下分类:
- 不喜欢的人
- 魅力一般的人
- 极具魅力的人
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。
海伦收集的样本数据主要包含以下3种特征:
- 每年获得的飞行常客里程数
- 玩视频游戏所消耗时间百分比
- 每周消费的冰淇淋公升数
第一步:准备数据-从文本文件中解析数据
python代码:
1 # -*- coding: UTF-8 -*- 2 import numpy as np 3 """ 4 函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力 5 6 Parameters: 7 filename - 文件名 8 Returns: 9 returnMat - 特征矩阵 10 classLabelVector - 分类Label向量 11 """ 12 def file2matrix(filename): 13 fr = open(filename) #打开文件 14 #读取文件所有内容 15 arrayOLines = fr.readlines() #readlines()是读取整个文件,将文件内容分析成一个行的列表,该列表可由for..in处理 16 numberOfLines = len(arrayOLines) #得到文件行数 17 returnMat = np.zeros((numberOfLines,3)) #返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列 18 classLabelVector = [] #返回的分类标签向量 19 index = 0 #行的索引值 20 for line in arrayOLines: 21 #s.strip(rm),当rm空时,默认删除空白符(包括\'\\n\',\'\\r\',\'\\t\',\' \') 22 line = line.strip() #strip()用于移除字符串头尾指定的字符(默认为空格) 23 listFromLine = line.split(\'\\t\') #split()通过指定分隔符对字符串切片,返回分割后的字符串列表(list) 24 returnMat[index,:] = listFromLine[0:3] #将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵 25 #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力 26 if listFromLine[-1] == \'didntLike\': 27 classLabelVector.append(1) 28 elif listFromLine[-1] == \'smallDoses\': 29 classLabelVector.append(2) 30 elif listFromLine[-1] == \'largeDoses\': 31 classLabelVector.append(3) 32 index += 1 33 return returnMat, classLabelVector 34 35 """ 36 函数说明:main函数 37 38 """ 39 if __name__ == \'__main__\': 40 filename = "datingTestSet.txt" #打开的文件名 41 datingDataMat, datingLabels = file2matrix(filename) 42 print(datingDataMat) 43 print(datingLabels)
执行结果(部分截图)为:
顺利导入了数据,并对数据进行解析,格式化为分类器需要的数据格式。接着我们需要了解数据的真正含义。可以通过友好、直观的图形化的方式观察数据。
第二步:分析数据-使用Matplotlib创建散点图
# -*- coding: UTF-8 -*- from matplotlib.font_manager import FontProperties #matplotlib默认不支持中文,python调用系统自带字体 import matplotlib.lines as mlines import matplotlib.pyplot as plt import numpy as np """ 函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力 Parameters: filename - 文件名 Returns: returnMat - 特征矩阵 classLabelVector - 分类Label向量 """ def file2matrix(filename): #打开文件 fr = open(filename) #读取文件所有内容 arrayOLines = fr.readlines() #得到文件行数 numberOfLines = len(arrayOLines) #返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列 returnMat = np.zeros((numberOfLines,3)) #返回的分类标签向量 classLabelVector = [] #行的索引值 index = 0 for line in arrayOLines: #s.strip(rm),当rm空时,默认删除空白符(包括\'\\n\',\'\\r\',\'\\t\',\' \') line = line.strip() #使用s.split(str="",num=string,cout(str))将字符串根据\'\\t\'分隔符进行切片。 listFromLine = line.split(\'\\t\') #将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵 returnMat[index,:] = listFromLine[0:3] #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力 if listFromLine[-1] == \'didntLike\': classLabelVector.append(1) elif listFromLine[-1] == \'smallDoses\': classLabelVector.append(2) elif listFromLine[-1] == \'largeDoses\': classLabelVector.append(3) index += 1 return returnMat, classLabelVector """ 函数说明:可视化数据 Parameters: datingDataMat - 特征矩阵 datingLabels - 分类Label """ def showdatas(datingDataMat, datingLabels): # 设置汉字格式,python调用系统自带字体,路径是:"C:\\Windows\\Fonts"(如果系统不在C盘,请切换盘符),使用 # 字体类型是"simsun.ttc"是中文简体 font = FontProperties(fname=r"c:\\windows\\fonts\\simsun.ttc", size=14) #将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8) #当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域 fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8)) numberOfLabels = len(datingLabels) LabelsColors = [] for i in datingLabels: if i == 1: LabelsColors.append(\'black\') if i == 2: LabelsColors.append(\'orange\') if i == 3: LabelsColors.append(\'red\') #画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5 axs[0][0].scatter(x=datingDataMat[:,0], y=datingDataMat[:,1], color=LabelsColors,s=15, alpha=.5) #设置标题,x轴label,y轴label axs0_title_text = axs[0][0].set_title(u\'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比\',FontProperties=font) axs0_xlabel_text = axs[0][0].set_xlabel(u\'每年获得的飞行常客里程数\',FontProperties=font) axs0_ylabel_text = axs[0][0].set_ylabel(u\'玩视频游戏所消耗时间占\',FontProperties=font) plt.setp(axs0_title_text, size=9, weight=\'bold\', color=\'red\') plt.setp(axs0_xlabel_text, size=7, weight=\'bold\', color=\'black\') plt.setp(axs0_ylabel_text, size=7, weight=\'bold\', color=\'black\') #画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5 axs[0][1].scatter(x=datingDataMat[:,0], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5) #设置标题,x轴label,y轴label axs1_title_text = axs[0][1].set_title(u\'每年获得的飞行常客里程数与每周消费的冰激淋公升数\',FontProperties=font) axs1_xlabel_text = axs[0][1].set_xlabel(u\'每年获得的飞行常客里程数\',FontProperties=font) axs1_ylabel_text = axs[0][1].set_ylabel(u\'每周消费的冰激淋公升数\',FontProperties=font) plt.setp(axs1_title_text, size=9, weight=\'bold\', color=\'red\') plt.setp(axs1_xlabel_text, size=7, weight=\'bold\', color=\'black\') plt.setp(axs1_ylabel_text, size=7, weight=\'bold\', color=\'black\') #画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5 axs[1][0].scatter(x=datingDataMat[:,1], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5) #设置标题,x轴label,y轴label axs2_title_text = axs[1][0].set_title(u\'玩视频游戏所消耗时间占比与每周消费的冰激淋公升数\',FontProperties=font) axs2_xlabel_text = axs[1][0].set_xlabel(u\'玩视频游戏所消耗时间占比\',FontProperties=font) axs2_ylabel_text = axs[1][0].set_ylabel(u\'每周消费的冰激淋公升数\',FontProperties=font) plt.setp(axs2_title_text, size=9, weight=\'bold\', color=\'red\') plt.setp(axs2_xlabel_text, size=7, weight=\'bold\', color=\'black\') plt.setp(axs2_ylabel_text, size=7, weight=\'bold\', color=\'black\') #设置图例 didntLike = mlines.Line2D([], [], color=\'black\', marker=\'.\', markersize=6, label=\'didntLike\') smallDoses = mlines.Line2D([], [], color=\'orange\', marker=\'.\', markersize=6, label=\'smallDoses\') largeDoses = mlines.Line2D([], [], color=\'red\', marker=\'.\', markersize=6, label=\'largeDoses\') #添加图例 axs[0][0].legend(handles=[didntLike,smallDoses,largeDoses]) axs[0][1].legend(handles=[didntLike,smallDoses,largeDoses]) axs[1][0].legend(handles=[didntLike,smallDoses,largeDoses]) #显示图片 plt.show() """ 函数说明:main函数 """ if __name__ == \'__main__\': #打开的文件名 filename = "datingTestSet.txt" #打开并处理数据 datingDataMat, datingLabels = file2matrix(filename) showdatas(datingDataMat, datingLabels)
第三步:准备数据-归一化数值
样本 | 玩游戏所耗时间百分比 | 每年获得的飞行常用里程数 | 每周消费的冰淇淋公升数 | 样本分类 |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 0.1 | 2 |
若计算样本3和4的距离,用
但是第二个参数对结果的影响太大,不合适。
因此,在处理不同取值范围的特征值时,通常采用的方法是将数值归一化,如将取值范围处理到0到1或者-1到1之间。下面公式可转化为0到1区间的值:
newValue = (oldValue - min) / (max - min)
其中,min和max分别是数据集中的最小特征值和最大特征值。
1 """ 2 函数说明:对数据进行归一化 3 4 Parameters: 5 dataSet - 特征矩阵 6 Returns: 7 normDataSet - 归一化后的特征矩阵 8 ranges - 数据范围 9 minVals - 数据最小值 10 11 """ 12 def autoNorm(dataSet): 13 #获得数据的最小值 14 minVals = dataSet.min(0) 15 maxVals = dataSet.max(0) 16 #最大值和最小值的范围 17 ranges = maxVals - minVals 18 #shape(dataSet)返回dataSet的矩阵行列数 19 normDataSet = np.zeros(np.shape(dataSet)) 20 #返回dataSet的行数 21 m = dataSet.shape[0] 22 #原始值减去最小值 23 normDataSet = dataSet - np.tile(minVals, (m, 1)) 24 #除以最大和最小值的差,得到归一化数据 25 normDataSet = normDataSet / np.tile(ranges, (m, 1)) 26 #返回归一化数据结果,数据范围,最小值 27 return normDataSet, ranges, minVals
测试算法:作为完整程序验证分类器
机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。对于分类器来说,错误率就是分类器给出错误结果的次数除以测试数据的总数。
""" 函数说明:分类器测试函数 Returns: normDataSet - 归一化后的特征矩阵 ranges - 数据范围 minVals - 数据最小值 """ def datingClassTest(): #打开的文件名 filename = "datingTestSet.txt" #将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中 datingDataMat, datingLabels = file2matrix(filename) #取所有数据的百分之十 hoRatio = 0.10 #数据归一化,返回归一化后的矩阵,数据范围,数据最小值 normMat, ranges, minVals = autoNorm(datingDataMat) #获得normMat的行数 m = normMat.shape[0] #百分之十的测试数据的个数 numTestVecs = int(m * hoRatio) #分类错误计数 errorCount = 0.0 for i in range(numTestVecs): #前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集 classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 4) print("分类结果:%d\\t真实类别:%d" % (classifierResult, datingLabels[i])) if classifierResult != datingLabels[i]: errorCount += 1.0 print("错误率:%f%%" %(errorCount/float(numTestVecs)*100)) """
使用算法:构建完整可用算法
1 """ 2 函数说明:通过输入一个人的三维特征,进行分类输出 3 4 """ 5 def classifyPerson(): 6 #输出结果 7 resultList = [\'讨厌\',\'有些喜欢\',\'非常喜欢\'] 8 #三维特征用户输入 9 precentTats = float(input("玩视频游戏所耗时间百分比:")) 10 ffMiles = float(input("每年获得的飞行常客里程数:")) 11 iceCream = float(input("每周消费的冰激淋公升数:")) 12 #打开的文件名 13 filename = "datingTestSet.txt" 14 #打开并处理数据 15 datingDataMat, datingLabels = file2matrix(filename) 16 #训练集归一化 17 normMat, ranges, minVals = autoNorm(datingDataMat) 18 #生成NumPy数组,测试集 19 inArr = np.array([precentTats, ffMiles, iceCream]) 20 #测试集归一化 21 norminArr = (inArr - minVals) / ranges 22 #返回分类结果 23 classifierResult = classify0(norminArr, normMat, datingLabels, 3) 24 #打印结果 25 print("你可能%s这个人" % (resultList[classifierResult-1]))
完整实现:
1 # -*- coding: UTF-8 -*- 2 3 import numpy as np 4 import operator 5 6 """ 7 函数说明:kNN算法,分类器 8 9 Parameters: 10 inX - 用于分类的数据(测试集) 11 dataSet - 用于训练的数据(训练集) 12 labes - 分类标签 13 k - kNN算法参数,选择距离最小的k个点 14 Returns: 15 sortedClassCount[0][0] - 分类结果 16 17 """ 18 def classify0(inX, dataSet, labels, k): 19 #numpy函数shape[0]返回dataSet的行数 20 dataSetSize = dataSet.shape[0] 21 #在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向) 22 diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet 23 #二维特征相减后平方 24 sqDiffMat = diffMat**2 25 #sum()所有元素相加,sum(0)列相加,sum(1)行相加 26 sqDistances = sqDiffMat.sum(axis=1) 27 #开方,计算出距离 28 distances = sqDistances**0.5 29 #返回distances中元素从小到大排序后的索引值 30 sortedDistIndices = distances.argsort() 31 #定一个记录类别次数的字典 32 classCount = {} 33 for i in range(k): 34 #取出前k个元素的类别 35 voteIlabel = labels[sortedDistIndices[i]] 36 #dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。 37 #计算类别次数以上是关于KNN的主要内容,如果未能解决你的问题,请参考以下文章