按照《机器学习实战》一书中的顺序进行学习,总结自己的理解。
简单说,k近邻算法采用测量不同特征值之间的距离方法进行分类。
即本算法是一个分类算法。
问题格式:
当输入一部电影M,包含特征值 (亲吻次数=5,打斗次数=100),问电影M的所属类别(爱情片or动作片)?
算法思路:
1.首选要有样本测试集,即n部包含对应特征值和标签的电影列表,如:
2.将电影M与n部电影作比较(通过欧氏距离公式来计算特征值之间的距离)
3.将计算结果由小到大排序,找出距离最近的前3部(一般不大于20条)电影,如:
4.判断这三部已知电影的标签:
都是爱情片 => M为爱情片
2部是动作片,1部是爱情片 =>:M为动作片
……
代码注解1:python中List、Array和numpy.array与数组的关系
代码注解2:tile函数的作用
代码注解3:argsort函数的作用
代码注解4:字典的get方法
代码注解5:max函数搭配字典用法
#导入科学计算包numpy
from numpy import *
#创建已知数据集
def createDataSet():
#注解1:python中List、Array和numpy.array与数组的关系
group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
#Func:将所给新纪录进行分类;
#Param:
#inX-新纪录;
#dataSet-已有数据集;
#labels-已有数据集中每条记录的对应标签
#k:从前k个距离最近的点中进行判断类型
def classify0(inX, dataSet, labels, k):
# 获取旧样本集的行数(记录数)
dataSetSize = dataSet.shape[0]
"""一、根据欧式距离公式,计算输入记录与样本集中每条数据之间的距离 √(特征值a1-特征值a2)^2+(特征值b1-特征值b2)^2+(特征值c1-特征值c2)^2"""
# 1.使用tile将输入的一条记录转为dataSetSize条(注解2:tile函数)
# 2.通过矩阵来计算相减结果
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
# 3.对相减结果取平方
sqDiffMat = diffMat ** 2
# 4.对每条记录,将不同特征值相减结果的平方求和
sqDistances = sqDiffMat.sum(axis=1)
# 5.开方,得到输入记录与样本集中每条记录的距离
distances = sqDistances ** 0.5
"""二、从计算的结果中选取距离最小的前k个,判断这k个当中属于哪个标签类别的最多"""
# 1.根据距离排序从小到大的排序,并返回其在原矩阵数组对应的下标值(注解3:argsort函數)
sortedDistIndicies = distances.argsort()
# 2.选择距离最小的前k个,并算出其中的每个标签类别的出现次数
classCount = {}
for i in range(k):
# 获取第i个所属的标签类别
voteIlabel = labels[sortedDistIndicies[i]]
# 在标签类别字典中将该类别数目加1(注解4:字典的get方法)
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 3. 返回出现最多的那个类型
# 利用max函数,直接返回字典中value最大的key(注解5:max函数搭配字典用法)
maxClassCount = max(classCount, key=classCount.get)
return maxClassCount
def test1():
"""
第一个例子演示
"""
group, labels = createDataSet()
print(str(group))
print(str(labels))
print(classify0([0.1, 0.1], group, labels, 3))
运行结果:根据计算,前三个距离最近的记录中B类2个,A类1个,因此得出特征值为[0.1,0.1]的记录标签分类应该是B。
距离结果:[1.3453624 1.27279221 0.14142136 0.1]
距离最近3个中类别数目:{'B': 2, 'A': 1}
新样本分类:B
注解1:python中List、Array和numpy.array与数组的关系
Python中的List可以当做数组使用。其中元素类型可能不同,因此保存的是对象的指针;即使保存同数据类型元素的列表[1,2,3],也需要三个指针和三个整数对象。当我们主要用数组进行数值运算时,这种结构显然不够高效。
Python中的Array模块只支持一维数组,不支持多维数组,也没有各种运算函数。因而也不适合数值运算。
NumPy中的array则支持多维数组,且可以使用多种索引方式和计算,因而经常在机器学习中被使用。
注解2:tile函数的作用
tile(x,y)函数的作用为:根据原矩阵将其扩充为x行y列的新矩阵,如:
对arr=[1,2,3],tile(inx, (3, 1))为
array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])对arr=[1,2,3],tile(inx, (3, 2))为
array([[1, 2, 3, 1, 2, 3],
[1, 2, 3, 1, 2, 3],
[1, 2, 3, 1, 2, 3]])
注解3:argsort函数的作用
argsort(a) 函数的作用是:将矩阵a中的元素从小到大排列,并根据其在原矩阵数组中的下标,输出到y。
例如:a=[3,-1,0], y=a.argsort(a) ,则y=[1,2,0]
a[1]最小,所以y[0]=1;
a[0]最大,所以y[2]=0…
注解4:字典的get方法
字典的map.get(k,d)方法,相当于从字典中查找是否存在参数k:如果存在,则返回对应的value值;不存在,则返回d.
例如:# map = {5:2,3:4}
map.get(3,0)返回的值是4;
map.get(1,0)返回值是0;
注解5:max函数搭配字典用法
1、max() 函数中没有 key 参数时,求的是 key 的最大值
2、max() 函数中有 key 参数时,求的是 value 最大的key值例如:map = {5:2,3:4;1:6}
max(map) = 5
max(map,k=map.get) = 1
代码注解1:python中一次open后 “只能使用一次readlines ”的误解
代码注解2:numpy中对多维数组求最大最小值
代码注解3:numpy.array 的shape属性理解
from numpy import *
#分类函数,同上
def classify0(inX, dataSet, labels, k):
# 获取旧样本集的行数(记录数)
dataSetSize = dataSet.shape[0]
"""一、根据欧式距离公式,计算输入记录与样本集中每条数据之间的距离 √(特征值a1-特征值a2)^2+(特征值b1-特征值b2)^2+(特征值c1-特征值c2)^2"""
# 1.使用tile将输入的一条记录转为dataSetSize条(注解2:tile函数)
# 2.通过矩阵来计算相减结果
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
# 3.对相减结果取平方
sqDiffMat = diffMat ** 2
# 4.对每条记录,将不同特征值相减结果的平方求和
sqDistances = sqDiffMat.sum(axis=1)
# 5.开方,得到输入记录与样本集中每条记录的距离
distances = sqDistances ** 0.5
"""二、从计算的结果中选取距离最小的前k个,判断这k个当中属于哪个标签类别的最多"""
# 1.根据距离排序从小到大的排序,并返回其在原矩阵数组对应的下标值(注解3:argsort函數)
sortedDistIndicies = distances.argsort()
# 2.选择距离最小的前k个,并算出其中的每个标签类别的出现次数
classCount = {}
for i in range(k):
# 获取第i个所属的标签类别
voteIlabel = labels[sortedDistIndicies[i]]
# 在标签类别字典中将该类别数目加1(注解4:字典的get方法)
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 3. 返回出现最多的那个类型
# 利用max函数,直接返回字典中value最大的key(注解5:max函数搭配字典用法)
maxClassCount = max(classCount, key=classCount.get)
return maxClassCount
# 读取文件中的数据并转为已有数据集矩阵
def file2matrix(filename):
"""
导入训练数据
:param filename: 数据文件路径
:return: 数据矩阵returnMat和对应的类别classLabelVector
"""
fr = open(filename)
# 获得文件中已有数据集的行数(记录数)
numberOfLines = len(fr.readlines())
# 生成对应的空矩阵,zeros(2,3)就是生成一个 2*3的矩阵,各个位置上全是 0
returnMat = zeros((numberOfLines, 3))
classLabelVector = [] # 存储将要返回的标签列表
# readlines后指针位于文档末尾,使用seek(0)将指针返回至文件首
fr.seek(0)
index = 0
for line in fr.readlines():
# str.strip()用于去除字符串首尾空格
line = line.strip()
# 以 '\t' 切割字符串
listFromLine = line.split('\t')
# 将每条记录的各个属性特征值存入矩阵
returnMat[index, :] = listFromLine[0:3]
# 每列的类别数据,就是 label 标签数据
classLabelVector.append(int(listFromLine[-1]))
index += 1
# 返回数据矩阵returnMat和对应的类别classLabelVector
return returnMat, classLabelVector
def autoNorm(dataSet):
"""
将所给数据集的各条记录的特征值归一化,消除属性之间量级不同导致的影响
:param dataSet: 数据集
:return: 归一化后的数据集normDataSet,ranges和minVals即最小值与范围,并没有用到
归一化公式:
Y = (X-Xmin)/(Xmax-Xmin)
其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。
"""
# 计算每种属性的最大值、最小值、范围
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
# 极差
ranges = maxVals - minVals
#新建全0矩阵
normDataSet = zeros(shape(dataSet))
#获取原数据集行数
m = dataSet.shape[0]
# 生成与最小值之差组成的矩阵
normDataSet = dataSet - tile(minVals, (m, 1))
# 将最小值之差除以范围组成矩阵
normDataSet = normDataSet / tile(ranges, (m, 1))
return normDataSet, ranges, minVals
# 测试约会网站,返回错误率
def datingClassTest():
# 数据集中测试集的比例为hoRatio(训练集比例=1-hoRatio)
hoRatio = 0.1
# 从文件中加载数据
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
# 归一化数据
normMat, ranges, minVals = autoNorm(datingDataMat)
# m 表示数据的行数
m = normMat.shape[0]
# 设置用于测试的样本数量
numTestVecs = int(m * hoRatio)
print('测试样本数目为:', numTestVecs)
errorCount = 0.0
for i in range(numTestVecs):
# 根据已有数据集,对100条数据逐个测试:
# 计算每个数据与原数据集中每条记录的距离,取k=3来确定分类
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
print("分类器分类结果为 %d, 真实分类为 %d" % (classifierResult, datingLabels[i]))
#将分类结果与真实分类情况作比,分了错误则错误次数+1
if (classifierResult != datingLabels[i]): errorCount += 1.0
#统计分类错误率
print("分类错误率为: %f %%" % (errorCount / float(numTestVecs)*100))
if __name__ == '__main__':
datingClassTest()