基本思想
基於用戶的協同過濾算法是通過用戶的歷史行為數據發現用戶對商品或內容的喜歡(如商品購買,收藏,內容評論或分享),並對這些喜好進行度量和打分。根據不同用戶對相同商品或內容的態度和偏好程度計算用戶之間的關系。在有相同喜好的用戶間進行商品推薦。簡單的說就是如果A,B兩個用戶都購買了x、y、z三本圖書,並且給出了5星的好評。那么A和B就屬於同一類用戶。可以將A看過的圖書w也推薦給用戶B。
所以,協同過濾算法主要分為兩個步驟:
1、尋找相似的用戶集合;
2、尋找集合中用戶喜歡的且目標用戶沒有的進行推薦。
具體實現
一、尋找用戶間的相似度
1、Jaccard公式
Jaccard系數主要用於計算符號度量或布爾值度量的個體間的相似度,因為個體的特征屬性都是由符號度量或者布爾值標識,因此無法衡量差異具體值的大小,只能獲得“是否相同”這個結果,所以Jaccard系數只關心個體間共同具有的特征是否一致這個問題。如果比較X與Y的Jaccard相似系數,只比較xn和yn中相同的個數。
2、皮爾遜相關系數
皮爾遜相關系統是比歐幾里德距離更加復雜的可以判斷人們興趣相似度的一種方法。它在數據不是很規范時,會傾向於給出更好的結果。
假定有兩個變量X、Y,那么兩變量間的皮爾遜相關系數可通過以下公式計算:
- 公式一:
- 公式二:
- 公式三:
- 公式四:
上述四個公式等價,其中E是數學期望,cov表示協方差,N表示變量取值的個數。
3、歐幾里德距離
假定兩個用戶X、Y,均為n維向量,表示用戶對n個商品的評分,那么X與Y的歐幾里德距離就是:
數值越小則代表相似度越高,但是對於不同的n,計算出來的距離不便於控制,所以需要進行如下轉換:
使得結果分布在(0,1]上,數值越大,相似度越高。
4、余弦距離
余弦距離,也稱為余弦相似度,是用向量空間中兩個向量余弦值作為衡量兩個個體間差異大小的度量值。
與前面的歐幾里德距離相似,用戶X、Y為兩個n維向量,套用余弦公式,其余弦距離表示為:
即兩個向量夾角的余弦值。但是相比歐式距離,余弦距離更加注意兩個向量在方向上的相對差異,而不是在空間上的絕對距離,具體可以借助下圖來感受兩者間的區別:
二、推薦物品
在選取上述方法中的一種得到各個用戶之間相似度后,針對目標用戶u,我們選出最相似的k個用戶,用集合S(u,k)表示,將S中所有用戶喜歡的物品提取出來並去除目標用戶u已經喜歡的物品。然后對余下的物品進行評分與相似度加權,得到的結果進行排序。最后由排序結果對目標用戶u進行推薦。其中,對於每個可能推薦的物品i,用戶u對其的感興趣的程度可以用如下公式計算:
rvi表示用戶v對i的喜歡程度,即對i的評分,wuv表示用戶u和v之間的相似度。
三、 收集用戶偏好
要從用戶的行為和偏好中發現規律,並基於此給予推薦,如何收集用戶的偏好信息成為系統推薦效果最基礎的決定因素。用戶有很多方式向系統提供自己的偏好信息,而且不同的應用也可能大不相同,下面舉例進行介紹:
表 1 用戶行為和用戶偏好
| 用戶行 為 | 類型 | 特征 | 作用 |
| 評分 | 顯式 | 整數量化的偏好,可能的取值是 [0, n];n 一般取值為 5 或者是 10 | 通過用戶對物品的評分,可以精確的得到用戶的偏好 |
| 投票 | 顯式 | 布爾量化的偏好,取值是 0 或 1 | 通過用戶對物品的投票,可以較精確的得到用戶的偏好 |
| 轉發 | 顯式 | 布爾量化的偏好,取值是 0 或 1 | 通過用戶對物品的投票,可以精確的得到用戶的偏好。 如果是站內,同時可以推理得到被轉發人的偏好(不精確) |
| 保存書簽 | 顯示 | 布爾量化的偏好,取值是 0 或 1 | 通過用戶對物品的投票,可以精確的得到用戶的偏好。 |
| 標記標簽 (Tag) |
顯示 | 一些單詞,需要對單詞進行分析,得到偏好 | 通過分析用戶的標簽,可以得到用戶對項目的理解,同時可以分析出用戶的情感:喜歡還是討厭 |
| 評論 | 顯示 | 一段文字,需要進行文本分析,得到偏好 | 通過分析用戶的評論,可以得到用戶的情感:喜歡還是討厭 |
| 點擊流 ( 查看 ) |
隱式 | 一組用戶的點擊,用戶對物品感興趣,需要進行分析,得到偏好 | 用戶的點擊一定程度上反映了用戶的注意力,所以它也可以從一定程度上反映用戶的喜好。 |
| 頁面停留時間 | 隱式 | 一組時間信息,噪音大,需要進行去噪,分析,得到偏好 | 用戶的頁面停留時間一定程度上反映了用戶的注意力和喜好,但噪音偏大,不好利用。 |
| 購買 | 隱式 | 布爾量化的偏好,取值是 0 或 1 | 用戶的購買是很明確的說明這個項目它感興趣。 |
以上列舉的用戶行為都是比較通用的,推薦引擎設計人員可以根據自己應用的特點添加特殊的用戶行為,並用他們表示用戶對物品的喜好。
在一般應用中,我們提取的用戶行為一般都多於一種,關於如何組合這些不同的用戶行為,基本上有以下兩種方式:
- 將不同的行為分組:一般可以分為“查看”和“購買”等等,然后基於不同的行為,計算不同的用戶 / 物品相似度。類似於當當網或者 Amazon 給出的“購買了該圖書的人還購買了 …”,“查看了圖書的人還查看了 …”
- 根據不同行為反映用戶喜好的程度將它們進行加權,得到用戶對於物品的總體喜好。一般來說,顯式的用戶反饋比隱式的權值大,但比較稀疏,畢竟進行顯示反饋的用戶是少數;同時相對於“查看”,“購買”行為反映用戶喜好的程度更大,但這也因應用而異。
收集了用戶行為數據,我們還需要對數據進行一定的預處理,其中最核心的工作就是:減噪和歸一化。
- 減噪:用戶行為數據是用戶在使用應用過程中產生的,它可能存在大量的噪音和用戶的誤操作,我們可以通過經典的數據挖掘算法過濾掉行為數據中的噪音,這樣可以是我們的分析更加精確。
- 歸一化:如前面講到的,在計算用戶對物品的喜好程度時,可能需要對不同的行為數據進行加權。但可以想象,不同行為的數據取值可能相差很大,比如,用戶的查看數據必然比購買數據大的多,如何將各個行為的數據統一在一個相同的取值范圍中,從而使得加權求和得到的總體喜好更加精確,就需要我們進行歸一化處理。最簡單的歸一化處理,就是將各類數據除以此類中的最大值,以保證歸一化后的數據取值在 [0,1] 范圍中。
進行的預處理后,根據不同應用的行為分析方法,可以選擇分組或者加權處理,之后我們可以得到一個用戶偏好的二維矩陣,一維是用戶列表,另一維是物品列表,值是用戶對物品的偏好,一般是 [0,1] 或者 [-1, 1] 的浮點數值。
四、基於用戶的 CF(User CF)
基於用戶的 CF 的基本思想相當簡單,基於用戶對物品的偏好找到相鄰鄰居用戶,然后將鄰居用戶喜歡的推薦給當前用戶。計算上,就是將一個用戶對所有物品的偏好作為一個向量來計算用戶之間的相似度,找到 K 鄰居后,根據鄰居的相似度權重以及他們對物品的偏好,預測當前用戶沒有偏好的未涉及物品,計算得到一個排序的物品列表作為推薦。圖 2 給出了一個例子,對於用戶 A,根據用戶的歷史偏好,這里只計算得到一個鄰居 – 用戶 C,然后將用戶 C 喜歡的物品 D 推薦給用戶 A。

圖 2 基於用戶的 CF 的基本原理
五、基於物品的 CF(Item CF)
基於物品的 CF 的原理和基於用戶的 CF 類似,只是在計算鄰居時采用物品本身,而不是從用戶的角度,即基於用戶對物品的偏好找到相似的物品,然后根據用戶的歷史偏好,推薦相似的物品給他。從計算的角度看,就是將所有用戶對某個物品的偏好作為一個向量來計算物品之間的相似度,得到物品的相似物品后,根據用戶歷史的偏好預測當前用戶還沒有表示偏好的物品,計算得到一個排序的物品列表作為推薦。圖 3 給出了一個例子,對於物品 A,根據所有用戶的歷史偏好,喜歡物品 A 的用戶都喜歡物品 C,得出物品 A 和物品 C 比較相似,而用戶 C 喜歡物品 A,那么可以推斷出用戶 C 可能也喜歡物品 C。

圖 3 基於物品的 CF 的基本原理
六、User CF vs. Item CF
前面介紹了 User CF 和 Item CF 的基本原理,下面我們分幾個不同的角度深入看看它們各自的優缺點和適用場景:
- 計算復雜度
Item CF 和 User CF 是基於協同過濾推薦的兩個最基本的算法,User CF 是很早以前就提出來了,Item CF 是從 Amazon 的論文和專利發表之后(2001 年左右)開始流行,大家都覺得 Item CF 從性能和復雜度上比 User CF 更優,其中的一個主要原因就是對於一個在線網站,用戶的數量往往大大超過物品的數量,同時物品的數據相對穩定,因此計算物品的相似度不但計算量較小,同時也不必頻繁更新。但我們往往忽略了這種情況只適應於提供商品的電子商務網站,對於新聞,博客或者微內容的推薦系統,情況往往是相反的,物品的數量是海量的,同時也是更新頻繁的,所以單從復雜度的角度,這兩個算法在不同的系統中各有優勢,推薦引擎的設計者需要根據自己應用的特點選擇更加合適的算法。
- 適用場景
在非社交網絡的網站中,內容內在的聯系是很重要的推薦原則,它比基於相似用戶的推薦原則更加有效。比如在購書網站上,當你看一本書的時候,推薦引擎會給你推薦相關的書籍,這個推薦的重要性遠遠超過了網站首頁對該用戶的綜合推薦。可以看到,在這種情況下,Item CF 的推薦成為了引導用戶瀏覽的重要手段。同時 Item CF 便於為推薦做出解釋,在一個非社交網絡的網站中,給某個用戶推薦一本書,同時給出的解釋是某某和你有相似興趣的人也看了這本書,這很難讓用戶信服,因為用戶可能根本不認識那個人;但如果解釋說是因為這本書和你以前看的某本書相似,用戶可能就覺得合理而采納了此推薦。
相反的,在現今很流行的社交網絡站點中,User CF 是一個更不錯的選擇,User CF 加上社會網絡信息,可以增加用戶對推薦解釋的信服程度。
- 推薦多樣性和精度
研究推薦引擎的學者們在相同的數據集合上分別用 User CF 和 Item CF 計算推薦結果,發現推薦列表中,只有 50% 是一樣的,還有 50% 完全不同。但是這兩個算法確有相似的精度,所以可以說,這兩個算法是很互補的。
關於推薦的多樣性,有兩種度量方法:
第一種度量方法是從單個用戶的角度度量,就是說給定一個用戶,查看系統給出的推薦列表是否多樣,也就是要比較推薦列表中的物品之間兩兩的相似度,不難想到,對這種度量方法,Item CF 的多樣性顯然不如 User CF 的好,因為 Item CF 的推薦就是和以前看的東西最相似的。
第二種度量方法是考慮系統的多樣性,也被稱為覆蓋率 (Coverage),它是指一個推薦系統是否能夠提供給所有用戶豐富的選擇。在這種指標下,Item CF 的多樣性要遠遠好於 User CF, 因為 User CF 總是傾向於推薦熱門的,從另一個側面看,也就是說,Item CF 的推薦有很好的新穎性,很擅長推薦長尾里的物品。所以,盡管大多數情況,Item CF 的精度略小於 User CF, 但如果考慮多樣性,Item CF 卻比 User CF 好很多。
如果你對推薦的多樣性還心存疑惑,那么下面我們再舉個實例看看 User CF 和 Item CF 的多樣性到底有什么差別。首先,假設每個用戶興趣愛好都是廣泛的,喜歡好幾個領域的東西,不過每個用戶肯定也有一個主要的領域,對這個領域會比其他領域更加關心。給定一個用戶,假設他喜歡 3 個領域 A,B,C,A 是他喜歡的主要領域,這個時候我們來看 User CF 和 Item CF 傾向於做出什么推薦:如果用 User CF, 它會將 A,B,C 三個領域中比較熱門的東西推薦給用戶;而如果用 ItemCF,它會基本上只推薦 A 領域的東西給用戶。所以我們看到因為 User CF 只推薦熱門的,所以它在推薦長尾里項目方面的能力不足;而 Item CF 只推薦 A 領域給用戶,這樣他有限的推薦列表中就可能包含了一定數量的不熱門的長尾物品,同時 Item CF 的推薦對這個用戶而言,顯然多樣性不足。但是對整個系統而言,因為不同的用戶的主要興趣點不同,所以系統的覆蓋率會比較好。
從上面的分析,可以很清晰的看到,這兩種推薦都有其合理性,但都不是最好的選擇,因此他們的精度也會有損失。其實對這類系統的最好選擇是,如果系統給這個用戶推薦 30 個物品,既不是每個領域挑選 10 個最熱門的給他,也不是推薦 30 個 A 領域的給他,而是比如推薦 15 個 A 領域的給他,剩下的 15 個從 B,C 中選擇。所以結合 User CF 和 Item CF 是最優的選擇,結合的基本原則就是當采用 Item CF 導致系統對個人推薦的多樣性不足時,我們通過加入 User CF 增加個人推薦的多樣性,從而提高精度,而當因為采用 User CF 而使系統的整體多樣性不足時,我們可以通過加入 Item CF 增加整體的多樣性,同樣同樣可以提高推薦的精度。
- 用戶對推薦算法的適應度
前面我們大部分都是從推薦引擎的角度考慮哪個算法更優,但其實我們更多的應該考慮作為推薦引擎的最終使用者 — 應用用戶對推薦算法的適應度。
對於 User CF,推薦的原則是假設用戶會喜歡那些和他有相同喜好的用戶喜歡的東西,但如果一個用戶沒有相同喜好的朋友,那 User CF 的算法的效果就會很差,所以一個用戶對的 CF 算法的適應度是和他有多少共同喜好用戶成正比的。
Item CF 算法也有一個基本假設,就是用戶會喜歡和他以前喜歡的東西相似的東西,那么我們可以計算一個用戶喜歡的物品的自相似度。一個用戶喜歡物品的自相似度大,就說明他喜歡的東西都是比較相似的,也就是說他比較符合 Item CF 方法的基本假設,那么他對 Item CF 的適應度自然比較好;反之,如果自相似度小,就說明這個用戶的喜好習慣並不滿足 Item CF 方法的基本假設,那么對於這種用戶,用 Item CF 方法做出好的推薦的可能性非常低。
七、矩陣分解
Spark推薦模型庫當前只包含基於矩陣分解(matrix factorization)的實現,由此我們也將重點關注這類模型。它們有吸引人的地方。首先,這些模型在協同過濾中的表現十分出色。而在Netflix Prize等知名比賽中的表現也很拔尖
1,顯式矩陣分解
要找到和“用戶物品”矩陣近似的k維(低階)矩陣,最終要求出如下兩個矩陣:一個用於表示用戶的U × k維矩陣,以及一個表征物品的I × k維矩陣。
這兩個矩陣也稱作因子矩陣。它們的乘積便是原始評級矩陣的一個近似。值得注意的是,原始評級矩陣通常很稀疏,但因子矩陣卻是稠密的。
特點:因子分解類模型的好處在於,一旦建立了模型,對推薦的求解便相對容易。但也有弊端,即當用戶和物品的數量很多時,其對應的物品或是用戶的因子向量可能達到數以百萬計。
這將在存儲和計算能力上帶來挑戰。另一個好處是,這類模型的表現通常都很出色。
2,隱式矩陣分解(關聯因子分確定,可能隨時會變化)
隱式模型仍然會創建一個用戶因子矩陣和一個物品因子矩陣。但是,模型所求解的是偏好矩陣而非評級矩陣的近似。類似地,此時用戶因子向量和物品因子向量的點積所得到的分數
也不再是一個對評級的估值,而是對某個用戶對某一物品偏好的估值(該值的取值雖並不嚴格地處於0到1之間,但十分趨近於這個區間)
3,最小二乘法(Alternating Least Squares ALS):解決矩陣分解的最優化方法
ALS的實現原理是迭代式求解一系列最小二乘回歸問題。在每一次迭代時,固定用戶因子矩陣或是物品因子矩陣中的一個,然后用固定的這個矩陣以及評級數據來更新另一個矩陣。
之后,被更新的矩陣被固定住,再更新另外一個矩陣。如此迭代,直到模型收斂(或是迭代了預設好的次數)。
八、Spark下ALS算法的應用
數據來源電影集ml-100k
基於用戶相似度片段代碼:
val movieFile=sc.textFile(fileName) val RatingDatas=movieFile.map(_.split("\t").take(3)) //轉為Ratings數據 val ratings=RatingDatas.map(x =>Rating(x(0).toInt,x(1).toInt,x(2).toDouble)) //獲取用戶評價模型,設置k因子,和迭代次數,隱藏因子lambda,獲取模型 val model=ALS.train(ratings,50,10,0.01) //基於用戶相似度推薦 println("userNumber:"+model.userFeatures.count()+"\t"+"productNum:"+model.productFeatures.count()) //指定用戶及商品,輸出預測值 println(model.predict(789,123)) //為指定用戶推薦的前N商品 model.recommendProducts(789,11).foreach(println(_)) //為每個人推薦前十個商品 model.recommendProductsForUsers(10).take(1).foreach{ case(x,rating) =>println(rating(0)) }
基於商品相似度代碼:
計算相似度的方法有相似度是通過某種方式比較表示兩個物品的向量而得到的。常見的相似度衡量方法包括皮爾森相關系數(Pearson correlation)、針對實數向量的余弦相似度(cosine similarity)和針對二元向量的傑卡德相似系數(Jaccard similarity)。
val itemFactory=model.productFeatures.lookup(567).head val itemVector=new DoubleMatrix(itemFactory) //求余弦相似度 val sim=model.productFeatures.map{ case(id,factory)=> val factorVector=new DoubleMatrix(factory) val sim=cosineSimilarity(factorVector,itemVector) (id,sim) } val sortedsim=sim.top(11)(Ordering.by[(Int,Double),Double]{ case(id,sim)=>sim }) println(sortedsim.take(10).mkString("\n")) def cosineSimilarity(vec1:DoubleMatrix,vec2:DoubleMatrix):Double={ vec1.dot(vec2)/(vec1.norm2()*vec2.norm2()) }
均方差評估模型代碼:
//模型評估,通過均誤差 //實際用戶評估值 val actualRatings=ratings.map{ case Rating(user,item,rats) => ((user,item),rats) } val userItems=ratings.map{ case(Rating(user,item,rats)) => (user,item) } //模型的用戶對商品的預測值 val predictRatings=model.predict(userItems).map{ case(Rating(user,item,rats)) =>((user,item),rats) } //聯合獲取rate值 val rates=actualRatings.join(predictRatings).map{ case x =>(x._2._1,x._2._2) } //求均方差 val regressionMetrics=new RegressionMetrics(rates) //越接近0越佳 println(regressionMetrics.meanSquaredError)
全局准確率評估(MAP):
使用MLlib的 RankingMetrics 類來計算基於排名的評估指標。類似地,需要向我們之前的平均准確率函數傳入一個鍵值對類型的RDD。其鍵為給定用戶預測的推薦物品的ID數組,而值則是實際的物品ID數組。
//全局平均准確率(MAP) val itemFactors = model.productFeatures.map { case (id, factor) => factor }.collect() val itemMatrix = new DoubleMatrix(itemFactors) //分布式廣播商品的特征矩陣 val imBroadcast = sc.broadcast(itemMatrix) //計算每一個用戶的推薦,在這個操作里,會對用戶因子矩陣和電影因子矩陣做乘積,其結果為一個表示各個電影預計評級的向量(長度為 //1682,即電影的總數目) val allRecs = model.userFeatures.map{ case (userId, array) => val userVector = new DoubleMatrix(array) val scores = imBroadcast.value.mmul(userVector) val sortedWithId = scores.data.zipWithIndex.sortBy(-_._1) val recommendedIds = sortedWithId.map(_._2 + 1).toSeq //+1,矩陣從0開始 (userId, recommendedIds) } //實際評分 val userMovies = ratings.map{ case Rating(user, product, rating) => (user, product)}.groupBy(_._1) val predictedAndTrueForRanking = allRecs.join(userMovies).map{ case (userId, (predicted, actualWithIds)) => val actual = actualWithIds.map(_._2) (predicted.toArray, actual.toArray) } //求MAP,越大越好吧 val rankingMetrics = new RankingMetrics(predictedAndTrueForRanking) println("Mean Average Precision = " + rankingMetrics.meanAveragePrecision)
以上代碼匯總如下:
package com.spark.milb.study import org.apache.log4j.{Level, Logger} import org.apache.spark.mllib.evaluation.{RankingMetrics, RegressionMetrics} import org.apache.spark.mllib.recommendation.{ALS, Rating} import org.apache.spark.{SparkConf, SparkContext} import org.jblas.DoubleMatrix /** * 協同過濾(處理對象movie,使用算法ALS:最小二乘法(實現用戶推薦) * 余弦相似度實現商品相似度推薦 */ object cfTest { def main(args: Array[String]): Unit = { Logger.getLogger("org.apache.spark").setLevel(Level.WARN) Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF) val conf=new SparkConf().setMaster("local").setAppName("AlsTest") val sc=new SparkContext(conf) CF(sc,"ml-100k/u.data") } def CF(sc:SparkContext,fileName:String): Unit ={ val movieFile=sc.textFile(fileName) val RatingDatas=movieFile.map(_.split("\t").take(3)) //轉為Ratings數據 val ratings=RatingDatas.map(x =>Rating(x(0).toInt,x(1).toInt,x(2).toDouble)) //獲取用戶評價模型,設置k因子,和迭代次數,隱藏因子lambda,獲取模型 /* * rank :對應ALS模型中的因子個數,也就是在低階近似矩陣中的隱含特征個數。因子個 數一般越多越好。但它也會直接影響模型訓練和保存時所需的內存開銷,尤其是在用戶 和物品很多的時候。因此實踐中該參數常作為訓練效果與系統開銷之間的調節參數。通 常,其合理取值為10到200。 iterations :對應運行時的迭代次數。ALS能確保每次迭代都能降低評級矩陣的重建誤 差,但一般經少數次迭代后ALS模型便已能收斂為一個比較合理的好模型。這樣,大部分 情況下都沒必要迭代太多次(10次左右一般就挺好)。 lambda :該參數控制模型的正則化過程,從而控制模型的過擬合情況。其值越高,正則 化越嚴厲。該參數的賦值與實際數據的大小、特征和稀疏程度有關。和其他的機器學習 模型一樣,正則參數應該通過用非樣本的測試數據進行交叉驗證來調整。 * */ val model=ALS.train(ratings,50,10,0.01) //基於用戶相似度推薦 println("userNumber:"+model.userFeatures.count()+"\t"+"productNum:"+model.productFeatures.count()) //指定用戶及商品,輸出預測值 println(model.predict(789,123)) //為指定用戶推薦的前N商品 model.recommendProducts(789,11).foreach(println(_)) //為每個人推薦前十個商品 model.recommendProductsForUsers(10).take(1).foreach{ case(x,rating) =>println(rating(0)) } //基於商品相似度(使用余弦相似度)進行推薦,獲取某個商品的特征值 val itemFactory=model.productFeatures.lookup(567).head val itemVector=new DoubleMatrix(itemFactory) //求余弦相似度 val sim=model.productFeatures.map{ case(id,factory)=> val factorVector=new DoubleMatrix(factory) val sim=cosineSimilarity(factorVector,itemVector) (id,sim) } val sortedsim=sim.top(11)(Ordering.by[(Int,Double),Double]{ case(id,sim)=>sim }) println(sortedsim.take(10).mkString("\n")) //模型評估,通過均誤差 //實際用戶評估值 val actualRatings=ratings.map{ case Rating(user,item,rats) => ((user,item),rats) } val userItems=ratings.map{ case(Rating(user,item,rats)) => (user,item) } //模型的用戶對商品的預測值 val predictRatings=model.predict(userItems).map{ case(Rating(user,item,rats)) =>((user,item),rats) } //聯合獲取rate值 val rates=actualRatings.join(predictRatings).map{ case x =>(x._2._1,x._2._2) } //求均方差 val regressionMetrics=new RegressionMetrics(rates) //越接近0越佳 println(regressionMetrics.meanSquaredError) //全局平均准確率(MAP) val itemFactors = model.productFeatures.map { case (id, factor) => factor }.collect() val itemMatrix = new DoubleMatrix(itemFactors) //分布式廣播商品的特征矩陣 val imBroadcast = sc.broadcast(itemMatrix) //計算每一個用戶的推薦,在這個操作里,會對用戶因子矩陣和電影因子矩陣做乘積,其結果為一個表示各個電影預計評級的向量(長度為 //1682,即電影的總數目) val allRecs = model.userFeatures.map{ case (userId, array) => val userVector = new DoubleMatrix(array) val scores = imBroadcast.value.mmul(userVector) val sortedWithId = scores.data.zipWithIndex.sortBy(-_._1) val recommendedIds = sortedWithId.map(_._2 + 1).toSeq //+1,矩陣從0開始 (userId, recommendedIds) } //實際評分 val userMovies = ratings.map{ case Rating(user, product, rating) => (user, product)}.groupBy(_._1) val predictedAndTrueForRanking = allRecs.join(userMovies).map{ case (userId, (predicted, actualWithIds)) => val actual = actualWithIds.map(_._2) (predicted.toArray, actual.toArray) } //求MAP,越大越好吧 val rankingMetrics = new RankingMetrics(predictedAndTrueForRanking) println("Mean Average Precision = " + rankingMetrics.meanAveragePrecision) } //余弦相似度計算 def cosineSimilarity(vec1:DoubleMatrix,vec2:DoubleMatrix):Double={ vec1.dot(vec2)/(vec1.norm2()*vec2.norm2()) } }
