[TOC] 更新、更全的《機器學習》的更新網站,更有python、go、數據結構與算法、爬蟲、人工智能教學等着你:http://www.javashuo.com/article/p-vozphyqp-cm.htmlhtml
k近鄰算法中講到它有一個較爲致命的缺點就是每一個實例到將來新數據之間都須要計算一次距離,若是實例數趨於無窮,那麼計算量是很龐大的。可是咱們要知道的是計算距離是爲了找到距離目標點最近的$k$個實例,那麼是否是有另一種更好的方法,可以更快速找到這$k$個最近的實例呢?由此kd樹被髮明瞭出來。python
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()
![png](http://www.chenyoude.com/ml/02-17 kd樹_5_0.png?x-oss-process=style/watermark)數據結構
這裏將繼續沿用k近鄰算法的例子。dom
如上圖所示,假設現有一部《未知類型的電影》,k近鄰的思想是計算出數據集中每一部電影到《未知類型的電影》的距離,以後找到$k$個近鄰對《未知類型的電影》分類。可是能夠發現如電影C這樣偏僻位置的電影明顯是能夠不用計算它們與《未知類型的電影》的距離的。機器學習
那如今假設有沒有這麼一種可能?咱們再也不計算每部電影到《未知類型的電影》的距離,而是直接獲得《未知類型的電影》最近的$k$部電影?首先這種假設應該不可能,其次真的有,那還要其餘機器學習算法做甚?那麼再想一想能不能經過某種方法找到離《未知類型的電影》較近的電影呢?咱們假設有這種方法,而且這種方法被稱做kd樹,咱們看看它是如何實現的。學習
上述整個過程其實就是kd樹實現的一個過程,接下來將從理論層面抽象的講解kd樹。測試
kd樹主要是不斷地劃分$k$維空間。優化
在一個二維空間平面中,kd樹則是不斷地使用垂直於$x$軸或$y$軸的直線把平面劃分紅一系列較小的矩形。在$k$維空間中,kd樹則是不斷地用垂直於座標軸的超平面將$k$維空間切分紅一系列的$k$維超矩形區域。網站
假設有$m$個實例$n$維特徵的數據集
其中$x_i$是實例的特徵向量即$({(1)},{(2)},\cdots,^{(n)})$,$y_i$是實例的類別,數據集有${c_1,c_2,\cdots,c_j}$共$j$個類別。
構造kd樹時須要注意左子結點對應座標$x^{(l)}$小與切分點的$x^{(l)}$座標,右子結點對應座標$x^{(l)}$大於切分點的$x^{(l)}$座標。
假設有一個二維空間$(x_1,x_2)$的數據集
首先$x$軸上的數據有$(二、四、五、七、八、9)$,中位數爲$7$(注:若是以$5$劃分也行,可是本文都是以較大的做爲切分點,可是不能以${\frac{5+7}{2}}$劃分,總之劃分點符合kd樹的構造要求便可),則以$x=7$劃分一次矩形區域;左子區域$y$軸上的數據有$(三、四、7)$,中位數爲$4$,則以$y=4$劃分一次數據,接着以$x$軸上的數據劃分,而$x$軸數據只剩下$(2,4)$,直接以$x=4$劃分左子區域的右子區域,以$x=2$劃分左子區域的左子區域;右子區域$y$軸上數據有$(一、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()
![png](http://www.chenyoude.com/ml/02-17 kd樹_12_0.png?x-oss-process=style/watermark)
同超矩形區域劃分同樣能夠構造以下圖所示的kd二叉樹
首先以$x$軸做爲劃分軸,$x$軸上的數據有$(二、四、五、七、八、9)$,中位數爲$7$,根結點爲$(7,2)\(,左子結點\)(2,3)、(4,7)、(5,4)\(,右子結點\)(8,1)、(9,6)$;以後以$y$軸做爲劃分軸,左子結點$y$軸有數據$(三、四、7)$,則左子結點的中位數爲$4$,則左子結點$(5,4)\(,左子結點的左孫子結點\)(2,3)\(,左子結點的右孫子結點\)(4,7)$;右子結點$y$軸有數據$(一、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()
![png](http://www.chenyoude.com/ml/02-17 kd樹_14_0.png?x-oss-process=style/watermark)
從根結點出發,遞歸的向下訪問kd樹,若是目標點$x$的$x^{(l)}$維的座標小於切分點的座標,則移動到左子結點,不然移動到右子結點,直到子結點爲葉子結點爲止,並假設該葉子結點爲目標點的當前最近點。
遞歸的從該葉子結點向上回退,對每一個結點上的實例都進行以下操做:
上述kd樹搜索的過程能夠看出,因爲不少實例點所在的超矩形區域和超球體不相交,壓根不須要計算距離,打打節省了計算時間。
可是值得注意的是kd樹的平均計算複雜度爲$O(\log)$,當特徵維數接近訓練集實例數時,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()
![png](http://www.chenyoude.com/ml/02-17 kd樹_19_0.png?x-oss-process=style/watermark)
有$m$個實例$n$維特徵的數據集
其中$x_i$是實例的特徵向量即$({(1)},{(2)},\cdots,^{(n)})$。
距離目標點最近的$k$個實例及$k$個實例到目標點的距離。
kd樹更多的使用了二叉樹的思路,固然它也很好的實現了咱們想要實現的,這就能夠了。
若是你細心的話,能夠發如今搜索的過程當中,若是超球體碰到了超矩形的一點棱角就須要去搜索超矩形區域內有沒有離目標點更近的點,固然有解決方法,可使用球樹(ball tree),可是其實較kd樹並無那麼明顯的優化,這裏就很少敘述了。簡而言之就是球樹相較於kd樹使用的是超球體切割數據集,有興趣的能夠本身思考思考(先構造一個最小球體包含全部數據,而後遞歸構造小球體——先選擇一個離第一個球體中心最遠的點A,再選擇一個離點A最遠的點B,那個數據離哪一個點近則屬於哪個點的那一類,這樣就完美把一個球體切分紅兩個球體)。
kd樹講完,k近鄰也就告一段落了。下一篇的機器學習算法聽起來可能和傳統算法中的樹結構有關,但它究竟是不是一個樹結構的機器學習算法呢?看完了就知道了——決策樹。