02-17 kd樹

[TOC] 更新、更全的《機器學習》的更新網站,更有python、go、數據結構與算法、爬蟲、人工智能教學等着你:http://www.javashuo.com/article/p-vozphyqp-cm.htmlhtml

kd樹

k近鄰算法中講到它有一個較爲致命的缺點就是每一個實例到將來新數據之間都須要計算一次距離,若是實例數趨於無窮,那麼計算量是很龐大的。可是咱們要知道的是計算距離是爲了找到距離目標點最近的$k$個實例,那麼是否是有另一種更好的方法,可以更快速找到這$k$個最近的實例呢?由此kd樹被髮明瞭出來。python

kd樹(k-dimensional tree)簡單而言就是$k$個特徵維度的二叉樹,要注意這裏的$k$值和k近鄰算法中的$k$值不一樣。算法

1、kd樹學習目標

  1. kd樹的構造與搜索
  2. kd樹流程
  3. kd樹優缺點

2、kd樹引入

# 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樹,咱們看看它是如何實現的。學習

  1. 好比kd樹找到電影A即離《未知類型的電影》較近的某部電影,而後把距離大於電影A到《未知類型的電影》之間的電影排除掉,或者是繼續找找看有沒有哪部電影到《未知類型的電影》的距離更近?
  2. 若是能夠找到電影A,則也能夠kd樹找到電影B
  3. 遞歸使用kd樹搜索直到找到最近的某幾部電影,中止搜索

上述整個過程其實就是kd樹實現的一個過程,接下來將從理論層面抽象的講解kd樹。測試

3、kd樹詳解

3.1 構造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$是實例的特徵向量即$({(1)},{(2)},\cdots,^{(n)})$,$y_i$是實例的類別,數據集有${c_1,c_2,\cdots,c_j}$共$j$個類別。

  1. 構造k維空間的超矩形區域。若是是有兩個特徵$x_1$和$x_2$的二維空間,則是由$(x1_,x1_,x2_,x2_)$構成的矩形區域
  2. 選擇特徵$x^{(1)}$爲座標軸,以$T$中全部實例的$x^{(1)}$座標的中位數做爲切分點,將根結點對應的超矩形區域切分紅左右兩個子區域。將$x^{(1)}$對應值小於切分點的實例劃入左子區域;將$x^{(1)}$對應值大於切分點的實例劃入右子區域;將切分點保留在根結點
  3. 重複2步驟,即選擇$x^{(l)}, \quad (l=1,2,\cdots,n)$爲切分的座標軸,以該結點中還剩下的$k$個實例的$x^{(l)}$座標的中位數做爲切分點,將該結點對應的超矩形區域切分紅左右兩個子區域。將$x^{(l)}$對應值小於切分點的實例劃入左子區域;將$x^{(l)}$對應值大於切分點的實例劃入右子區域;將切分點保留在該結點
  4. 切割後的子區域沒有實例時,即沒法劃分時結束

構造kd樹時須要注意左子結點對應座標$x^{(l)}$小與切分點的$x^{(l)}$座標,右子結點對應座標$x^{(l)}$大於切分點的$x^{(l)}$座標。

3.1.1 示例

假設有一個二維空間$(x_1,x_2)$的數據集

\[ T = \{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\} \]

首先$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)

3.2 kd樹搜索

從根結點出發,遞歸的向下訪問kd樹,若是目標點$x$的$x^{(l)}$維的座標小於切分點的座標,則移動到左子結點,不然移動到右子結點,直到子結點爲葉子結點爲止,並假設該葉子結點爲目標點的當前最近點。

遞歸的從該葉子結點向上回退,對每一個結點上的實例都進行以下操做:

  1. 若是該實例到目標點的距離更近,則以該實例點爲當前最近點
  2. 檢查當前最近點父結點的另外一子結點對應的區域是否有更近的點,即另外一子結點的超矩形區域是否與以目標點爲球心、以目標點到當前最近點的半徑造成的超球體是否相交
    1. 若是相交,則另外一子結點對應的超矩形區域內存在離目標點更近的點,移到另外一子結點繼續遞歸搜索
    2. 若是不相交,則向上回退
  3. 當回退到根結點時結束,目標點的最近鄰點爲當前最近點

上述kd樹搜索的過程能夠看出,因爲不少實例點所在的超矩形區域和超球體不相交,壓根不須要計算距離,打打節省了計算時間。

可是值得注意的是kd樹的平均計算複雜度爲$O(\log)$,當特徵維數接近訓練集實例數時,kd樹的搜索效率則會迅速降低,幾乎接近線性掃描。

3.2.1 示例

接下來將拿上一節構造的kd樹舉例kd樹搜索的整個過程,假設目標點爲$(2,4.5)$。

  1. 搜索目標點對應的葉子結點
    1. 從根結點$(7,2)\(開始查找,因爲\)(7,2)$是由$x=7$劃分的,而且目標點的$x$值爲$2$,因爲$2<7$則到左子$(5,4)$結點
    2. 因爲$(5,4)$是由$y=4$分隔超平面的,而且目標點的$y$值爲$4.5$,因爲$4.5>4$則到右子$(4,7)$葉子結點
    3. 假設$(4,7)$葉子結點爲當前最近結點
    4. 目標掉搜索到葉子結點的路徑爲$(7,2)\rightarrow(5,4)\rightarrow(4,7)$
  2. 搜索目標點對應的當前最近點
    1. 當前最近結點$(4,7)$到目標點的距離爲$3.2$,而當前最近結點的父節點$(5,4)$到目標點的距離爲$3.0$,則當前最近結點更新爲$(5,4)$
    2. 如下圖藍色五角星目標點$(2,4.5)\(爲圓心,以當前最近結點\)(5,4)$到目標點的距離$3.0$爲半徑作一個超球體,即下圖的紅色圓圈
    3. 能夠發現紅色圓圈和$y=4$超平面相交,所以進入$(5,4)\(的左子結點查找,即找到\)(2,3)$
    4. 計算$(2,3)$到目標點的距離爲$1.5$,則當前最近結點更新爲$(2,3)$
    5. 如下圖藍色五角星目標點$(2,4.5)\(爲圓心,以當前最近結點\)(2,3)$到目標點的距離$1.5$爲半徑作一個超球體,即下圖的綠色圓圈
    6. 因爲結點$(2,3)\(爲葉子結點,開始沿父結點回溯,父結點\)(5,4)\(已經考慮過,繼續往上回溯到\)(7,2)$
    7. 綠色圓圈和$x=7$沒有相交,又因爲$(7,2)$爲根節點,搜索結束
    8. 經過上述搜索目標點最近點爲$(2,3)$,最近距離爲$1.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)

4、kd樹流程

4.1 輸入

有$m$個實例$n$維特徵的數據集

\[ T=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\} \]

其中$x_i$是實例的特徵向量即$({(1)},{(2)},\cdots,^{(n)})$。

4.2 輸出

距離目標點最近的$k$個實例及$k$個實例到目標點的距離。

4.3 流程

  1. 構造kd樹
  2. 搜索離目標點最近的$k$個實例點
  3. 分類問題依據多數表決法便可得到目標點的類別;若是是迴歸問題,取 𝑘 個近鄰點的標記值的平均數或中位數便可。

5、kd樹優缺點

5.1 優勢

  1. 簡單易懂,容易實現,能夠作分類也能夠作迴歸
  2. 基於實例學習,對數據沒有假設,不須要經過模型訓練得到參數
  3. 較於k近鄰算法計算量減小了

5.2 缺點

  1. 不管哪種距離度量方式都須要使用到特徵值之間的差值,若是某些特徵值之間的差值過大會掩蓋差值較小特徵對預測值的影響(通常使用數據預處理中的歸一化方法處理特徵值)
  2. kd樹的創建須要大量的內存消耗(換內存吧!!!)
  3. 相比較決策樹,解釋型不強(客戶真的想要解釋性強的模型能夠換決策樹試一試)

6、小結

kd樹更多的使用了二叉樹的思路,固然它也很好的實現了咱們想要實現的,這就能夠了。

若是你細心的話,能夠發如今搜索的過程當中,若是超球體碰到了超矩形的一點棱角就須要去搜索超矩形區域內有沒有離目標點更近的點,固然有解決方法,可使用球樹(ball tree),可是其實較kd樹並無那麼明顯的優化,這裏就很少敘述了。簡而言之就是球樹相較於kd樹使用的是超球體切割數據集,有興趣的能夠本身思考思考(先構造一個最小球體包含全部數據,而後遞歸構造小球體——先選擇一個離第一個球體中心最遠的點A,再選擇一個離點A最遠的點B,那個數據離哪一個點近則屬於哪個點的那一類,這樣就完美把一個球體切分紅兩個球體)。

kd樹講完,k近鄰也就告一段落了。下一篇的機器學習算法聽起來可能和傳統算法中的樹結構有關,但它究竟是不是一個樹結構的機器學習算法呢?看完了就知道了——決策樹。

相關文章
相關標籤/搜索