MapReduce 矩陣乘法

一、對樸素簡單直接方法

把m*n 和n*l的矩陣A和B相乘,這估計是最容易想到的方法了:

把A(m*n)的元素,每個發送l次,把B(n*l)的元素每個發送m次。將發送到一起的數據相乘求和,得到最後的結果。


優點:在知道座標的情況下,這個過程就一輪mapreduce。
缺點:每個值要被髮送多次。m*n 和n*l的矩陣,發送的元素有m*l*2次,比如100萬的方正相乘,那麼中間文件有100*100百萬*百萬的記錄,這麼多元素在網絡上傳輸,結果你懂的。另外,這個並沒有有效的避開0元素,也就是說對稀疏矩陣來說很浪費。而且必須要知道矩陣的下標和行列數。因爲在map裏需要用到。實際情況是,現實中的矩陣,並不一定都是用遞增的整數來表示下標的。有可能是人名,帳號等。

上面的方法乍一看還ok,用小數據測試一下也是正確的,不過這個方法問題在於,reduce裏收到的數據也許非常非常多,多到內存放不下。然後節點就heapSpace overFlow了。話說回來如果一定要在這個方法上改進,可以把map reduce分成兩步。第一步還是和上面一樣,但是key中再加一個字段k,表示,這是結果矩陣中座標爲(i,j)的元素的第k個用來求和的元素。k其實是A的列號和B的行號。這樣上面的過程就變成 

A: key(1,1,1) value(a,1)  key(1,1,2) value(a,2)  key(1,1,3) value(a,3)
       key(1,2,1) value(a,1)  key(1,2,2) value(a,2) ..........
B:   key(1,1,1) value(b,1)    key(1,1,2) value(b,6) key(1,1,3) value(b,0)
      key(2,1,1) value(b,1)    key(2,1,2) value(b,6) ...........
然後在reduce中對元素求乘積 生成:
      key(1,1)  value= (a,1)*(b,1)   key(1,1)  value= (a,2)*(b,6)    key(1,1)  value= (a,3)*(b,0). 其實連元素來自A還是B都可以省了。
再來第二輪mapreduce,對上一步mapreduce的結果再group by,求和。這樣改進之後,沒有reduce內存溢出的問題。看起來像是多大的矩陣都可以計算,其實由於沒能解決一個A或B的元素要發送很多次的問題,在計算超級大的矩陣的時候,這個方法還是完全不能用。

二、 分解成行和列來控制

觀察上面的最細的矩陣乘法,如果矩陣是比較稀疏的,其實有很多東西就白白髮送了,發出去在reduce裏其實沒有用到。也是這個原因導致中間文件急劇膨脹,如果矩陣超級大,即使在mapreduce裏,也要跑相當長時間。再來回憶一下矩陣乘法,能發現一些規律。
假設現在有兩個矩陣,分別是同現矩陣和用戶評分矩陣,現在要把兩個矩陣相乘來獲得推薦分數。



通過觀察,把A矩陣的一列和B矩陣的一行發送到同一個reduce中,然後在reduce中,對來自A的元算和來自B的元素做笛卡爾乘積,對笛卡爾乘積的結果再做一次mapReduce,對相應的座標求和就可以得到結果矩陣的一個個元素了。
優點:相對與最細的矩陣乘法而言,大大壓縮了中間文件的大小,對稀疏矩陣而言,避免了找不到對應元素而造成的浪費。在這個方法裏,爲0的元素不發送就行了。另外一個明顯的優點就是,我用不着一定用數字來表示矩陣的座標,是字符串也行。只要A矩陣的列座標和B矩陣的行座標對的上就行。
缺點:變成了兩輪mapreduce才搞得定。另一個明顯缺點是,在一個reduce的節點內存裏面,未必放的下A的一行+B的一列。因爲僅憑key不能區分數據是來自A矩陣還是B矩陣,最起碼要往內存裏讀一次才知道怎麼做笛卡爾乘積。當然寫成一個文件放到reduce本地,然後再來算也行,不過這樣有點低效了,不如直接在內存裏來的快。這樣其實還是限制了這個方法的範圍,我覺得1百萬乘以1百萬的稀疏矩陣用這個方法來做還是撐的住的,當然得看矩陣稀疏程度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章