赞
踩
1. K—近邻算法(KNN)的概念
K Nearest Neighbor 算法又称KNN算法,这个算法是机器学习里一个比较经典的算法,总体来说是比较简单的。
1)定义
一个样本与在特征空间中k个最相似(即样本空间中距离最近)的大多数样本属于同一个类别。
来源:KNN算法最早是由Cover和Hart提出的一种分类算法。
2)距离公式
两个样本的距离可以通过如下公式计算,又叫欧式距离:
2. 电影类型分析
假设我们现在有很多部电影。
序号 | 电影名称 | 搞笑镜头 | 拥抱镜头 | 打斗镜头 | 电影类型 |
---|---|---|---|---|---|
1 | 功夫熊猫 | 39 | 0 | 31 | 喜剧片 |
2 | 叶问3 | 3 | 2 | 65 | 动作片 |
3 | 二次曝光 | 2 | 3 | 55 | 爱情片 |
4 | 代理情人 | 8 | 34 | 17 | 爱情片 |
5 | 新步步惊心 | 8 | 34 | 17 | 爱情片 |
6 | 谍影重重 | 5 | 2 | 57 | 动作片 |
7 | 美人鱼 | 21 | 17 | 5 | 喜剧片 |
8 | 宝贝当家 | 45 | 2 | 9 | 喜剧片 |
9 | 唐人街探案 | 23 | 3 | 17 | ? |
其中9号电影不知道类别,如何去预测?我们就可以用到K近邻算法的思想,其中每一个样本都可以看做是一个n维向量,使用公式 d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\sum \limits_{k=1}^n(x_{1k}-x_{2k})^2} d12=k=1∑n(x1k−x2k)2 来计算9号电影和其余样本的相似程度。
序号 | 电影名称 | 搞笑镜头 | 拥抱镜头 | 打斗镜头 | 电影类型 | 与9号电影的距离 | K=5时 |
---|---|---|---|---|---|---|---|
1 | 功夫熊猫 | 39 | 0 | 31 | 喜剧片 | 21.47 | 相似 |
2 | 叶问3 | 3 | 2 | 65 | 动作片 | 52.01 | |
3 | 二次曝光 | 2 | 3 | 55 | 爱情片 | 43.42 | |
4 | 代理情人 | 8 | 34 | 17 | 爱情片 | 40.57 | 相似 |
5 | 新步步惊心 | 8 | 34 | 17 | 爱情片 | 34.44 | 相似 |
6 | 谍影重重 | 5 | 2 | 57 | 动作片 | 43.87 | |
7 | 美人鱼 | 21 | 17 | 5 | 喜剧片 | 18.55 | 相似 |
8 | 宝贝当家 | 45 | 2 | 9 | 喜剧片 | 23.43 | 相似 |
找出了K个距离最近的电影,此时K=5,就找了5个最相似的电影。
3. KNN算法流程总结
1)计算已知类别数据集中的点与当前点之间的距离
2)按照距离递增次序排序
3)选取与当前点距离最小的K个点
4)统计前K个点所在类别出现的频率
5)返回前K个点出现频率最高的类别作为当前点的预测分类
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5)
:参数只有一个n_neighbors
,为int
类型,默认值为5,表示查询默认使用的邻居数。
下面我们用一个案例来使用以下这个函数。
1. 步骤分析
1)获取数据集
2)数据基本处理(该案例中省略)
3)特征工程(该案例中省略)
4)机器学习
5)模型评估(该案例中省略)
2. 代码实现
# 1. 导入模块 from sklearn.neighbors import KNeighborsClassifier # 2. 构造数据集 x = [[1], [2], [10], [20]] # 特征值 y = [0, 0, 1, 1] # 目标值 # 3. 机器学习 —— 模型训练 # 3.1 实例化api estimator = KNeighborsClassifier(n_neighbors=1) # 3.2 使用fit方法进行训练 estimator.fit(x, y) # 3.3 进行预测 ret = estimator.predict([[1]]) # 打印预测结果 print(ret)
输出:
[0]
1. 欧式距离(Euclidean Distance)
1)二维平面上点a(x1, y1)与b(x2, y2)的欧式距离: d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2} d12=(x1−x2)2+(y1−y2)2
2)三维空间点a(x1, y1, z1)与b(x2, y2, z2)的欧氏距离: d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} d12=(x1−x2)2+(y1−y2)2+(z1−z2)2
3)n维空间点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的欧式距离(两个n维向量): d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\sum \limits_{k=1}^n(x_{1k}-x_{2k})^2} d12=k=1∑n(x1k−x2k)2
2. 曼哈顿距离(Manhattan Distance)
在曼哈顿街区,要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”,曼哈顿距离也称为“城市街区距离”(City Block distance)。
二维平面两点a(x1, y1)与b(x2, y2)间的曼哈顿距离: d 12 = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d_{12}=|x_1-x_2|+|y_1-y_2| d12=∣x1−x2∣+∣y1−y2∣
n维空间点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的曼哈顿距离: d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ d_{12}=\sum \limits_{k=1}^n|x_{1k}-x_{2k}| d12=k=1∑n∣x1k−x2k∣
3. 切比雪夫距离(Chebyshev Distance)
国际象棋中,国王可以直行、横行、斜行,所以国王走一步可以移动到相邻8个方格中的任意一个。国王从格子(x1, y1)走到格子(x2, y2)最少需要多少步?这个距离就叫切比雪夫距离。
4. 闵可夫斯基距离(Minkowski Distance)
闵氏距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。两个n维向量a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的闵可夫斯基距离定义为:
d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ p p d_{12}=\sqrt[p]{\sum \limits_{k=1}^n|x_{1k}-x_{2k}|^p} d12=pk=1∑n∣x1k−x2k∣p
其中p是一个变量参数:
根据p的不同,闵氏距离可以表示某一类/种的距离。
1)注意:闵氏距离、曼哈顿距离、欧氏距离、和切比雪夫距离都存在明显的缺点:
2)闵氏距离的特点:
5. 标准化欧氏距离(Standardized EuclideanDistance)
标准化欧氏距离是针对欧氏距离的缺点进行的一种改进。思路:既然数据各分量的分布不一样,那就先将各个分量都“标准化”到均值,方差相等。
s k s_k sk表示各个维度的标准差:
如果将方差的倒数看成一个权重,也可以称之为加权欧氏距离(Weighted Euclidean distance)。
6. 余弦距离(Consine Distance)
几何中,夹角余弦可用来衡量两个向量方向的差异。机器学习中,借用这一概念来衡量样本之间的差异。
夹角余弦取值范围为[-1, 1]。余弦越大表示两个向量的夹角越小,余弦越小表示两向量的夹角越大。当两个向量的方向重合时,余弦取最大值1;相反时,取最小值-1。
1. 直观解释:
序号 | 电影名称 | 搞笑镜头 | 拥抱镜头 | 打斗镜头 | 电影类型 |
---|---|---|---|---|---|
1 | 功夫熊猫 | 39 | 0 | 31 | 喜剧片 |
2 | 叶问3 | 3 | 2 | 65 | 动作片 |
3 | 二次曝光 | 2 | 3 | 55 | 爱情片 |
4 | 代理情人 | 8 | 34 | 17 | 爱情片 |
5 | 新步步惊心 | 8 | 34 | 17 | 爱情片 |
6 | 谍影重重 | 5 | 2 | 57 | 动作片 |
7 | 美人鱼 | 21 | 17 | 5 | 喜剧片 |
8 | 宝贝当家 | 45 | 2 | 9 | 喜剧片 |
9 | 唐人街探案 | 23 | 3 | 17 | ? |
就比如有一个电影和唐人街探案的距离很近,但是这个电影是异常的,这时就会得到不准确的结果;如果上述各类型的电影分布很均匀,那么当K值过大时,筛选出的相似电影可能类型分布也很均匀,这时就会出现问题。
2. 概念性解释:
1)选择较小的K值,就相当于用较小领域中的训练实例进行预测,“学习”近似误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是“学习”的估计误差会增大,也就是说K值的减小就意味着整体模型变得复杂,容易发生过拟合;
2)选择较大的K值,就相当于用较大领域中的训练实例进行预测,其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时,与输入实例较远(不相似)的训练实例也会对预测器作用,是预测发生错误,且K值的增大就意味着整体的模型变得简单,容易欠拟合;
3)K=N(N为训练样本个数),完全不足取,因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单,忽略了训练实例中的大量有用信息。
在实际应用中,K一般取较小值。
问题导入:
实现k近邻算法时,主要考虑的问题是如何对训练数据进行快速k近邻搜索。这在特征空间的维数大及训练数据容量时尤其必要。
k近邻算法最简单的实现是线性扫描(穷举搜索),即要计算输入实例与每一个训练实例的距离。计算并存储好以后,再查找k近邻。当训练集很大时,计算非常耗时。
为了提高KNN搜索的效率,可以考虑使用特殊的结构存储训练数据,以减小计算距离的次数。
1. 什么是kd树?
kd树是一种对k维空间中的实例点进行存储,以便对其进行快速检索的树形数据结构。
2. 构造kd树
先输入k维空间数据集 T = {x1, x2, …, xN},其中 xi = (xi(1), xi(2), …, xi(k))T,再输出kd树:
1)开始:构造根节点。
2)重复
3)结束
3. 上例题:以一个二维的样本空间为例
输入:训练集 T = { ( 2 , 3 ) , ( 5 , 4 ) , ( 9 , 6 ) , ( 4 , 7 ) , ( 8 , 1 ) , ( 7 , 2 ) } T=\{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)\} T={(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}
x ( 1 ) x^{(1)} x(1)轴对应的数: 2 , 5 , 9 , 4 , 8 , 7 2,5,9,4,8,7 2,5,9,4,8,7; x ( 2 ) x^{(2)} x(2)轴对应的数: 3 , 4 , 6 , 7 , 1 , 2 3,4,6,7,1,2 3,4,6,7,1,2。
1)开始:
2)重复:
以
x
(
2
)
x^{(2)}
x(2)为坐标轴,选择中位数,左边区域为4,右边区域两个任取一个,这里取较大数6。故左边区域切分点为
(
5
,
4
)
(5,4)
(5,4),右边区域切分点为
(
9
,
6
)
(9,6)
(9,6);
划分左边区域:此时
I
=
1
I=1
I=1,重新从最开始选取的轴
x
(
1
)
x^{(1)}
x(1)开始切。以
x
(
1
)
x^{(1)}
x(1)为坐标轴,选择中位数,此时上下两个区域都只剩一个点了,全选上作为切分点;
划分右边区域:同上,以
x
(
1
)
x^{(1)}
x(1)为左边轴,选取切分点(只有一个)。
3)输出kd树:
输入:已构造的kd树,目标点x。
输出:x的最近邻点。
1. 基本流程:
1)寻找“当前最近点”
2)回溯
3)结束
2. 实例:
现有kd树:
1)输入目标点 x = ( 2.1 , 3.1 ) x=(2.1,3.1) x=(2.1,3.1),求最近邻点。
先找到根节点 x ( 7 , 2 ) x(7,2) x(7,2),因为目标节点 ( 2.1 , 3.1 ) (2.1,3.1) (2.1,3.1)的横坐标小于7,所以先从kd树的左子树(左边区域)开始搜索;因为目标节点的纵坐标小于4,所以从下边区域开始搜索;下边区域只有一个点 ( 2 , 3 ) (2,3) (2,3),目标点 ( 2.1 , 3.1 ) (2.1,3.1) (2.1,3.1)就在点 ( 2 , 3 ) (2,3) (2,3)的右边区域中,故 ( 2 , 3 ) (2,3) (2,3)就是当前的最近邻点。
以目标点为圆心,到当前最近邻点 ( 2 , 3 ) (2,3) (2,3)的距离为半径画圆(如果是多维空间,就是画一个超球面了)。先回溯到目标节点的父节点,也就是当前最近邻点 ( 2 , 3 ) (2,3) (2,3),发现圆和 ( 2 , 3 ) (2,3) (2,3)划分的左半部分平面(不含分界线)是有交集的,所以要先搜索左半部分平面;发现左半部分平面没有一个点,重新回溯到 ( 2 , 3 ) (2,3) (2,3);再回溯到当前最近邻点的父节点 ( 5 , 4 ) (5,4) (5,4),计算这个节点到目标点的距离,发现比圆的半径大,不更新数据;随后发现图中圆和 ( 5 , 4 ) (5,4) (5,4)划分的上半部分平面(不含分界线)是没有交集的,说明不可能从 ( 5 , 4 ) (5,4) (5,4)上半部分所对应的子区域中,找到更近的点;回溯到根节点,发现图中圆和根节点划分的右半部分区域也是没有交集的,所以最近邻点也不可能在根节点划分的右半部分,也不可能是根节点。
回溯到了根节点,停止回溯,找到最近邻点 ( 2 , 3 ) (2,3) (2,3)。
2)输入目标点 x = ( 2 , 4.5 ) x=(2,4.5) x=(2,4.5),求最近邻点。
和1中的步骤一样,先找到了当前最近邻点 ( 4 , 7 ) (4,7) (4,7)。
以目标点为圆心,到当前最近邻点的距离为半径画圆。先回溯到目标节点的父节点 ( 4 , 7 ) (4,7) (4,7),发现圆和 ( 4 , 7 ) (4,7) (4,7)所划分的右半部分平面是有交集的,先搜索右半部分平面;发现没有一个点,重新回溯到 ( 4 , 7 ) (4,7) (4,7);再回溯到父节点 ( 5 , 4 ) (5,4) (5,4),计算该节点到目标点的距离,发现比原来的圆半径小,更新最近邻点,重新画圆;随后发现,重新画的圆和 ( 5 , 4 ) (5,4) (5,4)划分的下半部分区域有交集,搜索下半部分区域,发现只有一个点;计算父节点的下半部分子区域中的子节点到目标点的距离,发现该距离比原来的圆半径要小,更新最近邻点为 ( 2 , 3 ) (2,3) (2,3),并重新画圆;
直接回溯到根节点,计算根节点到目标点的距离,发现比图中圆的半径要大,不更新数据;随后发现图中圆和根节点划分的右半部分区域没有交集,所以最近邻点不可能在根节点划分的右半部分,也不可能是根节点。
回溯到了根节点,停止回溯,找到最近邻点 ( 2 , 3 ) (2,3) (2,3)。
适用于n维向量:
import math import numpy as np class Node: def __init__(self, data, lchild = None, rchild = None): self.data = data self.lchild = lchild self.rchild = rchild class KdTree: def __int__(self): self.kdTree = None def create(self, dataSet, depth): # 构造kd树 if len(dataSet) > 0: # 还有实例,就往下分 m, n = np.shape(dataSet) # 求出样本的行和列 midIndex = m // 2 # 求出中位数的索引 if depth == 0: # 深度等于0时说明是根节点,轴已经选好,不用再判断 arr = list(dataSet.var(axis=0)) # 先转成list,不然无法使用index函数 max_value = max(arr) # 确定方差最大的那一列 axis = arr.index(max_value) # 确定初始划分坐标 else: axis = depth % n # 判断以哪个轴划分数据,由于索引从0开始,所以不用+1 sortedDataSet = sorted(dataSet, key=lambda x: x[axis]) # 按照指定轴进行排序 node = Node(sortedDataSet[midIndex]) # 在树中放入节点 node.lchild = self.create(sortedDataSet[: midIndex], depth + 1) # 递归构造左子树 node.rchild = self.create(sortedDataSet[midIndex + 1 :], depth + 1) # 递归构造右子树 return node # 返回根节点 else: return None def preOrder(self, node): # 前序遍历二叉树 if node != None: print(node.data) self.preOrder(node.lchild) self.preOrder(node.rchild) def search(self, node, x): # 搜索kd树 self.nearestPoint = None # 保存当前最近点 self.nearestValue = math.inf # 保存当前最近距离(圆的半径)math.inf是无穷大 def travel(node, depth = 0): # 递归搜索 if node != None: # 递归终止条件 if depth == 0: # 深度等于0时说明是根节点,轴已经选好,不用再判断 arr = list(dataSet.var(axis=0)) # 先转成list,不然无法使用index函数 max_value = max(arr) # 确定方差最大的那一列 axis = arr.index(max_value) # 确定初始划分坐标 else: axis = depth % len(x) # 判断以哪个轴划分数据,由于索引从0开始,所以不用+1 if x[axis] < node.data[axis]: # 如果数据小于节点对应坐标数据,则往左节点搜索 travel(node.lchild, depth + 1) else: travel(node.rchild, depth + 1) else: return # 递归完毕后,开始回溯 distNodeAndX = self.dist(x, node.data) # 目标点和当前最近点的距离 if self.nearestValue > distNodeAndX: # 更新最近邻点 self.nearestPoint = node.data self.nearestValue = distNodeAndX if abs(x[axis] - node.data[axis]) < self.nearestValue: # 确定是否要去子节点的区域找最近邻点(圆的判断) if x[axis] < node.data[axis]: travel(node.rchild, depth + 1) else: travel(node.lchild, depth + 1) travel(node) return self.nearestPoint # 返回最近邻点 def dist(self, x1, x2): # 欧式距离计算 return ((x1 - x2) ** 2).sum() ** 0.5 if __name__ == '__main__': # 准备数据 dataSet = np.array([[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]]) x = [2, 4.5] # 目标点 # 处理数据 kdtree = KdTree() # 初始化kd树 tree_root = kdtree.create(dataSet, 0) # 接收根节点(根节点深度为0) # 前序遍历 kdtree.preOrder(tree_root) # 检查kd树是否正确建立 # 搜索最近邻点 print("最近邻点:", kdtree.search(tree_root, x))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。