标签:位置 坐标轴 优缺点 平均数 you 距离 python 因此 路径
目录
更新、更全的《机器学习》的更新网站,更有python、go、数据结构与算法、爬虫、人工智能教学等着你:https://www.cnblogs.com/nickchen121/
k近邻算法中讲到它有一个较为致命的缺点就是每个实例到未来新数据之间都需要计算一次距离,如果实例数趋于无穷,那么计算量是很庞大的。但是我们要知道的是计算距离是为了找到距离目标点最近的\(k\)个实例,那么是不是有另外一种更好的方法,能够更快速找到这\(k\)个最近的实例呢?由此kd树被发明了出来。
kd树(k-dimensional tree)简单而言就是\(k\)个特征维度的二叉树,要注意这里的\(k\)值和k近邻算法中的\(k\)值不同。
# kd树引入图例
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc')
# 测试点
plt.scatter(5, 6, marker='s', c='k', s=50)
plt.text(5, 5, s='《未知类型的电影》(5,6)', fontproperties=font, ha='center')
# 生成动作片
np.random.seed(1)
action_x1 = np.random.randint(1, 7, 15)
action_x2 = np.random.randint(8, 17, 15)
plt.scatter(action_x1, action_x2, marker='o', s=30, c='r', label='动作片')
# 生成爱情片
romance_x1 = np.random.randint(8, 17, 15)
romance_x2 = np.random.randint(1, 7, 15)
plt.scatter(romance_x1, romance_x2, marker='x', s=30, c='g', label='爱情片')
# 测试文本标记
plt.text(4, 11, s='A', ha='center')
plt.text(5, 7, s='B', ha='center')
plt.text(1, 14, s='C', ha='center')
plt.xlim(0, 18)
plt.ylim(0, 18)
plt.xlabel('接吻镜头(次)', fontproperties=font)
plt.ylabel('打斗场景(次)', fontproperties=font)
plt.title('kd树引入图例', fontproperties=font, fontsize=20)
plt.legend(prop=font)
plt.show()
这里将继续沿用k近邻算法的例子。
如上图所示,假设现有一部《未知类型的电影》,k近邻的思想是计算出数据集中每一部电影到《未知类型的电影》的距离,之后找到\(k\)个近邻对《未知类型的电影》分类。但是可以发现如电影C这样偏僻位置的电影明显是可以不用计算它们与《未知类型的电影》的距离的。
那现在假设有没有这么一种可能?我们不再计算每部电影到《未知类型的电影》的距离,而是直接得到《未知类型的电影》最近的\(k\)部电影?首先这种假设应该不可能,其次真的有,那还要其他机器学习算法作甚?那么再想想能不能通过某种方法找到离《未知类型的电影》较近的电影呢?我们假设有这种方法,并且这种方法被称作kd树,我们看看它是如何实现的。
上述整个过程其实就是kd树实现的一个过程,接下来将从理论层面抽象的讲解kd树。
kd树主要是不断地划分\(k\)维空间。
在一个二维空间平面中,kd树则是不断地使用垂直于\(x\)轴或\(y\)轴的直线把平面划分成一系列较小的矩形。在\(k\)维空间中,kd树则是不断地用垂直于坐标轴的超平面将\(k\)维空间切分成一系列的\(k\)维超矩形区域。
假设有\(m\)个实例\(n\)维特征的数据集
\[
T=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}
\]
其中\(x_i\)是实例的特征向量即\(({x_i}^{(1)},{x_i}^{(2)},\cdots,{x_i}^{(n)})\),\(y_i\)是实例的类别,数据集有\(\{c_1,c_2,\cdots,c_j\}\)共\(j\)个类别。
构造kd树时需要注意左子结点对应坐标\(x^{(l)}\)小与切分点的\(x^{(l)}\)坐标,右子结点对应坐标\(x^{(l)}\)大于切分点的\(x^{(l)}\)坐标。
假设有一个二维空间\((x_1,x_2)\)的数据集
\[
T = \{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\}
\]
首先\(x\)轴上的数据有\((2、4、5、7、8、9)\),中位数为\(7\)(注:如果以\(5\)划分也行,但是本文都是以较大的作为切分点,但是不能以\({\frac{5+7}{2}}\)划分,总之划分点符合kd树的构造要求即可),则以\(x=7\)划分一次矩形区域;左子区域\(y\)轴上的数据有\((3、4、7)\),中位数为\(4\),则以\(y=4\)划分一次数据,接着以\(x\)轴上的数据划分,而\(x\)轴数据只剩下\((2,4)\),直接以\(x=4\)划分左子区域的右子区域,以\(x=2\)划分左子区域的左子区域;右子区域\(y\)轴上数据有\((1、6)\),由于划分点需要大于左子结点的,因此右子结点为\((9,6)\),即以\(y=6\)划分左子区域,接着只剩下一个点\((8,1)\),又以\(x\)轴划分,则以\(x=8\)划分。
通过以上划分,即可得以下特征空间划分图。
# kd树的构造示特征空间划分图例
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc')
plt.scatter(2, 3, c='r', s=30)
plt.text(2, 3.5, s='$(2,3)$', ha='center')
plt.scatter(5, 4, c='r', s=30)
plt.text(5, 4.5, s='$(5,4)$', ha='center')
plt.scatter(9, 6, c='r', s=30)
plt.text(9, 6.5, s='$(9,6)$', ha='center')
plt.scatter(4, 7, c='r', s=30)
plt.text(4, 7.5, s='$(4,7)$', ha='center')
plt.scatter(8, 1, c='r', s=30)
plt.text(8, 1.5, s='$(8,1)$', ha='center')
plt.scatter(7, 2, c='r', s=30)
plt.text(7, 2.5, s='$(7,2)$', ha='center')
plt.hlines(4, 0, 7, linestyle='-', color='k')
plt.hlines(6, 7, 10, linestyle='-', color='k')
plt.vlines(2, 0, 4, linestyle='-', color='k')
plt.vlines(4, 4, 10, linestyle='-', color='k')
plt.vlines(7, 0, 10, linestyle='-', color='k')
plt.vlines(8, 0, 6, linestyle='-', color='k')
plt.xlim(0, 10)
plt.ylim(0, 10)
plt.title('kd树构造-示例图例1', fontproperties=font, fontsize=20)
plt.show()
同超矩形区域划分一样可以构造如下图所示的kd二叉树
首先以\(x\)轴作为划分轴,\(x\)轴上的数据有\((2、4、5、7、8、9)\),中位数为\(7\),根结点为\((7,2)\),左子结点\((2,3)、(4,7)、(5,4)\),右子结点\((8,1)、(9,6)\);之后以\(y\)轴作为划分轴,左子结点\(y\)轴有数据\((3、4、7)\),则左子结点的中位数为\(4\),则左子结点\((5,4)\),左子结点的左孙子结点\((2,3)\),左子结点的右孙子结点\((4,7)\);右子结点\(y\)轴有数据\((1、6)\),则有右子结点\((9,6)\),右子结点的左孙子结点\((8,1)\)。
# kd树的构造kd二叉树形成图例
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc')
plt.scatter(20, 15, c='white', edgecolor='k', s=2000)
plt.text(20, 14.5, s='$(7,2)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(18.5, 13.5), xy=(13.5, 11.5),
arrowprops=dict(arrowstyle="-", color='b'))
plt.scatter(12, 10, c='white', edgecolor='k', s=2000)
plt.text(12, 9.5, s='$(5,4)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(12, 8), xy=(9, 5),
arrowprops=dict(arrowstyle="-", color='b'))
plt.scatter(8, 3, c='white', edgecolor='k', s=2000)
plt.text(8, 2.5, s='$(2,3)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.scatter(16, 3, c='white', edgecolor='k', s=2000)
plt.text(16, 2.5, s='$(4,7)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(12, 8), xy=(16, 5),
arrowprops=dict(arrowstyle="-", color='b'))
plt.scatter(28, 10, c='white', edgecolor='k', s=2000)
plt.text(28, 9.5, s='$(9,6)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(21.5, 13.5), xy=(26, 11),
arrowprops=dict(arrowstyle="-", color='b'))
plt.scatter(24, 3, c='white', edgecolor='k', s=2000)
plt.text(24, 2.5, s='$(8,1)$', ha='center',
fontsize=15, color='k', fontweight='bold')
plt.annotate(s='', xytext=(27, 8), xy=(24.5, 5),
arrowprops=dict(arrowstyle="-", color='b'))
plt.hlines(15, 24, 38, linestyle='--', color='r')
plt.text(39, 14.5, ha='center', color='r', s='X', fontsize=15)
plt.hlines(10, 32, 38, linestyle='--', color='b')
plt.text(39, 9.5, ha='center', color='b', s='Y', fontsize=15)
plt.hlines(3, 28, 38, linestyle='--', color='r')
plt.text(39, 2.5, ha='center', color='r', s='X', fontsize=15)
plt.xlim(-1, 40)
plt.ylim(-1, 20)
plt.title('kd树构造-示例图例2', fontproperties=font, fontsize=20)
plt.show()
从根结点出发,递归的向下访问kd树,如果目标点\(x\)的\(x^{(l)}\)维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点,直到子结点为叶子结点为止,并假设该叶子结点为目标点的当前最近点。
递归的从该叶子结点向上回退,对每个结点上的实例都进行如下操作:
上述kd树搜索的过程可以看出,由于很多实例点所在的超矩形区域和超球体不相交,压根不需要计算距离,打打节省了计算时间。
但是值得注意的是kd树的平均计算复杂度为\(O(\log{N})\),当特征维数接近训练集实例数时,kd树的搜索效率则会迅速下降,几乎接近线性扫描。
接下来将拿上一节构造的kd树举例kd树搜索的整个过程,假设目标点为\((2,4.5)\)。
值得注意的是上述都是以最近邻展开讨论kd树的,如果想得到\(k\)个近邻,只需要最后保留\(k\)个最近邻的结点上的实例点即可,之后依据多数表决法即可获得目标点的类别;如果是回归问题,取\(k\)个近邻点的标记值的平均数或中位数即可。
# kd树的构造示特征空间划分图例
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc')
plt.scatter(2, 3, c='r', s=30)
plt.text(2, 3.5, s='$(2,3)$', ha='center')
plt.scatter(5, 4, c='r', s=30)
plt.text(5, 4.5, s='$(5,4)$', ha='center')
plt.scatter(9, 6, c='r', s=30)
plt.text(9, 6.5, s='$(9,6)$', ha='center')
plt.scatter(4, 7, c='r', s=30)
plt.text(4, 7.5, s='$(4,7)$', ha='center')
plt.scatter(8, 1, c='r', s=30)
plt.text(8, 1.5, s='$(8,1)$', ha='center')
plt.scatter(7, 2, c='r', s=30)
plt.text(7, 2.5, s='$(7,2)$', ha='center')
plt.hlines(4, 0, 7, linestyle='-', color='k')
plt.hlines(6, 7, 10, linestyle='-', color='k')
plt.vlines(2, 0, 4, linestyle='-', color='k')
plt.vlines(4, 4, 10, linestyle='-', color='k')
plt.vlines(7, 0, 10, linestyle='-', color='k')
plt.vlines(8, 0, 6, linestyle='-', color='k')
plt.scatter(2, 4.5, s=30410, c='white', edgecolor='r')
plt.scatter(2, 4.5, s=15000, c='white', edgecolor='g')
plt.scatter(2, 4.5, s=50, c='b', marker='*')
plt.text(2, 5, s='目标点$(2,4.5)$', ha='center',fontproperties=font)
plt.scatter(2, 3, c='r', s=30)
plt.scatter(4, 7, c='r', s=30)
plt.xlim(0, 10)
plt.ylim(0, 10)
plt.title('kd树搜索-示例图例', fontproperties=font, fontsize=20)
plt.show()
有\(m\)个实例\(n\)维特征的数据集
\[
T=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}
\]
其中\(x_i\)是实例的特征向量即\(({x_i}^{(1)},{x_i}^{(2)},\cdots,{x_i}^{(n)})\)。
距离目标点最近的\(k\)个实例及\(k\)个实例到目标点的距离。
kd树更多的使用了二叉树的思路,当然它也很好的实现了我们想要实现的,这就可以了。
如果你细心的话,可以发现在搜索的过程中,如果超球体碰到了超矩形的一点棱角就需要去搜索超矩形区域内有没有离目标点更近的点,当然有解决方法,可以使用球树(ball tree),但是其实较kd树并没有那么明显的优化,这里就不多叙述了。简而言之就是球树相较于kd树使用的是超球体切割数据集,有兴趣的可以自己思考思考(先构造一个最小球体包含所有数据,然后递归构造小球体——先选择一个离第一个球体中心最远的点A,再选择一个离点A最远的点B,那个数据离哪个点近则属于哪一个点的那一类,这样就完美把一个球体切分成两个球体)。
kd树讲完,k近邻也就告一段落了。下一篇的机器学习算法听起来可能和传统算法中的树结构有关,但它到底是不是一个树结构的机器学习算法呢?看完了就知道了——决策树。
标签:位置 坐标轴 优缺点 平均数 you 距离 python 因此 路径
原文地址:https://www.cnblogs.com/nickchen121/p/11686769.html