机器学习第一篇——最近邻kNN

时间:2022-05-16 09:23:08

机器学习监督学习中,根据解决问题的连续性和离散型,分为分类问题和回归问题。最邻近算法kNN是一种最为直接和简便的分类方法。

kNN本质上,是计算目标到模型的欧式距离,从而判定目标所属的类别。

首先,在解决机器学习问题的时候,我们首先,其实面对这样一个问题:对数据的清洗。因为通常的,我们的程序设计语言,只能处理诸如数组,矩阵,字符,以及其他我们在程序设计中常见的一些数据类型。而通常的,我们手中的数据都是以文件的格式给出。比如.TXT格式的。

所以,首先第一步:完成数据类型的转换:

下面给出一段python 代码,旨在说明其中的思想:

 def file2matrix(filename):
fr = open(filename) #打开文件
array0Lines = fr.readlines()#读取文件的内容(实际上,查看array0lines的内容时,发现文件的每一行,为一个字符串,且字符串中包含了“\t(制表位,就是对其的那个东西)”,“\n”)
number0fLines = len(array0Lines)#获取行数
returnMat = zeros((number0fLines,3))创造一个行数×3的矩阵
classLabelVector = []创建一个空的列表,注意这里只是列表
index = 0
for line in array0Lines: 开始完成数据清洗
line = line.strip()#去掉"\n",但此时制表位还在,数据格式仍然是我们处理不了的,如123     0.324   2.12(数据与数据的制表位仍然在)
listFromLine = line.split('\t')#去掉制表位(将每个数据转化为列表式数据),如['123', '0.324', '2.12'](很明显,变成了列表数据,列表数据是字符串,是我们可以处理的)
returnMat[index,:] = listFromLine[0:3]#将转化好的列表前三个数拷贝到矩阵每一行里面(主要疑问在于为什么字符串类型变成了浮点型(还是自己理解错误))
classLabelVector.append(int(listFromLine[-1]))#将listFromLine列表最后一个数据先转化为整型(请记住文件中数据为字符型),再存储到classLabelVector列表中
 index +=1  return returnMat,classLabelVector

上述Python代码的输入文件是一个n乘以4的“矩阵”数据(在文件中看起来像矩阵,但是需要转化成真正能够处理的数据类型),前三列为一组数据,最后一列为另一组数据。很明显,我们将前三列数据转化成了矩阵,最后一列数据保存成了列表。

数据转化成了可以处理的数据,前三列的数据分别表示三个不同的参考指标。但是每个指标的量纲不一样,数量级不一样,比如第一列的数据范围是[0,1],第二列数据范围是[0,10000],第三列数据范围是[100,1000].前面说过,kNN本质上要计算欧式距离,那么这里为:sqrt((x-x1)2+(x-x2)2+(x-x3)2),但是面临这样一个问题:比如x2范围在[0,10000],很明显x2对结果的影响是很大的,从而降低了其他参考量的影响因子,但很多时候,我们需要这些影响因子具有相同的权重,甚至我们给定不同的参考因子一个不同的权重。因此,我们通常需要对数据进行归一化的处理。

下面给出Python代码:

 def autoNorm(dataSet):
minvals = dataSet.min(0)#min(0)获取列最小值,min(1)获取行最小值
maxvals = dataSet.max(0)
ranges = maxvals-minvals
normDataSet = zeros(shape(dataSet))#shape()返回矩阵的行数和列数即(行,列)
m = dataSet.shape[0]#我们要理解这种打点的操作,对dataSet进行shape 操作取其第一个元素,即我们获取矩阵的行数信息(也可以用shape(dataSet)[0]这种用法)
normDataSet = dataSet-tile(minvals,(m,1))#tile的作用是复制,将minval数组复制成m行×1列的,这样是为了完成对应相减
normDataSet = normDataSet/tile(ranges,(m,1))#tile 的作用是相同的
return normDataSet,ranges,minvals #返回归一化后的数据矩阵,取值范围,最小值

上述两个过程,完成了对数据的基本清洗工作。

在完成数据分类之前,应该先构建一个数据分类器。这个分类器是分类的核心,Python代码如下:

 def classify0(inx,dataSet,labels,k):#inx 是我们要进行分类的数据,dataSet是知道类别标签的数据,labels是dataSet的类别标签。k是选择最邻近的数目
dataSetSize = dataSet.shape[0]#获取行数
diffMat = tile(inx,(dataSetSize,1))-dataSet#将要分类的数据复制成dataSetSize行×1列,从而保证相减的时候维数相等
sqDiffMat = diffMat**2#平方运算
sqDistances = sqDiffMat.sum(axis=1).sum是numpy模块中的函数,sum(axis=1)表示二维数组按照行相加,sum(axis=0)表示按列相加,很明显这里是按照行相加
distances = sqDistances**0.5#开根运算
sortedDistIndicies = distances.argsort()#.argsort()将数组从小到大排列,并返回其索引。比如x=([3,1,2]),则返回[1,2,0]
classCout = {}#定义字典

for i in range(k):
            voteIlabel = labels[sortedDistIndicies[i]]#返回前K个比较小的索引对应的分类
            classCout[voteIlabel] = classCout.get(voteIlabel,0)+1#?????????????????????????
        sortedClassCout = sorted(classCout.items(),key = operator.itemgetter(1),reverse = True)#????????????????????/
        return sortedClassCout[0][0]

完成分类器的构造之后,需要完成的是,对数据测试,即从数据中选出一定的数据建立模型,剩下的数据训练模型的出错率,Python代码如下:

 def datingClassTest():
horatio = 0.1#选定10%的数据作为检测数据
datingDataMat,datingLables = file2matrix('datingTestSet2.txt')
normMat,ranges,minvals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*horatio)#得到检测数据的数目
errorcount = 0.0
for i in range(numTestVecs):
classifirerResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLables[numTestVecs:m],3)
print("the classifier came back with:%d,the real answer is: %d"%(classifirerResult,datingLables[i]))
if (classifirerResult != datingLables[i]):
errorcount +=1.0#如果分类出错,分类计数加1
print("the total error rate is: %f"%(errorcount/float(numTestVecs)))#计算分类错误率

以上过程,完成了对数据完成kNN最邻近方法分类模型训练的全部过程。

在这其中,强调这么几点:

1上述频繁使用矩阵计算,使得程序和计算变的更加简单,但矩阵并不是python自带的数据结构,因此numpy在数学计算中是一个十分重要的python库

2 列表是[],数组是([]),矩阵是([],[]),不要把列表当成了数组。

3 python 是解释性语言,因此比如我们在某一个程序文件中用到kNN.py文件中的函数。我们在开头处使用了import kNN。在kNN文件中我们使用from numpy import *,在kNN文件中导入了numpy库,假如我们想在当前文件中使用numpy中的矩阵运算,并不能因为当前文件包含了kNN文件,而kNN文件中导入了numpy库,我们就认为当前文件导入了numpy。因为python 是解释性语言,因此在运行某个矩阵运算的时候,并不能找到该矩阵运算的来源,因此,我们除了要写导入了kNN,也要写重新写一句from numpy import *