海量數據分析系列一:近鄰搜索的概念及其應用


海量數據分析系列是我在學習和應用中對於一些比較常用技術的學習筆記和總結,網上有很多關於海量數據分析的寶貴資料,但很多都是英文的或專業性太強,新手學起來比較費勁。在這個系列中,將由淺入深,講解海量數據分析里的一些概念和常用的方法,希望能作為新學習海量數據分析的同學們的參考,同時也歡迎大家指出文章中存在的錯誤。(文章由本人原創發表在http://www.cnblogs.com/breakinen/上,歡迎轉載,轉載時請務必標明出處,謝謝!)

本文是海量數據分析系列的第一篇文章,主要介紹近鄰搜索的概念,意義,方法以及在進行企業級的海量數據分析時遇到的挑戰及解決思路。

最近鄰搜索(Nearest Neighbor Search)是一種被廣泛應用在客戶分析,文本聚類,商品推薦,模糊查詢等應用中的基本的數據挖掘技術,它的目標可以簡單理解為:在多維空間中,尋找離某一個數據點距離最近的點。這里的點是一個抽象概念,它可以是平面或空間里的點,也可以是一組特征(Feature)所形成的坐標點,例如用戶的購買記錄(對N種商品中各種商品的購買量),一篇文章(每個詞在文章中出現的次數)等,而這個距離由一種特定的距離度量方法得出,例如歐幾里德距離,Pearson距離,Cosine距離等。此外,K-近鄰,也就是常說的KNN,是對最近鄰搜索的一種擴展,用來尋找離某一個數據點距離最近的K個點。

             image

          在二維平面中對q點進行最近鄰搜索,得到q點的最近鄰點是p1點

通過對海量數據進行近鄰搜索,我們可以得到一系列有價值的應用,例如

  1. 商品推薦:根據某網絡商城里各用戶的購買記錄,得到與用戶A購買行為最相似的K個用戶,接着就可以將這K個用戶購買過而A沒有購買過的商品推薦給A(這也是User-Based推薦的基本思想,當然在User-Based推薦的過程中,計算過程比上面的描述稍為復雜)
  2. 層次聚類:對各用戶之間計算Pairwise Similarity,從而根據各用戶間的Similarity對客戶進行聚類,這樣形成的聚類中,每一個聚類都有其獨特的消費特征(例如喜歡購買新奇的電子產品,喜歡購買名牌護膚品等),如此以來商城就可以針對各聚類的消費特征來進行個性化營銷。
  3. 相似查詢(模糊搜索):根據客戶的查詢,返回和客戶查詢最相似的內容,例如Google去年推出的圖像搜索(Google Goggles),即用戶上傳一張圖片,Google根據這張圖片找出與之最相似的圖片並給出圖片信息,看起來很神奇但是思想卻很簡單(當然實現的難度很大,涉及到很多圖像特征抽取,查詢優化等專業知識),同理,我們也可以進行相似股票查詢、相似音樂查詢、相似愛好的朋友查詢等等,一般來說,只要是能用一組特征來表示的數據,都可以對其通過近鄰搜索進行相似查詢

    image

                                                         Google Goggles圖像搜索演示

  4. 文本聚類:文本聚類是一件很神奇的事情,用戶提供給計算機一系列文章,計算機自動根據每篇文章的內容來對文章進行聚類,這些類別可能包含商業、體育、教育、金融等。在這個過程中,計算機把每一篇文章看作是一個高維空間中的點,這個點的坐標是文章中各關鍵詞及其出現個數。接着對每兩個點直接進行距離測量,然后根據各篇文章之間的距離進行層次聚類(Hierarchical Clustering)或其它適合的聚類方法。

在使用近鄰搜索時,首先要考慮兩個最基本,也是最重要的問題:

 

如何把數據映射到高維空間中的數據點

一個高維空間上的點通常用一個Vector來表示其坐標,例如(1,2,1),(3,1,2),但是對於現實中的數據,怎樣將其映射到空間里的一個點呢?下面我將舉幾個例子:

  • 描述型數據:例如一種汽車,它有四個車門,自動檔,可載5人,黑色,則可以表示為 (4,1,5,1),其中第二位代表換擋方式,例如0代表手動,1代表自動,第四位代表顏色,例如1代表黑色,0代表白色,2代表藍色等,或者,我們也可以將這個汽車的數據表示成由0,1組成的向量,例如(0,0,0,1,0,1,0,0,0,0,1,0,1,0),其中前四位代表車門數,第5、6位代表換擋方式,第7-11位代表可載入數,第12-14位代表顏色。這兩種表示方式各有各的優缺點,在具體進行分析時,需要根據數據的復雜程度和距離度量的選擇來決定使用數值型的坐標還是boolean型的坐標來表示數據
  • 購買記錄數據:對於購買記錄數據,通常把每一種商品作為一個維度,假設有四種商品 - {打印機,顯示器,訂書機,掃描儀},將他們作為坐標點的4個維度,如果用戶有購買過某種產品,則相對應維度的數值為1,否則為0。例如:A用戶購買了顯示器和掃描儀,則它的購買記錄的坐標為(0,1,0,1),同時,也可以用購買的數量作為坐標值。
  • 文本數據:對於文本數據,通常將每一個關鍵詞作為一個維度,將這篇文本中每個關鍵詞出現的次數作為坐標值,例如有以下幾個關鍵詞- { 足球,北京,燃油,中國,上調,體育,計算機,數據 }組成一個8維的空間,則下面一段話“中國多個城市上調出租車燃油附加費 北京漲至3元”的內容映射到這個8維空間的坐標點后坐標為( 0,1,1,1,1,0,0,0),當然這只是一個粗略的表示方式,在文本數據挖掘的過程中有很多技巧(例如經典的TF-IDF等)來進行文本特征的抽取
  • 時間序列數據:可以將時間作為坐標的維度,將各時間的值作為坐標的值來把時間序列數據映射到坐標。在實際應用中,為了減少坐標的維度,可以使用很多方法(例如SAX,PAA)等方法實現時間序列的近似表達
  • 圖像數據:圖像數據有很多種映射到坐標點的方式,例如抽取圖像特征作為坐標維度,將顏色直方圖映射為坐標,或者直接把圖像中各像素點的顏色值映射成坐標等,對圖像數據分析感興趣的同學可以先學習一下圖像特征抽取的相關內容

距離度量方法的選擇

距離度量的選擇取決於數據坐標所代表的內容(比如,是代表文本數據還是購買記錄),坐標的維度,坐標值的類型(如數值型還是0/1型),坐標值的大小,異常點的數量等,一般來說,比較常用的距離度量方法有以下幾種:

  • 歐幾里德距離(歐氏距離,Euclidean distance):最常用的距離度量方式,對於N維空間中的坐標,兩兩坐標直接的距離計算公式為:

  d(p, q) = \sqrt{(p_1 - q_1)^2 + (p_2 - q_2)^2+...+(p_i - q_i)^2+...+(p_n - q_n)^2}.

其中p,q代表兩個點,pi和qi代表p點和q點在i維度上的值,如果維度為2的話,歐氏距離就可以看作是平面直角坐標系中兩點間的距離。歐氏距離因為計算速度快且穩定,因此得到廣泛的使用。

不過歐氏距離的缺點也很明顯:1)不適合高維度的坐標點間距離的計算,例如文本數據和購買記錄數據;2)受到異常值的影響較大,而且由於取了坐標值的平方和,因此加大了這種不利影響。

在使用歐氏距離時,如果目的只是比較距離的遠近而不在乎距離的具體值,例如對一個點做最近鄰搜索時,可以省去最后開方的步驟,因為如果 d2(q,p1)>d2(q,p2),那么d(q,p1)>d(q,p2)總是成立的(注意d(q,p)總是非負數)

  • 余弦相似度(Cosine Similarity):將數據點視為向量,計算兩向量間夾角的Cosine值,取值范圍為[-1,1],兩組數據的夾角越小,Cosine越大,說明兩組數據越相似。雖然它的值代表相似度,但是可以通過變換(例如1- similarity)使之表示兩兩坐標點之間的距離(相似度越高距離越小,相似性越小距離越大),注意這里的距離已經不再是普通意義上的距離(幾何距離)。余弦相似度的計算公式如下:

\text{similarity} = \cos(\theta) = {A \cdot B \over \|A\| \|B\|} = \frac{ \sum\limits_{i=1}^{n}{A_i \times B_i} }{ \sqrt{\sum\limits_{i=1}^{n}{(A_i)^2}} \times \sqrt{\sum\limits_{i=1}^{n}{(B_i)^2}} }

余弦相似度對維度的增加不太敏感,適合對高維度數據進行分析,比如購買記錄或文本數據。

如果余弦相似度為0,即兩組數據的夾角為90度,則表示兩組數據沒有重合值,例如兩篇文章沒有共同出現的關鍵字,兩個客戶沒有共同購買過的產品。

  • 皮爾遜相關系數(Pearson's Correlation):用來計算兩組變量的相關性,取值范圍為[-1,1],皮爾遜相關系數為-1時,表示兩組變量負相關,為1時表示兩組變量正相關,如果值為0則表示兩組變量不相關,雖然它是代表相關的系數,但是可以通過變換來使之表示兩組變量(即兩個點)之間的距離。皮爾遜相關系數的計算公式為:


r_{xy}=\frac{\sum\limits_{i=1}^n (x_i-\bar{x})(y_i-\bar{y})}{(n-1) s_x s_y}
      =\frac{\sum\limits_{i=1}^n (x_i-\bar{x})(y_i-\bar{y})}
            {\sqrt{\sum\limits_{i=1}^n (x_i-\bar{x})^2 \sum\limits_{i=1}^n (y_i-\bar{y})^2}},

  • Jaccard相似度(JaccardSimilarity):Jaccard相似度非常容易理解。假設有集合A和集合B,則他們的Jaccard相似度

   J(A,B) = {{|A \cap B|}\over{|A \cup B|}}.

例如我們觀察兩個人的購物籃,A買了啤酒,餅干,蛋糕,B買了蘋果,蛋糕,牛奶和餅干,則他們的Jaccard相似度為 Count{蛋糕,餅干}/Count{啤酒,餅干,蛋糕,蘋果,牛奶} = 2/5 = 0.4,一般來說Jaccard相似度只適用於0/1型(或布爾型的數據)

Jaccard廣泛應用於高維數據的分析中,以后的文章中會詳細講解一種高速的針對高維數據的Jaccard相似度的近似算法:MinHashing

 

普通的近鄰搜索的方法 - 遍歷

一般來說,近鄰搜索最簡單的方法就是遍歷,或者成為暴力搜索(Brute-Force Search),例如一個班有30名同學,根據每個人的興趣愛好(可以多選)找出與學生A的興趣愛好最相似的學生。在這個過程中,我們先把30名同學按照興趣愛護映射到一個N維的空間中(N等於所有可能的興趣愛好),比如:

學生ID  -   足球,籃球,冰球,網球,排球,滑冰,滑雪

A            1    0    0    1     0    0    1

B            0    1    1    0     0    1    1

… …

然后,我們遍歷除A以外的所有同學,計算A與這些同學的兩兩之間的距離(Distance(A,B),Distance(A,C),…),找出其中與A兩兩距離最小的同學,則這名同學就是與A興趣愛好最相似的同學。

同時,為了找到每個同學的最相似同學,可以計算這30個同學兩兩之間的距離(Pairwise Distance),記錄在矩陣中:

     A       B       C       D       E     …
A    -       0.31    0.45    0.27    0.67        
B    0.31    -       0.41    0.35    0.67  
C    0.45    0.41    -       0.76    0.51
D    0.27    0.35    0.76    -       0.16
E    0.67    0.67    0.51    0.16    -   
              

這樣,對於每個同學,只要找到矩陣中代表這個同學與其他同學距離的行(或列),找到其中最小的值,這個值所對應的列(或行)的序號就是要找的最相似的同學。

這種方法的步驟簡單清楚,可靠性高,但是因為每計算一個點的最近鄰就要對集合中所有點進行一次遍歷,時間代價太大。我們來分析一下使用遍歷法計算這個矩陣的時間復雜度:

假設集合中共有N個元素,那么計算一次兩兩之間距離的矩陣需要進行(N*N)/2次比較,

時間復雜度為 O(n2)

如此高的時間復雜度,在進行實際的海量數據分析時是不可想象的,通過下面Google News的例子不難理解。

在海量數據中進行近鄰搜索的問題:

借用Google News個性化推薦的場景。Google News是一個綜合的新聞網站,它在全世界包含大約4500個新聞來源,據Google統計,每天的訪問此網站的獨立用戶數就達到數百萬,而這些用戶中,每個用戶的新聞瀏覽記錄在0個到數百個之間。Google News可以為根據每一個用戶瀏覽新聞的記錄來向其推薦適合其偏好的新聞。

場景看似簡單,但是我們都知道,新聞的時效性很強,毫不誇張的說,在某些領域(如金融、體育)中,1個小時之前的新聞可能就已經對用戶失去了吸引力,同時,幾乎在每一分鍾都有新的新聞進來,需要被推薦給用戶。另外,由於用戶需要實時獲取推薦結果,因此需要在一秒鍾內看到推薦頁面。除去因網絡延遲、前端服務器生成HTML頁面、瀏覽器對HTML進行排版的時間,留給推薦系統的時間只有幾百毫秒。

image

假設我們用上文提到的方式 - 根據每一個用戶的新聞瀏覽記錄,得到與用戶A瀏覽偏好最相似的K個用戶,將這K個用戶瀏覽過而A沒有瀏覽過的新聞推薦給A。在這個過程中,最關鍵的一步就是根據這個用戶A的瀏覽記錄來搜索他的K近鄰。而這個搜索過程必須在幾百毫秒內完成!

如果你是Google的架構師,面對這個苛刻的需求(其實對大多數基於海量數據的實時推薦系統來說,都面臨着這個問題),如何實現在數百毫秒內找到用戶的K近鄰呢?

我們來計算一下使用遍歷,或者暴力搜索的時間:

假設系統中有500萬用戶,一共有10萬條候選新聞,平均每個用戶有10條瀏覽記錄,每個用戶的瀏覽記錄以稀疏向量的形式存儲在內存中(因為一共有10萬條候選新聞,用戶瀏覽記錄矩陣是非常稀疏的),我們使用1 - Jaccard Similarity來作為距離度量。

經過測試,進行10 000次兩兩比較需要花費10ms~20ms的時間,而使用暴力搜索需要進行5 000 000次比較,按照每次比較花費10ms來計算,進行一次近鄰搜索需要5 000ms也就是5秒的時間,在實時系統中,這么長的等待時間對於用戶來說是難以忍受的。

使用緩存?

讀者可能不難想到利用緩存的方式來幫助進行近鄰搜索,即事先對每一位用戶都進行一次最近鄰搜索並將結果保存起來,等對用戶進行新聞推薦時直接使用保存的結果,這樣就避免了實時的近鄰搜索。這確實是一個好辦法,但是仍然不能滿足實時新聞推薦的需求,因為:

    • 對每一個用戶都進行一次近鄰搜索需要進行(5 000 000*5 000 000/2)次比較,需要12 500 000秒,也就是3472小時,或者4個月,這顯然是不可接受的。當然我們也可以在分布式平台上進行並行計算,但即使有1000個節點的集群,進行一次計算也需要近4個小時
    • 新聞更新的速度很快,幾乎每一分鍾都有新的新聞進入系統。同時,每一分鍾都有成千上萬個用戶瀏覽記錄產生,如果對每一個用戶的推薦只是4個小時前的新聞,對於新聞推薦系統來說,“實時”推薦已經失去了意義

可見,對於這個龐大的新聞推薦系統來說,使用緩存並不能解決用遍歷來進行近鄰搜索的速度問題。

針對海量數據加速近鄰搜索的思路

有很多方法可以加快針對海量數據的近鄰搜索速度,個人總結出以下幾種思路:(假設現在我們要選出A點的K-近鄰點)

1. 減少兩兩比較過程的時間:使用算法優化或數據結構優化來加快距離計算的速度(對高維數據進行距離計算的速度一般來說比較慢),或者使用快速的算法來求距離的近似值,例如,使用MinHash技術來求出Jaccard距離的近似值。在海量數據處理系統中,是允許存在少量距離誤差的,只要這個誤差不足以對結果產生較大的影響

2. 減少兩兩比較的次數

    • 不考慮不可能成為近鄰的點(剪枝):即剪去那些離A點距離比較遠,不可能成為K-近鄰的點,只把剩下的點作為候選點來進行距離比較,找出K-近鄰
    • 只考慮最可能成為近鄰的點:在進行比較前先進行一次預選,只選出那些離A點距離比較近的點作為候選點來和A點進行距離比較,找出K-近鄰

例如,在根據新聞瀏覽記錄來進行近鄰搜索時,可以先過濾掉那些和當前用戶沒有瀏覽過同一篇文章的用戶,因為這些用戶的瀏覽記錄和當前用戶的瀏覽記錄做交集后,交集內的元素數一定是0,因此Jaccard 相似度一定也為0(因為公式的分母是交集中元素的個數)。或者只選出那些看過當前用戶瀏覽記錄中新聞的用戶來進行近鄰搜索,當然單憑這種方法並不能使近鄰搜索的時間降到數百毫秒以內。

在接下來的文章中,將從這兩個思路展開,詳細介紹幾種針對海量數據實現實時近鄰搜索的方法。

歡迎轉載,轉載請注明出處: http://www.cnblogs.com/breakinen/archive/2012/03/31/bigdata_nearest_neighbor_search.html 謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM