续前文 Python3.2 实现基于KNN算法的数据分类。今天看完了《机器学习实战》中关于KNN算法的一个完整案例,将所有代码加了注释,并利用tkinter 将其改造为GUI式的交互界面。(文中没有直接作用的方法我已经注释)
from numpy import *
import operator
import matplotlib
import matplotlib.pyplot as plt
from tkinter import *
from tkinter import messagebox
'''
由于本程序中涉及较多的方法,有必要做一下说明
程序正常执行流程:
1、用户输入合法数据,点击“判断一下”按钮
2、窗口获取用户输入值,并以此为参数调用classfifyPerson方法
3、在classfifyPerson方法内部,首先调用file2matrix方法,对训练数据文件进行格式转换
4、然后调用autoNormal方法进行归一化处理
5、调用classify0方法对用户输入值进行分类
6、将结果返回到窗口
其它说明:
含有表单验证,以及退出确认
'''
'''
作用:将数据文件转换为knn支持的格式
参数:
filename:保存数据的文件路径
'''
def file2matrix(filename):
fr=open(filename); #打开存放原始数据的文件
arrayOfLines=fr.readlines();
numberOfLines=len(arrayOfLines); #获取文件数据行数
returnMat=zeros((numberOfLines,3)) ;#创建一个numberOfLines行3列的纯0数组,用于保存格式化的数据
returnLabel=[] ;# 创建一个空列表,用于保存从数据文件中读取的每一行的分类标签
index=0;
for line in arrayOfLines:
line=line.strip(); #去掉空格
listFromLine=line.split('\t'); #根据\t进行分隔
returnMat[index,:]=listFromLine[0:3]; #复制数据
if (listFromLine[-1]=='largeDoses'):
tempLabel=3;
elif(listFromLine[-1]=='smallDoses'):
tempLabel=2;
elif(listFromLine[-1]=='didntLike'):
tempLabel=1;
returnLabel.append(tempLabel); #获取分类标签
index+=1;
return returnMat,returnLabel;
'''
作用:将待分类数据集与已有数据集以其标签进行计算,从而得出待分类数据集最有可能所属的类别
参数:
inX:待分类数据集
dataSet:已有数据集,通过createDataSet()函数获取
labels:已有数据集对应的分类标签,通过createDataSet()函数获取
k:设置最小距离数
'''
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] #获取数据集的行数
#计算距离
#tile(a,(b,c)):将a的内容在行上重复b次,列上重复c次
#下面这一行代码的结果是将待分类数据集扩展到与已有数据集同样的规模,然后再与已有数据集作差
diffMat = tile(inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2 #对上述差值求平方
sqDistances = sqDiffMat.sum(axis=1) #对于每一行数据求和
distances = sqDistances**0.5 #对上述结果开方
sortedDistIndicies = distances.argsort() #对开方结果建立索引
#计算距离最小的k个点的Lable
classCount={} #建立空字典,类别字典,保存各类别的数目
for i in range(k): #通过循环寻找k个近邻
voteIlabel = labels[sortedDistIndicies[i]] #先找出开方结果索引表中第i个值对应的Label值
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # 存入当前label以及对应的类别值
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #对类别字典进行逆排序,级别数目多的往前放
#返回结果
return sortedClassCount[0][0] #返回级别字典中的第一个值,也就是最有可能的Label值
'''
作用:根据数据生成图
参数:
x、y:为数据区域
l:为标签区域
'''
# def showPlot(x,y,l):
# fig=plt.figure();
# #plt.scatter(mat[:,1],mat[:,2])
# ax=fig.add_subplot(1,1,1);
# ax.scatter(x,y,15.0*array(l),15.0*array(l));
# plt.show();
'''
作用:数据归一化
参数:dataSet:数据集
'''
def autoNormal(dataSet):
minVal=dataSet.min(0); #求得每一列最小值
maxVal=dataSet.max(0); #求得每一列最大值
ranger=maxVal-minVal; # 两者做差,得到差值矩阵
normalDataSet=zeros(shape(dataSet)); #创建一个与dataSet规模相同的纯0矩阵
m=dataSet.shape[0]; #获取DateSet的数据行数
normalDataSet=dataSet-tile(minVal,(m,1)); #将最小值矩阵扩展到m行,1列,再用DataSet去做差,结果存入normalDataSet中
normalDataSet=normalDataSet/tile(ranger,(m,1)); #将最大值矩阵扩展到m行,1列,再用normalDataSet去除以差值矩阵,得到最终结果
return normalDataSet,ranger,minVal;
'''
作用:从归一化数据集中抽出前100条做为测试数据,交给分类器分类,计算分类效果
'''
# def test():
# hoRatio=0.1; #设置测试集在整体数据集中的比例
# mat,lab=file2matrix('datingTestSet.txt'); #文件格式转换
# normMat,ranges,minValues=autoNormal(mat); # 数据归一化
# m=normMat.shape[0]; # 获取数据行数
# numTestVecs=int(m*hoRatio); #获取测试数据行数,即1000*0.1=100
# errorCount=0.0; # 设置错误计数器
# for i in range(numTestVecs): # 遍历前100行数据,交给分类器进行分类,并计算错误率
# classifierResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],lab[numTestVecs:m],3); # 将当前第i条数据交给分类器进行分类
# print('ML结果:',classifierResult,',实际结果:',lab[i]); # 显示当前行数据的分类结果
# if(classifierResult!=lab[i]): # 如果出错,错误计数器加1
# errorCount+=1.0;
# print('错误率:',(classifierResult/float(numTestVecs))*100,"%"); # 显示最终结果
'''
作用:用户输入一组信息,程序对其进行分类测试
'''
def classfifyPerson(flight,game,cream):
resultList=['屌丝','文艺青年','高富帅'];
flight=float(flight);
game=float(game);
cream=float(cream);
mat,lab=file2matrix('datingTestSet.txt');
norMat,ranges,minVal=autoNormal(mat);
arrays=array([flight,cream,game]);
claResult=classify0((arrays-minVal)/ranges,norMat,lab,3);
# print('你的相亲极97%的可能性为:',resultList[claResult-1]);
return '老夫掐指一算,该人为:'+resultList[claResult-1]
#生成窗口
root=Tk();
root.title('相亲对象预测系统');
'''
作用:窗口居中方法,由于tkinter自身没有直接设置窗口居中的方法,只能来手工计算了.这也一个通用的窗口居中方法
参数:
obj:为窗口对象
w:为窗口宽度
h:为窗口高度
'''
def center_window(obj,w, h):
# get screen width and height
ws = obj.winfo_screenwidth()
hs = obj.winfo_screenheight()
# calculate position x, y
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
obj.geometry('%dx%d+%d+%d' % (w, h, x, y))
center_window(root,400, 300)
#设置窗口控件对应的变量类型及初始值
txtFlight=StringVar();
txtFlight.set('');
txtGame=StringVar();
txtGame.set('');
txtEat=StringVar();
txtEat.set('');
txtResult=StringVar();
txtResult.set('');
'''
作用:单击事件执行的方法,获取用户值,交给分类器,在Label中显示结果。
其中try块的作用检测用户输入值的合法性
'''
def submit():
try:
getFlight=float(txtFlight.get());
getGame=float(txtGame.get());
getEat=float(txtEat.get());
resultStr=classfifyPerson(getFlight,getGame,getEat); # 获取用户输入值并调用分类方法
txtResult.set(resultStr) ; # 在Label中显示结果
except(Exception):
messagebox.showwarning("警告", "每项值都不为空,且只能为实型!") ;
def closeWin(): #退出按钮事件
result=messagebox.askokcancel(title='确认',message='确定要退出么亲?'); # 弹出确认窗口,捕获用户选择
if(result==True): # 如果用户选择是,则退出程序
root.destroy();
'''
生成各控件
控件布局采用grid方式;
row和column指定控件在grid中的位置;
pady设定控件与其它控件之间在y方向上的距离(类似于CSS中的margin属性);
columnspan设定水平方向上合并的grid数,类似于excel或word中的水平合并单元格,html的table中也有这个属性
sticky设置控件在grid中的位置,可选值有e,s,w,n,ne,nw,se,sw,其实对应的是四面八方或上下左右等8个位置,默认为center,居中
'''
labelFligt=Label(root,text='飞行里程:',font=("微软雅黑",14),fg="blue");
labelFligt.grid(row=0,column=0,pady=10);
inputFlight=Entry(root,textvariable=txtFlight,font=("微软雅黑",14),fg="blue");
inputFlight.grid(row=0,column=1,pady=10);
labelGame=Label(root,text='游戏时间:',font=("微软雅黑",14),fg="blue");
labelGame.grid(row=1,column=0,pady=10);
inputGame=Entry(root,textvariable=txtGame,font=("微软雅黑",14),fg="blue");
inputGame.grid(row=1,column=1,pady=10);
labelEat=Label(root,text='吃货程度:',font=("微软雅黑",14),fg="blue");
labelEat.grid(row=2,column=0,pady=10);
inputEat=Entry(root,textvariable=txtEat,font=("微软雅黑",14),fg="blue");
inputEat.grid(row=2,column=1,pady=10);
submitBtn=Button(root,text='判断一下',command=submit); # 绑定事件
submitBtn.grid(row=3,column=0,pady=10);
labelResult=Label(root,text='',textvariable=txtResult,font=("微软雅黑",14),fg="red");
labelResult.grid(row=4,column=0,columnspan=2,pady=10,sticky='e');
closeBtn=Button(root,text='关 闭',command=closeWin);
closeBtn.grid(row=5,column=2);
root.mainloop();