1. 程式人生 > >SparkML之推薦引擎(一)—— 電影推薦

SparkML之推薦引擎(一)—— 電影推薦

本文將使用 SparkML 來構建推薦引擎。
推薦引擎演算法大致分為 基於內容的過濾、協同過濾、矩陣分解,本文將使用基於屬於矩陣分解的 最小二乘法 演算法來構建推薦引擎。
對於推薦引擎模組這裡將分為兩篇文章,第一篇文章主要是以實現推薦功能為主,第二篇文章主要是對模型進行評估
文章將按照以下章節來進行書寫: 需求分析、獲取資料、提取特徵、訓練模型、使用模型(推薦)

一、需求分析

假設我們是 MovieStream 團隊,專門為使用者提供線上電影和電視節目的內容服務。
現在我們有個需求::給使用者推薦電影!
就這麼簡單,哈哈~


二、獲取資料

可從

http://files.grouplens.org/datasets/movielens/ml-100k.zip 下載模擬的資料集。
對於推薦模型,主要用到了裡面的三個檔案:

  • u.user(使用者屬性檔案)
  • u.item(電影元資料)
  • u.data(使用者對電影的評級)
資料檔案說明:
1、u.user(使用者屬性檔案)

欄位及格式說明:user id | age | gender | occupation(職業) | zip code
樣例:

1|24|M|technician|85711
2|53|F|other|94043
3|23|M|writer|
32067 4|24|M|technician|43537 5|33|F|other|15213
2、u.item(電影資訊資料)

欄位及格式說明:
movie id | movie title | release date | video release date | IMDb URL | unknown | Action | Adventure | Animation | Children’s | Comedy | Crime | Documentary | Drama | Fantasy | Film-Noir | Horror | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western |
樣例:

1|Toy Story (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Toy%20Story%20(1995)|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
2|GoldenEye (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?GoldenEye%20(1995)|0|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0
3|Four Rooms (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Four%20Rooms%20(1995)|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0
4|Get Shorty (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Get%20Shorty%20(1995)|0|1|0|0|0|1|0|0|1|0|0|0|0|0|0|0|0|0|0
5|Copycat (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Copycat%20(1995)|0|0|0|0|0|0|1|0|1|0|0|0|0|0|0|0|1|0|0
3、u.data(使用者對電影的評分)

欄位及格式說明:user_id item_id rating timestamp(注意:分隔符為 “\t”)
樣例:

196 242 3   881250949
186 302 3   891717742
22  377 1   878887116
244 51  2   880606923
166 346 1   886397596

三、提取特徵

/* 生成使用者評分資料的RDD,格式為:使用者 電影 評分 時間戳 */
val rawData: RDD[String] = sc.textFile("file:///E:/spark/ml-100k/u.data")
/* 去掉時間戳的欄位,格式變為:使用者 電影 評分; rawRating型別為Array */
val rawRatings = rawData.map(_.split("\\t").take(3))
/* 格式變為:Rating(使用者 電影 評分),作為後續訓練模型的引數 */
val ratings = rawRatings.map{case Array(user, movie, rating) =>{
    //封裝成Rating
    Rating(user.toInt, movie.toInt, rating.toDouble)
}}

四、訓練模型

最小二乘法的模型需要以下三個引數:

1、rank

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

2、iterations

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

3、lambda

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

這裡將使用的 rank、iterations 和 lambda 引數的值分別為50、10和0.01
程式碼如下:

import org.apache.spark.mllib.recommendation.{Rating, ALS}
//這就得到了推薦的模型
val model = ALS.train(ratings, 50, 10, 0.01)

五、使用模型(推薦)

1、使用者推薦

為 id 為 789 的使用者推薦10個電影

//為指定的使用者推薦 N 個商品
val userID = 789
val K = 10
val topKRecs: Array[Rating] = model.recommendProducts(userID, K)
println(topKRecs.mkString("\n"))

輸出為:

Rating(789,715,5.931851273771102)
Rating(789,12,5.582301095666215)
Rating(789,959,5.516272981542168)
Rating(789,42,5.458065302395629)
Rating(789,584,5.449949837103569)
Rating(789,750,5.348768847643657)
Rating(789,663,5.30832117499004)
Rating(789,134,5.278933936827717)
Rating(789,156,5.250959077906759)
Rating(789,432,5.169863417126231)

2、物品推薦(作為了解)

物品推薦可以理解為:給定一個物品,推薦 K 個與該物品相似的物品
我們上面得到的推薦模型中沒有提供物品推薦的方法,但是謀問題,我們自己可以根據餘弦相似度來實現。

科普:餘弦相似度是兩個兩個向量在n維空間裡兩者夾角的度數。它的值是兩個向量的點積與各向量範數(或長度)的乘積的商。該值的取值範圍是 -1 到 1 之間,1表示完全相似,0表示不相關,-1表示兩者不僅不相關而且還完全不同。

ok,我們來寫一個計算餘弦相似度的函式,在寫之前需要引入 jblas 線性代數庫,該庫中有一個 DoubleMatrix 類物件,向量和矩陣都用該物件來表示

import org.jblas.DoubleMatrix
/**
  * 用於商品推薦
  * 通過傳入兩個向量,返回這兩個向量之間的餘弦相似度
  * @param vec1
  * @param vec2
  * @return
  */
def cosineSimilarity(vec1: DoubleMatrix, vec2: DoubleMatrix): Double = {
  vec1.dot(vec2) / (vec1.norm2() * vec2.norm2())
}

開始根據物品推薦:

/**
  * 基於商品進行推薦
  */
/*通過商品ID獲得與該商品相似的商品*/
val itemId = 567
val itemFactor: Array[Double] = model.productFeatures.lookup(itemId).head
val itemVector: DoubleMatrix = new DoubleMatrix(itemFactor)
//獲得每個商品與給出的商品的餘弦相似度
val sims = model.productFeatures.map{case (id, factor) => {
  val factorVector = new DoubleMatrix(factor)
  val sim = cosineSimilarity(factorVector, itemVector)
  (id, sim)
}}
//打印出前10的商品
val topItem: Array[(Int, Double)] = sims.sortBy(-_._2).take(10)
println("與567商品相似的商品:\n" + topItem.mkString("\n") + "\n")

輸出為:

與567商品相似的商品:
(567,1.0)
(1471,0.6932331537649621)
(670,0.6898690594544726)
(201,0.6897964975027041)
(343,0.6891221044611473)
(563,0.6864214133620066)
(294,0.6812075443259535)
(413,0.6754663844488256)
(184,0.6702643811753909)
(109,0.6594872765176396)

很正常,排名第一的最相似物品就是我們給定的物品。但是注意,因為模型的初始化是隨機的,所以後面的商品可能跟你的不一樣,這很正常哈~

完整程式碼可以參考下篇文章哈~


本文參考自:《Spark機器學習》和Spark官網 http://spark.apache.org/docs/1.6.3/mllib-guide.html