目录
K-近邻算法实战(二):约会网站配对效果判断
一般的K-近邻算法流程:
1.收集数据:方法有爬虫进行数据收集或是使用第三方提供的数据
2.准备数据:用Python解析、预处理数据
3.分析数据:对数据进行分析,例如使用Matplotlib将数据可视化
4.测试算法:计算算法的错误率
5.使用算法:在错误率可接受的范围内,运用k-近邻算法进行分类
实战
1.背景介绍
线上网站寻找适合自己的约会对象,海伦女士对自己的喜好进行一番总结,她发现可以将交往过的人进行如下分类:
1.不喜欢的人
2.魅力一般的人
3.极具魅力的人
海伦收集交往过的人的数据主要包含3个特征:
1.每年获得的飞行常客里程数
2.玩视频游戏所消耗时间百分比
3.每周消耗的冰淇淋公升数
采用的数据集包含三个特征和一个喜欢的类型,放在文本文件datingTesingSet.txt中,数据下载:Machine-Learning/datingTestSet.txt at master · Jack-Cherish/Machine-Learning (github.com)
文件中的数据格式如下:
2.准备数据:数据分类
在机器学习:K-近邻算法(一)_晶哥哥&的博客-CSDN博客中,学习了K-近邻算法的代码实现,就是写成了分类器,那么要使用分类器就要将待处理的数据格式改变为分类器能接受的格式,即特征矩阵和分类标签向量。
(1)编写代码如下:
'''
函数说明:对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力
输入:文件名(filename)
输出:特征矩阵(returnMat)、分类标签(classLabelVector)
'''
def file2matrix(filename):
#打开文件
file=open(filename)
#得到文件的所有内容
arrayofLines=file.readlines()
#得到文件的行数
numberofLines=len(arrayofLines)
#初始化返回的特征矩阵,注意这里函数里边的矩阵类型必须加括号
returnMat=np.zeros((numberofLines,3))
#初始化分类标签向量
calssLabelVector=[]
#行的索引值
index=0
for line in arrayofLines:
#删除每一行的首尾空白符
line=line.strip()
#将每一行的内容按照分隔符切片
listFormLine=line.split('\t')
#将切片后的列表中前三行内容存放到特征矩阵中
returnMat[index,:]=listFormLine[0:3]
#将文本中的喜欢程度进行分类
if listFormLine[-1]=='didntLine':
classLabelVector.append(1)
elif listFormLine[-1]=='smallDoses':
classLabelVector.append(2)
elif listFormLine[-1]=='largeDoses':
listLabelVector.append(3)
index+=1
return returnMat,classLabelVector
(2)不会的知识点:
A.文件的readlines()
返回一个列表,将文件中的每一行作为一个列表项
例如:x.txt文件的内容为:
B.字符串的strip()方法
用于删除字符串首尾指定的字符(参数为空时,默认删除空白符,包括:‘\t’,‘\n’,‘\r’,‘ ’)或序列
C.字符串的split()方法
通过指定分隔符来对字符串进行切片
split(str='',num=string.count(str))
str----分隔符,默认为所有空白符,包括:空格、换行、制表符等
num--分割次数,默认为-1,即分割所有
返回值:返回切片后的字符串的列表
(3)测试效果如下:
3.分析数据:数据可视化
将数据进行可视化的方式展示,方便进行观察
(1)编写的代码如下:
'''
函数说明:可视化数据
输入:特征矩阵(datingDataMat)、分类标签向量(datingLables)
'''
def showdatas(datingDataMat,datingLabels):
#设置汉字格式
font=FontProperties(fname=r'c:\windows\fonts\simsun.ttc',size=14)
#创建画布,将fig画布分割成1行1列,不共享x轴y周,fig画布的到校为(13,8)
#nrow=2,ncols=2,代表将fig画布分为四个区域,axs[0][0]表示第一行第一个区域
fig,axs=plt.subplots(nrows=2,ncols=2,sharex=False,sharey=False,figsize=(13,8))
numberofLabels=len(datingDataMat)
LabelsColors=[]
#设置标签颜色
for i in datingLabels:
if i==1:
LabelsColors.append('black')
if i==2:
LabelsColors.append('orange')
if i==3:
LabelsColors.append('red')
#画出散点图1,以特征矩阵的第一列和第二列数据画散点,散点大小为15
axs[0][0].scatter(x=datingDataMat[:,0],
y=datingDataMat[:,1],color=LabelsColors,s=15,alpha=0.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='blod',color='red')
plt.setp(axs0_xlabel_text,size=9,weight='blod',color='black')
plt.setp(axs0_ylabel_text,size=9,weight='blod',color='black')
#画出散点图2,以特征矩阵的第一列和第三列数据画散点,散点大小为15
axs[0][1],scatter(x=datingDataMat[:,0],
y=datingDataMat[:,2],s=15,alpha=0.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'每年获得的飞行常客里程数',FontPreperties=font)
axs1_ylabel_text=axs[0][1].set_ylabel(u'每周消耗的冰淇淋公升数',FontProperties=font)
#设置对象属性
plt.setp(axs1_title_text,size=9,weight='blod',color='red')
plt.setp(axs1_xlabel_text,size=9,weight='blod',color='black')
plt.setp(axs1_ylabel_text,size=9,weight='blod',color='black')
#画出散点图3,以特征矩阵的第二列和第三列数据画散点,散点大小为15
axs[1][0].scatter(x=datingDataMat[:,1],
y=datingDataMat[:,2],s=15,alpha=0.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'玩视频游戏所消耗时间占比',FontProperteis=font)
axs2_ylabel_text=axs[1][0],set_ylabel(u'每周消耗的冰淇淋公升数',FontProperties=font)
#设置对象属性
plt.setp(set_title_text,size=9,weight='blod',color='red')
plt.setp(set_xlabel_text,size=9,weight='blod',color='black')
plt.setp(set_ylavel_text,size=9,weight='blod',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()
(2)不会的知识点
A.字符串前缀符r和u
在字符串前加r,表明这些字符是原生字符
对于原生字符的理解跟转义字符有关:
在字符串前加u,python2中用在字符串前,放置中文乱码,python3中所有的字符串默认都是unicode字符串
B.画散点图,使用创建子图的scattor()时,设置散点大小的关键字是s,不是size
C.plt.setp()用于设置对象属性
(3)代码实现结果如下:
4.准备数据:数据归一化
为什么要进行数据归一化?计算下面样本1和样本2之间的距离,使用欧式距离公式。
样本 | 每年获得的飞行常客里程数 | 玩视频游戏消耗时间占比 | 每周消费的冰淇淋公升数 | 样本分类 |
1 | 134000 | 12 | 0.9 | 3 |
2 | 32000 | 67 | 0.1 | 2 |
计算公式为:
从公式上可以看出,数字差值越大对计算结果影响越大,那么对于本项目来说,顾客飞行里程数这一特征比其他两个特征对结果的影响大且远远大,出现这样情况的原因是由于,飞行里程数的数量级远远大于另外两个特征,对于海伦来说三个特征同等重要。
处理这种不同取值范围的特征时,通常采用将数据归一化,将任意取值范围的特征值转化为0到1之间或-1到1之间,本次采用0到1之间。
newValue=(oldValue-min)/(max-min)
注:改变数值的取值范围会增加分类器的复杂度,但为了得到准确结果,必须这样做。
(1)编写代码如下:
'''
函数说明:将训练集的数据进行归一化
输入:特征矩阵(dataSet)
输出:归一化后的特征矩阵(normDataSet)
数据范围(ranges)
数据最小值(minVals)
'''
def autoNorm(dataSet):
#获取数据的最大值和最小值
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
#获取最大值和最小值的范围
ranges=maxVals-minVals
#初始化归一化后的特征矩阵
normDataSet=np.zeros(np.shape(dataSet))
#获取dataSet的行数
m=dataSet.shape[0]
#原数据减去最小值
normDataSet=dataSet-np.tile(minVals,(m,1))
#再除以最大值与最小值的差
normDataSet=normDataSet/np.tile(ranges,(m,1))
#返回归一化后的特征值,数据范围,最小值
return normDataSet,ranges,minVals
(2)不会的知识点:
A.列表的内置函数min()和max()
对于列表---->max(list)得到列表中最大的值,min()也是
对于二维矩阵--->假如x是一个4行3列的二维矩阵
x.mix(0)返回的列表是由每一列中的最小元素组成的,列表中元素个数为3
x.min(1)返回的列表是由每一行中的最小元素组成的,列表中元素个数为4
B.numpy的shape()
np.shape(x)返回x矩阵的行数和列数
x.shape[0]返回x矩阵的行数
x.shape[1]返回x矩阵的列数
(3)测试效果如下:
5.测试算法:验证分类器
机器学习算法对于评估算法正确率的缓解是必不可少的,通常我们会将训练样本的90%的数据来训练分类器,其余10%用来测试分类器,注意10%的测试数据应该是随机选择的。
(1)编写代码如下:
'''
函数说明:测试算法的正确率
输入:无
输出:打印出正确率
注:这里调用前面写好的代码块
分类特征矩阵和标签向量的函数---file2matrix(filename)
将数据归一化的函数---autoNorm(datingSet)
分类器函数(在前一篇文章KNN(一)中)----classify0(inX,dataSet,labels,k)
'''
def datingClassTest():
filename='datingTestSet.txt'
#获取特征矩阵和分类标签向量
datingDataMat,datingLabels=file2matrix(filename)
#设置测试数据百分比
hoRatio=0.10
#将数据归一化,返回归一化后的矩阵,数据范围,数据最小值
normMat,ranges,minVals=autoNorm(datingDataMat)
#获取数据的行数
m=datingDataMat.shape[0]
#得到测试数据的个数
numTestVecs=int(m*hoRatio)
#初始化错误分类计数
errorCount=0.0
for i in range(numTestVecs):
#前numTestVecs个数据作为测试集,后面的数据做训练集
classifierResult=classify0(normMat[i,:],
normMat[numTestVecs:,:],datingLabels[numTestVecs:],4)
print('分类结果:%d\t真是类别:%d'%(classifierResult,datingLabels[i]))
if classifierResult!=datingLabels[i]:
erroeCount+=1.0
#%%是指字符'%'
print('错误率:%f%%'%(errorCount/float(numTestVecs)*100))
(2)测试结果:
通过修改k值和训练集与测试集所占比例会改变错误率。
6.使用算法:构建完整可用系统
将前面写的代码段进行整合,写成一个小程序,海伦在网站上将某人的三个特征信息输入,程序就会给出喜欢程度的预测值。
(1)编写的代码如下:
'''
函数说明:KNN算法,分类器
'''
import numpy as np
def classify0(inX,dataSet,labels,k):
#获取dataSet的行数
dataSize=dataSet.shape[0]
diffMat=np.tile(inX,(dataSize,1))-dataSet
sqDiffMat=diffMat**2
sqDistances=sqDiffMat.sum(axis=1)
distances=sqDistances**0.5
#将计算出的距离元素进行排序,获得排序后的索引值
sortedDistIndices=distances.argsort()
#记录类别次数
classCount={}
for i range(k):
#取出前K个元素的类别
voteIlabel=labels[sortedDistIndices[i]]
#计算类别次数
classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
sortedClassCount=sorted(classCount,items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
'''
函数说明:将数据进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
分类为:特征矩阵和分类标签向量
'''
def file2matrix(filename):
#打开文件
file=open(filename)
#将数据存放在数组中
arrayoLines=file.readlines()
#获取数据的行数
numberofLines=len(arrayoLines)
#初始化特征矩阵
returnMat=np.zeros((numberofLines,3))
#初始化分类标签向量
classLabelVector=[]
#行的索引值
index=0
for line in arrayoLines:
#清除首尾空白符
line=line.strip()
#将数据切片
listFormLine=line.split('/t')
#将前三列数据存放到特征矩阵中
returnMat[index,:3]=listFormLine[:3]
#将标签分类
if listFormLine[-1]=='didntLike':
classLabelVector.append(1)
elif listFormLine[-1]=='smallDoses':
classLabelVector.append(2)
elif listFormLine[-1]=='largeDoses':
classLabelVector.append(3)
index+=1
return returnMat,classLabelVector
'''
函数说明:将数据归一化
'''
def autoNorm(dataSet):
#获取最大值和最小值
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
#得到数据范围
ranges=maxVals-minVals
#初始化归一化后的特征矩阵
normDataSet=np.zeros(np.shape(dataset))
#获取数据的行数
m=dataSet.shape[0]
normDataSet=dataSet-np.tile(minVals,(m,1))
normDataSet=normDataSet/np.tile(ranges,(m,1))
#返回归一化后的矩阵,数据范围,最小值
return normDataSet,ranges,minVals
'''
函数说明:使用算法测试
'''
def classifyPerson():
#输出结果列表
result=['讨厌','有些喜欢','非常喜欢']
#输入三个特征的数据
ffMiles=float(input('每年获得的飞行常客里程数:'))
precentTats=float(input('玩视频游戏所消耗时间占比:'))
iceCream=float(imput('每周消耗的冰淇淋公升数:'))
#文件名
filename='datingTestSet.txt'
#分类数据
datingDataMat,datingLabels=file2matrix(filename)
#将数据归一化
normMat,ranges,minVals=autoNorm(datingDataMat)
#生成测试集数组
inArr=np.array([ffMiles,precentTats,iceCream])
#将测试集数据归一化
norminArr=(inArr-minVals)/ranges
#调用分类器
classifierResult=classify0(norminArr,normMat,datingLabels,3)
#打印结果
print('你可能%s这个人'%(result[classifierResult-1]))
if __name__=='__main__':
classifyPerson()
(2)测试结果如下:
批注:我学习的博主Jack Cui,网址:Jack Cui | 关注人工智能及互联网的个人网站 (cuijiahua.com)
我就是看他的学习记录学习的,详细学习内容可进入他的网站学习。我写文章主要目的是为了再次熟悉代码和记录不会的知识点。