机器学习实战第一课——KNN近邻算法
纸上得来终觉浅,绝知此事要躬行
距离我“入门”机器已经半年了,之所以是假入门,是因为平时课程实在是太繁重了,刷均分,赶作业,完全顾不上认真学习。因为渐渐的觉得每周跟的组会上面研究生学长分享的那些东西听不太懂,是为基础过于薄弱,同时又进入了一个数据挖掘的课题组,不能当个半吊子(知识点懂一半,代码不会写)啥都不懂,所以打算趁着寒假时间还算充足,把《机器学习实战》上的算法都实现一遍(主要代码与内容学习还是参考apachecn的github所得,因为无商业利益,就不告知了)。
今天复现的是K-近邻算法,是所有机器学习算法中最简单的之一[wiki],主要思想是求出测试数据与全体训练数据集之间的欧氏距离,然后对欧氏距离进行排序,返回前k个距离最短的样本数据集,这k个数据项中出现次数最多的标签即为测试数据的预测标签。为防止各种标签在k个最短距离数据项中只出现一次,k应该大于标签类别数(我瞎猜的,应该是这样)
我的python基本上处于一个入门阶段,今天实操一遍,收获颇丰,也深感能力之不足,同志仍需努力
KNN没有训练过程,只有一次一次的计算与比较,所以说它的空间复杂度与计算复杂度都很高,下面是用python复现的代码
总的代码如下
1 from __future__ import print_function 2 from numpy import * 3 import operator 4 from os import listdir 5 from collections import Counter 6 7 fr=open("E:\MachineLearning\KNN\dataset\datingTestSet2.txt") 8 9 numberOfLines=len(fr.readlines()) 10 fr.seek(0) 11 returnMat=zeros((numberOfLines,3)) 12 classLabelVector=[] 13 index=0 14 for line in fr.readlines(): 15 line=line.strip() 16 listFromLine=line.split('\t') 17 returnMat[index,:]=listFromLine[0:3] 18 classLabelVector.append(int(listFromLine[-1])) # classLabelVector 与 returnMat 一一对应,前者为所属标签,后者围殴具体数据 19 index+=1 20 21 import matplotlib 22 import matplotlib.pyplot as plt 23 24 labels=classLabelVector 25 26 plt.figure(figsize=(8, 5), dpi=80) 27 axes = plt.subplot(111) 28 # 将三类数据分别取出来 29 # x轴代表飞行的里程数 30 # y轴代表玩视频游戏的百分比 31 type1_x = [] 32 type1_y = [] 33 type2_x = [] 34 type2_y = [] 35 type3_x = [] 36 type3_y = [] 37 38 for i in range(len(labels)): 39 if labels[i] == 1: # 不喜欢 40 type1_x.append(returnMat[i][0]) 41 type1_y.append(returnMat[i][1]) 42 43 if labels[i] == 2: # 魅力一般 44 type2_x.append(returnMat[i][0]) 45 type2_y.append(returnMat[i][1]) 46 47 if labels[i] == 3: # 极具魅力 48 type3_x.append(returnMat[i][0]) 49 type3_y.append(returnMat[i][1]) 50 51 type1 = axes.scatter(type1_x, type1_y, s=20, c='red') 52 type2 = axes.scatter(type2_x, type2_y, s=40, c='green') 53 type3 = axes.scatter(type3_x, type3_y, s=50, c='blue') 54 55 plt.xlabel('Miles earned per year') 56 plt.ylabel('Percentage of events spent playing video games') 57 axes.legend((type1, type2, type3), (u'dislike', u'Charismatic', u'Very attractive'), loc=2,) 58 59 plt.show() 60 61 """ 62 下面进行归一化操作 63 公式:线性转换: newValue=(oldValue-min)/(max-min) max和min为该组数据集中的最大最小特征值 64 对数转换: y=log10(x) 65 反余切转换:y=arctan(x)*2/PI 66 """ 67 68 normMat=zeros((numberOfLines,3)) 69 for i in [0,1,2]: 70 max=returnMat[:,i].max() 71 min=returnMat[:,i].min() 72 for j in range(1000): 73 normMat[j,i]=(returnMat[j,i]-min)/(max-min) 74 # done 75 76 """ 77 对于每一个在数据集中的数据点: 78 计算目标的数据点(需要分类的数据点)与该数据点的距离 79 将距离排序:从小到大 80 选取前K个最短距离 81 选取这K个中最多的分类类别 82 返回该类别来作为目标数据点的预测值 83 """ 84 85 inx=[0.40166314, 0.56719748, 0.52034602] # 作为输入的样本 86 dataSetSize=normMat.shape[0] 87 diffMat = tile(inx, (dataSetSize,1))-normMat 88 sqDiffMat = diffMat**2 89 distances=(sqDiffMat.sum(axis=1))**0.5 90 sortedDistIndicies = distances.argsort() # argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引号),所以并不会影响对应的label的值 91 # k取5 92 classCount={} 93 for i in range(5): 94 voteIlabel = labels[sortedDistIndicies[i]] 95 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 96 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
之所以折叠是想要分模块复习一下知识点,不让整体代码占了地方
准备模块:
1 from __future__ import print_function 2 from numpy import * 3 import operator 4 from os import listdir 5 from collections import Counter 6 import matplotlib 7 import matplotlib.pyplot as plt
- __future__模块可以引用未来版本python库中的函数,其实我的版本已经是3.x,所以没有必要走这一步
- 将numpy导入有多种方法,一般常用import numpy as np,这里的这种方法可以不加前缀直接调用numpy中的函数如zeros,这种方式类似于
数据读取模块
1 fr=open("E:\MachineLearning\KNN\dataset\datingTestSet2.txt") 2 numberOfLines=len(fr.readlines()) 3 fr.seek(0) 4 returnMat=zeros((numberOfLines,3)) 5 classLabelVector=[] 6 index=0 7 for line in fr.readlines(): 8 line=line.strip() 9 listFromLine=line.split('\t') 10 returnMat[index,:]=listFromLine[0:3] 11 classLabelVector.append(int(listFromLine[-1])) # classLabelVector 与 returnMat 一一对应,前者为所属标签,后者为具体数据 12 index+=1
这一部分呢,强调一下文件的读取,如今数据挖掘与数据分析方面更常用pandas,不过目前还不需要那么高深的功能,以后再用。
readlines()会按行读取数据(读取所有行并返回list),读取完毕之后指向最后一个字符,所以如果需要重新读取,要用seek(0)指向初始位置
接下来strip()函数可以删去str的头或尾的换行符或者空格或者,指定字符(需指明参数)
split()函数指定分隔符对str进行切片,在本代码中将str分成四个list,前三个为数据,存入returnMat中,最后一个是标签,用append()方法存入classLabelVector中
returnMat的数据结构为array,矩阵结构在存入数据时可以利用index索引,了解的不是很全,目前只知道这一个用法,以后再分析
可视化模块
1 labels=classLabelVector 2 3 plt.figure(figsize=(8, 5), dpi=80) 4 axes = plt.subplot(111) 5 # 将三类数据分别取出来 6 # x轴代表飞行的里程数 7 # y轴代表玩视频游戏的百分比 8 type1_x = [] 9 type1_y = [] 10 type2_x = [] 11 type2_y = [] 12 type3_x = [] 13 type3_y = [] 14 15 for i in range(len(labels)): 16 if labels[i] == 1: # 不喜欢 17 type1_x.append(returnMat[i][0]) 18 type1_y.append(returnMat[i][1]) 19 20 if labels[i] == 2: # 魅力一般 21 type2_x.append(returnMat[i][0]) 22 type2_y.append(returnMat[i][1]) 23 24 if labels[i] == 3: # 极具魅力 25 type3_x.append(returnMat[i][0]) 26 type3_y.append(returnMat[i][1]) 27 28 type1 = axes.scatter(type1_x, type1_y, s=20, c='red') 29 type2 = axes.scatter(type2_x, type2_y, s=40, c='green') 30 type3 = axes.scatter(type3_x, type3_y, s=50, c='blue') 31 32 plt.xlabel('Miles earned per year') 33 plt.ylabel('Percentage of events spent playing video games') 34 axes.legend((type1, type2, type3), (u'dislike', u'Charismatic', u'Very attractive'), loc=2,) 35 36 plt.show()
为了让画出来的图好看,指定不同标签不同颜色,所以画点的时候分三次,得到的图如下所示

归一化
1 normMat=zeros((numberOfLines,3)) 2 for i in [0,1,2]: 3 max=returnMat[:,i].max() 4 min=returnMat[:,i].min() 5 for j in range(1000): 6 normMat[j,i]=(returnMat[j,i]-min)/(max-min) 7 # done
归一化是将所有数据都映射到一个固定的区间之中,上述方法是在[0,1]区间之间,这么做的目的是使各类数据的权重保持一致,保证某一类数据的影响不会强于其他
常见归一化方法有
- 线性转换: newValue=(oldValue-min)/(max-min) max和min为该组数据集中的最大最小特征值
- 对数转换: y=log10(x)
- 反余切转换:y=arctan(x)*2/PI
KNN算法部分
1 inx=[0.40166314, 0.56719748, 0.52034602] # 作为输入的样本 2 dataSetSize=normMat.shape[0] 3 diffMat = tile(inx, (dataSetSize,1))-normMat 4 sqDiffMat = diffMat**2 5 distances=(sqDiffMat.sum(axis=1))**0.5 6 sortedDistIndicies = distances.argsort() # argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引号),所以并不会影响对应的label的值 7 # k取5 8 classCount={} 9 for i in range(5): 10 voteIlabel = labels[sortedDistIndicies[i]] 11 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 12 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
其实到了这一部分发现难度反而没有前面的高,可能是因为这个算法比较简单
值得注意的点有
- tile()函数用来对数组进行复制第二个参数(x,y)分别表示行复制多少次,列复制多少次
- argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引号),所以并不会影响对应的label的值
- classCount是一个构造字典的过程,对字典还不太熟,但今天的一天的已经结束了,没精力再去深究了,不干了,睡大觉
第一天的工作,over!
DAY 2 对KNN算法进行一个总结(来自apachecn上的小结,在此做一个备份):
近似误差与估计误差
近似误差:可以理解为对现有训练集的训练误差。
估计误差:可以理解为对测试集的测试误差。
近似误差关注训练集,如果近似误差小了会出现过拟合的现象,对现有的训练集能有很好的预测,但是对未知的测试样本将会出现较大偏差的预测。模型本身不是最接近最佳模型。
估计误差关注测试集,估计误差小了说明对未知数据的预测能力好。模型本身最接近最佳模型。
推荐使用交叉验证[知乎][wiki][CSDN]选取合适的k值(交叉验证是一种评估统计分析、机器学习算法对独立于训练数据的数据集的泛化能力)
距离度量 Metric/Distance Measure
度量距离除了本文中用的欧氏距离之外,还可以是Minkowski距离或者曼哈顿距离(更多细节可以参看 sklearn 中 valid_metric 部分)
算法
算法:(sklearn 上有三种)
Brute Force 暴力计算/线性扫描(数据结构中学过的BF算法,暴力模式匹配)
KD Tree 使用二叉树根据数据维度来平分参数空间[博客园]。对高维数据处理时经常会用到
Ball Tree 使用一系列的超球体来平分训练数据集。(Ball Tree了解的不是很多)

浙公网安备 33010602011771号