sklearn.neighbors提供基于邻居的有监督和无监督的学习方法。无监督最近邻方法是很多学习方法的基础,特别是流形学习和谱聚类。有监督的最近邻方法包括:离散数据的分类、连续数据的回归。
最近邻方法的原理是,找到指定数量的最近样本点,然后根据这些点去预测新的点。样本点的数量可以由用户定义(k-最近邻)或者基于点的局部密度。距离度量标准可以有很多种,欧式距离是最常用的选择。基于邻居的方法被称为non-generalizing machine learning,因为它只是“记住”训练数据(可能转化为一个快速的索引结构,如BallTree或KDTree)。
尽管很简单,但是最近邻方法能解决大量分类和回归问题,包括手写数字或卫星图像识别,作为一种非参数方法,在分类边界不规则的情况下通常是有效的。
无监督的最近邻
NearestNeighbors 执行无监督的最近邻方法,有三种不同的最近邻算法:BallTree、KDTree、a brute-force algorithm based on routines in sklearn.metrics.pairwise,邻居的搜索算法通过关键词 ‘algorithm’ 控制,选项包括['auto', 'ball_tree', 'kd_tree', 'brute'],当设置为‘auto’时,算法将通过训练数据决定最好的方法。
Warning:在最近邻算法中,当有两个点和预测点的距离相同但标签不同时,结果将依赖点在训练数据中的顺序。
KDTree和BallTree
可以使用KDTree或BallTree直接发现最近邻。
KDTree和BallTree的详细解释:
http://blog.csdn.net/skyline0623/article/details/8154911
最近邻分类
基于邻居的分类是一种基于实例的学习或者非泛化学习(a type of instance-based learning or non-generalizing learning),它不试图构建一个通用的内部模型,只是简单的存储训练数据的实例。通过每个点的最近邻的简单多数投票来分类,一个查询点被分配给它的最近邻中最有代表性的那个数据类。
scikit-learn 包括两种最近邻分类器:KNeighborsClassifier 和 RadiusNeighborsClassifier。KNeighborsClassifier是比较常用的一种,一般的,一个比较大的k值能抑制异常值的影响,但是也让分类边界的区分性下降。
如果是不均匀采样数据,RadiusNeighborsClassifier中的基于半径的最近邻分类是一个比较好的选择。用户指定一个固定的半径 r ,那些稀疏区域中的点使用较少的最近邻去分类,在高维参数空间,由于所谓的“维灾难”,这种方法就变得不那么有效。
基本的最近邻分类器使用一致权重,即采用最近邻数据的简单多数表决。在某些情况下,更近的数据点对模型的贡献更大,可以通过参数 weights 来设置。默认情况下,weights=‘uniform’,所有邻居的权重是相同的。weights=‘distance’ ,权重正比于到查询点的距离的倒数。另外,用户也可以自定义函数计算权重。
最近邻回归
当数据标签是离散的而不是连续的,可以使用最近邻回归方法。查询点的标签通过最近邻点的平均标签计算。
scikit-learn有两种最近邻回归方法:KNeighborsRegressor 和 RadiusNeighborsRegressor。
最近邻算法
Brute Force
快速计算最近邻是机器学习中一个活跃的研究领域。最简单的方法是计算数据集中每两个点之间的距离,在小型数据集上,brute-force很有竞争力,然而随着样本数的增长,brute-force变得不可行。
KDTree
为了解决brute-force方法计算效率低下,发明了各种基于树的数据结构。一般情况下,这些结构通过有效编码汇总样本距离信息,来减少所需的距离计算量。基本思想是,如果A离B比较远,B 离C比较近,所以A 离C比较远,而不用明确计算。
构建k-d树(createKDTree)
输入:数据点集Data-set和其所在的空间Range
输出:Kd,类型为k-d tree
1.If Data-set为空,则返回空的k-d tree
2.调用节点生成程序:
(1)确定split域:对于所有描述子数据(特征矢量),统计它们在每个维上的数据方差。
以SURF特征为例,描述子为64维,可计算64个方差。挑选出最大值,对应的维就是split域的值。
数据方差大表明沿该坐标轴方向上的数据分散得比较开,在这个方向上进行数据分割有较好的分辨率;
(2)确定Node-data域:数据点集Data-set按其第split域的值排序。
位于正中间的那个数据点被选为Node-data。此时新的Data-set' = Data-set\Node-data(除去其中Node-data这一点)。
3.dataleft = {d属于Data-set' && d[split] ≤ Node-data[split]}
Left_Range = {Range && dataleft} dataright = {d属于Data-set' && d[split] > Node-data[split]}
Right_Range = {Range && dataright}
4.left = 由(dataleft,Left_Range)建立的k-d tree,即递归调用createKDTree(dataleft,Left_
Range)。并设置left的parent域为Kd;
right = 由(dataright,Right_Range)建立的k-d tree,即调用createKDTree(dataright,Right_
Range)。并设置right的parent域为Kd。
查找算法
从root节点开始,DFS搜索直到叶子节点,同时在stack中顺序存储已经访问的节点。
如果搜索到叶子节点,当前的叶子节点被设为最近邻节点。
然后通过stack回溯:
如果当前点的距离比最近邻点距离近,更新最近邻节点.
然后检查以最近距离为半径的圆是否和父节点的超平面相交.
如果相交,则必须到父节点的另外一侧,用同样的DFS搜索法,开始检查最近邻节点。
如果不相交,则继续往上回溯,而父节点的另一侧子节点都被淘汰,不再考虑的范围中.
当搜索回到root节点时,搜索完成,得到最近邻节点。
选择方差最大的维度作为当前节点的划分维度,方差越大,说明这个维度上的数据波动越大,也就说明了他们就越不可能属于同一空间,需要在这个维度上对数据点进行划分。KDTree在维度小于20的情况下搜索是非常快的。
BallTree
为了解决高维问题,发明了BallTree。KDTree沿着笛卡尔轴划分数据,BallTree使用超球面。虽然建立树的成本大于KDTree,但是在高维数据上非常有效。
BallTree 递归地将数据划分成一个质心C和半径R定义的节点,使得每个点位于由R和C定义的超球体内。通过使用三角不等式减少搜索次数。
有了这个设置,测试点和质心之间的距离计算,已经足够确定测试点和这个节点内所有点的距离的上界和下界。由于BallTree 节点的球形几何形状,它可以执行高维的KD树,但实际的性能是高度依赖于训练数据的结构。
最近邻算法选择
选择最佳算法依赖很多因素:
- 样本数N和维度D
1.Brute force 时间复杂度 O(D N),
2.BallTree 时间复杂度 O(D log(N)),
3.KDTree,if D<20 ,O(D log(N));else O(D N)。
在样本数小于30的情况下,Brute force比BallTree和KDTree高效。 - 数据结构
数据内在维度和数据稀疏性。数据内在维度(秩)小于数据维度,参数空间是存在线性或非线性关系。稀疏性指数据填充参数空间的程度(这与“稀疏”矩阵中的概念区别开来)。数据矩阵可能没有零项,但结构仍然可以是“稀疏”在这个意义上说。
1.Brute force 与数据结构无关。
2 BallTree和KDTree在低内在维度的稀疏数据上将很快。 - K值
1.Brute force 不受影响,
2 BallTree和KDTree 随着K值的增大而变慢,首先,更大的K值导致需要搜索更大的参数空间,Second, using k > 1 requires internal queueing of results as the tree is traversed. - 查询数量
BallTree和KDTree都需要一个创建过程,当有大量查询时,创建的开销可以忽略。如果只是少量的查询,则Brute force 较好。
叶子大小
Brute force 在小样本上比树结构更高效,这解释了BallTree和KDTree 在叶子节点内部转换到Brute force 搜索。这种转换可以通过参数 leaf_size 设置,这个参数有很多影响:
创建时间:比较大的leaf_size ,创建比较快,因为需要创建的节点变少;
查询时间:默认 leaf_size=30
内存:随着leaf_size 增加,存储一棵树所需要的内存是下降的,在BallTree中这非常重要。BallTree 需要的内存空间近似是训练数据规模的1/leaf_size。