很多通俗语言描述,不严谨,请见谅。
详细理论有机会再补充。
未完待续…
- kNN不需要确定 模型 与 训练.
本节使用到的python与numpy函数
- 属性 dataSet.shape
dataSet是一个numpy array 数据集,dataSet.shape返回元组,元组每个位置表示对应维度的大小。
a = np.array([[[1.0, 1.1], [1.1, 1.0], [0, 0], [0, 0.1]]])
print(a)
print(a.shape)
[[[1. 1.1]
[1.1 1. ]
[0. 0. ]
[0. 0.1]]]
(1, 4, 2)
- 函数 np.tile(array,(m,n))
tile就是瓷砖的意思,顾名思义,就是将array重复m行n列。
注意: 当第二个变量只是一个数时,默认横向复制。
arr = np.array([1, 2])
arr1 = np.tile(arr, 3)
arr2 = np.tile(arr, (1, 3))
arr3 = np.tile(arr, (3, 2))
[1 2 1 2 1 2]
[[1 2 1 2 1 2]]
[[1 2 1 2]
[1 2 1 2]
[1 2 1 2]]
- 方法 arr.sum(axis = n)
arr 是一个numpy array,该方法表示对arr的第n个维度求和,求和后返回的array会小一个维度。
按照第一个维度求和举例: ( a ) i j k (a)_{ijk} (a)ijk是一个 ( m , n , l ) (m,n,l) (m,n,l)数组,对每一对 ( j , k ) (j,k) (j,k),在第一维上求和
∑ i m ( a i , j , k ) \sum_{i}^m(a_{i,j,k}) i∑m(ai,j,k)
简单来说,对第一维求和就是其他两个维度位置固定,按第一维的下标进行求和。
numpy数组最里面的括号维度最小,最外面的括号维度最大。
a = np.array([[[1.0, 1.1], [1.1, 1.0], [0, 0], [0, 0.1]]])
print(a)
print('sum:', a.sum(axis=1), sep='\n')
[[[1. 1.1]
[1.1 1. ]
[0. 0. ]
[0. 0.1]]]
sum:
[[2.1 2.2]]
- 方法 arr.argsort() 或 函数np.argsort()
看到sort说明这个方法关于排序,看到arg说明这个方法可能返回的时排序的index。
np.argsort(a, axis=-1, kind=‘quicksort’, order=None)
函数功能: 将a中的元素从小到大排列,提取其在排列前对应的index(索引)输出。
作用: 说明按照argsort给的index顺序对原有的数组进行索引,会得到原数组的递增序列。
rand_arr = np.random.randint(1, 10, 10)
print("original:", rand_arr, sep='\n')
sorted_indx = rand_arr.argsort()
print("sorted index:", sorted_indx, sep='\n')
# 用sorted_indx提取ran_arr最小的5个元素
print("extract:")
[print(rand_arr[sorted_indx[i]],end=' ') for i in range(5)]
original:
[6 4 2 1 4 4 1 4 3 2]
sorted index:
[3 6 2 9 8 1 4 5 7 0]
extract:
1 1 2 2 3
- 字典方法dict.get(key[,value])
key为指定键值
value是可选参数,若指定键值不存在时则返回value或者None。
注意: 如果字典dict已经有key对应的键值对,那么不管给不给value传参,都返回原value。如果没有key这个键,并且不给value传参,则返回None。如果没有key这个键,给value传参,就会创建key:value这个键值对,并且返回这个刚传的value。
mydict={'name':'naruto','age':24}
print("age:%s" % mydict.get('age'))
print("sex:%s" % mydict.get('sex'))
print('salary:%s' % mydict.get('salary',-999))
age:24
sex:None
salary:-999
该例子来自:https://blog.csdn.net/qq_40061206/article/details/124979462
- 函数 operator.itemgetter()
operator是一个模块,operator.itemgetter()返回的是一个callable对象/函数对象,功能和索引的功能很像。给这个对象传入一个可迭代对象,比如列表,返回指定索引下的值。
tup1 = (1, 2, 3, 4, 5, 6)
inx2_getter = operator.itemgetter(2) # 提取可迭代对象的第3个元素
print(inx2_getter(tup1))
3
作用: 经常与sorted()函数一起用。
sorted(iterable,key,reverse),key参数可传入一个自定义函数,可以是operator.itemgetter(2),意思就是以第三个元素的值为基准,进行排序。
经常用于 元组列表 和 字典 的排序。
students = [('james', 12, 'm'), ('tom', 13, 'm'), ('lucy', 10, 'f')]
sorted_stus = sorted(students, key=operator.itemgetter(1), reverse=True)
print(sorted_stus)
[('tom', 13, 'm'), ('james', 12, 'm'), ('lucy', 10, 'f')]
再看看sorted的另一个例子吧。
tup_list = [(1,), (1, 2), (1, 2, 3), (1, 2, 3, 4)]
sorted_list = sorted(tup_list, key=len, reverse=True)
print(sorted_list)
[(1, 2, 3, 4), (1, 2, 3), (1, 2), (1,)]
利用python实现kNN算法
伪代码
输入:数据集,数据集的已知类别,k,要分类的点
- 计算已知类别数据集中的点和要分类的点之间的距离;
- 将距离递增排序;
- 选取距离最小的k个点;
- 确定这k个点对应类别出现的频率;
- 返回频率最高的类别。
classify0()函数
自己写的
def classify0(inX, dataSet, labels, k):
"""
:kNN分类函数
:param inX: 需要分类的向量
:param dataSet: 训练样本集
:param labels: 与训练集对应的标签向量
:param k: 选择最邻近的数目
:return: inX的类型
这里dataSet的数据类型肯定是numpy array,labels的数据类型可以是python list也可以是np array"""
# 以下代码块计算距离
# 这里距离用欧式距离,即两点分量差的平方和开根号
dataSetSize = dataSet.shape[0]
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # diffMat存储每一个样本数据(向量)和inX(向量)的差
sqDiffMat = diffMat ** 2 # 每一个分量的平方
distances = sqDiffMat.sum(axis=1) ** 0.5 # 求行和然后每个分量开根号
# 以下代码块将距离排序,选择距离最小的k个点
sortedIndicies = distances.argsort()
label_list = [labels[sortedIndicies[i]] for i in range(k)] # 提取距离最小的k个数据点的label
class_count = {}
for x in label_list:
class_count[x] = class_count.get(x, 0) + 1 # 字典存储label出现的频率
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # 将频率排序
print(sorted_class_count)
return sorted_class_count[0][0]
书上的
def classify0(inX, dataSet, labels, k):
# 以下代码块计算距离
# 这里距离用欧式距离,即两点分量差的平方和开根号
dataSetSize = dataSet.shape[0]
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # diffMat存储每一个样本数据(向量)和inX(向量)的差
sqDiffMat = diffMat ** 2 # 每一个分量的平方
distances = sqDiffMat.sum(axis=1) ** 0.5 # 求行和然后每个分量开根号
# 以下代码块将距离排序,选择距离最小的k个点
sortedIndicies = distances.argsort()
class_count = {}
for i in range(k):
voteIlabel = labels[sortedIndicies[i]]
class_count[voteIlabel] = class_count.get(voteIlabel, 0) + 1
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # 元组列表
return sorted_class_count[0][0]
感想与总结
-
数据集dataSet的数据类型肯定是numpy array,因为需要做计算。标签向量的数据类型没有严格要求,可以是python list,也可以是numpy array;并且标签本身的数据类型也可以是整数或者字符。
-
排序使用argsort(),不直接用sorted和sort的原因:想保留原始数据的样子;对数据集进行排序的话,相应的标签lables也要同步操作,但是这两个数据是分开的两个对象,所以用index会比较方便。
-
注意: 字典本身不是可迭代对象,dict.itmes()/ dict.keys() / dict.values() 才是。
-
自己写的 和 书上的 主要区别在于,书上将 提取距离最小的k个点的标签 和 计算频率 一起在一个循环里实现了.
-
记住下面计算频率,并记录于字典的写法:
for i in range(k):
voteIlabel = labels[sortedIndicies[i]]
class_count[voteIlabel] = class_count.get(voteIlabel, 0) + 1
- 记住下面字典按值排序的写法:
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
- 其他
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # 元组列表
注意
class_count.items()
class_count[voteIlabel] = class_count.get(voteIlabel, 0) + 1
★这行代码集 初始化0+计数 于一体.
class_count.get(voteIlabel, 0)
字典的get()方法可以好好利用.