【Machine Learning in Action】学习笔记——(2)k-近邻算法
因为第一章完全是导入性质的,所以学习笔记从第二章开始.书本的源代码可以在www.manning.com/MachineLearninginAction中找到.
k-近邻算法概述
简单地说,k-近邻算法采用测量不同特征值之间距离的方法进行分类.
例如我们现在有6个样本(1-6),每个样本有两个特征值(A,B),其中1~3是I分类,而4-6是II分类.
为了确定样本7的分类,我们根据7的两个特征值计算出1~6号样本和7之间的距离,接着找到k个与样本7距离最小的样本.
接着根据距离最近的k个样本的分类决定样本7的分类.
例如我们取k=3,最近的三个样本分别是1,2,3.因此我们判断样本7为I分类.
K-近邻算法 |
---|
优点:精度高、对异常值不敏感、无数据输入假定 |
缺点:计算复杂度高、空间复杂度高 |
适用数据范围:数值型和标称型 |
K-近邻算法的一般流程
(1)收集数据
(2)准备数据:距离计算所需要的数值,最好是结构化的数据格式.
(3)分析数据
(4)训练算法:不适用于k-近邻算法(那这个标题改成机器学习算法的一般流程比较合理)
(5)测试算法:计算错误率
(6)使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入的数据属于哪个分类,最后应用对计算出的分类执行后续处理
使用python制造分类数据
书本里是分成了两个文件,不过我就在一个notebook里进行了
from numpy import *
import operator
def creatDataSet():
group=array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels=['A','A','B','B']
return group,labels
group,labels=createDataSet()#给样本赋值,得到样本特征值组合以及对应的标签
实施KNN分类算法
def classify0(inX,dataSet,labels,k):
#inX是想要对其分类的目标,dataSet是样本集,labels是样本集对应的目标
dataSetSize=dataSet.shape[0]
diffMat=tile(inX,(dataSetSize,1))-dataSet
#先把inX复制dataSetSize次,构成和dataSet相同大小的矩阵,接着与dataSet原矩阵相减,得到坐标距离的矩阵
sqDiffMat=diffMat**2
sqDistances=sqDiffMat.sum(axis=1)
#将每一项坐标差的取平方后求对应的和,得到距离的平方
distances=sqDistances**0.5
sortedDistIndicies=distances.argsort()
#计算距离,并且将其进行排序
classCount={}
for i in range(k):
voteIlabel=labels[sortedDistIndicies[i]]
classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
#记录k个样本中不同标签的出现次数
sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#再将标签按照出现次数降序排列,返回出现次数最多的标签
return sortedClassCount[0][0]
接着我们来使用这个算法
classify0([0,0],group,labels,3)
得到对[0,0]的分类结果,B.
如何测试分类器
引入错误率:分类器给出错误结果的次数除以测试执行的总数.完美分类器的错误率为0.最差分类器的错误率为1.0.
示例1:使用k-近邻算法改进约会网站的配对效果
(1)收集数据:导入文本文档datingTestSet2.txt
(2)准备数据:使用Python解析文本文件
(3)分析数据:使用Matplotlib画二维扩散图
(4)训练算法:不适用于k-近邻算法
(5)测试算法:使用海伦提供的部分数据作为测试样本
(6)使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否是自己喜欢的类型
准备数据:从文本文件中解析数据
def file2matrix(filename):
fr=open(filename)
arrayOLines=fr.readlines()
#得到文件行数
numberOfLines=len(arrayOLines)
returnMat=zeros((numberOfLines,3))
#构造一个行数*3的矩阵,所有元素均为0
classLabelVector=[]
index=0
for line in arrayOLines:
line=line.strip() #用strip函数去除每一行首尾的空白
listFromLine=line.split('\t')
returnMat[index,:]=listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index+=1
return returnMat,classLabelVector
接着使用上述函数
datingDataMat,datingLabels=file2matrix(r'./datingTestSet2.txt')
简单检查一下数据内容
>>>datingDataMat
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]])
分析数据:使用Matplotlib创建散点图
import matplotlib
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
#plt默认字体无法显示中文字符,这里设置成雅黑
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.xlabel("玩游戏所耗时间百分比")
plt.ylabel("每周消费的冰淇淋公升数")
#使用第二三列数据,分别是"玩游戏所耗时间百分比"和"每周消费的冰淇淋公升数"
plt.show()
由于没有使用样本分类的特征值,很难从图中看出任何有用的数据模式信息
下面用不同色彩来表示不同样本分类,以便更好地理解数据信息,使用Matplotlib提供地scatter函数
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.xlabel("玩游戏所耗时间百分比")
plt.ylabel("每周消费的冰淇淋公升数")
plt.show()
再试着将矩阵第一列和第二列来展示数据
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.ylabel("玩游戏所耗时间百分比")
plt.xlabel("每年获取的飞行常客里程数")
plt.show()
不难看出用第一列和第二列数据能得到更好的展示效果
准备数据:归一化数值
如果直接使用特征数值本身计算距离,数值差值大的属性对计算结果的影响更大.所以为了处理不同取值范围的特征值,我们将数据归一化处理,如将取值范围处理为0到1或者-1到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=(oldValue−min)/(max−min)
def autoNorm(dataSet):
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
ranges=maxVals-minVals#获取数据区间
normDataSet=zeros(shape(dataSet))
m=dataSet.shape[0]
normDataSet=dataSet-tile(minVals,(m,1))
normDataSet=normDataSet/tile(ranges,(m,1))
return normDataSet,ranges,minVals
normMat,ranges,minVals=autoNorm(datingDataMat)
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(normMat[:,0],normMat[:,1],15.0*array(datingLabels),15.0*array(datingLabels))
plt.ylabel("玩游戏所耗时间百分比")
plt.xlabel("每年获取的飞行常客里程数")
plt.show()
测试算法
def datingClassTest():
hoRatio = 0.10 #提取10%数据作为测试集
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)#载入数据并且归一化
m = normMat.shape[0]
numTestVecs = int(m*hoRatio)
errorCount = 0.0 #初始化错误率
for i in range(numTestVecs):
classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
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)
得到错误率为0.05,还是相当不错的
使用算法:构建完整可用系统
def classifyPerson():
resultList=["完全无感","有点心动","很有吸引力"]
percentTats=float(input("每周游戏时间的比例?"))
ffMiles=float(input("每年获取的飞行常客里程数?"))
iceCream=float(input("每周消费的冰淇淋公升数?"))
datingDataMat,datingLabels=file2matrix("datingTestSet2.txt")
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr=array([ffMiles,percentTats,iceCream])
classifierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)
print("你可能对这个人的好感度:",resultList[classifierResult-1])
classifyPerson函数为最终的预测函数