本文的项目是参照《机器学习实战》中的“约会网站配对效果”来实现的。进行了一些改变,并进行了更详细的说明。
k-近邻算法:准备大量的数据样本,然后计算目标向量(即需要预测的数据)和各个样本之间的“距离”, 取前k个数据中存在最多的标签做为目标向量的标签。
需要的包:numpy,operator,matplotlib
from numpy import *
import operator
import matplotlib
import matplotlib.pyplot as plt
方法介绍顺序:
预测数据——>读文件——>绘图——>归一化——>测试——>工程化
一、classify0()
功能说明:
通过比较,获取数据的预测值
参数说明:
输入向量inX, 训练样本集dataSet, 向量标签labels, k值
公式:
开发思路和步骤:
1、获取训练样本集的总条数
2、用过tile方法,让inX变为数量与训练样本相同的矩阵,并和训练样本集矩阵相减,获得一个新的矩阵
3、新获取的矩阵进行平方
4、对相减的数据进行相加
5、对相加后的数据进行开方
6、将获取的数据从小到大进行排序,并获得其下标
7、循环遍历k个数据,判断最多的值
8、返回输出
方法总结:
1、shape[0]:
用于获取矩阵的长度,0表示行(矩阵中有多少条数据),1表示列(每条数据几个字段)
2、tile:
将向量进行重复,并分别进行操作
tile(list,x)列重复
tile(list,(y,x)),行重复y次,列重复x次
3、sum(axis=1):
Numpy中的sum表示对矩阵进行求和,axis=0表是列求和,axis=1表示行求和
4、argsort():
将数组从小到大排序的索引返回为一个新的数据
5、items():
获取对象的迭代器(用于遍历)
在python2中使用iteritems()
6、itemgetter():
operator.itemgetter函数
operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号。看下面的例子
a = [1,2,3]
>>> b=operator.itemgetter(1) //定义函数b,获取对象的第1个域的值
>>> b(a)
7、get():
获取字典的某个值。get(x,y)中x表示字典的索引,y表示默认值。
def classify0(inX, dataSetMat, labels, k):
dataSize = dataSetMat.shape[0]
shotDataMat = (((tile(inX,(dataSize,1))-dataSetMat)**2).sum(axis=1))**0.5
shotDataMatIndex = shotDataMat.argsort()
labelDict = {}
for i in range(k):
label = labels[shotDataMatIndex[i]]
labelDict[label] = labelDict.get(label,0)+1
resultList = sorted(labelDict.items(),key=operator.itemgetter(1),reverse=True)
return resultList[0][0]
二、fileToMatrix()
功能说明:
读取文件,获取到训练样本矩阵
参数说明:
filename,文件所在路径
公式:无
开发思路和步骤:
1、读取文件
2、将文件按行转为数组
3、根据文件中所包含的属性个数(需要使用的)以及数据条数,创建一个矩阵
4、创建一个数组,用于存放标签向量集合
5、处理数据,并返回值
方法总结:
open():
根据路径,打开一个文件
readlines():
返回列表,包含所有的行。
zeros(shape,dtype,order):
shape:(行,列)
dtype:
t ,位域,如t4代表4位
b,布尔值,true or false
i,整数,如i8(64位)
u,无符号整数,u8(64位)
f,浮点数,f8(64位)
c,浮点负数,
o,对象,
s,a,字符串,s24
u,unicode,u24
order:可选参数,c代表与c语言类似,行优先;F代表列优先
创建一个0组成的矩阵、
strip():
清除数组前后的空格、回车
xxx[index,:]:
一个二维数组中,第一纬度的第index个。类似于取xxx[index]
遇到问题:
报错:
list indices must be integers or slices, not tuple
代码:
file_mat[index,:] = f_list[0,3]
修改代码:
file_mat[index,:] = f_list[0:3]
错误分析:
mat中不能接收字符串类型的数据,只可以是int或者slices。而不能是元组(字符串就是元组)
原因,取数组的区间,要使用:而不是, !!
def fileToMatrix(filename):
file = open(filename)
file_list = file.readlines()
file_count = len(file_list)
file_mat = zeros((file_count,3))
labels = []
index = 0
for f in file_list:
f = f.strip()
f_list = f.split("\t")
#file_mat[index,:] = f_list[0,3]
file_mat[index,:] = f_list[0:3]
labels.append(int(f_list[3]))
index+=1
return file_mat, labels
三、drawMatplotlib()
功能说明:
绘制散点图
参数说明:
matrix为展示的矩阵,labels为矩阵的向量标签,x轴的值对应的索引,y轴的值对应的索引
公式:无
开发思路:无
方法总结:
1、figure():
创建一个图形实例
2、add_subplot(x,y,z):
将画布分成x行y列,图在从左到右从上到下的第z块
3、scatter(x, y):
绘制散点图
4、show:
展示图形
def drawMatplotlib(matrix,labels,x,y):
fg = plt.figure()
pic = fg.add_subplot(111)
pic.scatter(matrix[:,x],matrix[:,y],15*array(labels),15.0*array(labels))
fg.show()
四、autoNorm()
功能说明:
数据归一化:
按照之前的计算方式,当某一项数值存在的差值过大的时候,就会严重的影响到结果。
而正常情况下,我们是不希望出现这种严重影响结果的数据(除非本身就想这样的)
所以我们通过一个算法,将它固定在一个0~1这样的范围里,如此就可以公平的比较。
参数说明:
matrix需要归一化的矩阵
公式:
val = (val-min)/(max+min)
开发思路:
1、获取矩阵最大值、最小值的矩阵
2、求最大值、最小值的差
3、创建一个新的矩阵
4、通过公式计算,将新的矩阵赋值到矩阵中
方法总结:
1、mat.min(0)/mat.max(0):
必须加0!
获取矩阵中每一列的最大/小值,并返回一个矩阵
def autoNorm(matrix):
#val_min = matrix.min()
#val_max = matrix.max()
val_min = matrix.min(0)
val_max = matrix.max(0)
ranges = val_max-val_min
result_mat = zeros(shape(matrix))
#up_val = tile(matrix,(matrix.shape[0],1))
up_val = matrix-tile(val_min,(matrix.shape[0],1))
#zero_matrix = up_val/ranges
result_mat = up_val/tile(ranges,(matrix.shape[0],1))
return result_mat,ranges,val_min
五、studingTest()
方法说明:
针对学习集,按照百分比进行拆分。前X%做为目标向量,剩余数据做为样本数据,对学习算法进行测试
参数说明:
filename为样本数据文件路径。percent为百分比
公式:无
开发思路:
1、调用fileToMatrix方法获取矩阵和标签
2、将所有矩阵中的数据进行归一化处理
3、循环前percent个数据,与样本数据进行比较测试,获取准确率
方法总结:无
思路分析:
疑问:dataLabels为什么是一个数组,怎么和矩阵进行对比?
答:在循环遍历的过程中,还是通过索引i来查询。而矩阵中的横向只是数据的属性。
所以labels通过一个单一的数组就可以对应上
def studingTest(filename, percent, k):
dataMat,dataLabels = fileToMatrix(filename)
dataNormMat, _, _= autoNorm(dataMat)
data_len = dataNormMat.shape[0]
testNum = int(percent*data_len)
errorNum = 0
for i in range(testNum):
test_result = classify0(dataNormMat[i,:],dataNormMat[testNum+1:],dataLabels[testNum+1:], k)#
print("old value = {}, test value = {}".format(dataLabels[i],test_result))
if test_result != dataLabels[i]:
errorNum+=1
print("errorNum={},percent={}".format(errorNum,errorNum/testNum))
六、classifyPerson()
方法说明:
传入一个person字典,包含mile,ice,play三个属性
参数说明:
person字典,包含mile,ice,play三个属性
公式:无
开发思路:
1、将person变为向量
2、获取样本数据
3、对比
--向量归一化
4、输出
方法总结:无
问题总结:
1、传入的person属性都需要为float类型
2、比较的时候必须将目标向量也归一化
3、目标向量的归一化不能直接调用autoNorm方法,而是要用样本矩阵所返回的min和ranges来计算
def classifyPerson(filename,person, k):
person_vec = array([person.get('mile'),person.get('play'),person.get('ice')])
data_mat,data_labels = fileToMatrix(filename)
data_norm_mat,ranges,min_val = autoNorm(data_mat)
rs = classify0((person_vec-min_val)/ranges,data_norm_mat,data_labels,k)
print(rs)
最后是执行代码的主函数:
if __name__ =="__main__":
person = {'mile':float(10000),'ice':float(1),'play':float(1)}
classifyPerson('datingTestSet2.txt',person,5)