從畢業加入Google開始做分散式機器學習,到後來轉戰騰訊廣告業務,至今已經七年了。我想說說我見到的故事和我自己的實踐經歷。這段經歷給我的感覺是:雖然在驗證一個新的並行演算法的正確性的時候,我們可以利用現有框架,儘量快速實現,但是任何一個有價值的機器學習思路,都值得擁有自己獨特的架構。所以重點在有一個分散式作業系統,方便大家開發自己需要的架構(框架),來支援相應的演算法。如果你關注大資料,聽完我說的故事,應該會有感觸。
大資料和分散式機器學習
特點
說故事之前,先提綱挈領的描述一下我們要解決的問題的特點。我見過的有價值的大規模機器學習系統,基本都有三個特點:
- 可擴充套件。可擴充套件的意思是“投入更多的機器,能處理更大的資料”。而傳統的平行計算要的是:“投入更多機器,資料大小不變,計算速度更快”。這是我認識中“大資料”和傳統平行計算研究目標不同的地方。如果只是求速度快,那麼multicore和GPU會比分散式機器學習的ROI更高。
- 有一個框架(比如MPI或者MapReduce或者自己設計的),支援fault recovery。Fault recovery是可擴充套件的基礎。現代機群系統都是很多使用者公用的,其中任何一個程式都有可能被更高優先順序的程式preempted。一個job涉及數千個程式(task processes),十分鐘裡一個程式都不掛的概率很小。而如果一個程式掛了,其他程式都得重啟,那麼整個計算任務可能永遠都不能完成。
- 數學模型要根據架構和資料做修改。這裡有兩個原因:
- 因為大資料基本都是長尾分佈的,而papers裡的模型基本都假設資料是指數分佈的(想想用SVD做component analysis其實假設了Gaussian distributed,latent Dirichlet allocation假設了multimonial distribution。)。真正能處理大資料的數學模型,都需要能更好的描述長尾資料。否則,模型訓練就是忽視長尾,而只關注從“大頭”資料部分挖掘“主流”patterns了。
- 很多機器學習演算法(比如MCMC)都不適合並行化。所以往往需要根據模型的特點做一些演算法的調整。有時候會是approximation。比如AD-LDA演算法是一種並行Gibbs sampling演算法,但是隻針對LDA模型有效,對其他大部分模型都不收斂,甚至對LDA的很多改進模型也不收斂。
- 引入更多機器的首要目的不是提升效能,而是能處理更大的資料。用更多的機器,處理同樣大小的資料,期待speedup提高——這是傳統平行計算要解決的問題——是multicore、SMP、MPP、GPU還是Beowolf cluster上得分散式計算不重要。在大資料情況下,困難點在問題規模大,資料量大。此時,引入更多機器,是期待能處理更大資料,總時間消耗可以不變甚至慢一點。分散式計算把資料和計算都分不到多臺機器上,在儲存、I/O、通訊和計算上都要消除瓶頸。
上述三個特點,會在實踐中要求“一個有價值的演算法值得也應該有自己獨特的框架”。
概念
在開始說故事之前,先正名幾個概念:MPI、MapReduce都是框架(framework)。MPICH2和Apache Hadoop分別是這兩個框架的實現(implementations)。後面還會提到BSP框架,它的一個著名實現是Google Pregel。
MPI這個框架很靈活,對程式結構幾乎沒有太多約束,以至於大家有時把MPI稱為一組介面(API)。
這裡,MPICH2和Hadoop都是很大的系統——除了實現框架(允許程式設計師方便的程式設計),還實現了資源管理和分配,以及資源排程的功能。這些功能在Google的系統裡是分散式作業系統負責的,而Google MapReduce和Pregel都是在分散式作業系統基礎上開發的,框架本身的程式碼量少很多,並且邏輯清晰易於維護。當然Hadoop已經意識到這個問題,現在有了YARN作業系統。(YARN是一個仿照UC Berkeley AMPLab的Mesos做的系統。關於這個“模仿”,又有另一個故事。)
故事
pLSA和MPI
我2007年畢業後加入Google做研究。我們有一個同事叫張棟,他的工作涉及pLSA模型的並行化。這個課題很有價值,因為generalized matrix decomposition實際上是collaborative filtering的generalization,是使用者行為分析和文字語義理解的共同基礎。幾年後的今天,我們都知道這是搜尋、推薦和廣告這三大網際網路平臺產品的基礎。
當時的思路是用MPI來做並行化。張棟和宿華合作,開發一套基於MPI的並行pLSA系統。MPI是1980年代流行的並行框架,進入到很多大學的課程裡,熟悉它的人很多。MPI這個框架提供了很多基本操作:除了點對點的Send, Recv,還有廣播Bdcast,甚至還有計算加通訊操作,比如AllReduce。
MPI很靈活,描述能力很強。因為MPI對程式碼結構幾乎沒有什麼限制——任何程式之間可以在任何時候通訊——所以很多人不稱之為框架,而是稱之為“介面”。
但是Google的平行計算環境上沒有MPI。當時一位叫白巨集傑的工程師將MPICH2移植到了Google的分散式作業系統上。具體的說,是重新實現MPI裡的Send, Recv等函式,呼叫分散式作業系統裡基於HTTP RPC的通訊API。
MPI的AllReduce操作在很多機器學習系統的開發裡都很有用。因為很多並行機器學習系統都是各個程式分別訓練模型,然後再合適的時候(比如一個迭代結束的時候)大家對一下各自的結論,達成共識,然後繼續迭代。這個“對一下結論,達成共識”的過程,往往可以通過AllReduce來完成。
如果我們關注一下MPI的研究,可以發現曾經有很多論文都在討論如何高效實現AllReduce操作。比如我2008年的博文裡提到一種當時讓我們都覺得很聰明的一種演算法。這些長年累月的優化,讓MPICH2這樣的系統的執行效率(runtime efficiency)非常出色。
基於MPI框架開發的pLSA模型雖然效率高,並且可以處理相當大的資料,但是還是不能處理Google當年級別的資料。原因如上節《概念》中所述——MPI框架沒有自動錯誤恢復功能,而且這個框架定義中提供的靈活性,讓我們很難改進框架,使其具備錯誤恢復的能力。
具體的說,MPI允許程式之間在任何時刻互相通訊。如果一個程式掛了,我們確實可以請分散式作業系統重啟之。但是如果要讓這個“新生”獲取它“前世”的狀態,我們就需要讓它從初始狀態開始執行,接收到其前世曾經收到的所有訊息。這就要求所有給“前世”發過訊息的程式都被重啟。而這些程式都需要接收到他們的“前世”接收到過的所有訊息。這種資料依賴的結果就是:所有程式都得重啟,那麼這個job就得重頭做。
一個job哪怕只需要10分鐘時間,但是這期間一個程式都不掛的概率很小。只要一個程式掛了,就得重啟所有程式,那麼這個job就永遠也結束不了了。
雖然我們很難讓MPI框架做到fault recovery,我們可否讓基於MPI的pLSA系統支援fault recovery呢?原則上是可以的——最簡易的做法是checkpointing——時不常的把有所程式接收到過的所有訊息寫入一個分散式檔案系統(比如GFS)。或者更直接一點:程式狀態和job狀態寫入GFS。Checkpointing是下文要說到的Pregel框架實現fault recovery的基礎。
但是如果一個系統自己實現fault recovery,那還需要MPI做什麼呢?做通訊?——現代後臺系統都用基於HTTP的RPC機制通訊了,比如Facebook開發的Thrift和Google的Poppy還有Go語言自帶的rpc package。做程式管理?——在開源界沒有分散式作業系統的那些年裡有價值;可是今天,Google的Borg、AMPLab的Mesos和Yahoo!的YARN都比MPICH2做得更好,考慮更全面,效能更高。
LDA和MapReduce
因為MPI在可擴充套件性上的限制, 我們可以大致理解為什麼Google的平行計算架構上沒有實現經典的MPI。同時,我們自然的考慮Google裡當時最有名的平行計算框架MapReduce。
MapReduce的風格和MPI截然相反。MapReduce對程式的結構有嚴格的約束——計算過程必須能在兩個函式中描述:map和reduce;輸入和輸出資料都必須是一個一個的records;任務之間不能通訊,整個計算過程中唯一的通訊機會是map phase和reduce phase之間的shuffuling phase,這是在框架控制下的,而不是應用程式碼控制的。
pLSA模型的作者Thomas Hoffmann提出的機器學習演算法是EM。EM是各種機器學習inference演算法中少數適合用MapReduce框架描述的——map phase用來推測(inference)隱含變數的分佈(distributions of hidden variables),也就是實現E-step;reduce phase利用上述結果來更新模型,也即是M-step。
但是2008年的時候,pLSA已經被新興的LDA掩蓋了。LDA是pLSA的generalization:一方面LDA的hyperparameter設為特定值的時候,就specialize成pLSA了。從工程應用價值的角度看,這個數學方法的generalization,允許我們用一個訓練好的模型解釋任何一段文字中的語義。而pLSA只能理解訓練文字中的語義。(雖然也有ad hoc的方法讓pLSA理解新文字的語義,但是大都效率低,並且並不符合pLSA的數學定義。)這就讓繼續研究pLSA價值不明顯了。
另一方面,LDA不能用EM學習了,而需要用更generalized inference演算法。學界驗證效果最佳的是Gibbs sampling。作為一種MCMC演算法(從其中C=Chain),顧名思義,Gibbs sampling是一個順序過程,按照定義不能被並行化。
但是2007年的時候,David Newman團隊發現,對於LDA這個特定的模型,Gibbs sampling可以被並行化。具體的說,把訓練資料拆分成多份,用每一份獨立的訓練模型。每隔幾個Gibbs sampling迭代,這幾個區域性模型之間做一次同步,得到一個全域性模型,並且用這個全域性模型替換各個區域性模型。這個研究發表在NIPS上,題目是:Distributed Inference for Latent Dirichlet Allocation。
這樣就允許我們用多個map tasks並行的做Gibbs sampling,然後在reduce phase中作模型的同步。這樣,一個訓練過程可以表述成一串MapReduce jobs。我用了一週實現實現了這個方法。後來在同事Matthew Stanton的幫助下,優化程式碼,提升效率。但是,因為每次啟動一個MapReduce job,系統都需要重新安排程式;並且每個job都需要訪問GFS,效率不高。在當年的Google MapReduce系統中,1/3的時間花在這些雜碎問題上了。後來實習生司憲策在Hadoop上也實現了這個方法。我印象裡Hadoop環境下,雜碎事務消耗的時間比例更大。
隨後白紅傑在我們的程式碼基礎上修改了資料結構,使其更適合MPI的AllReduce操作。這樣就得到了一個高效率的LDA實現。我們把用MapReduce和MPI實現的LDA的Gibbs sampling演算法發表在這篇論文裡了。
當我們躊躇於MPI的擴充套件性不理想而MapReduce的效率不理想時,Google MapReduce團隊的幾個人分出去,開發了一個新的並行框架Pregel。當時Pregel專案的tech lead訪問中國。這個叫Grzegorz Malewicz的波蘭人說服了我嘗試在Pregel框架下驗證LDA。但是在說這個故事之前,我們先看看Google Rephil——另一個基於MapReduce實現的並行隱含語義分析系統。
Rephil和MapReduce
Google Rephil是Google AdSense背後廣告相關性計算的頭號祕密武器。但是這個系統沒有發表過論文。只是其作者(博士Uri Lerner和工程師Mike Yar)在2002年在灣區舉辦的幾次小規模交流中簡要介紹過。所以Kevin Murphy把這些內容寫進了他的書《Machine Learning: a Probabilitic Perspecitve》裡。在吳軍博士的《數學之美》裡也提到了Rephil。
Rephil的模型是一個全新的模型,更像一個神經元網路。這個網路的學習過程從Web scale的文字資料中歸納海量的“語義”——比如“apple”這個詞有多個意思:一個公司的名字、一種水果、以及其他。當一個網頁裡包含”apple”, “stock”, “ipad”等詞彙的時候,Rephil可以告訴我們這個網頁是關於apple這個公司的,而不是水果。
這個功能按說pLSA和LDA也都能實現。為什麼需要一個全新的模型呢?
【唉呀媽呀,本來沒想寫這麼長。碼字太累了。今天先到這兒吧。要是大家感興趣。再補全內容。把下面幾個故事的標題先補全了。】