版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、從線段樹到KD樹在講KD樹之前,我們先來(lái)了解一下線段樹的概念。線段樹在機(jī)器學(xué)習(xí)領(lǐng)域 當(dāng)中不太常見(jiàn),作為高性能維護(hù)的數(shù)據(jù)結(jié)構(gòu),經(jīng)常出現(xiàn)在各種算法比賽當(dāng)中。 線段樹的本質(zhì)是一棵維護(hù)一段區(qū)間的平衡二叉樹。比如下圖就是一個(gè)經(jīng)典的線 段樹:2 3X 1從下圖當(dāng)中我們不難看出來(lái),這棵線段樹維護(hù)的是個(gè)區(qū)間內(nèi)的最大值。 比如樹根是8,維護(hù)的是整個(gè)區(qū)間的最大值,每一個(gè)中間節(jié)點(diǎn)的值都是以它為 樹根的子樹中所有元素的最大值。通過(guò)線段樹,我們可以在的時(shí)間內(nèi)計(jì)算出某一個(gè)連續(xù)區(qū)間的最大值。比如 我們來(lái)看下圖:b當(dāng)我們要求被框起來(lái)的區(qū)間中的最大值,我們只需要找到能夠覆蓋這個(gè)區(qū) 間的中間節(jié)點(diǎn)就行。我們可以發(fā)現(xiàn)被紅框框起來(lái)的兩
2、個(gè)節(jié)點(diǎn)的子樹剛好覆蓋這 個(gè)區(qū)間,于是整個(gè)區(qū)間的最大值,就是這兩個(gè)元素的最大值。這樣,我們就把 一個(gè)需要查找的問(wèn)題降低成了,不但如此,我們也可以做到復(fù)雜度內(nèi)的更新, 也就是說(shuō)我們不但可以快速查詢,還可以更新線段當(dāng)中的元素。當(dāng)然線段樹的 應(yīng)用非常廣泛,也有許多種變體,這里我們不過(guò)多深入,感興趣的同學(xué)可以期 待一下周三的算法與數(shù)據(jù)結(jié)構(gòu)專題,在之后的文章當(dāng)中會(huì)為大家分享線段樹的 相關(guān)內(nèi)容。在這里,我們只需要有一個(gè)大概的印象,線段樹究竟完成的是什么 樣的事情即可。線段樹維護(hù)的是一個(gè)線段,也就是區(qū)間內(nèi)的元素,也就是說(shuō)維護(hù)的是一個(gè)一維的序列。如果我們將數(shù)據(jù)的維度擴(kuò)充一下,擴(kuò)充到多維呢?是 的,你沒(méi)有猜錯(cuò),從
3、某種程度上來(lái)說(shuō),我們可以把KD-Tree看成是線段樹拓展到多維空間當(dāng)中的情況。KD-Tree 定義我們來(lái)看一下KD-Tree的具體定義,這里的 K指的是K維空間,D自然就 是dimension,也就是維度,也就是說(shuō) KD-Tree就是K維度樹的意思。在我們 構(gòu)建線段樹的時(shí)候,其實(shí)是一個(gè)遞歸的建樹過(guò)程,我們每次把當(dāng)前的線段一分 為二,然后用分成兩半的數(shù)據(jù)分別構(gòu)建左右子樹。我們可以簡(jiǎn)單寫一下偽代碼, 來(lái)更直觀地感受一下:class Node:def _init_(self, value, lchild, rchild):self.value = valueself.lchild = lchilds
4、elf.rchild = rchild def build(arr):n = len( arr):left, right = arr: n /2, arrn /2:Ichild, rchild = self.build(left), self.build(right)return Node(max(lchild.value, rchild.value), Ichild, rchild)我們來(lái)看一個(gè)二維的例子,在一個(gè)二維的平面當(dāng)中分布著若干個(gè)點(diǎn)。x軸。我們對(duì)我們首先選擇一個(gè)維度將這些數(shù)據(jù)一分為二,比如我們選擇 所有數(shù)據(jù)按照x軸的值排序,選出其中的中點(diǎn)進(jìn)行一分為二。在這根線左右兩側(cè)的點(diǎn)被分成了兩棵
5、子樹,對(duì)于這兩個(gè)部分的數(shù)據(jù)來(lái)說(shuō), 我們更換一個(gè)維度,也就是選擇 y軸進(jìn)行劃分。一樣,我們先排序,然后找到 中間的點(diǎn),再次一分為二。我們可以得到:我們重復(fù)上述過(guò)程,一直將點(diǎn)分到不能分為止,為了能更好地看清楚,我 們對(duì)所有數(shù)據(jù)標(biāo)上坐標(biāo)(并不精確)。如果我們把空間看成是廣義的區(qū)間,那么它和線段樹的原理是一樣的。最 后得到的也是一棵完美二叉樹,因?yàn)槲覀兠看味歼x擇了數(shù)據(jù)集的中點(diǎn)進(jìn)行劃分, 可以保證從樹根到葉子節(jié)點(diǎn)的長(zhǎng)度不會(huì)超過(guò)叩N。我們代入上面的坐標(biāo)之后, 我們最終得到的KD-Tree大概是下面這個(gè)樣子:KD-Tree 建樹在建樹的過(guò)程當(dāng)中,我們的樹深每往下延伸一層,我們就會(huì)換一個(gè)維度作 為衡量標(biāo)準(zhǔn)。原
6、因也很簡(jiǎn)單,因?yàn)槲覀兿M@棵樹對(duì)于這K維空間都有很好的表達(dá)能力,方便我們根據(jù)不同的維度快速查詢。在一些實(shí)現(xiàn)當(dāng)中,我們會(huì)計(jì)算 每一個(gè)維度的方差,然后選擇方差較大的維度進(jìn)行切分。這樣做自然是因?yàn)榉?差較大的維度說(shuō)明數(shù)據(jù)相對(duì)分散,切分之后可以把數(shù)據(jù)區(qū)分得更加明顯。但我 個(gè)人覺(jué)得這樣做意義不是很大,畢竟計(jì)算方差也是一筆開(kāi)銷。所以這里我們選 擇了最樸素的方法一一輪流選擇。也就是說(shuō)我們從樹根開(kāi)始,選擇第0維作為排序和切分?jǐn)?shù)據(jù)的依據(jù),然后到了樹深為1的這一層,我們選擇第一維,樹深為2的這一層,我們選擇第二維,以此類推。當(dāng)樹深超過(guò)了K的時(shí)候,我們就對(duì)樹深取模。明確了這一點(diǎn)之后,我們就可以來(lái)寫KD-Tree的建
7、樹代碼了,和上面二叉樹的代碼非常相似,只不過(guò)多了維度的處理而已。#外部暴露接口def build_model(self, dataset):self.root = self._build_model(dataset)#先忽略,容后再講self.set_father(self.root, None)#內(nèi)部實(shí)現(xiàn)的接口def _build_model(self, dataset, dep th=O): if len( dataset) = 0:return None#通過(guò)樹深對(duì)K取模來(lái)獲得當(dāng)前對(duì)哪一維切分axis = depth % self.Km = len( dataset) / 2#根據(jù)axi
8、s這一維排序dataset = sorted(dataset, key=lambda x: xaxis)#將數(shù)據(jù)一分為二left, mid, right = dataset:m, datasetm, datasetm+1:#遞歸建樹return KDTree.Node(midaxis,mid,axis,dep th,len( dataset),self._build_model(left, dep th+1), self._build_model(nght, dep th+1)這樣我們就建好了樹,但是在后序的查詢當(dāng)中我們需要訪問(wèn)節(jié)點(diǎn)的父節(jié)點(diǎn), 所以我們需要為每一個(gè)節(jié)點(diǎn)都賦值指向父親節(jié)點(diǎn)的指針。
9、這個(gè)值我們可以寫在 建樹的代碼里,但是會(huì)稍稍復(fù)雜一些,所以我把它單獨(dú)拆分了出來(lái),作為一個(gè) 獨(dú)立的函數(shù)來(lái)給每一個(gè)節(jié)點(diǎn)賦值。對(duì)于根節(jié)點(diǎn)來(lái)說(shuō),由于它沒(méi)有父親節(jié)點(diǎn),所 以賦值為Nones我們來(lái)看下set_father當(dāng)中的內(nèi)容,其實(shí)很簡(jiǎn)單,就是一個(gè) 樹的遞歸遍歷:def set_father(self, no de, father):if node is None:return#賦值no de.father = father#遞歸左右self.set_father( no de.lchild, no de)self.set_father( no de.rchild, no de)快速批量查詢KD-Tr
10、ee建樹建好了肯定是要來(lái)用的,它最大的用處是可以在單次查詢中 獲得距離樣本最近的若干個(gè)樣本。在分散均勻的數(shù)據(jù)集當(dāng)中,我們可以在的時(shí) 間內(nèi)完成查詢,但是對(duì)于特殊情況可能會(huì)長(zhǎng)一些,但是也比我們通過(guò)樸素的方 法查詢要快得多。我們很容易發(fā)現(xiàn),KD-Tree 一個(gè)廣泛的使用場(chǎng)景是用來(lái)優(yōu)化KNN算法。我們?cè)谥敖榻BKNN算法的文章當(dāng)中曾經(jīng)提到過(guò),KNN算法在預(yù)測(cè)的時(shí)候需要遍 歷整個(gè)數(shù)據(jù)集,然后計(jì)算數(shù)據(jù)集中每一個(gè)樣本與當(dāng)前樣本的距離,選出最近的 K個(gè)來(lái),這需要大量的開(kāi)銷。而使用KD-Tree,我們可以在一次查詢當(dāng)中直接查找到K個(gè)最近的樣本,因此大大提升 KNN算法的效率。那么,這個(gè)查詢操作又 是怎么實(shí)現(xiàn)的呢
11、?這個(gè)查詢基于遞歸實(shí)現(xiàn),因此對(duì)于遞歸不熟悉的小伙伴,可 能初看會(huì)比較困難,可以先閱讀一下之前關(guān)于遞歸的文章。首先我們先通過(guò)遞歸查找到KD-Tree上的葉子節(jié)點(diǎn),也就是找到樣本所在 的子空間。這個(gè)查找應(yīng)該非常容易,本質(zhì)上來(lái)說(shuō)我們就是將當(dāng)前樣本不停地與 分割線進(jìn)行比較,看看是在分割線的左側(cè)還是右側(cè)。和二叉搜索樹的元素查找 是一樣的:def iter_dow n( self, no de, data):#如果是葉子節(jié)點(diǎn),則返回if no de.lchild is None and no de.rchild is None:return node#如果左節(jié)點(diǎn)為空,則遞歸右節(jié)點(diǎn)if no de.lchi
12、ld is None:return self.iter_dow n(no de.rchild, data)#同理,遞歸左節(jié)點(diǎn)if no de.rchild is None:return self.iter_dow n(no de.rchild, data)#都不為空則和分割線判斷是左還是右axis = no de.axisn ext_ node = no de.lchild if dataaxis = no de.bo un dray else no de.rchild return self.iter_dow n(n ext_ no de, data)我們找到了葉子節(jié)點(diǎn),其實(shí)代表樣本空間當(dāng)中
13、的一小塊空間。我們來(lái)實(shí)際 走一下整個(gè)流程,假設(shè)我們要查找3個(gè)點(diǎn)。首先,我們會(huì)創(chuàng)建一個(gè)候選集,用來(lái)存儲(chǔ)答案。當(dāng)我們找到葉子節(jié)點(diǎn)之后,這個(gè)區(qū)域當(dāng)中只有一個(gè)點(diǎn),我們把它 加入候選集。在上圖當(dāng)中紫色的x代表我們查找的樣本,我們查找到的葉子節(jié)點(diǎn)之后, 在兩種情況下我們會(huì)把當(dāng)前點(diǎn)加入候選集。第一種情況是候選集還有空余,也 就是還沒(méi)有滿K個(gè),這里的K是我們查詢的數(shù)量,也就是3。第二種情況是當(dāng) 前點(diǎn)到樣本的距離小于候選集中最大的一個(gè),那么我們需要更新候選集。這個(gè) 點(diǎn)被我們?cè)L問(wèn)過(guò)之后,我們會(huì)打上標(biāo)記,表示這個(gè)點(diǎn)已經(jīng)訪問(wèn)過(guò)了。這個(gè)時(shí)候 我們需要判斷,整棵樹當(dāng)中的搜索是否已經(jīng)結(jié)束,如果當(dāng)前節(jié)點(diǎn)已經(jīng)是根節(jié)點(diǎn) 了,說(shuō)明
14、我們的遍歷結(jié)束了,那么返回候選集,否則說(shuō)明還沒(méi)有,我們需要繼 續(xù)搜索。上圖當(dāng)中我們用綠色表示樣本被放入了候選集當(dāng)中,黃色表示已經(jīng)訪 問(wèn)過(guò)。由于我們的搜索還沒(méi)有結(jié)束,所以需要繼續(xù)搜索。繼續(xù)搜索需要判斷樣 本和當(dāng)前分割線的距離來(lái)判斷和分割線的另一側(cè)有沒(méi)有可能存在答案。由于葉 子節(jié)點(diǎn)沒(méi)有另一側(cè),所以作罷,我們往上移動(dòng)一個(gè),跳轉(zhuǎn)到它的父親節(jié)點(diǎn)。此時(shí)候選集未滿,我們加入候選集,標(biāo)記 但是也沒(méi)有另一側(cè)的節(jié)點(diǎn),所以也跳過(guò)。 我們執(zhí)行同樣的判斷,發(fā)現(xiàn)此時(shí)候選集還我們計(jì)算距離并且查看候選集, 為已經(jīng)訪問(wèn)過(guò)。它雖然存在分割線, 我們?cè)偻希闅v到它的父親節(jié)點(diǎn), 有空余,于是將它繼續(xù)加入答案:但是當(dāng)我們判斷到分割線
15、距離的時(shí)候,我們發(fā)現(xiàn)這一次樣本到分割線的舉 例要比之前候選集當(dāng)中的最大距離要小,所以分割線的另一側(cè)很有可能存在答 案:這里的d1是樣本到分割線的距離,d2是樣本到候選集當(dāng)中最遠(yuǎn)點(diǎn)的距離。 由于到分割線更近,所以分割線的另一側(cè)很有可能也存在答案,這個(gè)時(shí)候我們 需要搜索分割線另一側(cè)的子樹,一直搜索到葉子節(jié)點(diǎn)。我們找到了葉子節(jié)點(diǎn),計(jì)算距離,發(fā)現(xiàn)此時(shí)候選集已經(jīng)滿了,并且它的距 離大于候選集當(dāng)中任何一個(gè)答案,所以不能構(gòu)成新的答案。于是我們只是標(biāo)記 它已經(jīng)訪問(wèn)過(guò),并不會(huì)加入候選集。同樣,我們繼續(xù)往上遍歷,到它的父節(jié)點(diǎn):J比較之后發(fā)現(xiàn),data到它的距離小于候選集當(dāng)中最大的那個(gè),于是我們更 新候選集,去掉距
16、離大于它的答案。然后我們重復(fù)上述的過(guò)程,直到根節(jié)點(diǎn)為 止。由于后面沒(méi)有更近的點(diǎn),所以候選集一直沒(méi)有更新,最后上圖當(dāng)中的三個(gè) 打了綠標(biāo)的點(diǎn)就是答案。我們把上面的流程整理一下,就得到了遞歸函數(shù)當(dāng)中 的邏輯,我們用Python寫出來(lái)其實(shí)已經(jīng)和代碼差不多了:def query (no de, data, an swers, K):#判斷node是否已經(jīng)訪問(wèn)過(guò)if no de.visited:#標(biāo)記訪問(wèn)no de.visited = True#計(jì)算data到node中點(diǎn)的距離dis = cal_dis(data, node.point)#如果小于答案中最大值則更新答案if dis max(a nswer
17、s):an swers. up date( node.point)#計(jì)算data到分割線的距離dis = cal_dis(data, no de.s plit)#如果小于最長(zhǎng)距離,說(shuō)明另一側(cè)還可能有答案if dis max(a nswers):#獲取當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)brother = self.get_brother( no de)if brother is not None:#往下搜索到葉子節(jié)點(diǎn),從葉子節(jié)點(diǎn)開(kāi)始尋找leaf = self.iter_dow n( brother, data)if leaf is not None:retur n self.query(leaf, data,
18、an swers, K)#如果已經(jīng)到了根節(jié)點(diǎn)了,退出if node is root:return an swers#遞歸父親節(jié)點(diǎn)return seif.query (no de.father, data, an swers, K) else:if node is root:return an swersreturn seif.query (no de.father, data, an swers, K)最終寫成的代碼和上面這段并沒(méi)有太多的差別,在得到距離之后和答案當(dāng) 中的最大距離進(jìn)行比較的地方,我們使用了優(yōu)先隊(duì)列。其他地方幾乎都是一樣 的,我也貼上來(lái)給大家感受一下:def _query_ ne
19、arest_k(self, no de, p ath, data, topK, K):#我們用set記錄訪問(wèn)過(guò)的路徑,而不是直接在節(jié)點(diǎn)上標(biāo)記if node not in p ath:p ath.add( no de)#計(jì)算歐氏距離dis = KDTree.dista nce( no de.value, data)if (len (to pK) K or dis top K-1dista nee):top K.a ppen d( no de: no de, dista nee: dis)#使用優(yōu)先隊(duì)列獲取topKtopK = hea pq.n smallest(K, topK, key=lamb
20、da x: xdista nee)axis = no de.axis#分割線都是直線,直接計(jì)算坐標(biāo)差dis = abs(dataaxis - no de.bo un dray)if len( to pK) K or dis = top K-1dista nee:brother = self.get_brother( no de, p ath)if brother is not None:n ext_ node = self.iter_dow n( brother, data)if n ext_ node is not None:retur n self._query_ nearest_k (n
21、 ext_ no de, p ath, data, topK, K)if node = self.root:return topKretu rn self._query_ nearest_k (no de.father, p ath, data, topK, K) else:if node = self.root:return topKretu rn self._query_ nearest_k (no de.father, p ath, data, topK, K)這段邏輯大家應(yīng)該都能看明白,但是有一個(gè)疑問(wèn)是,我們?yōu)槭裁床辉趎ode里面加一個(gè)visited的字段,而是通過(guò)傳入一個(gè)set來(lái)維護(hù)訪問(wèn)過(guò)的節(jié)點(diǎn)呢?這個(gè)邏輯只看代碼是很難想清楚的,必須要親手實(shí)驗(yàn)才會(huì)理解。如果在node當(dāng)中加入一個(gè)字段當(dāng)然也是可以的,如
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年證券經(jīng)紀(jì)代理與營(yíng)業(yè)部服務(wù)合作協(xié)議書
- 2024年利尿類藥物心腦血管類藥物項(xiàng)目建議書
- 開(kāi)展跨校合作的計(jì)劃
- 瓶裝液化氣危險(xiǎn)掌握培訓(xùn)
- 多元化評(píng)價(jià)系統(tǒng)的建立計(jì)劃
- 家園共育的重要性與實(shí)施策略計(jì)劃
- 大宗商品交易合同模板三篇
- 學(xué)期教研安排規(guī)劃計(jì)劃
- 智能家居裝修委托合同三篇
- 2024年金屬涂層光纖項(xiàng)目建議書
- 新教材大象版三年級(jí)下冊(cè)科學(xué)2-4導(dǎo)體與絕緣體教學(xué)課件
- 衛(wèi)健委2020年落實(shí)婦女兒童發(fā)展規(guī)劃情況的匯報(bào)
- GB∕T 22576.4-2021 醫(yī)學(xué)實(shí)驗(yàn)室 質(zhì)量和能力的要求 第4部分:臨床化學(xué)檢驗(yàn)領(lǐng)域的要求
- 使君子湯-幼科折衷卷上-方劑加減變化匯總
- Q∕SY 01010-2017 放空天然氣回收工程技術(shù)規(guī)范
- 口袋妖怪(寵物小精靈)1至649圖鑒
- 石油化工裝置應(yīng)急廣播綜合系統(tǒng)改造施工技術(shù)及組織方案
- 《正常人體結(jié)構(gòu)》課件NO4
- 武漢市干部掛職鍛煉
- 人教版數(shù)學(xué)七年級(jí)上冊(cè)課課練:1.5.3近似數(shù)(word、含答案)
- Q∕GDW 11612.2-2018 低壓電力線高速載波通信互聯(lián)互通技術(shù)規(guī)范 第2部分:技術(shù)要求
評(píng)論
0/150
提交評(píng)論