常見數據挖掘算法的Map-Reduce策略(1)


       大數據這個名詞是被炒得越來越火了,各種大數據技術層出不窮,做數據挖掘的也跟着火了一把,呵呵,現今機器學習算法常見的並行實現方式:MPI,Map-Reduce計算框架,GPU方面,graphlab的圖並行,Spark計算框架,本文講講一些機器學習算法的map-reduce並行策略,盡管有些算法確實不適合map-reduce計算,但是掌握一些並行思想策略總歸不是件壞事,大家如果對某個算法有更好的並行策略,也請多多指教,歡迎大家交流,OK,下面先從一個最基本的均值、方差的並行開始。
 
均值、方差的map-reduce
       一堆數字的均值、方差公式,相信都很清楚,具體怎么設計map跟reduce函數呢,可以先從計算公式出發,假設有n個數字,分別是a1,a2....an,那么  均值m=(a1+a2+...an) / n,方差 s= [(a1-m)^2+ (a2-m)^2+....+ (an-m)^2] / n
把方差公式展開來S=[(a1^2+.....an^2)+m^m*n-2*m*(a1+a2+....an) ] / n,根據這個我們可以把map端的輸入設定為(key,a1),輸出設定為(1,(n1,sum1,var1)),n1表示每個worker所計算的數字的個數,sum1是這些數字的和(例如a1+a2+a3...),var1是這些數字的平方和(例如a1^2+a2^2+...)
       reduce端接收到這些信息后緊接着把所有輸入的n1,n2....相加得到n,把sum1,sum2...相加得到sum,那么均值m=sum/n, 把var1,var2...相加得到var,那么最后的方差S=(var+m^2*n-2*m*sum)/n,reduce輸出(1,(m,S))。
算法代碼是基於mrjob的實現(https://pythonhosted.org/mrjob/,機器學習實戰第十五章)
 1 from mrjob.job import MRJob
 2 
 3  
 4 
 5 class MRmean(MRJob):
 6 
 7     def __init__(self, *args, **kwargs):
 8 
 9         super(MRmean, self).__init__(*args, **kwargs)
10 
11         self.inCount = 0
12 
13         self.inSum = 0
14 
15         self.inSqSum = 0
16 
17     
18 
19     def map(self, key, val): #needs exactly 2 arguments
20 
21         if False: yield
22 
23         inVal = float(val)
24 
25         self.inCount += 1
26 
27         self.inSum += inVal #每個元素之和
28 
29         self.inSqSum += inVal*inVal #求每個元素的平方
30 
31         
32 
33     def map_final(self):
34 
35         mn = self.inSum/self.inCount
36 
37         mnSq =self.inSqSum/self.inCount
38         yield (1, [self.inCount, mn, mnSq]) #map的輸出,不過這里的mn=sum1/mn,mnsq=var1/mn
39 
40  
41 
42     def reduce(self, key, packedValues):
43 
44         cumVal=0.0; cumSumSq=0.0; cumN=0.0
45 
46         for valArr in packedValues: #get values from streamed inputs 解析map端的輸出
47 
48             nj = float(valArr[0])
49 
50             cumN += nj
51 
52             cumVal += nj*float(valArr[1])
53 
54             cumSumSq += nj*float(valArr[2])
55 
56         mean = cumVal/cumN
57 
58         var = (cumSumSq - 2*mean*cumVal + cumN*mean*mean)/cumN
59 
60         yield (mean, var) #emit mean and var reduce的輸出
61 
62         
63 
64     def steps(self):
65 
66         return ([self.mr(mapper=self.map, mapper_final=self.map_final,\
67 
68                           reducer=self.reduce,)])
69 
70  
71 
72 if __name__ == '__main__':
73 
74     MRmean.run()

 

KNN算法的 map-reduce
       KNN算法作為機器學習里面經典的分類算法,它簡單有效,但很粗暴,是一個非參模型(非參並非指算法沒有參數,而是說它沒有假設底層數據的分布,盡管該算法的有效性要遵循流行/聚類假設),算法具體的步驟如偽代碼所示
 
for i in test_data
    從train_data中找與i樣本最相近的K個樣本(K是參數,過小引起過擬合,過大引起欠擬合)
    衡量相近的標准有多種(歐氏距離,馬氏距離等等)
    把這K個樣本的標簽出現最多的那個賦予給i
end for
 
       從上面的代碼可以看出該算法的計算量也是非常大的,但有個好處是非常適合拆分計算步驟,進行並行處理,具體設計map函數跟reduce函數如下
       map過程:每個worker節點load測試集和部分訓練集到本地(當然也可以訓練集和部分測試集,但感覺是不是很浪費磁盤?),map的輸入是<key,value>這不廢話么。。。具體的key是樣本行號,value是樣本的屬性跟標簽, map的輸出 <key,list(value)> key是測試樣本的行號,value是某個訓練樣本的標簽跟距離 ,具體的map函數偽代碼如下
 
for i in test_data
    for j in train_data
         取出j 里面的標簽L
         計算i與j的距離D
         context.write(測試樣本行號,vector(L,D))
    end for
end for
       
      reduce過程:這個相對就比較簡單,對輸入鍵值對,對同一個key的(L,D) 對D進行排序,取出前K個L標簽,計算出現最多的那個標簽,即為該key的結果。
      總結:上面是一個最基本的knn的map-reduce過程,正常情況下我們reduce的機器一般小於map的機器,如果完全把map的輸出,全扔到reduce那邊,會造成reduce過程耗時,一個優化的方向就是在map的最后階段,我們直接對每個key取前K個結果,這樣就更合理的利用了計算資源,另外高維情況下,近鄰的查找一般用局部敏感哈希算法。
 
朴素貝葉斯算法的 map-reduce   
        朴素貝葉斯算法也是一種經典的分類算法,多用於文本分類 、垃圾郵件的處理,算法簡單,計算較快,網絡上也有很好的介紹該算法的文章(首推,劉未鵬的平凡而又神奇的貝葉斯),整個算法的的框架是需要計算4部分,摘選文獻1的圖片
 
         包括先驗概率、每個屬性的值在特定類別下的條件概率,下面以一個例子詳細說明map-reduce過程,假設數據集如下所示
行號
類別
性別 風強度
溫度
01
02
03
中等
       首先把屬性都進行0/1編碼(把屬性連續化的一個好方法),效果如下所示
行號
類別(好)
類別(壞)
性別(男)
性別(女)
風強度(強)
風強度(中等)
風強度(弱)
溫度(熱)
溫度(冷)
01
1
0
1
0
1
0
0
1
0
02
0
1
0
1
0
0
1
0
1
03
0
1
1
0
0
1
0
1
0
         然后 map的輸入是<行號,樣本 >,在map中作如下操作,對於每條記錄record1=[[1,0],[1,0],[1,0,0],[1,0]], record2=[ [0,1],[0,1],[0,0,1],[0,1] ],record3=[ [0,1],[1,0],[0,1,0],[1,0] ],然后把標簽拆分,把類別作為key,這樣map的輸出端就是<類別,record1..n>,
       reduce接收到后,進行如下處理對於每個record1 轉化成矩陣形式
key=1                   key=0                    key=0
record1=1            record2=1            record3=1
                1,0                         0,1                         1,0
                1,0,0                      0,0,1                      0,1,0
                1,0                         0,1                         1,0
對於每一個類別相同的record相加得到
sum(類別=1)= 1,             sum(類別=0)=2              
                        1,0                                   1,1                         
                        1,0,0                                0,1,1                    
                        1,0                                   1,1    
 
對上面進行歸一化,得到
sum(類別=1)= 1,             sum(類別=0)=1              
                        1,0                                   1/2,1/2                         
                        1,0,0                                0,1/2,1/2                    
                        1,0                                   1/2,1/2         
 
最后輸出的就是這兩個東東了
具體分類的時候,假設一個test樣本是(女,強,熱)   
P(好 / ( 女,強,熱)  ) =P(好)*P(女/好)*P(強/好)*P(熱/好)
P(壞 / ( 女,強,熱)  ) =P(壞)*P(女/壞)*P(強/壞)*P(熱/壞)
       比較上面兩個概率的大小就好了,另外文中舉的這個例子不太好,出現了 P(女/好)=0,樣本足夠的情況下是不會的等於零的,即使真出現了0的情況,也可以用拉普拉斯平滑掉就好了。
 
       另外一種思路:map端輸出<'好',1>,<'好,男',1>,<'好,強',1>,< '好,弱',1 >,。。。。,reduce端輸出那些key的sum和。總來說跟前一種做法是差不多,都是求各種頻數
 
決策樹算法的 map-reduce   
        不用說又是一個經典的算法,該算法多簡單可解釋性強,在各行各業應用都是非常廣泛,以它為基礎的boosting,forest更是在互聯網行業和各大挖掘競賽上大顯身手(GBRT的預估,GBrank的排序,競賽中也基本是集成成百上千的model),哎,扯遠了,回到本文的話題來,由於決策樹是一個迭代性很強的算法,不太適合並行,當然如果訓練集達到單機無法承受,並行還是需要滴
        第一種思路:借鑒於上面貝葉斯並行的第二種策略,對於決策樹上的每一個節點,都啟動一次map-reduce過程,計算各個頻數,以求出熵增益,來尋找的最優分裂屬性跟值,然后依次這樣建樹。保存規則,對於測試的時候就很方便了,直接把測試數據進行分片,進入map,map的輸出就是<行號,預測的標簽>,就是結果,就不需要reduce階段。
        可以看到上面的那種思路會進行很多次的map-reduce任務,這無形中會造成很大的I/O壓力,下面第二種思路是Google開發的Planet並行決策樹集成系統,構建的是二叉樹,下圖是訓練過程框架
 
       
       該過程分4個部分:1) 主機節點負責維護InMemory樹節點列隊、FindBestSplit 樹節點列隊、模型規則存儲;2) 初始化過程負責確定樹節點分裂需要從哪些屬性選最優的,不過里面有一個trick,就是當屬性值特別是數值型的,如果量少的話,就可以每一個值作為候選的分裂值,但是如果量非常多的話,一個一個選會很影響性能,一個替代的方法就是把那些值分桶(也就是相當於離散化了,但是還是數值型的);3) InMemoryBuild 就是說如果到當前節點的時候,數據量比較小,能夠在內存里面搞就直接在內存里面搞了;4) FindBestSplit 是說 如果到當前節點的時候,數據量非常大就需要分布式搞,具體的map-reduce過程跟上面第一種策略有點相似 ;原 Planet系統還支持並行的bagging跟boosting,但是sample的時候只能支持無放回的采樣(不知道最近有沒有更新),功能也是非常強大
       總結:Mahout里面有隨機森林的的分布式建樹,它的策略是通過每個mapper來建一顆樹,partition的數據也是原有的1/10(默認條件),這個值越大,單棵樹的精度也越高,但是實際操作過程中,也並非樣本越多,精度越高,況且forest的精度不僅跟單個分類器的精度有關,也跟分類器之間的多樣性有關。其他的如梯度的boosting之前2009年的時候,雅虎算法研究院發過一篇該算法並行的文章。
 
K均值的 map-reduce   
       常用的聚類 算法,挖掘用戶的群組相似性,常用的用戶分層模型、細分模型都會用到相應的聚類的算法,該算法單機偽代碼如下
 
輸入:聚類數K,K個隨機的聚類中心,訓練集
輸出:K個聚類中心,每個樣本所屬的組別
for i in I(迭代停止條件,通常用戶可以自己設定或者讓算法自己收斂即每個樣本到各自中心的距離之和最短)
    for j in K:
        計算每個樣本到每個聚類中心的距離
    距離最短的,就把該樣本賦到該類別上
    根據新生成的K個組樣本,更新K個聚類中心
end for
 
       從 算法的執行過程可以看出,跟KNN算法一樣,非常適合並行處理,對於算法的每一次迭代可以分一下三個步驟
map端:輸入<key,value>key是樣本的行號,value就是樣本的信息了,輸出<key,value> key是該樣本所屬的中心點index, value就是樣本的信息了
具體操作:1) 從輸入的value里面解析特征屬性值 
                  2) for i in K
                           計算到i個中心的距離
                      key=最近的那個中心的index,value 就是樣本信息
Combine端:對Map的輸出屬於同一聚類的點做一個簡單的累加,輸入是map端的輸出,輸出是<kay,value>
是該樣本所屬的中心點index, value就是該類下的累加和跟總個數
Reduce端:任務是把Combine端的輸出進行歸總,更新聚類中心,輸出就是<聚類中心代號,聚類中心>
 
       總結: K均值的並行處理也有成熟的開源工具(Mahout,spark-mllib,GPUlib),該算法的結果不穩定,跟初始化中心有關系,對於這個問題的也有一些改進的算法如K中心算法,另外自動聚類(算法自動完成應該聚成幾類)也是學術界研究的熱點
 
先寫到這里了,一篇文章太長了,自己看的累,呵呵
下篇預告
1)logisitc regression(有損跟無損的並行)
2)SVM/NN的map-reduce
3)關聯挖掘(apriori,FP-growth)的並行策略
4)推薦系統的一些算法並行
 
參考資料:1)Map-Reduce for Machine Learning on Multicore NG的一篇nips文章;2 )Mining   of   Massive  Datasets;3) http://www.cnblogs.com/vivounicorn/archive/2011/10/08/2201986.html


免責聲明!

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



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