用Spark學習矩陣分解推薦演算法

步入量化學習艾莉絲發表於2018-09-30

轉/寬客線上

在矩陣分解在協同過濾推薦演算法中的應用中,我們對矩陣分解在推薦演算法中的應用原理做了總結,這裡我們就從實踐的角度來用Spark學習矩陣分解推薦演算法。

1.Spark推薦演算法概述

在Spark MLlib中,推薦演算法這塊只實現了基於矩陣分解的協同過濾推薦演算法。而基於的演算法是FunkSVD演算法,即將m個使用者和n個物品對應的評分矩陣M分解為兩個低維的矩陣:

用Spark學習矩陣分解推薦演算法

其中k為分解成低維的維數,一般遠比m和n小。如果大家對FunkSVD演算法不熟悉,可以複習對應的原理篇。

2.Spark推薦演算法類庫介紹

在Spark MLlib中,實現的FunkSVD演算法支援Python,Java,Scala和R的介面。由於前面的實踐篇我們都是基於Python,本文的後面的介紹和使用也會使用MLlib的Python介面。Spark MLlib推薦演算法python對應的介面都在pyspark.mllib.recommendation包中,這個包有三個類,Rating,MatrixFactorizationModel和ALS。雖然裡面有三個類,但是演算法只是FunkSVD演算法。

下面介紹這三個類的用途。Rating類比較簡單,僅僅只是為了封裝使用者,物品與評分這3個值。也就是說,Rating類裡面只有使用者,物品與評分三元組,並沒有什麼函式介面。ALS負責訓練我們的FunkSVD模型。之所以這兒用交替最小二乘法ALS表示,是因為Spark在FunkSVD的矩陣分解的目標函式優化時,使用的是ALS。

ALS函式有兩個函式,一個是train,這個函式直接使用我們的評分矩陣來訓練資料,而另一個函式trainImplicit則稍微複雜一點,它使用隱式反饋資料來訓練模型,和train函式相比,它多了一個指定隱式反饋信心閾值的引數,比如我們可以將評分矩陣轉化為反饋資料矩陣,將對應的評分值根據一定的反饋原則轉化為信心權重值。由於隱式反饋原則一般要根據具體的問題和資料來定,本文後面只討論普通的評分矩陣分解。

MatrixFactorizationModel類是我們用ALS類訓練出來的模型,這個模型可以幫助我們做預測。常用的預測有某一使用者和某一物品對應的評分,某使用者最喜歡的N個物品,某物品可能會被最喜歡的N個使用者,所有使用者各自最喜歡的N物品,以及所有物品被最喜歡的N個使用者。對於這些類的用法我們再後面會有例子講解。

3.Spark推薦演算法重要類引數

這裡我們再對ALS訓練模型時的重要引數做一個總結。 

1)ratings : 評分矩陣對應的RDD。需要我們輸入。如果是隱式反饋,則是評分矩陣對應的隱式反饋矩陣。

2)rank : 矩陣分解時對應的低維的維數。即PTm×kQk×nPm×kTQk×n中的維度k。這個值會影響矩陣分解的效能,越大則演算法執行的時間和佔用的記憶體可能會越多。通常需要進行調參,一般可以取10-200之間的數。

3)iterations :在矩陣分解用交替最小二乘法求解時,進行迭代的最大次數。這個值取決於評分矩陣的維度,以及評分矩陣的係數程度。一般來說,不需要太***如5-20次即可。預設值是5。

4)lambda: 在 python介面中使用的是lambda_,原因是lambda是Python的保留字。這個值即為FunkSVD分解時對應的正則化係數。主要用於控制模型的擬合程度,增強模型泛化能力。取值越大,則正則化懲罰越強。大型推薦系統一般需要調參得到合適的值。

5)alpha : 這個引數僅僅在使用隱式反饋trainImplicit時有用。指定了隱式反饋信心閾值,這個值越大則越認為使用者和他沒有評分的物品之間沒有關聯。一般需要調參得到合適值。

從上面的描述可以看出,使用ALS演算法還是蠻簡單的,需要注意調參的引數主要的是矩陣分解的維數rank, 正則化超引數lambda。如果是隱式反饋,還需要調參隱式反饋信心閾值alpha 。

4.Spark推薦演算法例項

下面我們用一個具體的例子來講述Spark矩陣分解推薦演算法的使用。

這裡我們使用MovieLens 100K的資料。

將資料解壓後,我們只使用其中的u.data檔案中的評分資料。這個資料集每行有4列,分別對應使用者ID,物品ID,評分和時間戳。由於我的機器比較破,在下面的例子中,我只使用了前100條資料。因此如果你使用了所有的資料,後面的預測結果會與我的不同。

首先需要要確保你安裝好了Hadoop和Spark(版本不小於1.6),並設定好了環境變數。一般我們都是在ipython notebook(jupyternotebook)中學習,所以最好把基於notebook的Spark環境搭好。當然不搭notebook的Spark環境也沒有關係,只是每次需要在執行前設定環境變數。如果你沒有搭notebook的Spark環境,則需要先跑下面這段程式碼。當然,如果你已經搭好了,則下面這段程式碼不用跑了。

import os
import sys

#下面這些目錄都是你自己機器的Spark安裝目錄和Java安裝目錄
os.environ['SPARK_HOME'] = "C:/Tools/spark-1.6.1-bin-hadoop2.6/"

sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/bin")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/pyspark")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/lib")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/lib/pyspark.zip")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/lib/py4j-0.9-src.zip")
sys.path.append("C:/Program Files (x86)/Java/jdk1.8.0_102")

from pyspark import SparkContext
from pyspark import SparkConf

sc = SparkContext("local", "testing")
複製程式碼

在跑演算法之前,建議輸出Spark Context如下,如果可以正常列印記憶體地址,則說明Spark的執行環境搞定了。

print sc
複製程式碼

比如我的輸出是:

<pyspark.context.SparkContext object at 0x07352950> 

首先我們將u.data檔案讀入記憶體,並嘗試輸出第一行的資料來檢驗是否成功讀入,注意複製程式碼的時候,資料的目錄要用你自己的u.data的目錄。程式碼如下:

#下面目錄要用解壓後u.data所在的目錄
user_data = sc.textFile("C:/Temp/ml-100k/u.data")
user_data.first()
複製程式碼

輸出如下:

[u'196', u'242', u'3']

此時雖然我們已經得到了評分矩陣陣列對應的RDD,但是這些資料都還是字串,Spark需要的是若干Rating類對應的陣列。因此我們現在將RDD的資料型別做轉化,程式碼如下:

from pyspark.mllib.recommendation import Rating
rates_data = rates.map(lambda x: Rating(int(x[0]),int(x[1]),int(x[2])))
print rates_data.first()
複製程式碼

輸出如下:

Rating(user=196, product=242, rating=3.0)

可見我們的資料已經是基於Rating類的RDD了,現在我們終於可以把整理好的資料拿來訓練了,程式碼如下, 我們將矩陣分解的維度設定為20,最大迭代次數設定為5,而正則化係數設定為0.02。在實際應用中,我們需要通過交叉驗證來選擇合適的矩陣分解維度與正則化係數。這裡我們由於是例項,就簡化了。

from  pyspark.mllib.recommendation import ALS
from pyspark.mllib.recommendation import MatrixFactorizationModel
sc.setCheckpointDir('checkpoint/')
ALS.checkpointInterval = 2
model = ALS.train(ratings=rates_data, rank=20, iterations=5, lambda_=0.02)
複製程式碼

將模型訓練完畢後,我們終於可以來做推薦系統的預測了。首先做一個最簡單的預測,比如預測使用者38對物品20的評分。程式碼如下:

print model.predict(38,20)
複製程式碼

輸出如下:

0.311633491603

可見評分並不高。現在我們來預測了使用者38最喜歡的10個物品,程式碼如下:

print model.recommendProducts(38,10)
複製程式碼

輸出如下:

[Rating(user=38, product=95, rating=4.995227969811873), Rating(user=38, product=304, rating=2.5159673379104484), Rating(user=38, product=1014, rating=2.165428673820349), Rating(user=38, product=322, rating=1.7002266119079879), Rating(user=38, product=111, rating=1.2057528774266673), Rating(user=38, product=196, rating=1.0612630766055788), Rating(user=38, product=23, rating=1.0590775012913558), Rating(user=38, product=327, rating=1.0335651317559753), Rating(user=38, product=98, rating=0.9677333686628911), Rating(user=38, product=181, rating=0.8536682271006641)]

可以看出使用者38可能喜歡的對應評分從高到低的10個物品。接著我們來預測下物品20可能最值得推薦的10個使用者,程式碼如下:

print model.recommendUsers(20,10)
複製程式碼

輸出如下:

[Rating(user=115, product=20, rating=2.9892138653406635), Rating(user=25, product=20, rating=1.7558472892444517), Rating(user=7, product=20, rating=1.523935609195585), Rating(user=286, product=20, rating=1.3746309116764184), Rating(user=222, product=20, rating=1.313891405211581), Rating(user=135, product=20, rating=1.254412853860262), Rating(user=186, product=20, rating=1.2194811581542384), Rating(user=72, product=20, rating=1.1651855319930426), Rating(user=241, product=20, rating=1.0863391992741023), Rating(user=160, product=20, rating=1.072353288848142)]

現在我們來看看每個使用者最值得推薦的三個物品,程式碼如下:

print model.recommendProductsForUsers(3).collect()
複製程式碼

由於輸出非常長,這裡就不將輸出copy過來了。而每個物品最值得被推薦的三個使用者,程式碼如下:

print model.recommendUsersForProducts(3).collect()
複製程式碼

同樣由於輸出非常長,這裡就不將輸出copy過來了。希望上面的例子對大家使用Spark矩陣分解推薦演算法有幫助。

閱讀原文

相關文章