Spark Machine Learning 04 構建基於Spark的推薦引擎 (待完善)

weixin_33806914發表於2016-04-29

Chap 04 基於Spark的推薦引擎

Amazon、Netflix、YouTube、Twitter、LinkedIn、Facebook 推薦是這些網站背後的核心功能之一

推薦引擎背後的想法是預測人們可能喜好的物品並通過探尋物品之間的聯絡來輔助這個過程。
與搜尋引擎不同,推薦引擎試圖向人們呈現的相關內容並不一定就是人們所搜尋的.

推薦引擎試圖對使用者與某類物品之間的聯絡建模。

實際上,推薦引擎的應用並不限於電影、書籍、產品、社交網路。

推薦引擎很適合如下兩類常見場景(兩者可兼有)。

(1) 可選項眾多:可選的物品越多,使用者就越難找到想要的物品。通過向使用者推薦相關物品,其中某些可能使用者事先不知道,將能幫助他們發現新物品。

(2) 偏個人喜好:當人們主要根據個人喜好來選擇物品時,推薦引擎能利用集體智慧,根據其他有類似喜好使用者的資訊來幫助他們發現所需物品。

本文涉及內容:

  • 介紹推薦引擎的型別;

  • 用使用者偏好資料來建立一個推薦模型;

  • 使用上述模型來為使用者進行推薦和求指定物品的類似物品(即相關物品);

  • 應用標準的評估指標來評估該模型的預測能力。

4.1 推薦模型的分類

最為流行的兩種方法是基於 內容的過濾協同過濾

4.1.1 基於內容的過濾

內容的過濾利用物品的內容或是屬性資訊以及某些相似度定義,來求出與該物品類似的物品。

這些屬性值通常是文字內容(比如標題、名稱、標籤及該物品的其他後設資料)。對多媒體來說,可能還涉及從音訊或視訊中提取的其他屬性。

對使用者的推薦可以根據使用者的屬性或是描述得出,之後再通過相同的相似度定義來與物品屬性做匹配。

4.1.2 協同過濾

協同過濾是一種利用大量已有的使用者偏好來估計使用者對其未接觸過的物品的喜好程度。其內在思想是相似度的定義

在基於使用者的方法的中,如果兩個使用者表現出相似的偏好,認為他們的興趣類似。要對他們中的一個使用者推薦一個未知物品,便可選取若干與其類似的使用者並根據他們的喜好計算出對各個物品的綜合得分。

同樣也可以藉助基於物品的方法來做推薦。這種方法通常根據現有使用者對物品的偏好或是評級情況,來計算物品之間的某種相似度。已有物品相似的物品被用來生成一個綜合得分,而該得分用於評估未知物品的相似度。

基於 使用者物品 的方法的得分取決於若干使用者或是物品之間依據相似度所構成的集合(即鄰居),故它們也常被稱為最近鄰模型。

對“使用者-物品”偏好建模

4.1.3 矩陣分解

Spark推薦模型庫 包含基於矩陣分解(matrix factorization)的實現,該模型在協同過濾中的表現十分出色。

1. 顯式矩陣分解

當要處理的那些資料是由使用者所提供的自身的偏好資料,這些資料被稱作顯式偏好資料。這類資料包括如物品評級、贊、喜歡等使用者對物品的評價。

轉換為以使用者為行、物品為列的二維矩陣。矩陣的每一個資料表示某個使用者對特定物品的偏好。大部分情況下單個使用者只會和少部分物品接觸,所以該矩陣只有少部分資料非零(即該矩陣很稀疏)。

Tom, Star Wars, 5
Jane, Titanic, 4
Bill, Batman, 3
Jane, Star Wars, 2
Bill, Titanic, 3

它們可轉為如下評級矩陣:

screenshow?key=150815fdfb10255a9fd8

對這個矩陣建模,可以採用矩陣分解(或矩陣補全)的方式。具體就是找出兩個低維度的矩陣,使得它們的乘積是原始的矩陣。因此這也是一種降維技術。假設我們的使用者和物品數目分別是 U 和 I,那對應的“使用者-物品”矩陣的維度為 U × I

圖4-2 一個稀疏的評級矩陣

要找到和“使用者-物品”矩陣近似的k維(低階)矩陣,最終要求出如下兩個矩陣:一個用於表示使用者的 U × k 維矩陣,以及一個表徵物品的 I × k 維矩陣。這兩個矩陣也稱作因子矩陣。它們的乘積便是原始評級矩陣的一個近似。值得注意的是,原始評級矩陣通常很稀疏,但因子矩陣卻是稠密的

圖4-3 使用者因子矩陣和物品因子矩陣

這類模型試圖發現對應“使用者-物品”矩陣內在行為結構的隱含特徵(這裡表示為因子矩陣),所以也把它們稱為隱特徵模型.隱含特徵或因子不能直接解釋,但它可能表示了某些含義,比如對電影的某個導演、種類、風格或某些演員的偏好。

由於是對“使用者-物品”矩陣直接建模,用這些模型進行預測也相對直接:要計算給定使用者對某個物品的預計評級,就從使用者因子矩陣和物品因子矩陣分別選取相應的行(使用者因子向量)與列(物品因子向量),然後計算兩者的點積即可。

圖4-4 用使用者因子矩陣和物品因子矩陣計算推薦

而對於物品之間相似度的計算,可以用最近鄰模型中用到的相似度衡量方法。不同的是,這裡可以直接利用物品因子向量,將相似度計算轉換為對兩物品因子向量之間相似度的計算

圖4-5 用物品因子矩陣計算相似度

優點

  1. 因子分解類模型建立,對推薦的求解 容易。

  2. 效果不錯

缺點

  1. 當使用者和物品的數量很多時,其對應的物品或是使用者的因子向量可能達到數以百萬計。在儲存和計算能力有挑戰。

2. 隱式矩陣分解

隱含在使用者與物品的互動之中。二後設資料(比如使用者是否觀看了某個電影或是否購買了某個商品)和計數資料(比如使用者觀看某電影的次數)便是這類資料。

處理隱式資料的方法相當多。MLlib實現了一個特定方法,它將輸入的評級資料視為兩個矩陣:一個二元偏好矩陣 P 以及一個信心權重矩陣C。

圖4-6 用物品因子矩陣計算相似度

隱式模型仍然會建立一個使用者因子矩陣和一個物品因子矩陣。但是,模型所求解的是偏好矩陣而非評級矩陣的近似。

3. 最小二乘法

最小二乘法(Alternating Least Squares,ALS)是一種求解矩陣分解問題的最優化方法。它功能強大、效果理想而且被證明相對容易並行化。

ALS的實現原理是迭代式求解一系列最小二乘迴歸問題。在每一次迭代時,固定使用者因子矩陣或是物品因子矩陣中的一個,然後用固定的這個矩陣以及評級資料來更新另一個矩陣。之後,被更新的矩陣被固定住,再更新另外一個矩陣。如此迭代,直到模型收斂(或是迭代了預設好的次數)。

Spark文件的協同過濾部分引用了ALS演算法的核心論文。對顯式資料和隱式資料的處理的元件背後使用的都是該演算法。具體參見:http://spark.apache.org/docs/latest/mllib-collaborative-filtering.html

4.2 提取有效特徵


>./bin/spark-shell –driver-memory 4g
val rawData = sc.textFile("/Users/hp/ghome/ml/ml-100k/u.data")
rawData.first()
val rawRatings = rawData.map(_.split("\t").take(3))
import org.apache.spark.mllib.recommendation.ALS

ALS.
asInstanceOf    isInstanceOf   main   toString        train           trainImplicit
ALS.train

上述輸出表明ALS模型需要一個由Rating記錄構成的RDD,而Rating類則是對使用者ID、影片ID(這裡是通稱product)和實際星級這些引數的封裝。我們可以呼叫map方法將原來的各ID和星級的陣列轉換為對應的Rating物件,從而建立所需的評級資料集。

scala> import org.apache.spark.mllib.recommendation.Rating
import org.apache.spark.mllib.recommendation.Rating

scala> val ratings = rawRatings.map { case Array(user, movie, rating) =>
     | Rating(user.toInt, movie.toInt, rating.toDouble) }
ratings: org.apache.spark.rdd.RDD[org.apache.spark.mllib.recommendation.Rating] = MapPartitionsRDD[3] at map at <console>:27

scala> ratings.first()
res3: org.apache.spark.mllib.recommendation.Rating = Rating(196,242,3.0)

scala> ratings.take(10)
res4: Array[org.apache.spark.mllib.recommendation.Rating] = Array(Rating(196,242,3.0), Rating(186,302,3.0), Rating(22,377,1.0), Rating(244,51,2.0), Rating(166,346,1.0), Rating(298,474,4.0), Rating(115,265,2.0), Rating(253,465,5.0), Rating(305,451,3.0), Rating(6,86,3.0))

4.3 訓練推薦模型

從原始資料提取出這些簡單特徵後,便可訓練模型。MLlib已實現模型訓練的細節,這不需要我們擔心。我們只需提供上述指定型別的新RDD以及其他所需引數來作為訓練的輸入即可。

4.3.1 使用MovieLens 100k資料集訓練模型

現在開始訓練模型了,所需的其他引數有以下幾個

  • rank:對應ALS模型中的因子個數,也就是在低階近似矩陣中的隱含特徵個數。因子個數一般越多越好。但它也會直接影響模型訓練和儲存時所需的記憶體開銷,尤其是在使用者和物品很多的時候。因此實踐中該引數常作為訓練效果與系統開銷之間的調節引數。通常,其合理取值為10到200。

  • iterations:對應執行時的迭代次數。ALS能確保每次迭代都能降低評級矩陣的重建誤差,但一般經少數次迭代後ALS模型便已能收斂為一個比較合理的好模型。這樣,大部分情況下都沒必要迭代太多次(10次左右一般就挺好)。

  • lambda:該引數控制模型的正則化過程,從而控制模型的過擬合情況。其值越高,正則化越嚴厲。該引數的賦值與實際資料的大小、特徵和稀疏程度有關。和其他的機器學習模型一樣,正則引數應該通過用非樣本的測試資料進行交叉驗證來調整。

scala> val model = ALS.train(ratings, 50, 10, 0.01)
model: org.apache.spark.mllib.recommendation.MatrixFactorizationModel = org.apache.spark.mllib.recommendation.MatrixFactorizationModel@2e835760

scala> model.userFeatures
res5: org.apache.spark.rdd.RDD[(Int, Array[Double])] = users MapPartitionsRDD[209] at mapValues at ALS.scala:255

scala> model.userFeatures.count
res6: Long = 943

4.3.2 使用隱式反饋資料訓練模型

MLlib中標準的矩陣分解模型用於顯式評級資料的處理。若要處理隱式資料,則可使用trainImplicit函式。其呼叫方式和標準的train模式類似,但多了一個可設定的alpha引數(也是一個正則化引數,lambda應通過測試和交叉驗證法來設定)。

alpha引數指定了信心權重所應達到的基準線。該值越高則所訓練出的模型越認為使用者與他所沒評級過的電影之間沒有相關性。

4.4 使用推薦模型

預測通常有兩種:為某個使用者推薦物品,或找出與某個物品相關或相似的其他物品。

4.4.1 使用者推薦

通過模型求出使用者可能喜好程度最高的前K個商品。

  1. 基於使用者的模型,則會利用相似使用者的評級來計算對某個使用者的推薦。

  2. 基於物品的模型,則會依靠使用者接觸過的物品與候選物品之間的相似度來獲得推薦。

利用矩陣分解方法時,是直接對評級資料進行建模,所以預計得分可視作相應使用者因子向量和物品因子向量的點積。

1. 從MovieLens 100k資料集生成電影推薦

MLlib的推薦模型基於矩陣分解,因此可用模型所求得的因子矩陣來計算使用者對物品的預計評級。下面只針對利用MovieLens中顯式資料做推薦的情形,使用隱式模型時的方法與之相同。

MatrixFactorizationModel類 提供了一個predict函式,以方便地計算給定使用者對給定物品的預期得分:

scala> val predictedRating = model.predict(789, 123)
16/05/04 16:13:08 WARN BLAS: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS
16/05/04 16:13:08 WARN BLAS: Failed to load implementation from: com.github.fommil.netlib.NativeRefBLAS
predictedRating: Double = 1.8390368814083764

scala> val predictedRating = model.predict(789, 123)
predictedRating: Double = 1.8390368814083764

scala> val userId = 789
userId: Int = 789

scala> val K = 10
K: Int = 10

scala> val topKRecs = model.recommendProducts(userId, K)
topKRecs: Array[org.apache.spark.mllib.recommendation.Rating] = Array(Rating(789,180,5.352418839062572), Rating(789,887,5.289455638310055), Rating(789,484,5.0301818688410025), Rating(789,475,5.011219778604191), Rating(789,150,5.003965038415291), Rating(789,663,4.991126084946501), Rating(789,56,4.974685008959871), Rating(789,48,4.965402351329832), Rating(789,9,4.963265626841469), Rating(789,127,4.963069165947614))

scala> println(topKRecs.mkString("\n"))
Rating(789,180,5.352418839062572)
Rating(789,887,5.289455638310055)
Rating(789,484,5.0301818688410025)
Rating(789,475,5.011219778604191)
Rating(789,150,5.003965038415291)
Rating(789,663,4.991126084946501)
Rating(789,56,4.974685008959871)
Rating(789,48,4.965402351329832)
Rating(789,9,4.963265626841469)
Rating(789,127,4.963069165947614)

  [1]: /img/bVvnpU
  [2]: /img/bVvnrp
  [3]: /img/bVvnqP
  [4]: /img/bVvnsA
  [5]: /img/bVvnsI

相關文章