这是第一篇,非常感谢宋志瑞给予我的鼓励,当然我不会跟他说谢谢,但是真的很感激,也很感激一直努力的自己!
本文并没有写比较基础的东西,也没有详细的展开,只是做一个自己学习过程中思路的总结
1、理论知识
1.1、算法简述
k近邻算法(以下称k-NN)是一种基本的分类和回归算法,这里只写分类问题中的k-NN;k-NN采用测量不同特征值之间的距离方法来进行分类,也就是利用数据集对对特征向量空间进行划分。
K-NN:
优点:精度高、对异常值不敏感、无输入数据假定
缺点:计算复杂度高、空间复杂度高
可应用案例:手写数字识别、鸢尾花分类、预测年收入、疾病诊断等一般分类问题
1.2、K-NN算法原理:
要预测一个样本的类别,首先在训练数据集中找到与该样本距离最近的k个实例,然后让这k个实例投票,得票最多的那个类别就是该样本的类别。
算法原理简单容易理解,但是我们看看kNN算法相关的参数问题:
1. 距离问题:
kNN算法一般使用Euclidean距离:
d i s t ( X , Y ) = ∑ i = 1 n ( x i − y i ) 2 dist(X, Y)=\sqrt{\sum^{n}_{i=1}(x_{i}-y_{i})^{2}} dist(X,Y)=i=1∑n(xi−yi)2
当然还有其他距离,Minkowski距离:
d
i
s
t
(
X
,
Y
)
=
∑
i
=
1
n
(
x
i
−
y
i
)
p
p
dist(X, Y)=\sqrt[^p]{\sum^{n}_{i=1}(x_{i}-y_{i})^{p}}
dist(X,Y)=pi=1∑n(xi−yi)p
当p=1时:Manhattan distance
当p=2时:Euclidean distance
当p=inf时:Chebyshev distance
当p=any时:你自己指定>1的数值
2. k值问题:
- k值一般选择奇数(投票时候容易得到结果),如果k=1,称为最邻近算法
- 在实际应用中,k值一般选择较小的值,通常采用交叉验证法,选取最终的k值
sklearn Module的kNN算法默认k=5,一般可以从5开始尝试。
- k值选择还有一个权重问题,根据离待分类样本距离增加权重值,距离越近,权重越大。
从下面这个图片可以看出来k值选择的重要性:
如果k=3(实线),那么绿色圆点属于红色一类
如果k=5(虚线),那么绿色圆点属于蓝色一类
1、选择较小的k值,学习的近似误差会减小,缺点是学习的估计误差会增大。换句话说,k值的减小就意味着整体模型变得复杂,容易发生过拟合。
2、选择较大的k值,其优点是可以减少学习的估计误差,缺点是会增大学习的近似误差,k值增大,就意味着整体模型变得简单。如果k=N,那么无论输入什么,都将会把他预测为数据集中样本最多的那个类,这时候,模型就过于简单了,忽略了数据集中大量的有用信息。
因此,一定要用交叉验证法来选择合适的k值。
3. 样本数值问题:
样本属性不同,数值会有很大的差异,一般会做归一化处理,这样预测结果更准确。
归一化有两种方法
- x_norm = (x - min) / (max - min) 归一化为(0~1)
- x_norm = (x - mean) / std 归一化为正态分布
4. 样本数量问题:
样本不均衡也会对预测结果产生影响,这个不均衡要看实际情况,如果样本比较少,不同类别相差1个都是不均衡的;如果样本比较多,相差1000个也是无所谓的。如果要使样本均衡可以使用上采样或者下采样方法方法。
# 举例:上采样方法
from imblearn.over_sampling import SMOTE
sm = SMOTE()
X2_train, y2_train = sm.fit_resample(X_train, y_train)
2、代码实战
2.1、代码模拟:
这里只写预测一个向量的情况进行模拟,主要是为了方便理解原理,实际中很少这么用。
class KNearestNeighbors(object):
# 这里用大X,虽然不符合PEP8,目的是同数学中矩阵一致,因为训练数据是矩阵
def __init__(self, X_trian, y_train, k):
self.X_train = X_trian
self.y_train = y_train
self.k = k
def classify(self, X_test):
sample_size = self.X_train.shape[0]
X_test_matrix = np.tile(X_test, (sample_size, 1))
distance1 = (X_test_matrix - self.X_train)**2
distance2 = distance1.sum(axis=1)
distance = distance2**0.5
distance_sort_arg = np.argsort(distance)
class_count = {}
for i in range(self.k):
vote_label = self.y_train[distance_sort_arg[i]]
class_count[vote_label] = class_count.get(vote_label, 0) + 1
sorted_class = sorted(class_count.items(), key=lambda x: x[1], reverse=True)
return sorted_class[0][0]
这里写的是最基础的分类方式,实际在分类中,这种Brute force的方法对于小样本量还可以,样本量和特征增大之后,效率就很低下,使分类变得不切实际,现在已经发明了大量的基于树的数据结构,比如kd-tree和ball-tree等,简单写一下我的理解:
1、kd-tree就是划分训练集的空间,将训练集分成方形的小空间。数据集的维数为
D
D
D ,一般来说要求数据的规模
N
N
N 满足
N
»
2
D
N» 2^D
N»2D,使用kd-tree才能达到高效的搜索。一般来说,数据集的维数小于20,使用kd-tree,如果维数大于20,kd-tree的搜索性能急剧下降,几乎接近贪婪线性扫描;
2、于是有了一系列的算法改进kd-tree,BBF(Best-Bin-First),BBF在kd-tree检索时候将另一个兄弟节点存储到一个优先队列中,在回溯的时候,先查询优先队列的节点,直至优先队列为空;
3、无论是kd-tree还是BBF都是把空间分成了有棱角的空间,会造成不必要的过多检索,因此有ball-tree;
4、高维特征向量的数据集,有VP-tree和MVP-tree
参考:
从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
2.2、使用模块:
使用sklearn库中的方法,
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train, y_train)
score = knn.score(X_test, y_test)
y_ = knn.predict(X_test)