三、k近邻法
- K-NN是一个基本的分类和回归方法,此处讨论基本的分类问题
- 输入为实例的特征向量,对应于特征空间的点,输出是实例的类别,可以取多个类
- k近邻是假设给定训练数据集,其中的实例类别已定,分类时根据k个最近邻的训练实例类别通过
多数表决
的方式进行预测,并没有显式的学习过程 - k值的
选择
、距离度量
和分类决策规则
是k近邻法的3个基本要素
3.1 k近邻算法
- 特殊的情况是k=1时,此时为最近邻算法
- k近邻算法没有显式的学习过程
3.2 k近邻模型与决策规则
3.2.1 模型
- k近邻模型主要有3个基本要素:距离度量、k值选择和分类决策规则,确定好上述后,给定一个输入实例,就可以唯一地确定它所属类
- 在特征空间当中,每一个训练实例点xi,距离该点比其他点更近的所有点组成的一个区域叫做
单元(cell)
,一个单元构成对于特征空间的一个划分- 最近邻算法是实例xi的类yi作为其单元中所有点的
类标记
(class label),从而每个单元实例点的类别可以确定,见下图所示
- 最近邻算法是实例xi的类yi作为其单元中所有点的
3.2.2 距离度量
- 两个实例点的
距离
反应的是两个实例点的相似程度,k近邻特征空间一般是R^n,距离使用的是欧式距离
,但更一般的是Lp距离
或Minkowski距离
,定义如下- 当p=2时,为欧式距离,当p=1时,为曼哈顿距离,当p=∞时,就是得到坐标距离的最大值
给出一个计算最近邻点的例子
- 这里需要注意的是,实际应用的时候我们需要进行
归一化
后,数据特征才会有意义- 比如(5, 10000)和(1, 10000)的数据,直接使用距离度量比较,x的偏移特征将会微乎其微
3.2.3 k值选择
- k值是一个超参数,后续在做实验的时候,特征工程是关键一环
- k值如果过小,那么几何意义上就是用较小的邻域来进行预测,近似误差过小,模型会过于复杂,容易
过拟合
- k值如果过大,那么几何意义上就是用较大的邻域来进行预测,近似误差过大,模型会过于简单,容易
欠拟合
- 一般我们会选择先取一个较小值,然后使用
交叉验证法
来选取最优的k值
3.2.4 分类决策规则
- 往往采用多数表决机制,即选取k个近邻实例中多数的类别,这等价于经验风险最小化,数学解释如下
3.3 k近邻算法实现_kd树
- 实现k近邻算法最简单的方式是线性扫描,但需要计算输入实例和每个训练实例的距离,训练集大时,计算会很耗时,因此不可行
- 所以提出使用k近邻算法提高k近邻搜索的效率
3.3.1 构造kd树
kd树
是一个二叉树,表示对k维空间的一个划分,构造kd树相当于不断地使用垂直于坐标轴的超平面将k维空间切分,构成一系列k维超矩形区域,kd树的一个结点对应于一个k维超矩形的区域- 构造kd树的方法
- 构造根节点,对应于包含所有实例点的超矩形区域
- 通过下述方式进行递归,不断的对k维空间进行切分,生成子结点
- 选择一个坐标轴,通过选定的切分点并垂直于选定的坐标轴上的切分点,确定一个超平面,该平面能够切分为左右两个子区域
(通常是依次选择坐标轴,然后选择中位数切分,注意,平衡的kd树未必是最优) - 直至最终子区域没有节点为止,此时终止的结点为叶子节点
- 选择一个坐标轴,通过选定的切分点并垂直于选定的坐标轴上的切分点,确定一个超平面,该平面能够切分为左右两个子区域
给出一个具体的例子
3.3.2 搜索kd树
最近邻搜索
以最近邻为例加以描述,我们的目标是给定目标点,搜索最近邻
- 首先需要找到包含目标点的叶子结点,然后从叶子节点触发,依次回退到父节点
- 不断查找与目标点最邻近的节点,当确定不可能存在更近的结点时终止,这样搜索就会限制在空间的局部空间上,效率大幅提高
如下图所示,包含目标点的叶子结点对应于目标点的最小超矩形区域,目标点的最邻近一定是在以目标点为中心并通过当前点的超球体的内部,分为如下情况
- 如果父结点的另一节点的超矩形区域于超球体相交,那么在相交区域会寻找是否存在更近的实例点,如果存在,就将该点作为新的最近点,转到更上一级的父节点
- 如果父结点另一结点超矩形区域与超球体不相交,那么不存在比当前点更近的点,停止搜索
具体的算法描述如下:
上述图片的例子
k近邻搜索
对于一般情况下 k近邻的算法详细搜索过程可见:https://zhuanlan.zhihu.com/p/23966698
核心算法思路:一开始我们先 设 L 为一个有 k 个空位的列表,用于保存已搜寻到的最近点。
- 一、根据 p 的坐标值和每个节点的切分向下搜索(也就是说,如果树的节点是照 xr=a 进行切分,并且 p 的 r 坐标小于 a,则向左枝进行搜索;反之则走右枝)。
- 二、当达到一个底部节点时,将其标记为访问过,然后根据下述规则判断是否加入列表。
- 如果 L 里不足 k 个点,则将当前节点的特征坐标加入 L ;
- 如果 L 不为空并且当前节点的特征与 p 的距离小于 L 里最长的距离,则用当前特征替换掉 L 中离 p 最远的点。
- 三、如果当前节点不是整棵树最顶端节点,执行 (a);反之,输出 L,算法完成。
- a. 向上爬一个节点。如果当前(向上爬之后的)节点未曾被访问过,将其标记为被访问过,然后执行 (1) 和 (2);如果当前节点被访问过,再次执行 (a)。
- \1. 首先根据列表的情况进行判断是否加入该节点
- 如果此时 L 里不足 k 个点,则将节点特征加入 L;
- 如果 L 中已满 k 个点,且当前节点与 p 的距离小于 L 里最长的距离,则用节点特征替换掉 L 中离最远的点。
- \2. 计算 p 和当前节点切分线的距离(李航老师是根据超球体是否相交的方式判断,思路相同但实现不同)。
- 如果该距离大于等于 L 中距离 p 最远的距离并且 L 中已有 k 个点,则在切分线另一边不会有更近的点,执行 (三);
- 如果该距离 小于 L 中最远的距离 或者 L 中不足 k 个点,则切分线另一边可能有更近的点,因此在当前节点的另一个枝从 (一) 开始执行。
- \1. 首先根据列表的情况进行判断是否加入该节点
- a. 向上爬一个节点。如果当前(向上爬之后的)节点未曾被访问过,将其标记为被访问过,然后执行 (1) 和 (2);如果当前节点被访问过,再次执行 (a)。
大概就探讨这个层面吧,暂时就不深究具体代码实现了,后续就是用现成的机器学习包 scikit-learn 来进行计算,做一名合格的调参侠