利用Hadoop實現超大矩陣相乘之我見(一)


  前記

最近,公司一位挺優秀的總務離職,歡送宴上,她對我說“你是一位挺優秀的程序員”,剛說完,立馬道歉說“對不起,我說你是程序員是不是侮辱你了?”我挺詫異,程序員現在是很低端,很被人瞧不起的工作嗎?或許現在連賣盜版光盤的,修電腦的都稱自己為搞IT的,普通人可能已經分不清搞IT的到底是做什么的了。其實我想說,程序員也分很多種的,有些只能寫if-then-else,有些只能依葫蘆畫瓢,但真正的程序員我想肯定是某個領域的專家,或許他是一位數學家,或許他是一位物理學家,再或許他是計算機某個細分領域的專家,他是理論與現實的結合,是凌駕於純理論的存在!而筆者我正立志成為這樣的能讓人感到驕傲的程序員。

切入正題吧,談到雲計算,不得不提大數據,處理大數據,肯定逃不離分布式計算。互聯網行業,無論是商品推薦還是好友推薦,還是PageRank,所要處理的Items規模、用戶規模都是極其龐大的,小則數以百萬、千萬記,大則數以億記。在此數據基礎上,誕生了很多優秀的推薦算法,推薦算法中大部分會運用到矩陣運算。如此大規模的數據,一台計算機已經沒有能力處理,說簡單點,一台服務器的內存可能連加載半個矩陣數據都不夠,更別談處理了。“當一頭牛拉不動車時,很少有人去找一頭更大更強壯的牛,而是找來更多的牛一起拉。”,這就是分布式計算,而Hadoop就是在分布式集群上處理大記錄集的強大利器。

筆者最近對推薦算法挺感興趣的,也研究了一些!部分算法數學公式研究的透徹了,便有自己想實現的沖動,可公式里的矩陣運算可不是那么簡單!所以就想從研究超大規模矩陣相乘開始,一方面為以后做大規模矩陣運算、實現推薦算法做技術儲備;另一方面也想真正體驗一把用Hadoop實現分布式運算的樂趣;最重要的是能夠寫一些包含獨特思想,有研究成分,有技術含量的代碼。

  摘要

本文首先討論了目前現有的大矩陣運算方法,並指出其不足;接着提出自己的矩陣運算方法來解決目前現有方法所存在的問題,同時通過實驗來觀測本文方法所存在的問題,並針對這些問題,對本文方法進行再優化。

  現有方法

  • 行列相乘運算
    • 簡介

傳統的矩陣運算是A矩陣中的每一行分別與B矩陣中的每一列相乘。假設矩陣A的規模為(m*r),矩陣B的規模為(r*n),則矩陣C的規模為(m*n)。矩陣C中元素Ci,j是A中第i行與B中第j列元素依次對應相乘並匯總的結果。公式表示如下:

         

每一個Ci,j的計算都是獨立的,所以可以交由不同的計算節點完成。

  • 缺點

1、矩陣規模有一定限制,如果A矩陣或B矩陣有一個超大,則某個運算節點就很有可能由於內存限制,加載不了A矩陣的第i行或B矩陣的第j列。

2、對於稀疏矩陣計算沒優勢。若A,B中有稀疏矩陣存在,需判斷A中i行與B中第j列對應的位置上是否有0元素,換句話說,還是需要加載第i行,第j列的全部內容,若某個位置沒有輸入,在運算過程中需要將相應位置用0填充,這樣會造成上一點所存在的問題:內存放不下。

  • 矩陣分塊運算
    • 簡介

當矩陣大到一定程度時,一台服務器由於內存限制已經無法處理,不過由於矩陣具體天然的可分塊的特性,許多基於分塊的矩陣運算算法誕生了,《數學之美》這本書上介紹的大矩陣相乘方法就是基於分塊的,現簡單介紹如下:

1、當A矩陣縱向很大,橫向不大時,我們將A矩陣分塊,將A矩陣中的分塊分別與B矩陣相乘,通過Hadoop,這些計算可以並行進行,如圖1所示:

圖1

圖中A1*B=C1,A2*B=C2,…,每部分計算分別可在不同的計算節點完成,最后將結果組合在一起。

2、當A矩陣為一個真正的超大矩陣(橫向縱向都很大),與之相乘的B矩陣也必是一個超大矩陣(至少縱向很大),此時A,B矩陣都需要按行按列進行分塊,並將不同的分塊計算交由不同的計算節點完成,如圖2所示。

圖2

圖中,矩陣A中的每一塊都需要和矩陣B中對應位置的塊依次相乘,這些塊與塊之間的相乘運算可以由不同的計算節點完成,最后將不同塊與塊的運算結果,經過嚴密精確的控制,對相關結果進行合並(主要是相加),得到最終的運算結果C。

  • 缺點

1、對於不同的矩陣規模,如何分塊是難點,同時塊的大小受限於內存大小。

2、塊與塊之間的運算以及組織較繁瑣。

3、不太利於稀疏矩陣的運算(0值占用較多的存儲空間,以及會做很多無效運算)

  • 基於最小粒度相乘的算法

為了文檔的命名結構,筆者自己根據算法原理,起了這個名字。

  • 簡介

“行列相乘運算”和“分塊運算”都受限於計算節點的內存限制。那么有沒有一種運算,跟計算節點的內存大小無關呢?答案是:肯定有!總所周知,矩陣相乘的最小粒度計算是兩個矩陣中的兩個數相乘,比如,且計算結果是的一個組成部分。

假設有兩個超大矩陣A和B,A的規模是(m*r),B的規模是(r*n),將矩陣相乘中的最小粒度乘法運算進行統計,我們不難發現:A中每個元素Ai,k需要與B中第k行的元素Bk,j(j=1,2,...,n)依次相乘,計算結果分別為Ci,j的一個組成部分;而B中每個元素Bk,j需要與A中第j列的元素Ai,k(i=1,2,...,m)依次相乘,計算結果分別為Ci,j的一個組成部分。具體如圖3所示。

圖3

由於Ai,k*Bk,j是獨立的,因此可以由不同的計算節點進行運算,最后根據key (i,j)將運算結果進行匯總相加,得到結果Ci,j。同時,每個計算節點每次計算時都是只加載兩個數進行相乘,並不需要加載矩陣的某個塊或者某行某列,因此沒有內存的限制問題,理論上只要hadoop的HDFS文件系統夠大,就可以計算任意大規模的矩陣相乘。

在Map-Reduce過程中,由於Map的每條輸入記錄只被處理一次便不再使用,因此根據圖3理論,對於矩陣A中的每個元素,在實質進行乘法運算之前,我們需要生成n個副本,對於矩陣B中每個元素,我們需要生成m個副本,並將相應位置上的副本進行對應好。比如對於Ai,k需生成n個副本,並與B中相應元素對應好,並以A中元素的行號,B中元素的列號作為key:

以以上文件作為Map輸入,在Map中進行乘法運算,在Reduce階段按key進行加法運算,就得到矩陣相乘計算結果了。

  • 缺點及難點

1、矩陣元素副本准備。

如果想以以上格式作為初始Map輸入,那么我們就需要事先將數據整理成以上格式。對於兩個超大矩陣相乘來說,這是一個艱巨的任務。矩陣元素一般來源於數據庫(暫且如此假設,比如說做商品推薦,用戶數據,商品數據都是存在數據庫中的),那么整理成以上格式的文檔作為Map輸入文件,我們需要查詢數據庫的次數為:

m*r*n + r*n*m

由於m,r,n都是極其龐大的,這個查詢次數是我們萬萬不能忍受的。理想的數據庫查詢次數是:

m*r + r*n

即矩陣元素只取一次。

還有一種方法是矩陣元素只取一次,每個元素的副本生成交給Map-Reduce去做,但是這樣存在另一個問題:如果在Map-Reduce過程中將A矩陣和B矩陣中的元素進行副本拷貝,單個Map的運算時間有點讓人接受不了,打個比方,一個Map塊為64M,大約存了500萬條A矩陣的元素,同時B矩陣的n為10億,那么計算這個Map的節點需生成500萬*10億條副本,這個時間是難以忍受的。

2、兩個矩陣中需相乘的元素如何對應。

由於利用數據庫查詢進行矩陣元素對對應時間復雜度太高,一般不太可行。所以可以考慮利用Map-Reduce對相應元素進行對應。不過Map只對輸入記錄進行一次處理,處理完畢便結束,不存在內存的概念,所以對兩個矩陣中元素進行對應是一個難點。

3、文件大小規模。

對於超大規模矩陣,由於A中m和B中n太大,除去稀疏元素(值為0)不納入計算,需拷貝的元素依舊很多,拷貝完的文件大小是極其龐大的。筆者做了個實驗,將A(1000*1000) B(1000*1000)兩個稠密矩陣中的元素按規定(A中每個元素拷貝1000份,B中每個元素拷貝1000份)進行副本拷貝,拷貝完的記錄數為2*109條,文件大小達到24G。那么對於億萬規模的矩陣,文件大小將成指數級增長。

  本文方法

“行列相乘運算”對於稀疏矩陣可以,但是對於大型的稠密矩陣顯得有點力不從心;而對於“分塊矩陣運算”很多學者做了很多研究,但是筆者不太喜歡該算法,第一是邏輯控制麻煩,第二是對塊的大小優化來優化去,沒有解決本質上的問題。筆者我喜歡簡單的東西,所以更傾向於“基於最小粒度相乘的算法”。不過,就像我們之前所說的,“基於最小粒度相乘的運算”存在三個問題,接下來,筆者將針對其中的兩個問題闡述筆者自己的想法。

  • 新穎的矩陣相乘元素映射方法
    • 簡介

矩陣A*B=C中,Ci,j是A中第i行與B中第j列相乘的結果,如公式(1)所示。通俗點可以寫成如下格式:

傳統的方法在Map輸入段通常將輸入記錄組織成如下格式:

 

然后在Map端進行各條記錄的相乘運算,最后在Reduce階段進行匯總,得出最終矩陣相乘的結果。不過正像“基於最小粒度相乘的算法”中所說的,由於key i-j 不具備明顯的區分度,且Map過程中,內存不保留矩陣元素,將數據組織成以上格式是極其困難的。如果在Map輸入前,將數據組織成以上格式,查詢數據庫的時間復雜度也是難以接受的。

通過思考我們不難發現,最終結果Ci,j是由r個值相加而成的,第k個組成成分為:Ai,k-Bk,j,為了使key更有區分度,我們將key修改為:

 

這樣的key所代表的兩個值相乘,得到了Ci,j中第k個組成元素。所以對於A矩陣和B矩陣在Map階段完成數據副本拷貝完后,所有的Map數據記錄中,i-j-k的key有且至多只有兩個(由於稀疏元素不納入計算與拷貝,所以若為一個,則說明與之相乘的另一個元素為0,若一個也沒有,則說明Ai,k與Bk,j都為0,沒有納入計算與拷貝)。

由於A中每個元素理論上都需要被計算n遍,所以可以將A中元素按如下規則進行n遍拷貝,對於Ai,k,拷貝方式如下:

     

對於B中每個元素,理論上每個元素都需要被計算m遍,所以可以將B中元素按如下規則進行m遍拷貝,對於Bk,j,拷貝方式如下:

     

由於每個元素的副本拷貝都是獨立的,所以可以由不同的Map進行,大大加快了拷貝速度。

  • 實驗結果

筆者用以上方法做了實驗,A(m,r)*B(r,n)=C,其中m=r=n=1000,所以兩個矩陣中共有2*106個元素,A與B都為稠密矩陣,以“A-i-k value”和“B-k-j value”的形式存儲原始的A矩陣和B矩陣的元素,文件大小為24M。由於文件太小,所以只交給一個Map進行副本的拷貝工作,每個元素都被拷貝一千遍,拷貝完總記錄數為2*109條。消耗時間如下:


圖4

由圖4可以看出,一個Map執行的時間非常長,這是因為Map中的每條記錄都需要拷貝1000遍。如果在現實應用中,兩個矩陣超大,那么許多Map的塊大小都將被填滿,一個塊大概放500萬條記錄,同時由於每條記錄都被拷貝m或n遍(m,n很可能就是數以億計),那么一個Map的執行時間就是無底洞了。

為了減少每個Map的執行時間,筆者苦苦冥想,終於想出一種方法,將在接下來的小節進行介紹。

  • 創新的細胞分裂拷貝算法

上一小節中有講到Map的執行時間過長,有同事建議我說將Map的塊變小點,這樣里面的記錄數也少點,不同的塊由不同的節點執行。但是筆者認為這種想法不合理,一個塊是變小了,里面的記錄也小了,但是若每條記錄需拷貝的數量是龐大的,那根本於事無補。而且對於不同大小的矩陣相乘,矩陣元素需拷貝的數量也都是不一樣的,因此塊的大小很難控制。再者,對於Hadoop運算,正常情況下都是加大Map塊的大小,這樣有利於計算的集中。

而在本方法中,Map拷貝過程之所以時間太長,筆者認為是由於每條記錄拷貝的數量太多造成的,如果一條記錄的拷貝能分段在不同的節點完成就好了,出於這樣的想法,筆者設計了一種利用Map迭代進行拷貝的方法,由於迭代過程中Map數量的擴張有點像細胞分裂,筆者稱之為“細胞分裂拷貝算法”。

  • 簡介

由於每個矩陣元素需要拷貝多少遍是確定的,因此我們可以設計一種分段拷貝方法來讓不同的節點進行拷貝工作。這里有兩個變量需要介紹,一是num_split,代表一條記錄在一次迭代過程中最多被分的段數,另一個是num_copy,代表每個最終分段最多需拷貝的記錄數。在迭代過程中,如果某條記錄的某個分段范圍大於num_copy,則繼續進行分段,否則就進行拷貝工作。現舉例說明迭代過程。

對於A中元素Ai,k,其需要被拷貝1000遍,為了將其拷貝成公式(3)所示,我們利用細胞分裂拷貝算法將拷貝工作分配到不同的計算節點進行,該例中num_split和num_copy的數值都為10,那么迭代過程如下:


圖5

而對於B中元素,我們同理利用迭代拷貝的方法將其拷貝成公式(4)所示格式,即主要的范圍辨別集中在i上。

由圖5可以可以看到,每一次迭代,數據記錄都是成num_split的倍數增長,這樣,隨着記錄集文件大小的增長,文件被分成越來越多的Map,自然也就被分配到越來越多的計算節點進行執行。查看圖5中的第三次迭代工作,由於記錄范圍符合記錄生成條件,即記錄范圍<=num_copy,第三次迭代過程中,每個Map上的每條記錄只被copy了num_copy遍,相較於之前每條記錄被copy1000遍,時間大大減少,這種方法對於規模大的矩陣尤其適用。

此外值得一提的是,由於現實中矩陣A和矩陣B的規模往往不一樣,在實現“細胞分裂拷貝算法”時,需要設置兩個標志變量來判斷不同矩陣的記錄分段迭代過程是否結束,若兩個矩陣的分段迭代過程都結束了,則進入最后一次迭代過程:記錄拷貝的生成。

  • 實驗結果

筆者對,A(m,r)*B(r,n)=C,其中m=r=n=1000做實驗,共經歷三次迭代完成矩陣元素的拷貝工作,如圖6所示,第一次迭代,輸入只有24M,所以只有一個Map,輸出了3個Map,第二次迭代,由於輸入的3個Map,輸出了30個Map,符合num_split的擴張倍數,第三次迭代的工作是執行拷貝工作。


圖6

同時,我們可以看到,在最后生成拷貝的過程中,每個Map的執行時間比較穩定,如圖7所示,這樣,當我們的集群夠大時,這30個Map在一輪過程中便可以執行完畢。


圖7

最后,當矩陣元素拷貝工作與對應工作完成后,接下來就比較簡單了,再經歷兩輪Map-Reduce過程,就可以得到運算結果了。

  總結

本文方法針對“基於最小粒度相乘的算法”中所固有的缺點及難點,利用巧妙的設計,有效的利用Map-Reduce工具進行相乘元素的對應,同時為了減少單個元素在一個節點上拷貝太多記錄所造成的時間損耗,設計了“細胞分裂拷貝算法”,有效的將同一條記錄的拷貝工作分發到不同的節點進行,大大縮短了一個節點的執行時間,同時充分利用和發揮了集群運算的優勢。

但是本文由於算法的固有特性,並沒有解決“基於最小粒度相乘的算法”中最后一個缺點:文件占用空間太大。理論上說,這個缺點對於HDFS系統是不算一個缺點的,本身HDFS系統就有足夠大的空間容納足夠的數據。但是,通過實驗發現,這個文件實在是龐大的,對於(1000,1000)與(1000,1000)兩個稠密矩陣的相乘,所有元素的拷貝工作完成后,記錄數目達到2*109條,占用空間大小為二三十G,如圖8所示。那么對於更大規模的矩陣運算,文件空間要占用多大?答案是:難以估量。           

圖8

  后記

大部分算法都有其優勢與局限性,針對本文方法本質所固有的文件存儲空間占用大這個問題,筆者一直是耿耿於懷,至少這樣的算法是不完美的,雖然它解決了一些問題。連續幾天筆者是冥思又苦想,連做夢時腦海里都是兩個矩陣元素在打架了!黃天不負有心人,靈光一現,一種新的方法在筆者腦海里浮現!盡請期待下期《利用Hadoop實現超大矩陣相乘之我見(二)》,在下期中,筆者會分析本文方法本質上造成文件占用空間大的原因,同時介紹筆者新想到的自認為還比較完美的方法。新方法非常適用於大規模稠密矩陣與稀疏矩陣的相乘計算,尤其是對於稀疏矩陣,基本上沒有無效計算,也不會造成多余空間的浪費。

 

某研究開發中心雲計算組     小周    

 

抽空整理下之前寫的利用Hadoop實現超大矩陣相乘之我見(二),供大家參考,歡迎吐槽,更歡迎留下您更好的思想。

2014-03-14

 推薦一個自己業余時間開發的網盤搜索引擎,360盤搜www.360panso.com

  


免責聲明!

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



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