1. 程式人生 > >海量資料流的最佳演算法實戰

海量資料流的最佳演算法實戰

摘要:為了高效地分析海量的資料,科學家首先要將大數打破成碎片。作者循序漸進地闡述了一個處理海量資料流的最佳流式演算法,演算法經過不斷改進,是超大規模資料流高效能分析的新演算法,以下是譯文。

這裡寫圖片描述

當從消防水帶出來的水衝擊你的臉部時,很難計量水。從某種意義上說,這正是分析資料流所面臨的挑戰,資料來源源不斷地向我們奔流而來,永不停息。如果在Twitter上看一篇篇推文不停閃過,你可能想要暫停一下,以便弄清楚時下的流行話題是什麼。話雖這麼說,但這行不通,所以需要找到一種方法,以便實時記錄主題標籤。

執行這種實時計算的計算機程式稱為流式演算法。因為資料以龐大的量持續不斷地出現,流式演算法試圖記錄下重要的資料,同時戰略性遺忘其餘資料。30多年來,電腦科學家一直致力於構建更好的流式演算法。去年秋天,一個研究小組發明了一種近乎完美的演算法。

哈佛大學的電腦科學家傑拉尼·尼爾森(Jelani Nelson)表示:“我們開發了一種新演算法,從各個標準維度來看,也是最好的演算法”,他與丹麥奧胡斯大學的卡斯珀·格林·拉森(Kasper Green Larsen)、哥本哈根大學的邁克爾·託魯普(Mikkel Thorup)以及東北大學的阮惠(Huy Nguyen)一起提出了這個新演算法。
這裡寫圖片描述
哈佛大學的理論電腦科學家傑拉尼·尼爾森與別人共同開發出了新演算法。
圖片來源:Yaphet Teklu

這種一流的流式演算法的工作原理是,通過記住足夠多的資料來告訴你它最常看到的是什麼。這表明在資料流分析中固有的折衷實際上似乎並不必要。這也為戰略性遺忘的新紀元指明瞭前進的道路。

發現趨勢

流式演算法在監視連續不斷更新的資料庫的任何情況下都是有幫助的。這可能是AT&T持續不斷的選項卡資料包或者Google繪製永無止境的搜尋查詢流程。在這些情況下,流式演算法是非常有用的,甚至是必要的,即使沒有重新檢查或甚至記住你所見過的每一段資料,都有一種方法來回答資料方面的實時問題。

這裡有一個簡單的例子。假設有一連串的數字,想知道目前為止你所看到的所有數字的總和。這種情況下,很顯然不是記住每一個數字,只需要記住一個數字:累計總數。

然而,當想問的關於資料的問題變得更復雜時,這個挑戰就更難了。想象一下,假設不是想計算總和,而是想回答以下問題:哪些數字最常出現?看不太出來有什麼捷徑可以快速給出正確的答案。

這個特殊的謎題被稱為“頻繁項”(frequent item,或heavy hitter)問題。解決這個難題的第一個演算法是在20世紀80年代初由康奈爾大學的大衛·格里斯(David Gries)和得克薩斯大學奧斯汀分校的賈亞德夫·米斯拉(Jayadev Misra)開發的。他們的演算法在許多方面是有效的,但卻不能處理所謂的“變化檢測”。它可以告訴你最常搜尋的詞語,但是無法告訴哪些詞語很流行。以Google為例,它可以將“維基百科”視為一個受歡迎的搜尋詞語,卻發現不了與艾爾瑪颶風等重大事件相伴隨的熱搜。

沃裡克大學的電腦科學家格雷厄姆·科爾莫德(Graham Cormode)說:“這是一個編碼問題 ——你將資訊編碼為簡潔的摘要,並嘗試提取資訊,讓你恢復最初的內容。”

在接下來的30多年裡,科爾莫德和其他電腦科學家改進了Gries和Misra的演算法。比如說,一些新的演算法能夠檢測出流行詞語,另一些演算法能夠更精細地定義某個詞語怎樣才算是頻繁詞語。所有這些演算法都有其弊端,比如犧牲速度以換去準確性,或者犧牲記憶體消耗來保證可靠性。

這方面的工作大多數依賴索引(index)。想象一下,設若你試圖識別頻繁的搜尋字詞。一個辦法就是為英語中的每個單詞分配一個編號,然後將該編號與跟蹤該詞搜尋了多少次的第二個編號配對起來。也許“aardvark” 以單詞編號17被編入索引,並在資料庫中顯示為(17,9),意思是單詞編號為17的單詞已被搜尋9次。這種方法離將最頻繁的項放在手邊更近了一步,因為在任何特定時刻,你知道每個單詞到底被搜尋了多少次。

儘管如此,它還是有缺點的,那就是要花很多的時間用一個演算法來梳理英語中成千上萬的單詞。

但要是字典裡只有100個單詞,將會怎麼樣?羅伊·尼爾森說:“遍歷字典中的每個單詞花不了太長時間。”

可惜,字典裡的單詞數量實在是太多了。正如新演算法的作者發現的那樣,除非你可以把大字典分解成更小的字典,並找到一種巧妙的方法把它重新組合起來,否則無計可施。

小資料

小數字比大數字容易跟蹤。

想象一下,假如你正在監視0到50000000之間的一個數字流(這項任務類似於按照IP地址來記錄網際網路使用者)。可以使用50000000項索引來跟蹤這些數字,但是很難處理這麼龐大的索引。更好的方法是把每個八位數字視為連線在一起的四個兩位數字。

設若看到數字12345678。一種高效記憶的方法是將其分解成四個兩位數塊:12,34,56,78。然後,可以將每個塊傳送給計算專案頻率的子演算法:12發給子演算法一,34發給子演算法二,56發給子演算法三,78發給子演算法四。

每個子演算法都為它看到的內容建有各自的索引,但由於每個版本都不會看到比兩位數大的索引,所以每個索引只是從0到99。

這種分拆的一個重要特點是,如果大數字——12345678 在整個資料流中頻繁出現,其兩位數字塊部分也將頻繁出現。如果要求每個子演算法識別出最常看到的數字,子演算法一會給出12,子演算法二將給出34,依此類推。只要在四個短得多的列表中查詢頻繁項,就能夠找到龐大列表中最常見的內容。

這裡寫圖片描述
圖片來源:Lucy Reading-Ikkanda/《Quanta》雜誌

尼爾森說:“不是花5000萬個時間單位遍歷整個宇宙,只是四個演算法,花了100個時間單位。”

這種分而治之策略存在的主要問題是,雖然很容易把一個大數字分解成小的數字,但將小數重新組合成大數比較棘手— 很難找出正確的小數來重新組合,從而獲得正確的大數。

想象一下,你的資料流通常包含幾個數字相同的兩個數:12,345,678和12,999,999。兩者都從12開始。你的演算法將每個大數字分成四個較小的數字,然後將每個小數字傳送給子演算法。然後你問每個子演算法,“哪些數最常看到?”子演算法1將會說:“我經常看到數字12.” 一種演算法試圖識別是哪個八位數字,在這種情況下,經常不知道這些12是屬於其中某一個八位數的,還是屬於該例中兩個不同的八位數的。

尼爾森說:“難就難在搞清楚哪些兩位數塊與另外哪些兩位數塊連在一起。”

新演算法的作者通過將每個兩位數塊封裝成一個不佔用大量記憶體的小標籤來解決這個困境,但是仍然允許演算法將兩位數的塊以正確的方式重新組合在一起。

要檢視標籤如何工作的簡單方法,不妨從12,345,678開始,並將其拆分成兩位數塊。但這一次,在將每個塊傳送到其各自的子演算法之前,使用一對唯一的標識號封裝塊,標識號可以將塊重新組合在一起。這些標籤中的第一個用作塊的名稱,第二個標籤作為環。這樣一來,12345678成為:

12, 0, 1 34, 1, 2 56, 2, 3 78, 3, 4

這裡,數字12的名稱為“0”,並連結到名為“1”的數字。數字34的名稱為“1”,並連結到名為“2”的數字,依此類推。

現在,當子演算法返回最常見的兩位數字塊時,12找尋一個標有“1”的數字,從而找到34,然後34找尋一個標有“2”的數字,從而找到56, 56找尋一個標有“3”的數字,從而找到78。

這麼一來,可以把兩位數字塊視為鏈中的環,並通過這些額外的標記號將這些環連線在一起。

當然許多鏈的問題在於,其牢固性完全取決於最薄弱的那一環。而這些鏈幾乎可以保證會斷掉。

構建塊

沒有一種演算法能在每次執行時都完美地執行——即使最好的演算法也會在很小的一段時間內失效。在上述的例子中,一個失效可能意味著第二個兩位數的塊,34,被分配了一個不正確的標籤,結果就是當它去找尋它應該連線的塊時,它沒有找到56所需要的資訊。一旦鏈中的一個環節失敗,整個工作就土崩瓦解了。
這裡寫圖片描述
哥本哈根大學的電腦科學家邁克爾·託魯普幫助開發了一種防止錯誤的方法來記住資料。
圖片來源:UNIAVISEN.DK

為了避免這個問題,研究人員使用所謂的“擴充套件圖”。在擴充套件圖中,每兩個數字塊形成一個點。這些點按線連線(遵循上述的標記過程)形成一個聚類。擴充套件圖的一個重要特點是,不只是將每個點與其相鄰的塊連線起來,而是將每個兩個數字塊與多個其它塊連線起來。以12345678為例,不僅將12與34連線,也與56連線,這樣即使12和34之間的連結失敗,仍然可以知道12和56屬於同一個數字。

擴充套件圖的結果並不總是完美的。有時它無法連結本該連結的兩個塊,或者會連結不屬於一起的兩個塊。為了克服這個不足,研究人員開發了其演算法的最後環節:“聚類保留”子演算法,它可以調查擴充套件圖,準確地確定哪些點旨在聚合起來、哪些點不是,即使某幾條線丟失、虛假線新增上去也沒關係。

託魯普說:“這確保我可以恢復看起來酷似原始聚類的內容。”

雖然Twitter不會明天就使用這個擴充套件圖,但其底層技術適用於解決極其廣泛的電腦科學問題,而不是僅僅用來統計推文。該演算法還證明了不需要作出某些犧牲,也即以前在回答頻繁專案問題的時候似乎非常必要的犧牲。以前的演算法總是放棄一些東西——準確性很高,但很費記憶體,或者速度很快,但是無法確定哪些頻繁項很流行。這個新的演算法表明,若有正確的方法來編碼大量資訊,你最後能夠集眾者之所長:不僅可以儲存頻繁項,還可以取回頻繁項。