最近在优达学城学习机器学习和深度学习的课程,因此买了一本《机器学习实战》自己研究。在优达学城发现很多和我一样,非专业出身,因为兴趣或者未来想往人工智能方向发展而学习相关课程的朋友。由于编程和数学基础不扎实,学习过程中会遇到各类额外的问题。这个系列作为《机器学习实战》学习笔记,不仅关注算法实现的本身,也把过程中我自己之前不了解或者太懂的编程和数学知识做一个记录,供自己记录以及与跟我一样的朋友们沟通交流,也期望有大神能够不吝指可能出存在的各种错误。
kNN(k nearest neighbors)算法简介
概念:kNN算法采用测量不同特征值之间的距离的方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度高、空间复杂度高
(以上摘自书中)
优点里面精度高、对异常值不敏感比较好理解,何为无数据输入假定呢?查了一下,可能是指预先估计的参数,比如权值。
缺点也比较明显,因为每次分类都要先计算所有距离,所以计算量大。kNN算法是不需要事先训练模型的,但每次分类都要进行大量计算,如果数据集很大,可想而知在执行预测时效率很低。计算复杂度我没有google到,只找到了时间复杂度和空间复杂度,这里计算复杂度可能是指时间复杂度。空间复杂度指耗费的储存空间,这里我的理解是始终要储存全部数据集,而不像其他一些算法,训练一次完成,就不需要再储存之前的数据集,因此耗费储存空间大,空间复杂度高。kNN的时间和空间复杂度应该都是O(n),这两点应该都是针对分类过程,因为训练过程复杂度为0。
算法的简单实现
创建一个数据集
def createDateSet():
group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group,labels
计算距离函数
def cacu_distance_2(x,dataSet):
dif = np.tile(x,(dataSet.shape[0],1)) - dataSet
distance = ((dif**2).sum(axis=1))**0.5
return distance
分类方法
def kNN_Classifier(inX,dataSet,labels,k):
distances = cacu_distance_2(inX,dataSet)
###返回按从小到大顺序排列的列表项的下标
sortedDistIndicies = distances.argsort()
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0)+1
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
方法包含4个参数,输入inX,数据集dataSet,数据集的标签labels,k值。过程中遇到的一些问题记录如下:
1.tile()
tile()是Numpy提供的对nd-array在不同维度进行repeat的方法。
tile(A,reps),其中A是输入的array,reps是在不同维度repeat的倍数。官方例子:
>>> a = np.array([0, 1, 2])
>>> np.tile(a, 2)
array([0, 1, 2, 0, 1, 2])
>>> np.tile(a, (2, 2))
array([[0, 1, 2, 0, 1, 2],
[0, 1, 2, 0, 1, 2]])
>>> np.tile(a, (2, 1, 2))
array([[[0, 1, 2, 0, 1, 2]],
[[0, 1, 2, 0, 1, 2]]])
>>> b = np.array([[1, 2], [3, 4]]) 看这里!
>>> np.tile(b, 2)
array([[1, 2, 1, 2],
[3, 4, 3, 4]])
>>> np.tile(b, (2, 1))
array([[1, 2],
[3, 4],
[1, 2],
[3, 4]]) 一直到这里!
>>> c = np.array([1,2,3,4])
>>> np.tile(c,(4,1))
array([[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4]])
注意,这里reps的维度是最后的值是最低维度,依次向前升高。默认值是-1,也就是最低维度。代码里我标注的地方看一下就能理解了。
通过tile将输入值变成与数据集相同的形状,就可以直接进行数学计算了,在numpy里直接使用符号计算,都是对每个元素单独计算或两个array的对应位置进行计算。
2.argsort()
这个方法也是今天才知道,用在这里简直是恰到好处。简单来说就是对nd-array里的值进行排序,并返回排序后的下标。举例说明:
>>> x = np.array([3, 1, 2])
>>> np.argsort(x)
array([1, 2, 0])
还可以对2维的nd-array使用
>>> x = np.array([[0, 3], [2, 2]])
>>> x
array([[0, 3],
[1, 2]])
>>>
>>> np.argsort(x, axis=0)
array([[0, 1],
[1, 0]])
>>>
>>> np.argsort(x, axis=1)
array([[0, 1],
[0, 1]])
axis表示在不同维度进行排序,0就是每行的同一列的值进行排序,1就是每列的同一行的值进行排序。
3.iteritems(), items()
字典对象通过该方法返回一个包含(key,value)的list。举例说明:
dict = {'Name': 'Zara', 'Age': 7}
print "Value : %s" % dict.items()
output:Value : [('Age', 7), ('Name', 'Zara')]
python3里用items()替代了iteritems(),继续使用iteritems()会报错
4.通过Counter()实现
前两天刚好在深度学习里用到Counter()类,于是试一下是否能简化代码。代码如下:
from collections import Counter
def kNN_Classifier_2(inX,dataSet,labels,k):
distances = cacu_distance_2(inX,dataSet)
sortedDistIndicies = distances.argsort()
classCount = Counter()
for i in range(k):
classCount.update([labels[sortedDistIndicies[i]]])
print(classCount)
return classCount.most_common(1)[0][0]
个人感觉使用Counter()类代码简洁了一些,主要是少了很多参数:)。多引入一个类,不知道具体运算效率是否有影响。注意这里update时,不能写成:
classCount.update(labels[sortedDistIndicies[i]])
否则会对每个字符单独计数。测试的话,可以把label改成单词,不用一个单独的大写字母。
第一篇笔记差不多到此为止,后面还会更新书里两个kNN的实例。以后要坚持记录,切忌不懂装懂,与各位共勉。