wiki百科:http://zh.wikipedia.org/wiki/%E5%86%B3%E7%AD%96%E6%A0%91%E5%AD%A6%E4%B9%A0
opencv學習筆記--二杈決策樹:http://blog.csdn.net/homechao/article/details/9061921
(1):從K近鄰算法、距離度量談到KD樹、SIFT+BBF算法 :http://blog.csdn.net/v_july_v/article/details/8203674
前言
? ? 前兩日,在微博 上說:“到今天為止,我至少虧欠了3篇文章待寫:1、KD樹;2、神經網絡;3、編程藝術第28章。你看到,blog內的文章與你于別處所見的任何都不同。于是,等啊等,等一臺電腦,只好等待.. ”。得益于田,借了我一臺電腦(借他電腦的時候,我連表示感謝,他說“能找到工作全靠你的博客,這點兒小忙還說,不地道”,有的時候,稍許感受到受人信任也是一種壓力,愿我不辜負大家對我的信任 ),于是今天開始Top 10 Algorithms in Data Mining 系列第三篇文章,即本文「從K近鄰算法談到KD樹、SIFT+BBF算法 」的創作。
? ? 一個人堅持自己的興趣是比較難的,因為太多的人太容易為外界所動了,而尤其當你無法從中得到多少實際性的回報時,所幸,我能一直堅持下來。畢達哥拉斯學派有句名言:“萬物皆數”,最近讀完「微積分概念發展史」后也感受到了這一點。同時,從算法到數據挖掘、機器學習,再到數學,其中每一個領域任何一個細節都值得探索終生,或許,這就是“終生為學”的意思。
? ? 本文各部分內容分布如下:
第一部分講K近鄰算法,其中重點闡述了相關的距離度量表示法, 第二部分著重講K近鄰算法的實現--KD樹,和KD樹的插入,刪除,最近鄰查找等操作,及KD樹的一系列相關改進(包括BBF,M樹等 ); 第三部分講KD樹的應用:SIFT+kd_BBF搜索算法。
? ? 同時,你將看到,K近鄰算法同本系列的前兩篇文章所講的決策樹分類貝葉斯分類,及支持向量機SVM一樣,也是用于解決分類問題的算法,
??
? ? 而本數據挖掘十大算法系列也會按照分類,聚類,關聯分析,預測回歸等問題依次展開闡述。
? ? OK,行文倉促,本文若有任何漏洞,問題或者錯誤,歡迎朋友們隨時不吝指正,各位的批評也是我繼續寫下去的動力之一。感謝。
第一部分、K近鄰算法
1.1、什么是K近鄰算法
? ? 何謂K近鄰算法,即K-Nearest Neighbor algorithm,簡稱KNN算法,單從名字來猜想,可以簡單粗暴的認為是:K個最近的鄰居,當K=1時,算法便成了最近鄰算法,即尋找最近的那個鄰居。為何要找鄰居?打個比方來說,假設你來到一個陌生的村莊,現在你要找到與你有著相似特征的人群融入他們,所謂入伙。
? ? 用官方的話來說,所謂K近鄰算法,即是給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最鄰近的K個實例(也就是上面所說的K個鄰居),這K個實例的多數屬于某個類,就把該輸入實例分類到這個類中。根據這個說法,咱們來看下引自維基百科上的一幅圖:
? ? 如上圖所示,有兩類不同的樣本數據,分別用藍色的小正方形和紅色的小三角形表示,而圖正中間的那個綠色的圓所標示的數據則是待分類的數據。也就是說,現在,我們不知道中間那個綠色的數據是從屬于哪一類(藍色小正方形or紅色小三角形),下面,我們就要解決這個問題:給這個綠色的圓分類。 ? ? 我們常說,物以類聚,人以群分,判別一個人是一個什么樣品質特征的人,常常可以從他/她身邊的朋友入手,所謂觀其友,而識其人。我們不是要判別上圖中那個綠色的圓是屬于哪一類數據么,好說,從它的鄰居下手。但一次性看多少個鄰居呢?從上圖中,你還能看到:
如果K=3,綠色圓點的最近的3個鄰居是2個紅色小三角形和1個藍色小正方形,少數從屬于多數,基于統計的方法,判定綠色的這個待分類點屬于紅色的三角形一類。 如果K=5,綠色圓點的最近的5個鄰居是2個紅色三角形和3個藍色的正方形,還是少數從屬于多數,基于統計的方法,判定綠色的這個待分類點屬于藍色的正方形一類。
? ? 于此我們看到,當無法判定當前待分類點是從屬于已知分類中的哪一類時,我們可以依據統計學的理論看它所處的位置特征,衡量它周圍鄰居的權重,而把它歸為(或分配)到權重更大的那一類。這就是K近鄰算法的核心思想。
1.2、近鄰的距離度量表示法
? ? 上文第一節,我們看到,K近鄰算法的核心在于找到實例點的鄰居,這個時候,問題就接踵而至了,如何找到鄰居,鄰居的判定標準是什么,用什么來度量。這一系列問題便是下面要講的距離度量表示法。但有的讀者可能就有疑問了,我是要找鄰居,找相似性,怎么又跟距離扯上關系了?
? ? 這是因為特征空間中兩個實例點的距離和反應出兩個實例點之間的相似性程度。K近鄰模型的特征空間一般是n維實數向量空間,使用的距離可以使歐式距離,也是可以是其它距離,既然扯到了距離,下面就來具體闡述下都有哪些距離度量的表示法,權當擴展。
1. 歐氏距離 ,最常見的兩點之間或多點之間的距離表示法,又稱之為歐幾里得度量,它定義于歐幾里得空間中,如點 x = (x1,...,xn) 和 y = (y1,...,yn) 之間的距離為:
(1)二維平面上兩點a(x1,y1)與b(x2,y2)間的歐氏距離 :
(2)三維空間兩點a(x1,y1,z1)與b(x2,y2,z2)間的歐氏距離:
(3)兩個n維向量a(x11,x12,…,x1n)與 b(x21,x22,…,x2n)間的歐氏距離:
也可以用表示成向量運算的形式:
其上,二維平面上兩點歐式距離, 代碼可以如下編寫:
?? double ?euclideanDistance(const ?vector<double >&?v1,?const ?vector<double >&?v2)??{?? ?????assert(v1.size()?==?v2.size());?? ?????double ?ret?=?0.0;?? ?????for ?(vector<double >::size_type?i?=?0;?i?!=?v1.size();?++i)?? ?????{?? ?????????ret?+=?(v1[i]?-?v2[i])?*?(v1[i]?-?v2[i]);?? ?????}?? ?????return ?sqrt(ret);?? ?}??
2. 曼哈頓距離 ,我們可以定義曼哈頓距離的正式意義為L1-距離或城市區塊距離,也就是在歐幾里得空間的固定直角坐標系上兩點所形成的線段對軸產生的投影的距離總和。例如在平面上,坐標(x1,?y1)的點P1與坐標(x2,?y2)的點P2的曼哈頓距離為:,要注意的是,曼哈頓距離依賴座標系統的轉度,而非系統在座標軸上的平移或映射。?
通俗來講,想象你在曼哈頓要從一個十字路口開車到另外一個十字路口,駕駛距離是兩點間的直線距離嗎?顯然不是,除非你能穿越大樓。而實際駕駛距離就是這個“曼哈頓距離”,此即曼哈頓距離名稱的來源, 同時,曼哈頓距離也稱為城市街區距離(City?Block?distance)。
(1)二維平面兩點a(x1,y1)與b(x2,y2)間的曼哈頓距離? (2)兩個n維向量a(x11,x12,…,x1n)與?b(x21,x22,…,x2n)間的曼哈頓距離?
? ? ? ? ? ? ? ? ? ? ? ? ??
3. 切比雪夫距離, 若二個向量或二個點p?、and?q,其座標分別為及,則兩者之間的切比雪夫距離定義如下:,
這也等于以下Lp度量的極值:,因此切比雪夫距離也稱為L∞度量。
以數學的觀點來看,切比雪夫距離是由一致范數(uniform?norm)(或稱為上確界范數)所衍生的度量,也是超凸度量(injective?metric?space)的一種。
在平面幾何中,若二點p及q的直角坐標系坐標為及,則切比雪夫距離為:。
玩過國際象棋的朋友或許知道,國王走一步能夠移動到相鄰的8個方格中的任意一個。那么國王從格子(x1,y1)走到格子(x2,y2)最少需要多少步?。你會發現最少步數總是max(?|?x2-x1?|?,?|?y2-y1?|?)?步?。有一種類似的一種距離度量方法叫切比雪夫距離。
(1)二維平面兩點a(x1,y1)與b(x2,y2)間的切比雪夫距離? (2)兩個n維向量a(x11,x12,…,x1n)與?b(x21,x22,…,x2n)間的切比雪夫距離? 這個公式的另一種等價形式是?
4.?閔可夫斯基距離(Minkowski?Distance), 閔氏距離不是一種距離,而是一組距離的定義。
(1)?閔氏距離的定義??????? 兩個n維變量a(x11,x12,…,x1n)與?b(x21,x22,…,x2n)間的閔可夫斯基距離定義為:?
其中p是一個變參數。
當p=1時,就是曼哈頓距離
當p=2時,就是歐氏距離
當p→∞時,就是切比雪夫距離???????
根據變參數的不同,閔氏距離可以表示一類的距離。?
5.?標準化歐氏距離?(Standardized?Euclidean?distance?) ,標準化歐氏距離是針對簡單歐氏距離的缺點而作的一種改進方案。標準歐氏距離的思路:既然數據各維分量的分布不一樣,那先將各個分量都“標準化”到均值、方差相等。至于均值和方差標準化到多少,先復習點統計學知識。 假設樣本集X的數學期望或均值(mean)為m,標準差(standard?deviation,方差開根)為s,那么X的“標準化變量”X*表示為:(X-m)/s,而且標準化變量的數學期望為0,方差為1。 即,樣本集的標準化過程(standardization)用公式描述就是: 標準化后的值?=??(?標準化前的值??-?分量的均值?)?/分量的標準差 經過簡單的推導就可以得到兩個n維向量a(x11,x12,…,x1n)與?b(x21,x22,…,x2n)間的標準化歐氏距離的公式: 如果將方差的倒數看成是一個權重,這個公式可以看成是一種加權歐氏距離(Weighted?Euclidean?distance)。? 6.?馬氏距離(Mahalanobis?Distance) (1)馬氏距離定義 ? ? ?? 有M個樣本向量X1~Xm,協方差矩陣記為S,均值記為向量μ,則其中樣本向量X到u的馬氏距離表示為:? (協方差矩陣中每個元素是各個矢量元素之間的協方差Cov(X,Y),Cov(X,Y) =?E{ [X-E(X)] [Y-E(Y)]},其中E為數學期望 ) 而其中向量Xi與Xj之間的馬氏距離定義為:? ?? 若協方差矩陣是單位矩陣(各個樣本向量之間獨立同分布),則公式就成了:? ? ? ? 也就是歐氏距離了。 若協方差矩陣是對角矩陣,公式變成了標準化歐氏距離。 (2)馬氏距離的優缺點:量綱無關,排除變量之間的相關性的干擾。? 「微博上的seafood高清版點評道:原來馬氏距離是根據協方差矩陣演變,一直被老師誤導了,怪不得看Killian在05年NIPS發表的LMNN論文時候老是看到協方差矩陣和半正定,原來是這回事 」 7、巴氏距離(Bhattacharyya?Distance ), 在統計中,Bhattacharyya距離測量兩個離散或連續概率分布的相似性。它與衡量兩個統計樣品或種群之間的重疊量的Bhattacharyya系數密切相關。Bhattacharyya距離和Bhattacharyya系數以20世紀30年代曾在印度統計研究所工作的一個統計學家A. Bhattacharya命名。同時,Bhattacharyya系數可以被用來確定兩個樣本被認為相對接近的,它是用來測量中的類分類的可分離性。
(1)巴氏距離的定義 對于離散概率分布?p和q在同一域?X,它被定義為:
其中:
是Bhattacharyya系數。 對于連續概率分布,Bhattacharyya系數被定義為:
在這兩種情況下,巴氏距離并沒有服從三角不等式.(值得一提的是,Hellinger距離不服從三角不等式)。? 對于多變量的高斯分布?,
,
和是手段和協方差的分布。 需要注意的是,在這種情況下,第一項中的Bhattacharyya距離與馬氏距離有關聯。? (2)Bhattacharyya系數 Bhattacharyya系數是兩個統計樣本之間的重疊量的近似測量,可以被用于確定被考慮的兩個樣本的相對接近。 計算Bhattacharyya系數涉及集成的基本形式的兩個樣本的重疊的時間間隔的值的兩個樣本被分裂成一個選定的分區數,并且在每個分區中的每個樣品的成員的數量,在下面的公式中使用
考慮樣品a?和?b?,n是的分區數,并且,被一個?和?b?i的日分區中的樣本數量的成員。更多介紹請參看:
http://en.wikipedia.org/wiki/Bhattacharyya_coefficient 。
8. 漢明距離(Hamming distance) , 兩個等長字符串s1與s2之間的漢明距離定義為將其中一個變為另外一個所需要作的最小替換次數。例如字符串“1111”與“1001”之間的漢明距離為2。應用:信息編碼(為了增強容錯性,應使得編碼間的最小漢明距離盡可能大)。
或許,你還沒明白我再說什么,不急,看下
上篇blog中第78題的第3小題 整理的一道面試題目,便一目了然了。如下圖所示:
?? ???? ?? f[i,j]?=?min?{?f[i-1,j]+1,??f[i,j-1]+1,??f[i-1,j-1]+(s[i]==t[j]?0:1)?}???? ???? ??
與此同時,
面試官還可以繼續問下去:那么,請問,如何設計一個比較兩篇文章相似性的算法?( 這個問題的討論可以看看這里: http://t.cn/zl82CAH,及這里關于simhash算法的介紹:
http://www.cnblogs.com/linecong/archive/2010/08/28/simhash.html ),接下來,便引出了下文關于夾角余弦的討論。 (上篇blog中第78題的第3小題給出了多種方法,讀者可以參看之。同時,程序員編程藝術系列第二十八章將詳細闡述這個問題)
9.?夾角余弦(Cosine) ,幾何中夾角余弦可用來衡量兩個向量方向的差異,機器學習中借用這一概念來衡量樣本向量之間的差異。
(1)在二維空間中向量A(x1,y1)與向量B(x2,y2)的夾角余弦公式:
(2)?兩個n維樣本點a(x11,x12,…,x1n)和b(x21,x22,…,x2n)的夾角余弦
?? ? ??
類似的,對于兩個n維樣本點a(x11,x12,…,x1n)和b(x21,x22,…,x2n),可以使用類似于夾角余弦的概念來衡量它們間的相似程度, 即: ? ? ? ?
夾角余弦取值范圍為[-1,1]。夾角余弦越大表示兩個向量的夾角越小,夾角余弦越小表示兩向量的夾角越大。當兩個向量的方向重合時夾角余弦取最大值1,當兩個向量的方向完全相反夾角余弦取最小值-1。?
10.?杰卡德相似系數(Jaccard?similarity?coefficient)
(1)?杰卡德相似系數??????? 兩個集合A和B的交集元素在A,B的并集中所占的比例,稱為兩個集合的杰卡德相似系數,用符號J(A,B)表示。
杰卡德相似系數是衡量兩個集合的相似度一種指標。 (2)?杰卡德距離??????? 與杰卡德相似系數相反的概念是杰卡德距離(Jaccard?distance)。 杰卡德距離可用如下公式表示:
杰卡德距離用兩個集合中不同元素占所有元素的比例來衡量兩個集合的區分度。 (3)?杰卡德相似系數與杰卡德距離的應用?????? 可將杰卡德相似系數用在衡量樣本的相似度上。
舉例:樣本A與樣本B是兩個n維向量,而且所有維度的取值都是0或1,例如:A(0111)和B(1011)。我們將樣本看成是一個集合,1表示集合包含該元素,0表示集合不包含該元素。
M
11 :樣本A與B都是1的維度的個數
M0 1 :樣本A是0,樣本B是1的維度的個數
M10 :樣本A是1,樣本B是0 的維度的個數
M
00 :樣本A與B都是0的維度的個數 依據上文給的杰卡德相似系數及杰卡德距離的相關定義,樣本A與B的杰卡德相似系數J可以表示為:
這里M
11 +
M 01 +
M 10 可理解為A與B的并集的元素個數,而M
11 是A與B的交集的元素個數。而樣本A與B的杰卡德距離表示為J':
11.皮爾遜系數(Pearson Correlation Coefficient) ? ? 在具體闡述皮爾遜相關系數之前,有必要解釋下什么是相關系數 ( Correlation coefficient )與相關距離(Correlation distance)。 相關系數?(?Correlation?coefficient?)的定義是:
(
其中,E為數學期望或均值,D為方差,D開根號為標準差,E{ [X-E(X)] [Y-E(Y)]}稱為隨機變量X與Y的協方差,記為Cov(X,Y),即Cov(X,Y) =?E{ [X-E(X)] [Y-E(Y)]},而兩個變量之間的協方差和標準差的商則稱為隨機變量X與Y的相關系數,記為 )
相關系數衡量隨機變量X與Y相關程度的一種方法,相關系數的取值范圍是[-1,1]。相關系數的絕對值越大,則表明X與Y相關度越高。當X與Y線性相關時,相關系數取值為1(正線性相關)或-1(負線性相關)。 具體的,如果有兩個變量:X、Y,最終計算出的相關系數的含義可以有如下理解:
當相關系數為0時,X和Y兩變量無關系。 當X的值增大(減小),Y值增大(減小),兩個變量為正相關,相關系數在0.00與1.00之間。 當X的值增大(減小),Y值減小(增大),兩個變量為負相關,相關系數在-1.00與0.00之間。 相關距離的定義是:
OK,接下來,咱們來重點了解下皮爾遜相關系數。 在統計學中,皮爾遜積矩相關系數(英語:Pearson product-moment correlation coefficient,又稱作 PPMCC或PCCs, 用r表示)用于度量兩個變量X和Y之間的相關(線性相關),其值介于-1與1之間。
通常情況下通過以下取值范圍判斷變量的相關強度: 相關系數???? 0.8-1.0?????極強相關 ???????????????? 0.6-0.8???? 強相關 ???????????????? 0.4-0.6???? 中等程度相關 ???????????????? 0.2-0.4???? 弱相關 ???????????????? 0.0-0.2???? 極弱相關或無相關
在自然科學領域中,該系數廣泛用于度量兩個變量之間的相關程度。它是由卡爾·皮爾遜從弗朗西斯·高爾頓在19世紀80年代提出的一個相似卻又稍有不同的想法演變而來的。這個相關系數也稱作“皮爾森相關系數r”。
(1)皮爾遜系數的定義 : 兩個變量之間的皮爾遜相關系數定義為兩個變量之間的協方差和標準差的商: 以上方程定義了總體相關系數, 一般表示成希臘字母ρ(rho)。基于樣本對協方差和方差進行估計,可以得到樣本標準差, 一般表示成r: 一種等價表達式的是表示成標準分的均值。基于(Xi, Yi)的樣本點,樣本皮爾遜系數是
? ? ? ? ? ? ? ?其中、?及?,分別是標準分、樣本平均值和樣本標準差。
或許上面的講解令你頭腦混亂不堪,沒關系,我換一種方式講解,如下:
假設有兩個變量X、Y,那么兩變量間的皮爾遜相關系數可通過以下公式計算:
注:勿忘了上面說過,“皮爾遜相關系數定義為兩個變量之間的協方差和標準差的商”,其中標準差的計算公式為:
以上列出的四個公式等價,其中E是數學期望,cov表示協方差,N表示變量取值的個數。
(2)皮爾遜相關系數的適用范圍 當兩個變量的標準差都不為零時,相關系數才有定義,皮爾遜相關系數適用于: 兩個變量之間是線性關系,都是連續數據。 兩個變量的總體是正態分布,或接近正態的單峰分布。 兩個變量的觀測值是成對的,每對觀測值之間相互獨立。 (3)如何理解皮爾遜相關系數 rubyist : 皮爾遜相關系數理解有兩個角度
其一, 按照高中數學水平來理解, 它很簡單, 可以看做將兩組數據首先做Z分數處理之后, 然后兩組數據的乘積和除以樣本數, Z分數一般代表正態分布中, 數據偏離中心點的距離.等于變量減掉平均數再除以標準差.(就是高考的標準分類似的處理)
樣本標準差則等于變量減掉平均數的平方和,再除以樣本數,最后再開方,也就是說,方差開方即為標準差,樣本標準差 計算公式為:
所以, 根據這個最樸素的理解,我們可以將公式依次精簡為:
其二, 按照大學的線性數學水平來理解, 它比較復雜一點,可以看做是兩組數據的向量夾角的余弦。 下面是關于此皮爾遜系數的 幾何學的解釋,先來看一幅圖,如下所示:
回歸直線: y=gx(x) [紅色] 和 x=gy(y) [藍色]
如上圖,對于沒有中心化的數據, 相關系數與兩條可能的回歸線y=gx(x) 和 x=gy(y) 夾角的余弦值一致。 對于沒有中心化的數據 (也就是說, 數據移動一個樣本平均值以使其均值為0), 相關系數也可以被視作由兩個隨機變量 向量 夾角 的 余弦值(見下方)。 舉個例子,例如,有5個國家的國民生產總值分別為 10, 20, 30, 50 和 80 億美元。 假設這5個國家 (順序相同) 的貧困百分比分別為 11%, 12%, 13%, 15%, and 18% 。 令 x 和 y 分別為包含上述5個數據的向量: x = (1, 2, 3, 5, 8) 和 y = (0.11, 0.12, 0.13, 0.15, 0.18)。 利用通常的方法計算兩個向量之間的夾角 ?(參見 數量積), 未中心化 的相關系數是:
我們發現以上的數據特意選定為完全相關: y = 0.10 + 0.01 x。 于是,皮爾遜相關系數應該等于1。將數據中心化 (通過E(x) = 3.8移動 x 和通過 E(y) = 0.138 移動 y ) 得到 x = (?2.8, ?1.8, ?0.8, 1.2, 4.2) 和 y = (?0.028, ?0.018, ?0.008, 0.012, 0.042), 從中
(4)皮爾遜相關的約束條件
從以上解釋, 也可以理解皮爾遜相關的約束條件:
1 兩個變量間有線性關系 2 變量是連續變量 3 變量均符合正態分布,且二元分布也符合正態分布 4 兩變量獨立 在實踐統計中,一般只輸出兩個系數,一個是相關系數,也就是計算出來的相關系數大小,在-1到1之間;另一個是獨立樣本檢驗系數,用來檢驗樣本一致性。
簡單說來,各種“距離”的應用場景簡單概括為,空間:歐氏距離,路徑:曼哈頓距離,國際象棋國王:切比雪夫距離,以上三種的統一形式:閔可夫斯基距離,加權:標準化歐氏距離,排除量綱和依存:馬氏距離,向量差距:夾角余弦,編碼差別:漢明距離,集合近似度:杰卡德類似系數與距離,相關:相關系數與相關距離。
1.3、K值的選擇
? ? 除了上述1.2節如何定義鄰居的問題之外,還有一個選擇多少個鄰居,即K值定義為多大的問題。不要小看了這個K值選擇問題,因為它對K近鄰算法的結果會產生重大影響。如李航博士的一書「統計學習方法」上所說:
如果選擇較小的K值,就相當于用較小的領域中的訓練實例進行預測,“學習”近似誤差會減小,只有與輸入實例較近或相似的訓練實例才會對預測結果起作用,與此同時帶來的問題是“學習”的估計誤差會增大,換句話說,K值的減小就意味著整體模型變得復雜,容易發生過擬合; 如果選擇較大的K值,就相當于用較大領域中的訓練實例進行預測,其優點是可以減少學習的估計誤差,但缺點是學習的近似誤差會增大。這時候,與輸入實例較遠(不相似的)訓練實例也會對預測器作用,使預測發生錯誤,且K值的增大就意味著整體的模型變得簡單。 K=N,則完全不足取,因為此時無論輸入實例是什么,都只是簡單的預測它屬于在訓練實例中最多的累,模型過于簡單,忽略了訓練實例中大量有用信息。
在實際應用中,K值一般取一個比較小的數值,例如采用交叉驗證法(簡單來說,就是一部分樣本做訓練集,一部分做測試集)來選擇最優的K值。
第二部分、K近鄰算法的實現:KD樹
2.0、背景
? ? 之前blog內曾經介紹過SIFT特征匹配算法,特征點匹配和數據庫查、圖像檢索本質上是同一個問題,都可以歸結為一個通過距離函數在高維矢量之間進行相似性檢索的問題,如何快速而準確地找到查詢點的近鄰,不少人提出了很多高維空間索引結構和近似查詢的算法。
? ? 一般說來,索引結構中相似性查詢有兩種基本的方式:
一種是范圍查詢,范圍查詢時給定查詢點和查詢距離閾值,從數據集中查找所有與查詢點距離小于閾值的數據 另一種是K近鄰查詢,就是給定查詢點及正整數K,從數據集中找到距離查詢點最近的K個數據,當K=1時,它就是最近鄰查詢。
? ? 同樣,針對特征點匹配也有兩種方法:
最容易的辦法就是線性掃描,也就是我們常說的窮舉搜索,依次計算樣本集E中每個樣本到輸入實例點的距離,然后抽取出計算出來的最小距離的點即為最近鄰點。此種辦法簡單直白,但當樣本集或訓練集很大時,它的缺點就立馬暴露出來了,舉個例子,在物體識別的問題中,可能有數千個甚至數萬個SIFT特征點,而去一一計算這成千上萬的特征點與輸入實例點的距離,明顯是不足取的。 另外一種,就是構建數據索引,因為實際數據一般都會呈現簇狀的聚類形態,因此我們想到建立數據索引,然后再進行快速匹配。索引樹是一種樹結構索引方法,其基本思想是對搜索空間進行層次劃分。根據劃分的空間是否有混疊可以分為Clipping和Overlapping兩種。前者劃分空間沒有重疊,其代表就是k-d樹;后者劃分空間相互有交疊,其代表為R樹。
? ? 而關于R樹本blog內之前已有介紹(同時,關于基于R樹的最近鄰查找,還可以看下這篇文章: http://blog.sina.com.cn/s/blog_72e1c7550101dsc3.html ),本文著重介紹k-d樹。
? ? 1975年,來自斯坦福大學的Jon Louis Bentley在ACM雜志上發表的一篇論文:Multidimensional Binary Search Trees Used for Associative Searching 中正式提出和闡述的了如下圖形式的把空間劃分為多個部分的k-d樹。
2.1、什么是KD樹
? ? Kd-樹是K-dimension tree的縮寫,是對數據點在k維空間(如二維(x,y),三維(x,y,z),k維(x1,y,z..) ) 中劃分的一種數據結構,主要應用于多維空間關鍵數據的搜索(如:范圍搜索和最近鄰搜索)。本質上說,Kd-樹就是一種平衡二叉樹。
? ? 首先必須搞清楚的是,k-d樹是一種空間劃分樹,說白了,就是把整個空間劃分為特定的幾個部分,然后在特定空間的部分內進行相關搜索操作。想像一個三維(多維有點為難你的想象力了)空間,kd樹按照一定的劃分規則把這個三維空間劃分了多個空間,如下圖所示:
2.2、KD樹的構建
? ? kd樹構建的偽代碼如下圖所示:
? ? 再舉一個簡單直觀的實例來介紹k-d樹構建算法。假設有6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},數據點位于二維空間內,如下圖所示。為了能有效的找到最近鄰,k-d樹采用分而治之的思想,即將整個空間劃分為幾個小部分,首先,粗黑線將空間一分為二,然后在兩個子空間中,細黑直線又將整個空間劃分為四部分,最后虛黑直線將這四部分進一步劃分。
? ? 6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}構建kd樹的具體步驟為:
確定:split域=x。具體是:6個數據點在x,y維度上的數據方差分別為39,28.63,所以在x軸上方差更大,故split域值為x; 確定:Node-data = (7,2)。具體是:根據x維上的值將數據排序,6個數據的中值(所謂中值,即中間大小的值 )為7,所以Node-data域位數據點(7,2)。這樣,該節點的分割超平面就是通過(7,2)并垂直于:split=x軸的直線x=7; 確定:左子空間和右子空間。具體是:分割超平面x=7將整個空間分為兩部分:x<=7的部分為左子空間,包含3個節點={(2,3),(5,4),(4,7)};另一部分為右子空間,包含2個節點={(9,6),(8,1)};
如上算法所述,kd樹的構建是一個遞歸過程,我們對左子空間和右子空間內的數據重復根節點的過程就可以得到一級子節點(5,4)和(9,6),同時將空間和數據集進一步細分,如此往復直到空間中只包含一個數據點。
? ? 與此同時,經過對上面所示的空間劃分之后,我們可以看出,點(7,2)可以為根結點,從根結點出發的兩條紅粗斜線指向的(5,4)和(9,6)則為根結點的左右子結點,而(2,3),(4,7)則為(5,4)的左右孩子(通過兩條細紅斜線相連),最后,(8,1)為(9,6)的左孩子(通過細紅斜線相連)。如此,便形成了下面這樣一棵k-d樹:
?
? ? k-d樹的數據結構
? ? 針對上表給出的kd樹的數據結構,轉化成具體代碼如下所示( 注,本文以下代碼分析基于Rob Hess維護的sift庫 ) :
?? struct ?kd_node??{?? ????int ?ki;??????????????????????//關鍵點直方圖方差最大向量系列位置?? ????double ?kv;???????????????????//直方圖方差最大向量系列中最中間模值?? ????int ?leaf;?????????????????????? ????struct ?feature*?features;?????? ????int ?n;????????????????????????? ????struct ?kd_node*?kd_left;??????? ????struct ?kd_node*?kd_right;?????? };??
? ? 也就是說,如之前所述,kd樹中,kd代表k-dimension,每個節點即為一個k維的點。每個非葉節點可以想象為一個分割超平面,用垂直于坐標軸的超平面將空間分為兩個部分,這樣遞歸的從根節點不停的劃分,直到沒有實例為止。經典的構造k-d tree的規則如下:
隨著樹的深度增加,循環的選取坐標軸,作為分割超平面的法向量。對于3-d tree來說,根節點選取x軸,根節點的孩子選取y軸,根節點的孫子選取z軸,根節點的曾孫子選取x軸,這樣循環下去。 每次均為所有對應實例的中位數的實例作為切分點,切分點作為父節點,左右兩側為劃分的作為左右兩子樹。
? ? 對于n個實例的k維數據來說,建立kd-tree的時間復雜度為O(k*n*logn)。
? ? 以下是構建k-d樹的代碼:
struct ?kd_node*?kdtree_build(?struct ?feature*?features,?int ?n?)??{?? ????struct ?kd_node*?kd_root;?? ?? ????if (?!?features??||??n?<=?0?)?? ????{?? ????????fprintf(?stderr,?"Warning:?kdtree_build():?no?features,?%s,?line?%d\n" ,?? ????????????????__FILE__,?__LINE__?);?? ????????return ?NULL;?? ????}?? ?? ?????? ????kd_root?=?kd_node_init(?features,?n?);???? ????expand_kd_node_subtree(?kd_root?);???? ?? ????return ?kd_root;?? }??
? ? 上面的涉及初始化操作的兩個函數kd_node_init,及expand_kd_node_subtree代碼分別如下所示:
static ?struct ?kd_node*?kd_node_init(?struct ?feature*?features,?int ?n?)??{??????????????????????????????????????? ????struct ?kd_node*?kd_node;?? ?? ????kd_node?=?(struct ?kd_node*)(malloc(?sizeof (?struct ?kd_node?)?));?? ????memset(?kd_node,?0,?sizeof (?struct ?kd_node?)?);??? ????kd_node->ki?=?-1;??? ????kd_node->features?=?features;?? ????kd_node->n?=?n;?? ?? ????return ?kd_node;?? }??
static ?void ?expand_kd_node_subtree(?struct ?kd_node*?kd_node?)??{?? ?????? ????if (?kd_node->n?==?1??||??kd_node->n?==?0?)?? ????{????? ????????kd_node->leaf?=?1;?? ????????return ;?? ????}?? ?? ????assign_part_key(?kd_node?);??? ????partition_features(?kd_node?);??? ??????????????????????????????????? ????if (?kd_node->kd_left?)?? ????????expand_kd_node_subtree(?kd_node->kd_left?);?? ????if (?kd_node->kd_right?)?? ????????expand_kd_node_subtree(?kd_node->kd_right?);?? }??
? ? 構建完kd樹之后,如今進行最近鄰搜索呢?從下面的動態gif 圖中,你是否能看出些許端倪呢?
? ? k-d樹算法可以分為兩大部分,除了上部分有關k-d樹本身這種數據結構建立的算法,另一部分是在建立的k-d樹上各種諸如插入,刪除,查找(最鄰近查找)等操作涉及的算法。下面,咱們依次來看kd樹的插入、刪除、查找操作。
2.3、KD樹的插入
? ? 元素插入到一個K-D樹的方法和二叉檢索樹類似。本質上,在偶數層比較x坐標值,而在奇數層比較y坐標值。當我們到達了樹的底部,(也就是當一個空指針出現),我們也就找到了結點將要插入的位置。生成的K-D樹的形狀依賴于結點插入時的順序。給定N個點,其中一個結點插入和檢索的平均代價是O(log2N)。
? ? 下面4副圖(來源:中國地質大學電子課件 )說明了插入順序為(a) Chicago, (b) Mobile, (c) Toronto, and (d) Buffalo,建立空間K-D樹的示例:
? ? 應該清楚,這里描述的插入過程中,每個結點將其所在的平面分割成兩部分。因比,Chicago 將平面上所有結點分成兩部分,一部分所有的結點x坐標值小于35,另一部分結點的x坐標值大于或等于35。同樣Mobile將所有x坐標值大于35的結點以分成兩部分,一部分結點的Y坐標值是小于10,另一部分結點的Y坐標值大于或等于10。后面的Toronto、Buffalo也按照一分為二的規則繼續劃分。
2.4、KD樹的刪除
KD樹的刪除可以用遞歸程序來實現。我們假設希望從K-D樹中刪除結點(a,b)。如果(a,b)的兩個子樹都為空,則用空樹來代替(a,b)。否則,在(a,b)的子樹中尋找一個合適的結點來代替它,譬如(c,d),則遞歸地從K-D樹中刪除(c,d)。一旦(c,d)已經被刪除,則用(c,d)代替(a,b)。假設(a,b)是一個X識別器,那么,
它得替代節點要么是(a,b)左子樹中的X坐標最大值的結點,要么是(a,b)右子樹中x坐標最小值的結點 。
也就是說,跟普通二叉樹(
包括如下圖所示的紅黑樹 )結點的刪除是同樣的思想:用被刪除節點A的左子樹的最右節點或者A的右子樹的最左節點作為替代A的節點(
比如,下圖紅黑樹中,若要刪除根結點26,第一步便是用23或28取代根結點26 )。
當(a,b)的右子樹為空時,找到(a,b)左子樹中具有x坐標最大的結點,譬如(c,d),將(a,b)的左子樹放到(c,d)的右子樹中,且在樹中從它的上一層遞歸地應用刪除過程(也就是(a,b)的左子樹) 。
下面來舉一個實際的例子(
來源:中國地質大學電子課件,原課件錯誤已經在下文中訂正 ),如下圖所示,原始圖像及對應的kd樹,現在要刪除圖中的A結點,請看一系列刪除步驟:
要刪除上圖中結點A,選擇結點A的右子樹中X坐標值最小的結點,這里是C,C成為根,如下圖:
從C的右子樹中找出一個結點代替先前C的位置,
這里是D,并將D的左子樹轉為它的右子樹,D代替先前C的位置,如下圖:
在D的新右子樹中,找X坐標最小的結點,這里為H,H代替D的位置,
在D的右子樹中找到一個Y坐標最小的值,這里是I,將I代替原先H的位置,從而A結點從圖中順利刪除,如下圖所示:
從一個K-D樹中刪除結點(a,b)的問題變成了在(a,b)的子樹中尋找x坐標為最小的結點。不幸的是尋找最小x坐標值的結點比二叉檢索樹中解決類似的問題要復雜得多。特別是雖然最小x坐標值的結點一定在x識別器的左子樹中,但它同樣可在y識別器的兩個子樹中。因此關系到檢索,且必須注意檢索坐標,以使在每個奇數層僅檢索2個子樹中的一個。
? ? 從K-D樹中刪除一個結點是代價很高的,很清楚刪除子樹的根受到子樹中結點個數的限制。用TPL(T)表示樹T總的路徑長度。可看出樹中子樹大小的總和為TPL(T)+N。 以隨機方式插入N個點形成樹的TPL是O(N*log2N),這就意味著從一個隨機形成的K-D樹中刪除一個隨機選取的結點平均代價的上界是O(log2N) 。
2.5、KD樹的最近鄰搜索算法
? ? 現實生活中有許多問題需要在多維數據的快速分析和快速搜索,對于這個問題最常用的方法是所謂的kd樹。在k-d樹中進行數據的查找也是特征匹配的重要環節,其目的是檢索在k-d樹中與查詢點距離最近的數據點。在一個N維的笛卡兒空間在兩個點之間的距離是由下述公式確定:
2.5.1、k-d樹查詢算法的偽代碼
? ? k-d樹查詢算法的偽代碼如下所示:
算法:k-d樹最鄰近查找?? 輸入:Kd,?????? ?????target???? 輸出:nearest,??? ?????dist???????? ?? 1.?If?Kd為NULL,則設dist為infinite并返回?? 2.??? ???Kd_point?=?&Kd;????????????????????? ???nearest?=?Kd_point?->?Node-data;???? ?? ???while (Kd_point)?? ??? push(Kd_point)到search_path中;??? ?? ??????If?Dist(nearest,target)?>?Dist(Kd_point?->?Node-data,target)?? ??? nearest??=?Kd_point?->?Node-data;?????? ??? Min_dist?=?Dist(Kd_point,target);???? ??? s?=?Kd_point?->?split;????????????????????????? ?? ??? If?target[s]?<=?Kd_point?->?Node-data[s]??????? ??? Kd_point?=?Kd_point?->?left;?? ??? else ?? ??? Kd_point?=?Kd_point?->right;?? ???End?while ?? ?? 3.??? ???while (search_path?!=?NULL)?? ??? back_point?=?從search_path取出一個節點指針;????? ??? s?=?back_point?->?split;???????????????????????? ?? ??? If?Dist(target[s],back_point?->?Node-data[s])?<?Max_dist????? ??? If?target[s]?<=?back_point?->?Node-data[s]?? ??? Kd_point?=?back_point?->?right;???? ??? else ?? ??? Kd_point?=?back_point?->?left;?????? ??? 將Kd_point壓入search_path堆棧;?? ?? ??? If?Dist(nearest,target)?>?Dist(Kd_Point?->?Node-data,target)?? ??? nearest??=?Kd_point?->?Node-data;??????????????????? ??? Min_dist?=?Dist(Kd_point?->?Node-data,target);???? ???End?while ???
? ?讀者來信點評@yhxyhxyhx ,在“將Kd_point壓入search_path堆棧;”這行代碼后,應該是調到步驟2再往下走二分搜索的邏輯一直到葉結點,我寫了一個遞歸版本的二維kd tree的搜索函數你對比的看看:
void ?innerGetClosest(NODE*?pNode,?PT?point,?PT&?res,?int &?nMinDis)??{?? ????if ?(NULL?==?pNode)?? ????????return ;?? ????int ?nCurDis?=?abs(point.x?-?pNode->pt.x)?+?abs(point.y?-?pNode->pt.y);?? ????if ?(nMinDis?<?0?||?nCurDis?<?nMinDis)?? ????{?? ????????nMinDis?=?nCurDis;?? ????????res?=?pNode->pt;?? ????}?? ????if ?(pNode->splitX?&&?point.x?<=?pNode->pt.x?||?!pNode->splitX?&&?point.y?<=?pNode->pt.y)?? ????????innerGetClosest(pNode->pLft,?point,?res,?nMinDis);?? ????else ?? ????????innerGetClosest(pNode->pRgt,?point,?res,?nMinDis);?? ????int ?rang?=?pNode->splitX???abs(point.x?-?pNode->pt.x)?:?abs(point.y?-?pNode->pt.y);?? ????if ?(rang?>?nMinDis)?? ????????return ;?? ????NODE*?pGoInto?=?pNode->pLft;?? ????if ?(pNode->splitX?&&?point.x?>?pNode->pt.x?||?!pNode->splitX?&&?point.y?>?pNode->pt.y)?? ????????pGoInto?=?pNode->pRgt;?? ????innerGetClosest(pGoInto,?point,?res,?nMinDis);?? }??
? ? 下面,以兩個簡單的實例( 例子來自圖像局部不變特性特征與描述一書 ) 來描述最鄰近查找的基本思路。
2.5.2、舉例:查詢點(2.1,3.1)
? ? 星號表示要查詢的點(2.1,3.1)。通過二叉搜索,順著搜索路徑很快就能找到最鄰近的近似點,也就是葉子節點(2,3)。而找到的葉子節點并不一定就是最鄰近的,最鄰近肯定距離查詢點更近,應該位于以查詢點為圓心且通過葉子節點的圓域內。為了找到真正的最近鄰,還需要進行相關的‘回溯'操作。 也就是說,算法首先沿搜索路徑反向查找是否有距離查詢點更近的數據點。
? ? 以查詢(2.1,3.1)為例:
二叉樹搜索:先從(7,2)點開始進行二叉查找,然后到達(5,4),最后到達(2,3),此時搜索路徑中的節點為<(7,2),(5,4),(2,3)>,首先以(2,3)作為當前最近鄰點,計算其到查詢點(2.1,3.1)的距離為0.1414, 回溯查找:在得到(2,3)為查詢點的最近點之后,回溯到其父節點(5,4),并判斷在該父節點的其他子節點空間中是否有距離查詢點更近的數據點。以(2.1,3.1)為圓心,以0.1414為半徑畫圓,如下圖所示。發現該圓并不和超平面y = 4交割,因此不用進入(5,4)節點右子空間中(圖中灰色區域)去搜索; 最后,再回溯到(7,2),以(2.1,3.1)為圓心,以0.1414為半徑的圓更不會與x = 7超平面交割,因此不用進入(7,2)右子空間進行查找。至此,搜索路徑中的節點已經全部回溯完,結束整個搜索,返回最近鄰點(2,3),最近距離為0.1414。
2.5.3、舉例:查詢點 (2,4.5 )
? ? 一個復雜點了例子如查找點為(2,4.5),具體步驟依次如下:
同樣先進行二叉查找,先從(7,2)查找到(5,4)節點,在進行查找時是由y = 4為分割超平面的,由于查找點為y值為4.5,因此進入右子空間查找到(4,7),形成搜索路徑<(7,2),(5,4),(4,7)>,但(4,7)與目標查找點的距離為3.202,而(5,4)與查找點之間的距離為3.041,所以(5,4)為查詢點的最近點; 以(2,4.5)為圓心,以3.041為半徑作圓,如下圖所示。可見該圓和y = 4超平面交割,所以需要進入(5,4)左子空間進行查找,也就是將(2,3)節點加入搜索路徑中得<(7,2),(2,3)>;于是接著 搜索至(2,3)葉子節點,(2,3)距離(2,4.5)比(5,4)要近,所以最近鄰點更新為(2,3),最近距離更新為1.5; 回溯查找至(5,4),直到最后回溯到根結點(7,2)的時候,以(2,4.5)為圓心1.5為半徑作圓,并不和x = 7分割超平面交割,如下圖所示。至此,搜索路徑回溯完,返回最近鄰點(2,3),最近距離1.5。
? ? 上述兩次實例表明,當查詢點的鄰域與分割超平面兩側空間交割時,需要查找另一側子空間,導致檢索過程復雜,效率下降。
一般來講,最臨近搜索只需要檢測幾個葉子結點即可,如下圖所示:
但是,如果當實例點的分布比較糟糕時,幾乎要遍歷所有的結點,如下所示:
? ? 研究表明N個節點的K維k-d樹搜索過程時間復雜度為:t worst =O(kN 1-1/k )。
? ? 同時,以上為了介紹方便,討論的是二維或三維情形。但在實際的應用中,如SIFT特征矢量128維,SURF特征矢量64維,維度都比較大,直接利用k-d樹快速檢索(維數不超過20)的性能急劇下降,幾乎接近貪婪線性掃描。假設數據集的維數為D,一般來說要求數據的規模N滿足N?2 D ,才能達到高效的搜索。所以這就引出了一系列對k-d樹算法的改進:BBF算法,和一系列M樹、VP樹、MVP樹等高維空間索引樹(下文2.6節 kd樹近鄰搜索算法的改進:BBF算法,與2.7節 球樹、M樹、VP樹、MVP樹 )。
2.6、kd樹近鄰搜索算法的改進:BBF算法
? ? 咱們順著上一節的思路,參考統計學習方法一書上的內容,再來總結下kd樹的最近鄰搜索算法:
輸入:以構造的kd樹,目標點x;
輸出:x 的最近鄰
算法步驟如下:
在kd樹種找出包含目標點x的葉結點:從根結點出發,遞歸地向下搜索kd樹。若目標點x當前維的坐標小于切分點的坐標,則移動到左子結點,否則移動到右子結點,直到子結點為葉結點為止。 以此葉結點為“當前最近點”。 遞歸的向上回溯,在每個結點進行以下操作: (a)如果該結點保存的實例點比當前最近點距離目標點更近,則更新“當前最近點”,也就是說以該實例點為“當前最近點”。 (b)當前最近點一定存在于該結點一個子結點對應的區域,檢查子結點的父結點的另一子結點對應的區域是否有更近的點。具體做法是,檢查另一子結點對應的區域是否以目標點位球心,以目標點與“當前最近點”間的距離為半徑的圓或超球體相交: 如果相交,可能在另一個子結點對應的區域內存在距目標點更近的點,移動到另一個子結點,接著,繼續遞歸地進行最近鄰搜索; 如果不相交,向上回溯。 當回退到根結點時,搜索結束 ,最后的“當前最近點”即為x 的最近鄰點。
? ? 如果實例點是隨機分布的,那么kd樹搜索的平均計算復雜度是O(NlogN),這里的N是訓練實例樹。所以說,kd樹更適用于訓練實例數遠大于空間維數時的k近鄰搜索,當空間維數接近訓練實例數時,它的效率會迅速下降,一降降到“解放前”:線性掃描的速度。
? ? 也正因為上述k最近鄰搜索算法的第4個步驟中的所述:“回退到根結點時,搜索結束” ,每個最近鄰點的查詢比較完成過程最終都要回退到根結點而結束,而導致了許多不必要回溯訪問和比較到的結點,這些多余的損耗在高維度數據查找的時候,搜索效率將變得相當之地下,那有什么辦法可以改進這個原始的kd樹最近鄰搜索算法呢?
? ? 從上述標準的kd樹查詢過程可以看出其搜索過程中的“回溯”是由“查詢路徑”決定的,并沒有考慮查詢路徑上一些數據點本身的一些性質。一個簡單的改進思路就是將“查詢路徑”上的結點進行排序,如按各自分割超平面(也稱bin)與查詢點的距離排序,也就是說,回溯檢查總是從優先級最高(Best Bin)的樹結點開始。
? ? 針對此BBF機制,讀者Feng&書童點評道:
在某一層,分割面是第ki維,分割值是kv,那么 abs(q[ki]-kv) 就是沒有選擇的那個分支的優先級,也就是計算的是那一維上的距離; 同時,從優先隊列里面取節點只在某次搜索到葉節點后才發生,計算過距離的節點不會出現在隊列的,比如1~10這10個節點,你第一次搜索到葉節點的路徑是1-5-7,那么1,5,7是不會出現在優先隊列的。換句話說,優先隊列里面存的都是查詢路徑上節點對應的相反子節點,比如:搜索左子樹,就把對應這一層的右節點存進隊列。
? ? 如此,就引出了本節要討論的kd樹最近鄰搜索算法的改進:BBF(Best-Bin-First)查詢算法,它是由發明sift算法的David Lowe在1997的一篇文章中針對高維數據提出的一種近似算法,此算法能確保優先檢索包含最近鄰點可能性較高的空間,此外,BBF機制還設置了一個運行超時限定。采用了BBF查詢機制后,kd樹便可以有效的擴展到高維數據集上。
? ? 偽代碼如下圖所示(圖取自圖像局部不變特性特征與描述一書 ):
? ? 還是以上面的查詢(2,4.5)為例,搜索的算法流程為:
將(7,2)壓人優先隊列中; 提取優先隊列中的(7,2),由于(2,4.5)位于(7,2)分割超平面的左側,所以檢索其左子結點(5,4)。同時,根據BBF機制”搜索左/右子樹,就把對應這一層的兄弟結點即右/左結點存進隊列 ”,將其(5,4)對應的兄弟結點即右子結點(9,6)壓人優先隊列中,此時優先隊列為{(9,6)},最佳點為(7,2);然后一直檢索到葉子結點(4,7),此時優先隊列為{(2,3),(9,6)},“最佳點”則為(5,4); 提取優先級最高的結點(2,3),重復步驟2,直到優先隊列為空。 ? ? 如你在下圖所見到的那樣(話說,用鼠標在圖片上寫字著實不好寫):
2.7、球樹、M樹、VP樹、MVP樹
2.7.1、球樹
? ? 咱們來針對上文內容總結回顧下,針對下面這樣一棵kd樹:
? ? 現要找它的最近鄰。
? ? 通過上文2.5節,總結來說,我們已經知道:
1、為了找到一個給定目標點的最近鄰,需要從樹的根結點開始向下沿樹找出目標點所在的區域,如下圖所示,給定目標點,用星號標示,我們似乎一眼看出,有一個點離目標點最近,因為它落在以目標點為圓心以較小長度為半徑的虛線圓內,但為了確定是否可能還村莊一個最近的近鄰,我們會先檢查葉節點的同胞結點,然葉節點的同胞結點在圖中所示的陰影部分,虛線圓并不與之相交,所以確定同胞葉結點不可能包含更近的近鄰。
2、于是我們回溯到父節點,并檢查父節點的同胞結點,父節點的同胞結點覆蓋了圖中所有橫線X軸上的區域。因為虛線圓與右上方的矩形( KD樹把二維平面劃分成一個一個矩形) 相交...
? ? 如上,我們看到,KD樹是可用于有效尋找最近鄰的一個樹結構,但這個樹結構其實并不完美,當處理不均勻分布的數據集時便會呈現出一個基本沖突:既邀請樹有完美的平衡結構,又要求待查找的區域近似方形,但不管是近似方形,還是矩形,甚至正方形,都不是最好的使用形狀,因為他們都有角。
? ? ? ??
? ? 什么意思呢?就是說,在上圖中,如果黑色的實例點離目標點星點再遠一點,那么勢必那個虛線圓會如紅線所示那樣擴大,以致與左上方矩形的右下角相交,既然相交了,那么勢必又必須檢查這個左上方矩形,而實際上,最近的點離星點的距離很近,檢查左上方矩形區域已是多余。于此我們看見,KD樹把二維平面劃分成一個一個矩形,但矩形區域的角卻是個難以處理的問題。
? ? 解決的方案就是使用如下圖所示的球樹:
先從球中選擇一個離球的中心最遠的點,然后選擇第二個點離第一個點最遠,將球中所有的點分配到離這兩個聚類中心最近的一個上,然后計算每個聚類的中心,以及聚類能夠包含它所有數據點所需的最小半徑。這種方法的優點是分裂一個包含n個殊絕點的球的成本只是隨n呈線性增加。
? ? 使用球樹找出給定目標點的最近鄰方法是,首先自上而下貫穿整棵樹找出包含目標點所在的葉子,并在這個球里找出與目標點最靠近的點,這將確定出目標點距離它的最近鄰點的一個上限值,然后跟KD樹查找一樣,檢查同胞結點,如果目標點到同胞結點中心的距離超過同胞結點的半徑與當前的上限值之和,那么同胞結點里不可能存在一個更近的點;否則的話,必須進一步檢查位于同胞結點以下的子樹。
? ? 如下圖,目標點還是用一個星表示,黑色點是當前已知的的目標點的最近鄰,灰色球里的所有內容將被排除,因為灰色球的中心點離的太遠,所以它不可能包含一個更近的點,像這樣,遞歸的向樹的根結點進行回溯處理,檢查所有可能包含一個更近于當前上限值的點的球。
? ? 球樹是自上而下的建立,和KD樹一樣,根本問題就是要找到一個好的方法將包含數據點集的球分裂成兩個,在實踐中,不必等到葉子結點只有兩個胡數據點時才停止,可以采用和KD樹一樣的方法,一旦結點上的數據點打到預先設置的最小數量時,便可提前停止建樹過程。
? ? 也就是上面所述,先從球中選擇一個離球的中心最遠的點,然后選擇第二個點離第一個點最遠,將球中所有的點分配到離這兩個聚類中心最近的一個上,然后計算每個聚類的中心,以及聚類能夠包含它所有數據點所需的最小半徑。這種方法的優點是分裂一個包含n個殊絕點的球的成本只是隨n呈線性增加(注:本小節內容主要來自參考條目19:數據挖掘實用機器學習技術,[新西蘭]Ian H.Witten 著,第4章4.7節 )。
2.7.2、VP樹與MVP樹簡介
? ??高維特征向量的距離索引問題是基于內容的圖像檢索的一項關鍵技術,目前經常采用的解決辦法是首先對高維特征空間做降維處理,然后采用包括四叉樹、kd樹、R樹族等在內的主流多維索引結構,這種方法的出發點是:目前的主流多維索引結構在處理維數較低的情況時具有比較好的效率,但對于維數很高的情況則顯得力不從心(即所謂的維數危機) 。
? ? 實驗結果表明當特征空間的維數超過20 的時候,效率明顯降低,而可視化特征往往采用高維向量描述,一般情況下可以達到10^2的量級,甚至更高。在表示圖像可視化特征的高維向量中各維信息的重要程度是不同的,通過降維技術去除屬于次要信息的特征向量以及相關性較強的特征向量,從而降低特征空間的維數,這種方法已經得到了一些實際應用。
? ??然而這種方法存在不足之處采用降維技術可能會導致有效信息的損失,尤其不適合于處理特征空間中的特征向量相關性很小的情況。另外主流的多維索引結構大都針對歐氏空間,設計需要利用到歐氏空間的幾何性質,而圖像的相似性計算很可能不限于基于歐氏距離。這種情況下人們越來越關注基于距離的度量空間高維索引結構可以直接應用于高維向量相似性查詢問題。
? ? 度量空間中對象之間的距離度量只能利用三角不等式性質,而不能利用其他幾何性質。向量空間可以看作由實數坐標串組成的特殊度量空間,目前針對度量空間的高維索引問題提出的索引結構有很多種大致可以作如下分類,如下圖所示:
其中,VP樹和MVP樹中特征向量的舉例表示為:
? ? ?讀者點評:
UESTC_HN_AY_GUOBO :現在主要是在kdtree的基礎上有了mtree或者mvptree,其實關鍵還是pivot的選擇,以及度量空間中算法怎么減少距離計算;mandycool:mvp-tree,是利用三角形不等式來縮小搜索區域的,不過mvp-tree的目標稍有不同,查詢的是到query點的距離小于某個值r的點;另外作者test的數據集只有20維,不知道上百維以后效果如何,而減少距離計算的一個思路是做embedding,通過不等式排除掉一部分點。
? ? 更多內容請參見論文1:DIST ANCE-BASED INDEXING FOR HIGH-DIMENSIONAL METRIC SP ACES ,作者:Tolga Bozkaya & Meral Ozsoyoglu ,及論文2:基于度量空間高維索引結構VP-tree及MVP-tree的圖像檢索 ,王志強,甘國輝,程起敏 。
? ? 當然,如果你覺得上述論文還不夠滿足你胃口的話,這里有一大堆nearest neighbor algorithms 相關的論文可供你看:http://scholar.google.com.hk/scholar?q=nearest+neighbor+algorithms&btnG=&hl=zh-CN&as_sdt=0&as_vis=1(其中,這篇可以看下 :Spill-Trees,An investigation of practical approximate nearest neighbor algorithms ) 。
第三部分、KD樹的應用:SIFT+KD_BBF搜索算法
3.1、SIFT特征匹配算法 ? ?
? ? 之前本blog內闡述過圖像特征匹配SIFT算法,寫過五篇文章,這五篇文章分別為:
九、圖像特征提取與匹配之SIFT算法? ? ??(sift 算法系列五篇文章) 九(續)、sift算法的編譯與實現 九(再續)、教你一步一步用c語言實現sift算法、上 九(再續)、教你一步一步用c語言實現sift算法、下 九(三續):SIFT算法的應用--目標識別 之Bag-of-words模型
? ? 不熟悉SIFT算法相關概念的可以看上述幾篇文章,這里不再做贅述。與此同時,本文此部分也作為十五個經典算法研究系列里SIFT算法的九之四續。
? ? OK,我們知道,在sift算法中,給定兩幅圖片圖片,若要做特征匹配,一般會先提取出圖片中的下列相關屬性作為特征點:
? ? ? ? ? ?? struct ?feature??{?? ????double ?x;???????????????????????? ????double ?y;???????????????????????? ????double ?a;???????????????????????? ????double ?b;???????????????????????? ????double ?c;???????????????????????? ????double ?scl;?????????????????????? ????double ?ori;?????????????????????? ????int ?d;??????????????????????????? ????double ?descr[FEATURE_MAX_D];????? ????int ?type;???????????????????????? ????int ?category;???????????????????? ????struct ?feature*?fwd_match;??????? ????struct ?feature*?bck_match;??????? ????struct ?feature*?mdl_match;??????? ????CvPoint2D64f?img_pt;????????????? ????CvPoint2D64f?mdl_pt;????????????? ????void *?feature_data;?????????????? ????char ?dense;??????????????????????? };??
? ? 而后在sift.h文件中定義兩個關鍵函數,這里,我們把它們稱之為函數一,和函數二,如下所示:
函數一的聲明:
extern ?int ?sift_features(?IplImage*?img,?struct ?feature**?feat?);??
函數一的實現:
int ?sift_features(?IplImage*?img,?struct ?feature**?feat?)??{?? ????return ?_sift_features(?img,?feat,?SIFT_INTVLS,?SIFT_SIGMA,?SIFT_CONTR_THR,?? ????????????????????????????SIFT_CURV_THR,?SIFT_IMG_DBL,?SIFT_DESCR_WIDTH,?? ????????????????????????????SIFT_DESCR_HIST_BINS?);?? }??
從上述函數一的實現中,我們可以看到,它內部實際上調用的是這個函數:_sift_features(..),也就是下面馬上要分析的函數二。
函數二的聲明:
extern ?int ?_sift_features(?IplImage*?img,?struct ?feature**?feat,?int ?intvls,????????????????????????????double ?sigma,?double ?contr_thr,?int ?curv_thr,?? ??????????????????????????int ?img_dbl,?int ?descr_width,?int ?descr_hist_bins?);??
函數二的實現:
int ?_sift_features(?IplImage*?img,?struct ?feature**?feat,?int ?intvls,?????????????????????double ?sigma,?double ?contr_thr,?int ?curv_thr,?? ???????????????????int ?img_dbl,?int ?descr_width,?int ?descr_hist_bins?)?? {?? ????IplImage*?init_img;?? ????IplImage***?gauss_pyr,?***?dog_pyr;?? ????CvMemStorage*?storage;?? ????CvSeq*?features;?? ????int ?octvs,?i,?n?=?0,n0?=?0,n1?=?0,n2?=?0,n3?=?0,n4?=?0;?? ????int ?start;?? ?? ?????? ????if (?!?img?)?? ????????fatal_error(?"NULL?pointer?error,?%s,?line?%d" ,??__FILE__,?__LINE__?);?? ?? ????if (?!?feat?)?? ????????fatal_error(?"NULL?pointer?error,?%s,?line?%d" ,??__FILE__,?__LINE__?);?? ?? ?????? ????start=GetTickCount();?? ????init_img?=?create_init_img(?img,?img_dbl,?sigma?);?? ????octvs?=?log(?(float )(MIN(?init_img->width,?init_img->height?))?)?/?log((float )(2.0))?-5;?? ????gauss_pyr?=?build_gauss_pyr(?init_img,?octvs,?intvls,?sigma?);?? ????dog_pyr?=?build_dog_pyr(?gauss_pyr,?octvs,?intvls?);?? ????fprintf(?stderr,?"?creat?the?pyramid?use?%d\n" ,GetTickCount()-start);?? ?? ????storage?=?cvCreateMemStorage(?0?);?????? ????start=GetTickCount();?? ????features?=?scale_space_extrema(?dog_pyr,?octvs,?intvls,?contr_thr,?? ????????curv_thr,?storage?);???? ????fprintf(?stderr,?"?find?the?extrum?points?in?DOG?use?%d\n" ,GetTickCount()-start);?? ?? ????calc_feature_scales(?features,?sigma,?intvls?);??? ?? ????if (?img_dbl?)?? ????????adjust_for_img_dbl(?features?);???? ????start=GetTickCount();?? ????calc_feature_oris(?features,?gauss_pyr?);???? ????fprintf(?stderr,?"?get?the?main?oritation?use?%d\n" ,GetTickCount()-start);?? ?? ????start=GetTickCount();?? ????compute_descriptors(?features,?gauss_pyr,?descr_width,?descr_hist_bins?);??? ????fprintf(?stderr,?"?compute?the?descriptors?use?%d\n" ,GetTickCount()-start);?? ?? ?????? ?????? ?????? ????n?=?features->total;?? ????*feat?=?(feature*)(calloc(?n,?sizeof (struct ?feature)?));?? ????*feat?=?(feature*)(cvCvtSeqToArray(?features,?*feat,?CV_WHOLE_SEQ?));??? ?? ????for (?i?=?0;?i?<?n;?i++?)?? ????{?? ????????free(?(*feat)[i].feature_data?);?? ????????(*feat)[i].feature_data?=?NULL;???? ????????if ((*feat)[i].dense?==?4)?++n4;?? ????????else ?if ((*feat)[i].dense?==?3)?++n3;?? ????????else ?if ((*feat)[i].dense?==?2)?++n2;?? ????????else ?if ((*feat)[i].dense?==?1)?++n1;?? ????????else ?????????????????????????++n0;?? ????}?? ?? ?????? ?????? ????fprintf(?stderr,?"In?the?total?feature?points?the?extent4?points?is?%d\n" ,n4);?? ????fprintf(?stderr,?"In?the?total?feature?points?the?extent3?points?is?%d\n" ,n3);?? ????fprintf(?stderr,?"In?the?total?feature?points?the?extent2?points?is?%d\n" ,n2);?? ????fprintf(?stderr,?"In?the?total?feature?points?the?extent1?points?is?%d\n" ,n1);?? ????fprintf(?stderr,?"In?the?total?feature?points?the?extent0?points?is?%d\n" ,n0);?? ????cvReleaseMemStorage(?&storage?);?? ????cvReleaseImage(?&init_img?);?? ????release_pyr(?&gauss_pyr,?octvs,?intvls?+?3?);?? ????release_pyr(?&dog_pyr,?octvs,?intvls?+?2?);?? ?????? ????return ?n;?? }??
? ? 說明:上面的函數二,包含了SIFT算法中幾乎所有函數,是SIFT算法的核心。本文不打算一一分析上面所有函數,只會抽取其中涉及到BBF查詢機制相關的函數。
3.2、K個最小近鄰的查找:大頂堆優先級隊列
? ? 上文中一直在講最近鄰問題,也就是說只找最近的那唯一一個鄰居,但如果現實中需要我們找到k個最近的鄰居。該如何做呢?對的,之前blog內曾相近闡述過尋找最小的k個數的問題,顯然,尋找k個最近鄰與尋找最小的k個數的問題如出一轍。
? ? 回憶下尋找k個最小的數中關于構造大頂堆的解決方案:
? ? “尋找最小的k個樹,更好的辦法是維護k個元素的最大堆,即用容量為k的最大堆存儲最先遍歷到的k個數,并假設它們即是最小的k個數,建堆費時O(k)后,有k1<k2<...<kmax(kmax設為大頂堆中最大元素)。繼續遍歷數列,每次遍歷一個元素x,與堆頂元素比較,x<kmax,更新堆(用時logk),否則不更新堆。這樣下來,總費時O(k+(n-k)*logk)= O(n*logk) 。此方法得益于在堆中,查找等各項操作時間復雜度均為logk。 ”
? ? 根據上述方法,咱們來寫大頂堆最小優先級隊列相關代碼實現:
void *?minpq_extract_min(?struct ?min_pq*?min_pq?)??{?? ????void *?data;?? ?? ????if (?min_pq->n?<?1?)?? ????{?? ????????fprintf(?stderr,?"Warning:?PQ?empty,?%s?line?%d\n" ,?__FILE__,?__LINE__?);?? ????????return ?NULL;?? ????}?? ????data?=?min_pq->pq_array[0].data;??? ????min_pq->n--;????? ????min_pq->pq_array[0]?=?min_pq->pq_array[min_pq->n];?? ????restore_minpq_order(?min_pq->pq_array,?0,?min_pq->n?);?? ???????????????????????????????????????????????? ????return ?data;?? }??
? ? 上述函數中,restore_minpq_order的實現如下:
static ?void ?restore_minpq_order(?struct ?pq_node*?pq_array,?int ?i,?int ?n?)??{???????????????????????????????????????????????????????????? ????struct ?pq_node?tmp;?? ????int ?l,?r,?min?=?i;?? ?? ????l?=?left(?i?);??? ????r?=?right(?i?);?? ????if (?l?<?n?)?? ????????if (?pq_array[l].key?<?pq_array[i].key?)?? ????????????min?=?l;?? ????if (?r?<?n?)?? ????????if (?pq_array[r].key?<?pq_array[min].key?)?? ????????????min?=?r;?? ?? ????if (?min?!=?i?)?? ????{?? ????????tmp?=?pq_array[min];?? ????????pq_array[min]?=?pq_array[i];?? ????????pq_array[i]?=?tmp;?? ????????restore_minpq_order(?pq_array,?min,?n?);?? ????}?? }??
3.3、KD樹近鄰搜索改進之BBF算法
? ? 原理在上文第二部分已經闡述明白,結合大頂堆找最近的K個近鄰思想,相關主體代碼如下:
?? int ?kdtree_bbf_knn(?struct ?kd_node*?kd_root,?struct ?feature*?feat,?int ?k,??????????????????????struct ?feature***?nbrs,?int ?max_nn_chks?)???????????????????? {????????????????????????????????????????????????? ????struct ?kd_node*?expl;?? ????struct ?min_pq*?min_pq;?? ????struct ?feature*?tree_feat,?**?_nbrs;?? ????struct ?bbf_data*?bbf_data;?? ????int ?i,?t?=?0,?n?=?0;?? ?? ????if (?!?nbrs??||??!?feat??||??!?kd_root?)?? ????{?? ????????fprintf(?stderr,?"Warning:?NULL?pointer?error,?%s,?line?%d\n" ,?? ????????????????__FILE__,?__LINE__?);?? ????????return ?-1;?? ????}?? ?? ????_nbrs?=?(feature**)(calloc(?k,?sizeof (?struct ?feature*?)?));??? ????min_pq?=?minpq_init();??? ????minpq_insert(?min_pq,?kd_root,?0?);??? ?? ?????? ????while (?min_pq->n?>?0??&&??t?<?max_nn_chks?)?? ????{??????????????????? ?????????? ?????????? ?????????? ????????expl?=?(struct ?kd_node*)minpq_extract_min(?min_pq?);?? ????????if (?!?expl?)?????????????????????????????????????????? ????????{????????????????????????????????????????????????????? ????????????fprintf(?stderr,?"Warning:?PQ?unexpectedly?empty,?%s?line?%d\n" ,?? ????????????????????__FILE__,?__LINE__?);??????????????????????????? ????????????goto ?fail;?? ????????}?? ?? ?????????? ?????????? ?????????? ?????????? ????????expl?=?explore_to_leaf(?expl,?feat,?min_pq?);??? ????????if (?!?expl?)???????????????????????????????????? ????????{??????????????????????????????????????????????? ????????????fprintf(?stderr,?"Warning:?PQ?unexpectedly?empty,?%s?line?%d\n" ,?? ????????????????????__FILE__,?__LINE__?);????????????????????? ????????????goto ?fail;???????????????????????????????????? ????????}??????????????????????????????????????????????? ?? ????????for (?i?=?0;?i?<?expl->n;?i++?)?? ????????{??? ?????????????? ????????????tree_feat?=?&expl->features[i];?? ????????????bbf_data?=?(struct ?bbf_data*)(malloc(?sizeof (?struct ?bbf_data?)?));?? ????????????if (?!?bbf_data?)?? ????????????{?? ????????????????fprintf(?stderr,?"Warning:?unable?to?allocate?memory," ?? ????????????????????"?%s?line?%d\n" ,?__FILE__,?__LINE__?);?? ????????????????goto ?fail;?? ????????????}?? ????????????bbf_data->old_data?=?tree_feat->feature_data;?? ????????????bbf_data->d?=?descr_dist_sq(feat,?tree_feat);??? ????????????tree_feat->feature_data?=?bbf_data;?? ?? ?????????????? ????????????n?+=?insert_into_nbr_array(?tree_feat,?_nbrs,?n,?k?);?? ????????}???????????????????????????????????????????????????? ????????t++;?? ????}?? ?? ????minpq_release(?&min_pq?);?? ????for (?i?=?0;?i?<?n;?i++?)??? ????{?? ????????bbf_data?=?(struct ?bbf_data*)(_nbrs[i]->feature_data);?? ????????_nbrs[i]->feature_data?=?bbf_data->old_data;?? ????????free(?bbf_data?);?? ????}?? ????*nbrs?=?_nbrs;?? ????return ?n;?? ?? fail:?? ????minpq_release(?&min_pq?);?? ????for (?i?=?0;?i?<?n;?i++?)?? ????{?? ????????bbf_data?=?(struct ?bbf_data*)(_nbrs[i]->feature_data);?? ????????_nbrs[i]->feature_data?=?bbf_data->old_data;?? ????????free(?bbf_data?);?? ????}?? ????free(?_nbrs?);?? ????*nbrs?=?NULL;?? ????return ?-1;?? }??
? ?依據上述函數kdtree_bbf_knn從上而下看下來,注意幾點:
? ? 1、上述函數kdtree_bbf_knn中,explore_to_leaf的代碼如下:
static ?struct ?kd_node*?explore_to_leaf(?struct ?kd_node*?kd_node,?struct ?feature*?feat,??????????????????????????????????????????struct ?min_pq*?min_pq?)????????? {?? ????struct ?kd_node*?unexpl,?*?expl?=?kd_node;?? ????double ?kv;?? ????int ?ki;?? ?? ????while (?expl??&&??!?expl->leaf?)?? ????{?????????????????????? ????????ki?=?expl->ki;?? ????????kv?=?expl->kv;?? ?? ????????if (?ki?>=?feat->d?)?? ????????{?? ????????????fprintf(?stderr,?"Warning:?comparing?imcompatible?descriptors,?%s" ?\?? ????????????????????"?line?%d\n" ,?__FILE__,?__LINE__?);?? ????????????return ?NULL;?? ????????}?? ????????if (?feat->descr[ki]?<=?kv?)???? ????????{?????????????????????????????? ????????????unexpl?=?expl->kd_right;?? ????????????expl?=?expl->kd_left;?? ????????}?? ????????else ?? ????????{?? ????????????unexpl?=?expl->kd_left;?????? ????????????expl?=?expl->kd_right;?? ????????}?? ?? ?????????? ????????if (?minpq_insert(?min_pq,?unexpl,?ABS(?kv?-?feat->descr[ki]?)?)?)?? ????????{??????????????????????????????????????? ????????????fprintf(?stderr,?"Warning:?unable?to?insert?into?PQ,?%s,?line?%d\n" ,?? ????????????????????__FILE__,?__LINE__?);?? ????????????return ?NULL;?? ????????}?? ????}?? ????return ?expl;?? }??
? ? 2、上述查找函數kdtree_bbf_knn中的參數k可調,代表的是要查找近鄰的個數,即number of neighbors to find,在sift特征匹配中,k一般取2
k?=?kdtree_bbf_knn(?kd_root_0,?feat,?2,?&nbrs,?KDTREE_BBF_MAX_NN_CHKS_0?);?? ????????if (?k?==?2?)???
? ? 3、上述函數kdtree_bbf_knn中“bbf_data->d = descr_dist_sq(feat, tree_feat); //計算兩個關鍵點描述器差平方和 ”,使用的計算方法是本文第一部分1.2節中所述的歐氏距離。
3.3、SIFT+BBF算法匹配效果
? ? 之前試了下sift + KD + BBF算法, 用兩幅不同的圖片做了下匹配(當然,運行結果顯示是不匹配的),效果還不錯: http://weibo.com/1580904460/yDmzAEwcV#1348475194313 。
? ? “編譯的過程中,直接用的VS2010 + opencv(并沒下gsl)。2012.09.24”。 ....
? ? 本文完整源碼有pudn帳號的朋友可以前去這里下載: http://www.pudn.com/downloads340/sourcecode/graph/texture_mapping/detail1486667.html ( 沒有pudn賬號的同學請加群:169056165,至群共享下載,驗證信息:sift )。感謝諸位。
參考文獻及推薦閱讀
維基百科,http://en.wikipedia.org/wiki/K-nearest_neighbor_algorithm ; 機器學習中的相似性度量,http://www.cnblogs.com/heaad/archive/2011/03/08/1977733.html ; 杰卡德相似系數及距離,http://www.cnblogs.com/heaad/archive/2011/03/08/1977733.html ; 統計學習方法,李航; 概率論與數理統計 第四版 盛驟等編,高教版; 《圖像局部不變特性特征與描述》王永明 王貴錦 編著; 數據挖掘:實用機器學習技術,[新西蘭]Ian H.Witten 著,第4章4.7節; 模式分類,第4章 非參數技術,[美] IRichard O. Duda / Peter E. Hart / David G. Stork 著; http://underthehood.blog.51cto.com/2531780/687160 ; http://grunt1223.iteye.com/blog/921371 ; http://www.cnblogs.com/eyeszjwang/articles/2429382.html ;http://blog.csdn.net/ijuliet/article/details/4471311 ; Rob Hess維護的sift庫,http://blogs.oregonstate.edu/hess/code/sift/ ; 酷殼,http://coolshell.cn/articles/8052.html ; rubyist,http://segmentfault.com/q/1010000000094674 ; 皮爾遜相關系數維基百科頁面,http://t.cn/zjy6Gpg; 皮爾遜相關系數的一個應用:http://www.sobuhu.com/archives/567 ; http://blog.csdn.net/wsywl/article/details/5727327 ; 標準差,http://zh.wikipedia.org/wiki/%E6%A0%87%E5%87%86%E5%B7%AE ; 協方差與相關性,http://t.cn/zjyXFRB ;電子科大kd樹電子課件:http://t.cn/zjbpXna ; 編程藝術之尋找最小的k個數:http://blog.csdn.net/v_JULY_v/article/details/6403777 ; 機器學習那些事兒,http://vdisk.weibo.com/s/ix_9F ; 大嘴巴漫談數據挖掘,http://vdisk.weibo.com/s/bUbzJ ; http://www.codeproject.com/Articles/18113/KD-Tree-Searching-in-N-dimensions-Part-I ; 一個庫:http://docs.pointclouds.org/trunk/group__kdtree.html ; 3D上使用kd樹:http://pointclouds.org/ ; 編輯數學公式:http://webdemo.visionobjects.com/equation.html?locale=zh_CN ; 基于R樹的最近鄰查找:http://blog.sina.com.cn/s/blog_72e1c7550101dsc3.html ; 包含一個demo:http://www.leexiang.com/kd-tree ; 機器學習相關降維算法,http://www.cnblogs.com/xbinworld/category/337861.html ; Machine Learning相關topic,http://www.cnblogs.com/jerrylead/tag/Machine%20Learning/ ; 機器學習中的數學,http://www.cnblogs.com/LeftNotEasy/category/273623.html ; 一堆概念性wikipedia 頁面; 基于度量空間高維索引結構VP-tree及MVP-tree的圖像檢索,王志強,甘國輝,程起敏; Spill-Trees,An investigation of practical approximate nearest neighbor algorithms ; DIST ANCE-BASED INDEXING FOR HIGH-DIMENSIONAL METRIC SP ACES,作者:Tolga Bozkaya & Meral Ozsoyoglu; “Multidimensional Binary Search Trees Used for Associative Searching”,Jon Louis Bentley 。
后記
從當天下午4點多一直寫,一直寫,直接寫到了隔日凌晨零點左右,或參考或直接間接引用了很多人的作品及一堆
wikipedia 頁面(
當然,已經注明在上面參考文獻及推薦閱讀中前半部分條目,后半部分條目則為行文之后看到的一些好的資料文章,可以課外讀讀 ),但本文還是有諸多地方有待修補完善,也歡迎廣大讀者不吝賜教 & 指正,感謝大家。完。July、二零一二年十一月二十一日零點五分。.
預告 ? ? 本文之后,待寫的幾篇文章羅列如下: 數據挖掘中所需的概率論與數理統計知識,估計年底前完成( 更新:已經完成,點擊左邊文字鏈接即可打開 ); 機器學習中相關的降維方法,如 PCA/LDA 等等,明年年初完成; 神經網絡入門學習導論,明年1月內完成; 程序員編程藝術第二十八章,時間待定; ... ? ? 歡迎期待。July、二零一二年十二月十七日。
更多 3
上一篇:九月十月百度人搜,阿里巴巴,騰訊華為筆試面試八十題(第331-410題) 下一篇:數據挖掘中所需的概率論與數理統計知識、上
頂
64 踩
2
查看評論
51樓
x_hh2013-12-26 17:46發表 [回復] 6個數據點在x,y維度上的數據方差分別為39,28.63,這兩個數據怎么算出來的,我看網上好多都是這個數據,今天還和人討論著,就是算不出來,不知道是網上的數據錯的還是我們的方法錯了,大俠指點一下 50樓
瓜瓜東西2013-12-07 22:45發表 [回復] 雖然我看不到,但我知道樓主很牛掰 49樓
zhanglibin_12222013-12-07 11:02發表 [回復] 支持,大力支持。 48樓
一生愛花2013-11-03 21:54發表 [回復] 求博主聯系方式,有很多問題想請教!最好有QQ Re: v_JULY_v2013-11-04 13:43發表 [回復] 回復u012710853:你好,咱們可以在微博私信上聯系:http://weibo.com/julyweibo,多謝關注。 47樓
thinker2013-10-30 22:31發表 [回復] If?Dist(target[s],back_point?->?Node-data[s])?<?Max_dist????? ??? If?target[s]?<=?back_point?->?Node-data[s]???? ??? Kd_point?=?back_point?->?right;???? ??? else ???? ??? Kd_point?=?back_point?->?left;?????? ??? 將Kd_point壓入search_path堆棧;?? 默認target點一定在Kd_point 的一個子空間中,但是,當target不在其中的任一子空間時,會丟失對一個子空間的搜索。所以,用該偽代碼寫的程序,能夠得到的最近鄰都值得懷疑,最直接的結果就是,本來用其他方法實現的最近鄰算法能夠正確分類,而本算法則會分錯!!! 另外,樓主偽代碼中用到的有些成員變量,在節點結構定義的時候并沒有出現,導致理解困難,望及時改正。 46樓
youlan91152013-10-25 22:38發表 [回復] 樓主好強大!我決定要把你的博文通通學習一遍~~~ 45樓
fengjianbang2013-09-16 14:57發表 [回復] 樓主,您好,最近您這篇文章看了很多遍,但是一直有一個問題不能理解。就是kd樹里面確定分割維的時候,你的那個方差值是怎么算出來的,還請賜教啊。 44樓
xxc16056298952013-09-11 09:56發表 [回復] 大神,請問 k-d樹查詢算法中回溯的時候用到的 Max_dist 是什么呢? 43樓
wzlang2013-09-05 09:20發表 [回復] 博主您好,小弟使用KD-BBF算法用于SIFT時發現速度很慢,比窮舉法要慢很多很多。 42樓
catTom2013-08-26 02:20發表 [回復] 看您把問題都研究挺深入的,可以寫一下后綴數組嗎?我對于后綴數組的計數排序實現很不明白 41樓
JackDanny2013-08-05 20:06發表 [回復] 很喜歡博主的文章,剛剛用豆約翰博客備份專家備份了您的全部博文。 40樓
MATH_F2013-08-03 00:50發表 [回復] 或者說 為什么kd樹中刪除的時候一會兒比較x 一會人比較y 有什么標準嘛 以及 那個示例好像不是kd樹啊。。 39樓
MATH_F2013-08-03 00:19發表 [回復] KD樹的刪除 它得替代節點要么是(a,b)左子樹中的X坐標最大值的結點,要么是(a,b)右子樹中x坐標最小值的結點 這個是不是寫錯了 右子樹種Y最小吧 是嘛? 38樓
孟小子2013-07-07 11:48發表 [回復] 很喜歡博主的文章,剛剛用豆約翰博客備份專家備份了您的全部博文。 37樓
見死不救No12013-06-02 20:08發表 [回復] 樓主真厲害,學習了! 36樓
wz884235112013-05-23 15:33發表 [回復] http://www.cs.umd.edu/class/spring2008/cmsc420/L19.kd-trees.pdf 你怎么看待不存在右子樹的時候尋找左子樹最大值的問題 35樓
wz884235112013-05-23 15:33發表 [回復] http://www.cs.umd.edu/class/spring2008/cmsc420/L19.kd-trees.pdf 文章中提到了關于右子樹不存在的情況下尋找左子樹中的最大值可能存在的問題,求賜教,你怎么看待這個問題 34樓
wz884235112013-05-23 15:31發表 [回復] http://www.cs.umd.edu/class/spring2008/cmsc420/L19.kd-trees.pdf 看一下文章中對delete kd-tree的看法 Delete in kd-trees --- No Right Subtree Possible idea: Find the max in the left subtree? Why might this not work? 求指教 ,你怎么看待這個問題 33樓
燎原2013-05-03 10:43發表 [回復] 博主,您在K-D樹如何構建的那個例子中,舉例有6個點,(2,3)、(5,4)。。。它們在X,Y維度上的方差是怎樣計算的啊?就是用X方向上的坐標值求得平均值,再按照方差的計算公式去求嗎?可是我算出來的數據跟您的差別好大好大啊,還煩請博主詳述一下吧。。。我真的是小白。。。 32樓
奇異果2013-04-30 13:06發表 [回復] 在選擇用哪個維度切分的時候,是順序循環選擇,還是每次選擇都選擇方差最大的呢?如5維(a,b,c,d,e),是abcdeabcdeab...這樣選,還是每次都計算方差選最大的? 再有,允許相鄰兩次選擇同一維度進行劃分嗎,如abb這種? Re: wz884235112013-05-23 15:35發表 [回復] 回復liukang0618:采用方差最大。是為了找到點分布最分散的維度,這樣可以平衡做右子樹,所以可以連續對同一維度進行切割的。 31樓
飛信天下2013-03-25 17:26發表 [回復] 樓主,您好!我有個問題請教您哈! int kdtree_bbf_knn( ....)函數里,大循環while里通過expl=explore_to_leaf(expl,feat,min_pq); ............................................ n+=insert_into_nbr_array(tree_feat,_nbrs,n,k);(1) 這里,我注意到程序僅僅只能把也kd樹的葉子才能運行語句(1),例如在上面例子中的6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}中無法與 非葉子的根節點例如(5,4)比較。因為目標點也可能離(5,4)更近。 實際上在bbf算法里搜索kd樹時都會在搜索路徑上確定最佳點,即不斷尋找離目標最短距離的節點。求關注! Re: Day_plot2013-10-26 11:48發表 [回復] 回復google0802:按照Rob Hess的代碼,其實他已經將kd-tree的構造改了,和博主介紹的不一樣,改成了將所有的數據都設成了葉子節點。你用上面的例子構造一個kd-tree,打印出所有節點就知道了 Re: 飛信天下2013-10-30 21:15發表 [回復] 回復Day_plot:你的意思是說rob hess的代碼的kd樹把所有的數據當為葉子?我試一試打印所有葉子,謝謝你哈! 30樓
daduzifufu2013-01-16 21:46發表 [回復] 學習的近似誤差, 學習的估計誤差區別是什么? 29樓
heroalone20122013-01-10 16:09發表 [回復] 博主你好,最近一直在學習你這篇博文,受益匪淺 但我有個弱弱的問題,希望博主指點。 你代碼中struct kd_node中的struct feature應該是儲存用來決定該點的所有特征數據點 這個struct feature結構體內具體應該選擇什么樣的形式? 如果是std::vector,那后面還可以用malloc( sizeof( struct kd_node ) )分配內存嗎? 如果是二維數組,不得不一開始就確定數組的長度,那對于大型點云數據整個kd樹建立過程內存開銷又太龐大大了。 所以我覺得這兩種都不合適啊。 最近被導師盯著做點云的k近鄰,我導師說了,寒假前如果做不出,就不用回家過年了。。。。 跪求指點啊!!! 28樓
visionfans2012-12-28 00:15發表 [回復] 好文章,加油 27樓
fengzhe04112012-12-22 15:53發表 [回復] 隨著樹的深度增加,循環的選取坐標軸,作為分割超平面的法向量。對于3-d tree來說,根節點選取x軸,根節點的孩子選取y軸,根節點的孫子選取z軸,根節點的曾孫子選取x軸,這樣循環下去。 到底是按照方差最大的選取劃分維度,還是按x,y,x循環 26樓
v_JULY_v2012-12-17 11:56發表 [回復] 預告 本文之后,待寫的幾篇文章羅列如下: 1、數據挖掘中所需的概率論與數理統計知識,估計年底前完成; 2、機器學習中相關的降維方法,如PCA/LDA等等,明年年初完成; 3、神經網絡入門導論,明年1月內完成; 4、程序員編程藝術第二十八章,時間待定; 5、... 歡迎期待。July、二零一二年十二月十七日。 25樓
swuxd2012-12-11 20:29發表 [回復] 非常好評論一下 Re: v_JULY_v2012-12-12 23:13發表 [回復] 回復swuxd:thank you,文章2.6節、kd樹近鄰搜索算法的改進:BBF算法 剛做了再次修改,有何問題,歡迎各位讀者不吝指出,謝謝。 24樓
lvjun932012-12-10 19:41發表 [回復] 樓主,你在寫完svm和knn之后,能否解釋一下2012百度機器學習與數據挖掘校招的筆試題最后一題?? 3.一個廠商準備上線一個在線圖片分類算法,數據量大 ,服務器資源有限,查詢時間需要快一些,請在linear SVM, logistic regression,discriminate regression等算法中選擇一個實現,并說明理由和分析,并與k-NN進行優勢與劣勢比較。 Re: v_JULY_v2012-12-11 00:28發表 [回復] 回復lvjun93:建議你先去了解一下這幾個算法: linear SVM:http://blog.csdn.net/v_july_v/article/details/7624837; logistic regression..; discriminate regression..; k-NN:http://blog.csdn.net/v_july_v/article/details/8203674。 23樓
empty162012-12-09 13:01發表 [回復] 樓主博文觀點思路很明確,學習目標也很清晰,目標堅定,道路清晰。給我們這些學習之人帶來很多思路,講解了很多好多算法,謝謝樓主 22樓
lovingdiana2012-12-05 15:39發表 [回復] 正好在弄SIFT算法和KD-TREE搜索算法,樓主的博文實在是對我幫助很大,謝謝哈!期待您更好的博文,不知道樓主有沒有關于SIFT改進算法的研究? Re: v_JULY_v2012-12-05 19:29發表 [回復] 回復lovingdiana:SIFT改進算法,以后有機會研究下:-) 21樓
jAmEs_2012-12-04 17:31發表 [回復] 很強大,要看需要慢慢消化 20樓
maxiao0012012-12-01 20:51發表 [回復] 樓主確實全程高能。 19樓
nmcfyangyang2012-12-01 18:46發表 [回復] 2.確定:Node-data = (7,2)。具體是:根據x維上的值將數據排序,6個數據的中值為7,所以Node-data域位數據點(7,2)。這樣,該節點的分割超平面就是通過(7,2)并垂直于:split=x軸的直線x=7; 這段中,提到6個數據的中值是7,中值是中位數的意思吧?應該是6吧!? Re: v_JULY_v2012-12-01 22:03發表 [回復] 回復nmcfyangyang:“6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},根據x維上的值將數據排序,6個數據的中值為7” 這里所謂的中值非中位數的意思,而是中間大小的值,6個數據點按照x維上的值排序后,是{(2,3),(4,7),(5,4),(7,2),(8,1),(9,6)},所以中值為點(7,2)中的7(不存在x值為6的點)。 18樓
天才在左瘋子在右2012-11-29 21:14發表 [回復] 有點小深奧 17樓
meiguang10282012-11-29 15:00發表 [回復] 最近也在做關于應用knn算法的研究,讀了一下博主的文章,感覺把KNN算法介紹的非常通俗。但是我覺得可以再介紹一些nonparametric的內容,畢竟knn是nonparametric的一種方法,以及knn為何有效(也許這部分內容需要一些數學證明,為了通俗,所以博主省略了)。還有就是距離的選擇應該是根據應用場合而定,個人感覺是這樣的。所以覺得沒有比較利用這么大的篇幅來介紹。最后想說的是,非常喜歡博主寫的文章,以后多向博主學習,希望看到更多的技術文章。 Re: v_JULY_v2012-11-29 20:26發表 [回復] 回復meiguang1028:非常好的建議,謝謝!我也了解下nonparametric,你有相關的資料介紹么? Re: meiguang10282012-11-30 13:19發表 [回復] 回復v_JULY_v:pattern classification, 很經典的一個教材,里頭nonparametric章節介紹的比較詳細。 Re: v_JULY_v2012-11-30 14:23發表 [回復] 回復meiguang1028:好!是模式分類那一本書吧,晚上回去一定好好看下 16樓
v_JULY_v2012-11-28 21:10發表 [回復] 一群友欲編譯本文源碼,但不知”宏”的定義,后知剛學編程。另一群友提示說,學編程要從基礎一點一點開始,不要一上來就搞算法,偶強烈贊同。自己09年10月C、C++、數據結構,10年底算法,12年5月開始DM&ML,近期遂補數學。何謂基礎,就是你我容易忽略的簡單的初級的東西,少說也得花心思好好學3年。 15樓
江南煙雨2012-11-27 16:07發表 [回復] 膜拜一下~學習一下,算法很難,也很重要~明年找工作之前一定要把這里面的算法都自己實現一遍~~ Re: v_JULY_v2012-11-27 18:28發表 [回復] 回復xiajun07061225:期待你自己的實現~ 14樓
UESTC_HN_AY_GUOBO2012-11-26 10:28發表 [回復] 首先 謝謝博主的勞動 其實在KD tree之前呢 其實還有很多過度的知識介紹 我能力有限,如果博主想深入 可以看看similarity search and metric space approach 還有其實knn算法是很經典了,例如在kdtree 也罷 還是mtree也罷 都會有基于樹形結構的knn算法,但是knn算法也是獨立的一個算法 很想搞清楚 如果把空間數據進行了kdtree 或者 mtree的索引 ,然后使用knn 那么與直接使用knn算法在空間數據上 結果會有不同嗎? Re: v_JULY_v2012-11-27 10:56發表 [回復] 回復UESTC_HN_AY_GUOBO:謝謝。 “如果把空間數據進行了kdtree 或者 mtree的索引 ,然后使用knn 那么與直接使用knn算法在空間數據上 結果會有不同嗎?” 我覺得你的問題等價于:索引有用么? 13樓
v_JULY_v2012-11-26 00:19發表 [回復] 全文第二次修訂完畢,補充了kd樹的插入,刪除情況的圖例分析。 12樓
guaimaer2012-11-24 17:01發表 [回復] 頂禮膜拜中 11樓
mandycool2012-11-24 10:48發表 [回復] kd樹做高維數據還是很困難,即使加入bbf,現在用的較多的是randomized kd-tree,lz也可添加介紹。將繼續關注lz~~ Re: UESTC_HN_AY_GUOBO2012-11-26 10:31發表 [回復] 回復mandycool:現在主要是在kdtree的基礎上有了mtree或者mvptree 其實關鍵還是pivot的選擇 度量空間中算法還是怎么能減少 距離計算 使用度量空間的法則 過濾距離計算 Re: mandycool2012-11-26 15:56發表 [回復] 回復UESTC_HN_AY_GUOBO:大致查了一下,mtree和mvp-tree是2000年前提出的吧,感覺是比較老的技術了;現在在kd樹的基礎上,有做基變換的方法,有做pca的方法;根據flann作者的說法,multi randomized kd-tree經過比較是比較高效的了;另外還有在每一層上做聚類的方法,據說效果也不錯;如有誤,歡迎指正 Re: v_JULY_v2012-11-26 18:37發表 [回復] 回復mandycool:有相關的資料鏈接或論文么? Re: mandycool2012-11-26 19:31發表 [回復] 回復v_JULY_v:FAST APPROXIMATE NEAREST NEIGHBORS WITH AUTOMATIC ALGORITHM CONFIGURATION Re: v_JULY_v2012-11-26 19:39發表 [回復] 回復mandycool:建議你寫一篇blog記錄下學習心得或體會哈 Re: mandycool2012-11-26 19:47發表 [回復] 回復v_JULY_v:自己比較浮躁,沒法寫的像lz這么細致,還配圖的;等做完畢設看能不能寫點吧。。。 Re: v_JULY_v2012-11-26 19:56發表 [回復] 回復mandycool:好的,期待! 到時候向你文章學習 Re: mandycool2012-11-26 21:07發表 [回復] 回復v_JULY_v:lz的博文寫的很好,我才該多學習,哈哈 Re: v_JULY_v2012-11-26 10:54發表 [回復] 回復UESTC_HN_AY_GUOBO:針對你說的 “關鍵還是pivot的選擇 度量空間中算法還是怎么能減少 距離計算 使用度量空間的法則 過濾距離計算” 你有何體會或使用心得么? Re: mandycool2012-11-26 19:41發表 [回復] 回復v_JULY_v:剛看了一下mvp-tree,是利用三角形不等式來縮小搜索區域的,不過mvp-tree的目標稍有不同,查詢的是到query點的距離小于某個值r的點;另外作者test的數據集只有20維,不知道上百維以后效果如何 Re: v_JULY_v2012-11-30 22:25發表 [回復] 回復mandycool:補充了球樹,VP樹,MVP樹的相關簡介,資料還是有限,無法進一步深入.. Re: mandycool2012-11-30 23:48發表 [回復] 回復v_JULY_v:Distance-based indexing for high-dimensional metric spaces vp-tree和mvp-tree均可參見此篇,中文資料確實很少 Re: v_JULY_v2012-12-01 11:20發表 [回復] 回復mandycool:網絡還真不給力,半天下不下來,打不開 Re: mandycool2012-11-26 15:58發表 [回復] 回復v_JULY_v:減少距離計算的一個思路是做embedding,通過不等式排除掉一部分點 Re: v_JULY_v2012-11-24 11:44發表 [回復] 回復mandycool:好,非常感謝,我了解下randomized kd-tree ! 10樓
leifeng4573279972012-11-24 00:13發表 [回復] 太深奧了 9樓
Felven2012-11-23 16:58發表 [回復] 最近上研究生學 模式識別,機器學習,人工智能,再加上自己在實驗室幫老師搞數據挖掘,覺得這些技術很不錯,學好絕對有很大幫助。 Re: v_JULY_v2012-11-23 17:01發表 [回復] 回復jj12345jj198999:研究生有機會在實驗室里 做各種實驗,真心幸福丫 8樓
v_JULY_v2012-11-23 12:11發表 [回復] OK,本文基本修訂完畢。 Re: v_JULY_v2012-11-23 16:54發表 [回復] 回復v_JULY_v:補充了本文完整源碼的下載地址。 7樓
無為Rookie_CEO2012-11-22 13:07發表 [回復] 頂一下 6樓
syzcch2012-11-21 13:07發表 [回復] july 還是一如既往的給力 Re: v_JULY_v2012-11-22 14:32發表 [回復] 回復syzcch:多謝關注哦! 5樓
SuperFC2012-11-21 09:27發表 [回復] KD樹:統計、計算、挖掘! 4樓
銘毅天下2012-11-20 23:15發表 [回復] 引用“zhucegepp”的評論:博主本科? 去了什么公司? 貌似QQ,博主說過。。 Re: v_JULY_v2012-11-21 00:16發表 [回復] 回復wojiushiwo987:NO,:-) 3樓
zhucegepp2012-11-20 23:03發表 [回復] 博主本科? 去了什么公司? Re: Mini校長2012-11-26 10:52發表 [回復] 回復zhucegepp:我覺得可以去google,現在博主在上海 2樓
potentialman2012-11-20 22:53發表 [回復] mark一下 明天考linux 考了再靜心來看。 Re: v_JULY_v2012-11-22 00:47發表 [回復] 回復potentialman:文章補充了第一部分中的皮爾遜相關系數等距離度量表示法,不知你昨晚來看了沒? 1樓
zhangt022012-11-20 22:01發表 [回復] 之前看過局部特征不變向量那本書 你更加詳細的闡述了KD樹~~ Re: v_JULY_v2012-11-21 00:17發表 [回復] 回復zhangt02:EN,文中若有任何問題,歡迎隨時賜教 & 指正。
(2):決策樹 Python代碼:http://blog.csdn.net/wyb_009/article/details/9191325
決策樹是個極其易懂的算法,建好模型后就是一連串嵌套的if..else...或嵌套的switch。
優點:計算復雜度不高,輸出結果易于理解,對中間值的缺失不敏感,可以處理不相關特征數據;
缺點:可能會產生過度匹配的問題;
適用數據類型:數值型和標稱型。
決策樹的Python實現:
(一)先實現幾個工具函數:計算熵函數,劃分數據集工具函數,計算最大概率屬性;
(1)計算熵:熵代表集合的無序程度,集合越無序,熵越大;
[python] view plaincopyprint?
def ?entropy(dataset):??????from ?math?import ?log???? ????log2?=?lambda ?x:log(x)/log(2 )??? ?????? ????results={}???? ????for ?row?in ?dataset:???? ????????r?=?row[len(row)-1 ]?? ????????results[r]?=?results.get(r,?0 )?+?1 ?? ?????? ????ent?=?0.0 ?? ????for ?r?in ?results.keys():???? ????????p?=?float(results[r])?/?len(dataset)???? ????????ent=ent-p*log2(p)???? ????return ?ent???? ??????
(2)按屬性和值獲取數據集:
[python] view plaincopyprint?
def ?fetch_subdataset(dataset,?k,?v):??????return ?[d[:k]+d[k+1 :]?for ?d?in ?dataset?if ?d[k]?==?v]??
這個函數只有短短一行,他的意義是:從dataset序列中取得第k列的值為v的子集,并從獲得的子集中去掉第k列。python的簡單優美顯現無遺。
(3)計算最大概率屬性。在構建決策樹時,在處理所有決策屬性后,還不能唯一區分數據時,我們采用多數表決的方法來選擇最終分類:
[python] view plaincopyprint?
def ?get_max_feature(class_list):??????class_count?=?{}?? ????for ?cla?in ?class_list:?? ????????class_count[cla]?=?class_count.get(cla,?0 )?+?1 ?? ????sorted_class_count?=??sorted(class_count.items(),?key=lambda ?d:?d[1 ],?reverse=True )??? ????return ?sorted_class_count[0 ][0 ]??
(二)選取最優數據劃分方式函數:
選擇集合的最優劃分方式:以哪一列的值劃分集合,從而獲得最大的信息增益呢?
[python] view plaincopyprint?
def ?choose_decision_feature(dataset):??????ent,?feature?=?100000000 ,?-1 ?? ????for ?i?in ?range(len(dataset[0 ])?-?1 ):?? ????????feat_list?=?[e[i]?for ?e?in ?dataset]?? ????????unq_feat_list?=?set(feat_list)?? ????????ent_t?=?0.0 ?? ????????for ?f?in ?unq_feat_list:?? ????????????sub_data?=?fetch_subdataset(dataset,?i,?f)?? ????????????ent_t?+=?entropy(sub_data)?*?len(sub_data)?/?len(dataset)?? ?????????????? ????????if ?ent_t?<?ent:?? ????????????ent,?feature?=?ent_t,?i?? ?????????????? ????return ?feature??
(三)遞歸構建決策樹:
[python] view plaincopyprint?
def ?build_decision_tree(dataset,?datalabel):??????cla?=?[c[-1 ]?for ?c?in ?dataset]?? ????if ?len(cla)?==?cla.count(cla[0 ]):?? ????????return ?cla[0 ]?? ????if ?len(dataset[0 ])?==?1 :?? ????????return ?get_max_feature(dataset)?? ?????????? ????feature?=?choose_decision_feature(dataset)?? ????feature_label?=?datalabel[feature]?? ????decision_tree?=?{feature_label:{}}?? ????del (datalabel[feature])?? ?????? ????feat_value?=?[d[feature]?for ?d?in ?dataset]?? ????unique_feat_value?=?set(feat_value)?? ????for ?value?in ?unique_feat_value:?? ????????sub_label?=?datalabel[:]?? ????????decision_tree[feature_label][value]?=?build_decision_tree(\?? ????????????fetch_subdataset(dataset,?feature,?value),?sub_label)?? ?????????? ????return ?decision_tree??
(四)使用決策樹
[python] view plaincopyprint?
def ?classify(decision_tree,?feat_labels,?testVec):??????label?=?decision_tree.keys()[0 ]?? ????next_dict?=?decision_tree[label]?? ????feat_index?=?feat_labels.index(label)?? ????for ?key?in ?next_dict.keys():?? ????????if ?testVec[feat_index]?==?key:?? ????????????if ?type(next_dict[key]).__name__?==?'dict' :?? ????????????????c_label?=?classify(next_dict[key],?feat_labels,?testVec)?? ????????????else :?? ????????????????c_label?=?next_dict[key]?? ????return ?c_label??
(五)決策樹持久化
(1)存儲
[python] view plaincopyprint?
def ?store_decision_tree(tree,?filename):??????import ?pickle?? ????f?=?open(filename,?'w' )?? ????pickle.dump(tree,?f)?? ????f.close()??
(2)讀取
[python] view plaincopyprint?
def ?load_decision_tree(filename):??????import ?pickle?? ????f?=?open(filename)?? ????return ?pickle.load(f)??
(六)到了最后了,該回到主題了,給眼鏡男配眼鏡了。
下面的隱形眼鏡數據集來自UCI數據庫,它包含很多患者眼部狀況的觀察條件以及醫生推薦的隱形眼鏡類型,隱形眼鏡類型包括硬材料、軟材料和不適合佩戴隱形眼鏡。
數據如下:
[plain] view plaincopyprint?
young???myope???no??reduced?no?lenses?? young???myope???no??normal??soft?? young???myope???yes?reduced?no?lenses?? young???myope???yes?normal??hard?? young???hyper???no??reduced?no?lenses?? young???hyper???no??normal??soft?? young???hyper???yes?reduced?no?lenses?? young???hyper???yes?normal??hard?? pre?myope???no??reduced?no?lenses?? pre?myope???no??normal??soft?? pre?myope???yes?reduced?no?lenses?? pre?myope???yes?normal??hard?? pre?hyper???no??reduced?no?lenses?? pre?hyper???no??normal??soft?? pre?hyper???yes?reduced?no?lenses?? pre?hyper???yes?normal??no?lenses?? presbyopic??myope???no??reduced?no?lenses?? presbyopic??myope???no??normal??no?lenses?? presbyopic??myope???yes?reduced?no?lenses?? presbyopic??myope???yes?normal??hard?? presbyopic??hyper???no??reduced?no?lenses?? presbyopic??hyper???no??normal??soft?? presbyopic??hyper???yes?reduced?no?lenses?? presbyopic??hyper???yes?normal??no?lenses??
測試程序如下:
[python] view plaincopyprint?
def ?test():??????f?=?open('lenses.txt' )?? ????lense_data?=?[inst.strip().split('\t' )?for ?inst?in ?f.readlines()]?? ????lense_label?=?['age' ,?'prescript' ,?'astigmatic' ,?'tearRate' ]?? ????lense_tree?=?build_decision_tree(lense_data,?lense_label)??
我這里測試結果如下:
?
眼鏡男終于可以買到合適的眼鏡啦。。。
所有代碼黏在下面:
[python] view plaincopyprint?
def ?entropy(dataset):??????from ?math?import ?log???? ????log2?=?lambda ?x:log(x)/log(2 )??? ?????? ????results={}???? ????for ?row?in ?dataset:???? ????????r?=?row[len(row)-1 ]?? ????????results[r]?=?results.get(r,?0 )?+?1 ?? ?????? ????ent?=?0.0 ?? ????for ?r?in ?results.keys():???? ????????p?=?float(results[r])?/?len(dataset)???? ????????ent=ent-p*log2(p)???? ????return ?ent???? ?????? def ?fetch_subdataset(dataset,?k,?v):??????return ?[d[:k]+d[k+1 :]?for ?d?in ?dataset?if ?d[k]?==?v]?? ?? def ?get_max_feature(class_list):??????class_count?=?{}?? ????for ?cla?in ?class_list:?? ????????class_count[cla]?=?class_count.get(cla,?0 )?+?1 ?? ????sorted_class_count?=??sorted(class_count.items(),?key=lambda ?d:?d[1 ],?reverse=True )??? ????return ?sorted_class_count[0 ][0 ]?? ?? def ?choose_decision_feature(dataset):??????ent,?feature?=?100000000 ,?-1 ?? ????for ?i?in ?range(len(dataset[0 ])?-?1 ):?? ????????feat_list?=?[e[i]?for ?e?in ?dataset]?? ????????unq_feat_list?=?set(feat_list)?? ????????ent_t?=?0.0 ?? ????????for ?f?in ?unq_feat_list:?? ????????????sub_data?=?fetch_subdataset(dataset,?i,?f)?? ????????????ent_t?+=?entropy(sub_data)?*?len(sub_data)?/?len(dataset)?? ?????????????? ????????if ?ent_t?<?ent:?? ????????????ent,?feature?=?ent_t,?i?? ?????????????? ????return ?feature?? ?????????????? def ?build_decision_tree(dataset,?datalabel):??????cla?=?[c[-1 ]?for ?c?in ?dataset]?? ????if ?len(cla)?==?cla.count(cla[0 ]):?? ????????return ?cla[0 ]?? ????if ?len(dataset[0 ])?==?1 :?? ????????return ?get_max_feature(dataset)?? ?????????? ????feature?=?choose_decision_feature(dataset)?? ????feature_label?=?datalabel[feature]?? ????decision_tree?=?{feature_label:{}}?? ????del (datalabel[feature])?? ?????? ????feat_value?=?[d[feature]?for ?d?in ?dataset]?? ????unique_feat_value?=?set(feat_value)?? ????for ?value?in ?unique_feat_value:?? ????????sub_label?=?datalabel[:]?? ????????decision_tree[feature_label][value]?=?build_decision_tree(\?? ????????????fetch_subdataset(dataset,?feature,?value),?sub_label)?? ?????????? ????return ?decision_tree?? ?????????? def ?store_decision_tree(tree,?filename):??????import ?pickle?? ????f?=?open(filename,?'w' )?? ????pickle.dump(tree,?f)?? ????f.close()?? ?? def ?load_decision_tree(filename):??????import ?pickle?? ????f?=?open(filename)?? ????return ?pickle.load(f)?? ?????? def ?classify(decision_tree,?feat_labels,?testVec):??????label?=?decision_tree.keys()[0 ]?? ????next_dict?=?decision_tree[label]?? ????feat_index?=?feat_labels.index(label)?? ????for ?key?in ?next_dict.keys():?? ????????if ?testVec[feat_index]?==?key:?? ????????????if ?type(next_dict[key]).__name__?==?'dict' :?? ????????????????c_label?=?classify(next_dict[key],?feat_labels,?testVec)?? ????????????else :?? ????????????????c_label?=?next_dict[key]?? ????return ?c_label?? ?????? def ?test():??????f?=?open('lenses.txt' )?? ????lense_data?=?[inst.strip().split('\t' )?for ?inst?in ?f.readlines()]?? ????lense_label?=?['age' ,?'prescript' ,?'astigmatic' ,?'tearRate' ]?? ????lense_tree?=?build_decision_tree(lense_data,?lense_label)?? ????return ?lense_tree?? ?????? if ?__name__?==?"__main__" :??????tree?=?test()?? ????print ?tree?
總結
以上是生活随笔 為你收集整理的ML二:NNSearch数据结构--二叉树 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。