1. 程式人生 > >mahout之推薦系統原始碼筆記(4) ---總結與優化

mahout之推薦系統原始碼筆記(4) ---總結與優化

mahout之推薦系統原始碼筆記(4) —總結與優化

花了幾天的時間閱讀分析了mahout推薦系統中基於java單機和基於hadoop的分散式mapreduce原始碼。根據其推薦系統hadoop程式的job劃分寫了筆記1、2、3。在這裡,基於筆記1,2,3做一個總結。
我們先從相似度開始。

什麼是相似度,就是我們在構建推薦系統時,基於user或者基於item都需要計算出相應的候選item或者是user。那麼在mahout的hadoop程式中,他運用的是基於item的推薦系統,同樣的,也需要計算相似度。
計算相似度的公式我在之前的筆記中列舉過,有歐幾里得,皮爾森等等。
sim
可以看到這些計算公式都需要向量的乘積和平方。
什麼是一個向量呢,就是說我們比較item兩兩之間的相似度的時候,則使用者就是維度,這樣兩個向量就由< item , user>構成,而其中的值就是pref偏好。
然後我們通過將item或者user轉化為向量,運用公式計算相似度,就得到了item或者user兩兩之間的相似度。

那麼mahout需要計算相似度就要求向量之間的平方和、內積。他是怎麼操作的呢?
我們回到mahout的mapreduce轉化。
在準備階段,我們分別通過輸入資料得到了以下的結構:

輸入資料:
String: userid item pref
mapreduce:
Long: userid , Vector: <itemid , pref>
Long: itemid , Vector: <userid , pref>

得到這兩個結構以後,這兩個結構我們開始分開使用。首先我們需要用Long: userid , Vector: < itemid , pref>來計算相似度,的到物品兩兩之間的相似度矩陣,並且根據使用者輸入的最大行/列隊矩陣向量進行規範剪枝,來縮小矩陣的體積,刪去相似度低不會用到推薦系統的向量。具體的過程就不一一闡述,輸入輸出如下:

輸入資料:
Long: userid , Vector: <itemid , pref>
輸出資料
Long: itemA , Vector: <itemB , sim>  (剪枝後的結果)

接下來我們使用Long: itemid , Vector: < userid , pref>構建基於item的資訊集。什麼是資訊集合呢?因為推薦系統是半離線處理的,所以我們可以計算好所有使用者的推薦,然後根據我們的需求給各個使用者推薦item,所以這就需要我們構建一箇中間資料集合來儲存每個item的資訊,這個資訊中包含了item的相似矩陣、使用item的使用者以及這些使用者使用item以後的pref打分。我們有了這個中間資料集,稍作變換就可以求出最終的分數預測的資料集。同樣,輸入輸出如下:

輸入資料:
Long: itemA , Vector: <itemB , sim>  (每個item的相似矩陣)
Long: itemid , Vector: < userid , pref> (使用者-商品矩陣)
輸出資料
Long: itemA , ( Vector<itemB , sim> , List<userID> , List<pref> )

得到如上中間資料集合以後我們就可以求最後的預測分數了,我們根據預測分數排行以後就可以給每個使用者推薦了。過程如下:

輸入資料:
Long: itemA , ( Vector<itemB , sim> , List<userID> , List<pref> )
中間結果:
Long: userid , ( pref , Vector<itemB , sim> )
輸出資料:
Long: userid , Vector( item , predictPref )

最後將結果輸出。

總結了所有過程,可以看到拋去細節mahout做的並不是很難,而且其中有一些來回變換應用在mapreduce上比較佔用io讀寫,其實有很多步驟可以省略,當然具體到工作環境中,有時可以一將多個job簡化,比方說mahout的第一個job的第一個mapreduce,它將item的ID做了個內部索引的對映,當我們的itemID並不是特別特別巨大的時候根本沒有必要做這一步工作,還有後面構建中間資料集,其實可以略過,將Long: userid , Vector: < itemid , pref>中的itemID根據Long: itemA , Vector: < itemB , sim> (每個item的相似矩陣)進行合併,得到的結果就是我們最後一步的中間結果。
寫到這裡,有一些對於推薦系統應用在分散式環境的想法。
程式碼讀了三到四天,其實說到頭,執行的步驟不是那麼複雜,數學支援度也不高,推薦系統的預測結果也並不是那麼好,通過這些弊端,有以下兩個想法:
第一,程式設計環境。
不是吐槽hadoop,但是hadoop對於推薦系統這種偏重迭代、資料轉化的運算確實有點捉襟見肘,mapreduce執行的操作不多,程式碼量卻是鉅額,真正核心的東西沒有表達出來,預定義的介面環境變數等等卻定義了一堆,我想如果能夠簡化程式設計環境,讓我們的操作直接貼近核心資料,對真正的資料多做處理可能是一個比較好方向,當然我並不是說mahout所有的程式碼都是冗餘的,自定義的資料型別確實可以貼近資料做一些優化,可是真正用到的核心點並不多,資料量大但結構簡單的時候其實意義並不是很大,說到這裡就比較推薦RDD程式設計,抽象程度比較高,可以抽象得直接操作資料,寫起來不用考慮太多其他方面,這對資料處理方面都是一大進步。
第二,資料探勘方法。
推薦系統出來將近二十年了,可是mahout對海量資料做的推薦系統(基於hadoop)運用的還是最基本的協同過濾以及als方法,其實相較於協同過濾,我更推薦als演算法一些,因為als演算法可以抽象出使用者、商品的特徵,運算量越大,特徵表現越精準細緻,根據精確的特徵做推薦,可以精確得把握到使用者的喜好興趣。除此之外我們還可以根據使用者的行為運用很多不同的方法來構建推薦系統。使用者群體聚類、關聯規則分析、使用者資訊抽象(神經網路)等等,多個分類器的整合可以對推薦系統取得更好更優的結果。
同時,相應的另一方面來看,不同的應用場景我們需要使用不同的推薦分析方式,並不是所有的推薦都適用於一種推薦分析方法,所以具體的行業方面我們可以具體來個性化設計。
而且,隨著資料分析行業的發展, 這種user-item-pref的方式已經遠遠無法達到我們的需求,我們有更多的使用者資訊,諸如個人資料、ip、操作資訊、瀏覽歷史、自定義標籤等等不同的資訊,並不是所有的資訊都適用這種CF模式,所以我們需要對資料探勘的方法進行更新、優化。

下面補充一些關於mahout協同過濾的優化:
首先準備階段我們只需要得到:userID , Vector< itemID , pref > 即可。itemID的內部索引標準化這一步可以再外部資料輸入之前進行標準化,而不用寫在mapreduce中。那麼prepare的步驟如下:

輸入:userID itemID pref(text)
輸出:userID , Vector<itemID , pref>和min_int , Vector<userID , num>

得到userID-itemID矩陣以及每個使用者運算元量計數。
接下來我們需要計算相似度矩陣,得到itemA , Vector< itemB , sim >這種形式的相似度矩陣,具體步驟如下:
首先計算norms得到平方和:

map:
userID , Vector<itemID , pref>
reduce:
itemID , norms (pref平方和)

接下來計算agg(item兩兩pref乘積),同原mahout操作:

map:
userID , Vector<itemID , pref>
-> sortby itemID
-> 雙重迴圈對Vector<itemID , Pref>處理得到[ItemA , <itemB , aggregete>](itemA< itemB)
reduce:
itemA , Vector<itemB , agg>
->對所有itemA和itemB相同的條目進行加和
->通過儲存下來的norms,和加和後的[itemA , Iterable<itemB , aggregete>]計算相似度
->[itemA , Vector<itemB , sim>](itemA< itemB) 輸出

這樣我們就得到了item之間的相似度。
接下來我們進行推薦評估:

map:
讀取[itemA Vector<itemB , sim>]並根據輸入:
[userID , Vector<itemID , pref , Vector<null> >]
對[itemA , Vector<itemB , sim>]中的itemA和[userID , Vector<itemID , pref>]的itemID進行map合併轉化

reduce:
合併bykey得到[userID , Vector<itemA , pref , Vector<itemB , sim> >]
計算預測分數,得到[userID , Vector<itemB , pref>]輸出