K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
K 近邻算法使用的模型实际上对应于对特征空间的划分。K 值的选择,距离度量和分类决策规则是该算法的三个基本要素:
- K 值的选择会对算法的结果产生重大影响。K值较小意味着只有与输入实例较近的训练实例才会对预测结果起作用,但容易发生过拟合;如果 K 值较大,优点是可以减少学习的估计误差,但缺点是学习的近似误差增大,这时与输入实例较远的训练实例也会对预测起作用,使预测发生错误。在实际应用中,K 值一般选择一个较小的数值,通常采用交叉验证的方法来选择最优的 K 值。随着训练实例数目趋向于无穷和 K=1 时,误差率不会超过贝叶斯误差率的2倍,如果K也趋向于无穷,则误差率趋向于贝叶斯误差率。
- 该算法中的分类决策规则往往是多数表决,即由输入实例的 K 个最临近的训练实例中的多数类决定输入实例的类别
- 距离度量一般采用 Lp 距离,当p=2时,即为欧氏距离,在度量之前,应该将每个属性的值规范化,这样有助于防止具有较大初始值域的属性比具有较小初始值域的属性的权重过大。
# -*- coding:utf-8 -*-
from numpy import *
import operator
def createDataset():
group=array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels=['A','A','B','B']
# print group.shape
# print type(group)
# print type ( labels )
return group , labels
group,labels=createDataset()
# print group
# print labels
def classify0(inX,dataSet,labels,k):
dataSetSize=dataSet.shape[0] #矩阵的行数
diffMat=tile(inX,(dataSetSize,1))-dataSet#inx 构建成一个dataSetSize行 1列的array (目的是将dataSet全加一个负号)
sqDiffMat=diffMat**2 #diffMat矩阵平方
sqDistances=sqDiffMat.sum(axis=1) #对数组的每一列进行相加
distance=sqDistances**0.5
sortedDistIndicies=distance.argsort()#distance的索引进行排序
classCount={}#字典
for i in range(k):
voteIlable=labels[sortedDistIndicies[i]]
classCount[voteIlable]=classCount.get(voteIlable,0)+1 #classCount.get(voteIlabel,0)返回字典classCount中voteIlabel元素对应的值,若无,则进行初始化
#{'A': 1, 'B': 2}
sortClassCount=sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True)
# classCount.iteritems () 作用是将字典classCount分解为元组列表[('B', 2), ('A', 1)]
# operator.itemgetter ( 1 ) 定义函数b,获取对象的第1个域的值 reverse=True 降序
print sortClassCount
# print voteIlable
# print i
print classCount
print sortClassCount[0][0]
return sortClassCount[0][0]
# print distance
# print sqDistances
# print(sqDiffMat)
# print diffMat
# classify0([0,0],group,labels,4)
def file2Matrix(filename):
love_dictionary = {'largeDoses': 3, 'smallDoses': 2, 'didntLike': 1}
fr=open(filename)
arrayOflines=fr.readlines ()
# print arrayOflines
returnMat=zeros((len(arrayOflines),3))
index=0
classLabelVector=[]
# print returnMat.shape
for line in arrayOflines:
# s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
listFromLine=line.strip().split('\t')
# print(listFromLine)
# print listFromLine[0:3]
# 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
returnMat[index,:]=listFromLine[0:3] #需要思考一下
# print type ( returnMat )
# returnMat = listFromLine[0:3]
# print returnMat[index,:].shape
# print int(listFromLine[-1])
# 根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
if listFromLine[-1].isdigit():
classLabelVector.append(int(listFromLine[-1]))
else :
classLabelVector.append (love_dictionary.get(listFromLine[-1]) )
# print classLabelVector
index += 1
return returnMat,classLabelVector
returnMat,classLabelVector=file2Matrix('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\datingTestSet.txt')
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
# ax.scatter(returnMat[:,1],returnMat[:,2],25.0*array(classLabelVector),25.0*array(classLabelVector))
ax.scatter(returnMat[:,0],returnMat[:,1],25.0*array(classLabelVector),25.0*array(classLabelVector),label='gini')
# plt.xlabel(u"玩视频游戏所耗时间百分比")
# plt.ylabel(u"每周消费的冰淇凌公升数")
# ax.legend((1, 2, 3), (u'不喜欢', u'魅力一般', u'极具魅力'), loc=2)
plt.xlabel(u"每年获取的飞行常客里程数")
plt.ylabel(u"玩视频游戏所耗时间百分比")
plt.legend('x1')
# plt.legend("upper right")
plt.show()
# print mat(returnMat).shape
# print returnMat
###################
#归一化特征值
def autoNorm(dataSet):
#获得数据的最小值
minVals=dataSet.min(0)
# 获得数据的最大值
maxVals=dataSet.max(0)
# 最大值和最小值的范围
ranges=maxVals-minVals
# shape(dataSet)返回dataSet的矩阵行列数
normDataSet=zeros(shape(dataSet))
m=dataSet.shape[0]
# 返回dataSet的行数
# a=tile(minVals,(m,1))
# print normDataSet.shape
# 除以最大和最小值的差, 得到归一化数据
normDataSet=dataSet-tile(minVals,(m,1))###############
normDataSet=normDataSet/tile(ranges,(m,1))
# 返回归一化数据结果,数据范围,最小值
# print normDataSet
# print ranges
# print type(maxVals)
return normDataSet,ranges,minVals
normDataSet,ranges,minVals=autoNorm(returnMat)
# print normDataSet
# print ranges
# print minVals
###################
# 分类器针对约会网站的测试代码
def datingClassTest():
# 取所有数据的百分之十
hoRatio=0.10
# 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
returnMat, classLabelVector = file2Matrix ('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\datingTestSet.txt' )
normDataSet, ranges, minVals = autoNorm ( returnMat )
# 获得normDataSet的行数
m=normDataSet.shape[0]
# print m
#百分之十的测试数据的个数
numTestVecs=int(m*hoRatio)
# 分类错误计数
errorCount=0.0
# print numTestVecs
#对百分之十的测试数据进行预测
for i in range(numTestVecs):
# print 'a'
# 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
classifierResult=classify0(normDataSet[i,:],normDataSet[numTestVecs:m,:]
,classLabelVector[numTestVecs:m],3)
print "分类器返回的结果:%d,实际的情况是:%d" % (classifierResult,classLabelVector[i])
if (classifierResult !=classLabelVector[i]):
errorCount+=1.0
print "总共的错误率为:%f" %(errorCount/float(numTestVecs))
datingClassTest()
输出结果:
# 2.3 示例:手写识别系统
def img2vector(filename):
returnVect=zeros((1,1024))
fr=open(filename)
for i in range(32):
lineStr=fr.readline()
# print lineStr
for j in range(32):
returnVect[0,32*i+j]=int(lineStr[j])
return returnVect
# print returnVect
returnVect=img2vector('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\trainingDigits\\0_13.txt')
# print returnVect.shape
import os, sys
# 程序清单2-6 手写数字识别系统的测试代码
def handwritingClassTest():
hwLabels=[]
#获取训练集的目录文件转化为一个列表
trainingFileList=os.listdir ('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\trainingDigits' )
#获取列表的长度
m=len(trainingFileList)
trainingMat=zeros((m,1024))
train_listname='C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\trainingDigits\\'
for i in range(m):
fileNameStr=trainingFileList[i]
filestr=trainingFileList[i].split('.')[0]
classNumStr=int(filestr.split('_')[0])
hwLabels.append(classNumStr)
trainingMat[i,:]=img2vector(train_listname+fileNameStr)
# print mat(hwLabels).shape
# print train_listname + fileNameStr
#处理测试集相关信息
test_listname = 'C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\testDigits\\'
testingFileList=os.listdir(test_listname)
mTest=len(testingFileList)
# print mTest
errorCount=0.0
for i in range(mTest):
fileNameStr=testingFileList[i]
filestr=testingFileList[i].split('.')[0]
classNumStr=int(filestr.split('_')[0])
vertorUnderTest=img2vector(test_listname+fileNameStr)
# print vertorUnderTest
classifierResult=classify0(vertorUnderTest,trainingMat,hwLabels,3)
#选出错误的几个结果
if (classifierResult != classNumStr):
print "分类器分类结果为 : %d, 真正的结果为 : %d" % (classifierResult, classNumStr)
if (classifierResult!=classNumStr):
errorCount+=1.0
print '错误率为:',errorCount/float(mTest)
# print type(a)
handwritingClassTest()
结果为:
下面用机器学习库中库来实现k最近邻:
Sklearn简介
Scikit learn 也简称sklearn,是机器学习领域当中最知名的python模块之一。sklearn包含了很多机器学习的方式:
- Classification 分类
- Regression 回归
- Clustering 非监督分类
- Dimensionality reduction 数据降维
- Model Selection 模型选择
- Preprocessing 数据与处理
我们使用KNneighborsClassifier方法来预测:
KNneighborsClassifier参数说明:
- n_neighbors:默认为5,就是k-NN的k的值,选取最近的k个点。
- weights:默认是uniform,参数可以是uniform、distance,也可以是用户自己定义的函数。uniform是均等的权重,就说所有的邻近点的权重都是相等的。distance是不均等的权重,距离近的点比距离远的点的影响大。用户自定义的函数,接收距离的数组,返回一组维数相同的权重。
- algorithm:快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,brute是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。kd_tree,构造kd树存储数据以便对其进行快速检索的树形数据结构,kd树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于20时效率高。ball tree是为了克服kd树高纬失效而发明的,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。
- leaf_size:默认是30,这个是构造的kd树和ball树的大小。这个值的设置会影响树构建的速度和搜索速度,同样也影响着存储树所需的内存大小。需要根据问题的性质选择最优的大小。
- metric:用于距离度量,默认度量是minkowski,也就是p=2的欧氏距离(欧几里德度量)。
- p:距离度量公式。在上小结,我们使用欧氏距离公式进行距离度量。除此之外,还有其他的度量方法,例如曼哈顿距离。这个参数默认为2,也就是默认使用欧式距离公式进行距离度量。也可以设置为1,使用曼哈顿距离公式进行距离度量。
- metric_params:距离公式的其他关键参数,这个可以不管,使用默认的None即可。
- n_jobs:并行处理设置。默认为1,临近点搜索并行工作数。如果为-1,那么CPU的所有cores都用于并行工作。
Sklearn具体实现代码:
我们知道数字图片是32x32的二进制图像,为了方便计算,我们可以将32x32的二进制图像转换为1x1024的向量。对于sklearn的KNeighborsClassifier输入可以是矩阵,不用一定转换为向量,不过为了跟自己写的k-近邻算法分类器对应上,这里也做了向量化处理。然后构建kNN分类器,利用分类器做预测。创建py文件,编写代码如下:
# -*- coding: UTF-8 -*-
import numpy as np
import operator
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN
# 函数说明:将32x32的二进制图像转换为1x1024向量。
def img2vector(filename):
#创建1x1024零向量
returnVect = np.zeros((1, 1024))
#打开文件
fr = open(filename)
#按行读取
for i in range(32):
#读一行数据
lineStr = fr.readline()
#每一行的前32个元素依次添加到returnVect中
for j in range(32):
returnVect[0, 32*i+j] = int(lineStr[j])
#返回转换后的1x1024向量
return returnVect
def handwritingClassTest():
#测试集的Labels
hwLabels = []
#返回trainingDigits目录下的文件名
trainingFileList = listdir('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\trainingDigits\\')
#返回文件夹下文件的个数
m = len(trainingFileList)
#初始化训练的Mat矩阵,测试集
trainingMat = np.zeros((m, 1024))
#从文件名中解析出训练集的类别
for i in range(m):
#获得文件的名字
fileNameStr = trainingFileList[i]
#获得分类的数字
classNumber = int(fileNameStr.split('_')[0])
#将获得的类别添加到hwLabels中
hwLabels.append(classNumber)
#将每一个文件的1x1024数据存储到trainingMat矩阵中
trainingMat[i,:] = img2vector('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\trainingDigits\\/%s' % (fileNameStr))
#构建kNN分类器
neigh = kNN(n_neighbors = 3, algorithm = 'auto')
#拟合模型, trainingMat为测试矩阵,hwLabels为对应的标签
neigh.fit(trainingMat, hwLabels)
#返回testDigits目录下的文件列表
testFileList = listdir('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\testDigits\\')
#错误检测计数
errorCount = 0.0
#测试数据的数量
mTest = len(testFileList)
#从文件中解析出测试集的类别并进行分类测试
for i in range(mTest):
#获得文件的名字
fileNameStr = testFileList[i]
#获得分类的数字
classNumber = int(fileNameStr.split('_')[0])
#获得测试集的1x1024向量,用于训练
vectorUnderTest = img2vector('C:\\Users\\root\\Desktop\\2017machinelearning\\machinelearninginaction-master\\machinelearninginaction-master\\Ch02\\digits\\trainingDigits\\/%s' % (fileNameStr))
#获得预测结果
# classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
classifierResult = neigh.predict(vectorUnderTest)
if (classifierResult != classNumber):
print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
if(classifierResult != classNumber):
errorCount += 1.0
print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount/mTest * 100))
handwritingClassTest()
运行结果:
总结:
KNN算法的优点:
1、思想简单,理论成熟,既可以用来做分类也可以用来做回归;
2、可用于非线性分类;
3、训练时间复杂度为O(n);
4、准确度高,对数据没有假设,对outlier不敏感;
缺点:
1、计算量大;
2、样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
3、需要大量的内存;
其伪代码如下:
1. 计算已知类别数据集中的点与当前点之间的距离;
2. 按照距离递增次序排序;
3. 选择与当前距离最小的k个点;
4. 确定前k个点所在类别的出现概率
5. 返回前k个点出现频率最高的类别作为当前点的预测分类。