Recommendation System

本文結(jié)構(gòu)安排

  • Item-Based Collaboration Filtering
  • Slope One
  • Matrix Factorization

Item-based Collaboration Filtering

推薦系統(tǒng)本質(zhì)是在用戶需求不明確的情況下论皆,解決信息過載的問題雕蔽,聯(lián)系用戶和信息叽掘,一方面幫助用戶發(fā)現(xiàn)對自己有價值的信息,另一方面讓信息能夠展現(xiàn)在對它感興趣的用戶面前侈百,從而實現(xiàn)信息消費者和信息生產(chǎn)者的雙贏(這里的信息的含義可以非常廣泛,比如咨詢翰铡、電影和商品等钝域,統(tǒng)稱為item)

協(xié)同過濾主要分為基于鄰域以及基于隱語義模型《В基于鄰域的算法中例证,Item-based CF應(yīng)用廣泛,其主要思想為“喜歡item A的用戶大都喜歡用戶 item B”迷捧,通過挖掘用戶歷史的操作日志织咧,利用群體智慧,生成item的候選推薦列表漠秋。
原理是通過將用戶和其他用戶的數(shù)據(jù)進行比對來實現(xiàn)推薦的笙蒙。比對的具體方法就是通過計算兩個用戶數(shù)據(jù)之間的相似性,通過相似性的計算來說明兩個用戶數(shù)據(jù)之間的相似程度庆锦。相似度函數(shù)的設(shè)計必須滿足度量空間的三點要求捅位,即非負性,對稱性和三角不等性搂抒。常用的相似度的計算方法有:歐式距離法艇搀、皮爾遜相關(guān)系數(shù)法和夾角余弦相似度法。

User-based的基本思想是如果用戶A喜歡物品a燕耿,用戶B喜歡物品a中符、b、c誉帅,用戶C喜歡a和c淀散,那么認為用戶A與用戶B和C相似,因為他們都喜歡a蚜锨,而喜歡a的用戶同時也喜歡c档插,所以把c推薦給用戶A。該算法用最近鄰居(nearest-neighbor)算法找出一個用戶的鄰居集合亚再,該集合的用戶和該用戶有相似的喜好郭膛,算法根據(jù)鄰居的偏好對該用戶進行預(yù)測。

User-based算法存在兩個重大問題:1. 數(shù)據(jù)稀疏性氛悬。一個大型的電子商務(wù)推薦系統(tǒng)一般有非常多的物品则剃,用戶可能買的其中不到1%的物品耘柱,不同用戶之間買的物品重疊性較低,導(dǎo)致算法無法找到一個用戶的鄰居棍现,即偏好相似的用戶调煎。2. 算法擴展性。最近鄰居算法的計算量隨著用戶和物品數(shù)量的增加而增加己肮,不適合數(shù)據(jù)量大的情況使用士袄。

Iterm-based的基本思想是預(yù)先根據(jù)所有用戶的歷史偏好數(shù)據(jù)計算物品之間的相似性,然后把與用戶喜歡的物品相類似的物品推薦給用戶谎僻。還是以之前的例子為例娄柳,可以知道物品a和c非常相似,因為喜歡a的用戶同時也喜歡c艘绍,而用戶A喜歡a赤拒,所以把c推薦給用戶A。

因為物品直接的相似性相對比較固定诱鞠,所以可以預(yù)先在線下計算好不同物品之間的相似度需了,把結(jié)果存在表中,當(dāng)推薦時進行查表般甲,計算用戶可能的打分值肋乍,可以同時解決上面兩個問題。

Item-based算法詳細過程:

1敷存、相似度計算:Item-based算法首選計算物品之間的相似度墓造,計算相似度的方法有以下幾種:

(1). 基于余弦(Cosine-based)的相似度計算紊册,通過計算兩個向量之間的夾角余弦值來計算物品之間的相似性

cos_{}{X,Y} = \frac{XY}{||X|| * ||Y||} = \frac{\sum_{i=1}^{n}(x_{i} * y_{i})}{\sqrt{\sum_{i=1}^{n}x_{i}^{2}} * \sqrt{\sum_{i=1}^{n}y_{i}^{2}}}

(2). 基于關(guān)聯(lián)(Correlation-based)的相似度計算杖挣,計算兩個向量之間的Pearson-r關(guān)聯(lián)度

P_{X,Y}=\frac{cov(X,Y)}{\sigma_{x}\sigma_{y}} = \frac{E((X-\mu_{x})(Y-\mu_{y}))}{\sigma_{x}\sigma_{y}} = \frac{\sum_{u \in U}^{ }(R_{u,i}-\bar{R}_{i})(R_{u,j}-\bar{R}_{j})} {\sqrt{\sum_{u \in U}^{ }(R_{u,i}-\bar{R}_{i})^{2}} * \sqrt{\sum_{u \in U}^{ }(R_{u,j}-\bar{R}_{j})^{2}}}

2却嗡、\textbf{預(yù)測值計算}:加權(quán)求和. 用過對用戶u已打分的物品的分數(shù)進行加權(quán)求和辕漂,權(quán)值為各個物品與物品i的相似度,然后對所有物品相似度的和求平均通砍,計算得到用戶u對物品i打分

P_{u,i} = \bar{R}_{i} + \frac{\sum_{j \in N(i:u)}^{ }sim(i,j)*(R_{u,j}-\bar{R}_{j})}{\sum_{j \in N(i:u)}^{ }|sim(i,j)|}

Slope One

簡單高效的協(xié)同過濾算法厕宗。Slope One 和其它類似算法相比, 它的最大優(yōu)點在于算法很簡單, 易于實現(xiàn), 執(zhí)行效率高, 同時推薦的準(zhǔn)確性相對很高觉增。

Slope One算法是基于不同物品之間的評分差的線性算法彻亲,預(yù)測用戶對物品評分的個性化算法孕锄。主要兩步:

Step1:計算物品之間的評分差的均值,記為物品間的評分偏差(兩物品同時被評分)苞尝;

R(ij) = \frac{\sum_{u \in N(i)\cap N(j)}^{ }(R_{u,i}-R_{u,j})}{|N(i)\cap N(j)|} = \frac{\sum_{u \in N(i)\cap N(j)}^{ }(R_{u,i}-R_{u,j})}{card(N(i)\cap N(j))}

其中畸肆,R_{u,i}是用戶u對物品i的評分,R_{u,j}是用戶u對物品j的評分宙址,N(i)是對物品i評分過的用戶轴脐,N(i)\cap N(j)是對物品i和物品j都評分過的用戶,|N(i)\cap N(j)|是對物品i和物品j都評分過的用戶數(shù)量。

Step2:根據(jù)物品間的評分偏差和用戶的歷史評分大咱,預(yù)測用戶對未評分的物品的評分恬涧。

P_{u,i} = \frac{\sum_{j \in \{N(u)-\{j\}\}}^{ }(R_{u,j}-R(ij))}{card(\{N(u)-\{j\}\})}

其中N(u)是用戶u購買過的物品,card(\cdot)為集合中的元素個數(shù)

Step3:將預(yù)測評分排序碴巾,取top-K對應(yīng)的物品推薦給用戶气破。

Matrix factorization

評分預(yù)測過程通常包括用戶項目矩陣,
其中用戶users對應(yīng)于行和項目items到列餐抢。矩陣條目指定user對item的評分情況。我們研究的問題是如何通過使用可用的值來準(zhǔn)確預(yù)測用戶項目矩陣中的missing values低匙。

矩陣分解是指一組算法旷痕,其中矩陣被分解成兩個矩陣的乘積。
當(dāng)應(yīng)用矩陣分解來解決我們的研究問題時顽冶,前提是影響用戶觀察到的Web服務(wù)失敗概率的因素很少欺抗,
用戶觀察到的Web服務(wù)失敗概率取決于每個因素如何適用于用戶,
Web服務(wù)上的用戶失敗概率值對應(yīng)于這些因素與用戶特定系數(shù)的線性組合强重。

P為m行n列的user-item矩陣绞呈,l-factor模型旨在使W和H矩陣的乘積能夠近似P

P\approx WH
W為m行l(wèi)列矩陣,H為l行n列矩陣间景,
l是因素的個數(shù)佃声,矩陣分解產(chǎn)生的W的每一行都是用戶特定的用戶系數(shù),
矩陣分解產(chǎn)生的H的每一列都是包含Web服務(wù)的l因子值的因子向量倘要。

\textbf{Gradient Descent}

P和WH之間差異的最常見量度是總和誤差圾亏,其可以通過以下公式計算:
\sum_{i=1}^{m}\sum_{j=1}^{n}I_{ij}^{P}(p_{ij}-W_{i}H_{j})^{2}
在用戶項目矩陣 P中(表示W(wǎng)eb服務(wù)j先前已由用戶調(diào)用),如果p_{ij}中有值,則I_{ij}^{P}等于1封拧,否則等于0志鹃,
W_{i}是矩陣W的第i行(代表特定的用戶i的系數(shù)),而H_{j}是矩陣H的第j列(表示itemj的向量因子)泽西。

使用梯度下降法近似矩陣P曹铃。目標(biāo)函數(shù)是:
\min L(W,H)=\frac{1}{2}\sum_{i=1}^{m}\sum_{j=1}^{n}I_{ij}^{P}(p_{ij}-W_{i}H_{j})^{2}+\frac{\gamma}{2}||W||_{F}^{2}+\frac{\gamma}{2}||H||_{F}^{2}
正則化項可以這樣求:
||W||_{F}^{2}=WW^{T}
采用誤差反向傳播的方法迭代更新矩陣W和H。
\frac{\partial L}{\partial w_{il}}=\gamma w_{il}-\sum_{j=1}^{n}I_{ij}^{P}(p_{ij}-W_{i}H_{j})h_{li}
\frac{\partial L}{\partial h_{lj}}=\gamma h_{lj}-\sum_{i=1}^{m}I_{ij}^{P}(p_{ij}-W_{i}H_{j})h_{il}

w_{il}^{t+1}=w_{il}^{t}-\alpha \frac{\partial L}{\partial w_{il}}
h_{lj}^{t+1}=h_{lj}^{t}-\alpha \frac{\partial L}{\partial h_{lj}}
\alpha是學(xué)習(xí)率捧杉,直到更新到收斂為止陕见。

Key Code on Spark

Item-based Collaboration Filtering Spark實現(xiàn)

Spark采用Scala語言編寫,建立在統(tǒng)一抽象的RDD(分布式內(nèi)存抽象)之上,使得它可以以基本一致的方式應(yīng)對不同的大數(shù)據(jù)處理場景味抖。Spark提供廣泛的數(shù)據(jù)集操作類型(20+種)淳玩,不像Hadoop只提供了Map和Reduce兩種操作。Spark可以與Hadoop無縫連接,使用YARN作為他的集群管理器非竿。
本次實驗靈活spark操作算子蜕着,在設(shè)計推薦算法時,我的宗旨時堅決不使用for-loop,while-loop,并且借鑒數(shù)據(jù)庫select的思想完成本次實驗承匣,Spark提交任務(wù)至集群運行蓖乘,使得實驗中提供的較大規(guī)模的數(shù)據(jù)集也能高效的跑完。

\subsection{Item-based Collaboration Filtering}

數(shù)據(jù)預(yù)處理階段:配置spark韧骗,設(shè)置為集群模式嘉抒,導(dǎo)入數(shù)據(jù)源(訓(xùn)練集和測試集),并對數(shù)據(jù)進行分割操作,其中movielens數(shù)據(jù)集以"::"分割袍暴,并且我只需要提取user些侍,item和preference,不需要timestamp政模,所以在預(yù)處理時我直接過濾了時間戳的數(shù)據(jù)岗宣,并設(shè)置userid,itemid的數(shù)據(jù)為Long淋样,設(shè)置評分preference為Double耗式,至此TrainSetRDD和TestSetRDD的RDD構(gòu)成都為

(user,item,preference),實現(xiàn)如下:

val conf = new SparkConf().setAppName("IBCF")
val sc = new SparkContext(conf)

val datasource1 = "file:///usr/local/data/test.txt"
val Mini2TestFile = sc.textFile(datasource1,3)

val datasource2 = "file:///usr/local/data/train.txt"
val Mini2TrainFile = sc.textFile(datasource2,3)

val TrainSetRDD = Mini2TrainFile.map(line => {
    val fields = line.split("::")
    (fields(0).toLong, fields(1).toLong, fields(2).toDouble)
})

val TestSetRDD = Mini2TestFile.map(line => {
    val fields = line.split("::")
    (fields(0).toLong, fields(1).toLong, fields(2).toDouble)
})
\end{lstlisting}

對訓(xùn)練集做處理,按照用戶id分組趁猴,并且組內(nèi)按照用戶評分的大小進行排序刊咳,實現(xiàn)如下:
\begin{lstlisting}
var ratings = TrainSetRDD.groupBy(k=>k._1).flatMap(x=>(x._2.toList.sortWith((x,y)=>x._3>y._3)))

首先,以item為key儡司,將訓(xùn)練集以item進行分組娱挨。item2manyUser中包含該item對應(yīng)曾經(jīng)購買過這個item的userid和對該item的評分信息的集合
然后,以item為key捕犬,統(tǒng)計購買過該item
的用戶數(shù)量让蕾。numRatersPerItem中包含itemid和購買過該item的user數(shù)量。

最后或听,對item2manyUser和numRatersPerItem
進行連接操作探孝,目的是為了得到這樣結(jié)構(gòu)的RDD:

(user,item,preference,size)其中size對應(yīng)的是對item評過分的user數(shù)量,計算這個值是為了之后求相似度做準(zhǔn)備誉裆。實現(xiàn)如下:

//(item,((user,item,prefs),(user,item,prefs),...))
val item2manyUser = ratings.groupBy(tup => tup._2)
        
//(item,ratingsize)
val numRatersPerItem = item2manyUser.map(grouped => (grouped._1, grouped._2.size))
        

//(user,item,prefs,ratingsize)
val ratingsWithSize = item2manyUser.join(numRatersPerItem).flatMap(
    joined => {
    joined._2._1.map(f => (f._1, f._2, f._3, joined._2._2))
})

因為在IBCF算法中顿颅,最重要的是要求得item之間的相似度sim(i,j)

想要得到成對的(item_{i},item_{j}),
可以借用數(shù)據(jù)庫的表連接的思想足丢,

使ratingsWithSize以userid為key進行自連接操作粱腻,效果類似笛卡兒積,我們可以得到任意(item_{i},item_{j}).
選取(item_{i}<item_{j})的rdd,因為相似度計算滿足對稱性斩跌,所以為了減少計算绍些,我們計算一半的值即可。

ratingPairs這個RDD所蘊含的意義是user歷史曾經(jīng)對item_{i}item_{j}都有進行評分耀鸦。實現(xiàn)如下:

//(user,(user,item,prefs,size))
val ratings2 = ratingsWithSize.keyBy(tup => tup._1)

//(user,((user,item,prefs,size),(user,item,prefs,size)))
val ratingPairs =ratings2.join(ratings2).filter(f => f._2._1._2 < f._2._2._2)

以余弦相似度為例柬批,cos_{X,Y} = \frac{\sum_{i=1}^{n}(x_{i} * y_{i})}{\sqrt{\sum_{i=1}^{n}x_{i}^{2}} * \sqrt{\sum_{i=1}^{n}y_{i}^{2}}}

計算兩個item相似度時啸澡,是使用對這個itemPair都進行過評分的用戶評分數(shù)據(jù)決定的。
我設(shè)計的是先計算itemPair相似度公式中的某些中間變量氮帐,然后再進行求和操作嗅虏。

從余弦計算公式中可以觀察到,可以計算的中間變量有:分子部分的點乘(dotProduct)和分母部分的平方量上沐。

對ratingPairs進行map操作皮服,可以對每一行數(shù)據(jù)都進行相同的操作,提取出
(item_{i},item_{j})作為key参咙,中間變量作為values龄广,得到一個新的RDD。

值得注意的是蕴侧,ratingPairs的key是userid择同,而tempVectorCalcs的key是(item_{i},item_{j})

從ratingPairs中提取出來戈盈,并且沒有選取userid的信息,這意味著(item_ {i},item_{j})的key不唯一谆刨,因為多個user可以對(item_ {i},item_{j})都進行評分塘娶。

// ((item1,item2), (tempValues))
val tempVectorCalcs = ratingPairs.map(data => {
    val key = (data._2._1._2, data._2._2._2) //(item1,item2)
    val values =
    (data._2._1._3 * data._2._2._3, // prefs 1 * prefs 2
    data._2._1._3,                // item 1 prefs
    data._2._2._3,                // item 2 prefs
    math.pow(data._2._1._3, 2),   // square of item 1 prefs
    math.pow(data._2._2._3, 2),   // square of item 2 prefs
    data._2._1._4,                // item 1 ratingsize
    data._2._2._4)                // item 2 ratingsize
    (key, values)
})

使用groupByKey 將(item,item)key相同的rdd分組,并且對中間變量進行處理,然后定義余弦相似度的函數(shù),形如dotProduct(A, B)/(norm(A) * norm(B))痊夭,最終得到相似度結(jié)果

item之間的相似度保存在一個新的RDD中刁岸,(itemi,(itemj,Sim(i,j))),至此,Item-basedCF的第一步——計算物品間的相似性已經(jīng)完成她我。實現(xiàn)如下:

//((item1,item2), (size, dotProduct, ratingSum, rating2Sum, ratingSq, rating2Sq, numRaters, numRaters2))
val vectorCalcs = tempVectorCalcs.groupByKey().map(data => {
    val key = data._1 //(item1,item2)
    val vals = data._2 //stats
    val size = vals.size // the number of users rating(item1,item2)
    val dotProduct = vals.map(f => f._1).sum // sum of prefs 1 * prefs 2 = dotProduct
    val ratingSum = vals.map(f => f._2).sum // sum of prefs 1
    val rating2Sum = vals.map(f => f._3).sum // sum of prefs 2
    val ratingSq = vals.map(f => f._4).sum // sum of square prefs 1
    val rating2Sq = vals.map(f => f._5).sum // sum of square prefs 2
    val numRaters = vals.map(f => f._6).max
    val numRaters2 = vals.map(f => f._7).max
    (key, (size, dotProduct, ratingSum, rating2Sum, ratingSq, rating2Sq, numRaters, numRaters2))
})

//(itemi,(itemj,Sim(i,j)))
val tempSimilarities = vectorCalcsTotal.map(fields => {
    val key = fields._1
    val (size, dotProduct, ratingSum, rating2Sum, ratingNormSq, rating2NormSq, numRaters, numRaters2) = fields._2
    val cosSim = cosineSimilarity(dotProduct, scala.math.sqrt(ratingNormSq), scala.math.sqrt(rating2NormSq))*size/(numRaters*math.log10(numRaters2+10))
    (key._1,(key._2, cosSim))
})

val similarities = tempSimilarities.groupByKey().flatMap(x => { x._2.map(temp => (x._1,(temp._1,temp._2))).toList.sortWith((a,b)=>a._2._2>b._2._2).take(50)
})

其中cosineSimilarity可以定義函數(shù)進行計算虹曙,這樣也比較方便替換不同相似性度量的方法:

  // *************************
  // * SIMILARITY MEASURES
  // *************************

  // [n * dotProduct(A, B) - sum(A) * sum(B)] / sqrt{ [n * norm(A)^2 - sum(A)^2] [n * norm(B)^2 - sum(B)^2] }
  def correlation(size : Double, dotProduct : Double, ratingSum : Double, rating2Sum : Double, ratingNormSq : Double, rating2NormSq : Double) = {

    val numerator = size * dotProduct - ratingSum * rating2Sum
    val denominator = scala.math.sqrt(size * ratingNormSq - ratingSum * ratingSum) * scala.math.sqrt(size * rating2NormSq - rating2Sum * rating2Sum)

    numerator / denominator
  }

  //The cosine similarity between two vectors A, B is
  //dotProduct(A, B) / (norm(A) * norm(B))
  def cosineSimilarity(dotProduct : Double, ratingNorm : Double, rating2Norm : Double) = {
    dotProduct / (ratingNorm * rating2Norm)
  }

  //The Jaccard Similarity between two sets A, B is
  //|Intersection(A, B)| / |Union(A, B)|
  def jaccardSimilarity(usersInCommon : Double, totalUsers1 : Double, totalUsers2 : Double) = {
    val union = totalUsers1 + totalUsers2 - usersInCommon
    usersInCommon / union
  }

接下來是預(yù)測評分的部分,根據(jù)預(yù)測公式P_{u,i} = \bar{R}_{i} + \frac{\sum_{j \in N(i:u)}^{ }sim(i,j)*(R_{u,j}-\bar{R}_{j})}{\sum_{j \in N(i:u)}^{ }|sim(i,j)|}

需要預(yù)測的是用戶u對商品i的評分(itemi用戶沒有購買過)番舆,也可以先得到某些中間變量如:sim(i,j)*ratings然后求和再相除酝碳。

//(item,(user,prefs))
val ratingsInverse = ratings.map(x => (x._2,(x._1,x._3)))

//join : (item1,(user,prefs)) <- (item1,(item2,sim)) ==>> ((user,item2),(sim,sim*prefs))
val statistics = ratingsInverse.join(similarities).map( x=> ((x._2._1._1,x._2._2._1),(x._2._2._2,x._2._1._2*x._2._2._2)))

val predictResult = statistics.reduceByKey((x,y) => ((x._1+y._1),(x._2+y._2))).map(x=>(x._1,x._2._2/x._2._1))


val filterItem = TrainSetRDD.map(x=>((x._1,x._2),Double.NaN))
val totalScore = predictResult ++ filterItem

val finalResult = totalScore.reduceByKey(_+_).filter(x=> !(x._2 equals(Double.NaN))).map( x =>
    (x._1._1,x._1._2,x._2)).groupBy(x=>x._1).flatMap(x=>(x._2.toList.sortWith((a,b)=>a._3>b._3)))

最后計算測試集中真實評分和預(yù)測評分的RMSE,評估算法的效果恨狈。

val joinTestSetRDD = TestSetRDD.map(x => ((x._1,x._2),(x._3)))
val joinFinalResult = finalResult.map(x => ((x._1,x._2),(x._3)))
val measure = joinTestSetRDD.join(joinFinalResult)
val rmseSize = measure.count()
val rmse = measure.map(x => x._2._1-x._2._2).map(y => y*y).reduce(_+_)/rmseSize

Slope One Spark實現(xiàn)

數(shù)據(jù)預(yù)處理部分和IBCF一樣疏哗,本部分省略。

Step1-1:計算物品之間的評分差的均值禾怠,記為物品間的評分偏差(兩物品同時被評分)返奉;

R(ij) = \frac{\sum_{u \in N(i)\cap N(j)}^{ }(R_{u,i}-R_{u,j})}{|N(i)\cap N(j)|} = \frac{\sum_{u \in N(i)\cap N(j)}^{ }(R_{u,i}-R_{u,j})}{card(N(i)\cap N(j))}

其中,R_{u,i}是用戶u對物品i的評分吗氏,R_{u,j}是用戶u對物品j的評分芽偏,N(i)是對物品i評分過的用戶,N(i)\cap N(j)是對物品i和物品j都評分過的用戶弦讽,|N(i)\cap N(j)|是對物品i和物品j都評分過的用戶數(shù)量污尉。

也是利用自連接的思想,找出所有的itemPairs,和它們之間的評分差(prefsi-prefsj)。

因為一對商品(itemi,itemj)可以被多個用戶進行評分十厢,所以這就提供了計算物品間平均偏差的機會等太。當(dāng)以(itemi,itemj)進行Group操作時,
同時購買過(itemi,itemj)的用戶會被聚合在同一行數(shù)據(jù)中蛮放,也就是N(i)\cap N(j)缩抡。

DevOfItemsPairs-all就是(itemi,itemj)的偏差結(jié)果,rdd形如 ((itemi,itemj),dev(i,j)))

var ratings = TrainSetRDD.groupBy(k=>k._1).flatMap(x=>(x._2.toList.sortWith((x,y)=>x._3>y._3)))

val ratings2 = ratings.keyBy(tup => tup._1)

// itemi<itemj
val ratingPairs =ratings2.join(ratings2).filter(f => f._2._1._2 < f._2._2._2)

//(usertest,itemj)
val TestUser = TestSetRDD.map(line => (line._1,line._2))

//Pairs(itemi,itemj)
val ItemsPairs = ratingPairs.map(data => {
    val key = (data._2._1._2, data._2._2._2) //(item1,item2)
    val stats = (data._2._1._3-data._2._2._3) //(prefs1-prefs2)
    (key, stats)
})

//((item1,item2),dev))
val DevOfItemsPairs = ItemsPairs.groupByKey().map(data => {
    val key = data._1
    val values = data._2
    val Card = values.size
    val Ruj_Rui = values.sum
    var R_Card = Ruj_Rui/Card
    (key,R_Card)
})
val DevOfItemsPairs_inverse = DevOfItemsPairs.map(x => ((x._1._2,x._1._1),x._2))

//((item1,item2),dev))
val DevOfItemsPairs_all = DevOfItemsPairs ++ DevOfItemsPairs_inverse


//(user,Sertof(Items))
val GroupByUser_ItemSets = TrainSetRDD.map(line => (line._1,line._2)).groupByKey()

// (usertest,itemj) join (user,Sertof(Items)) => (user,(itemj,Setof(Items))) => ((itemj,items),user)
val TestUser_ItemSets = TestUser.join(GroupByUser_ItemSets).flatMap(data => data._2._2.map(x => ((data._2._1,x),data._1)))

//((itemj,item2)(usertest,dev_j2)) => ((usertest,itemj),(dev_j2))  =groupByKey=> Sum/Card

val DevOfTestItemPairs = TestUser_ItemSets.join(DevOfItemsPairs_all).map(x => ((x._2._1,x._1._1),x._2._2)).groupByKey().map(data => {
    val key = data._1
    val values = data._2
    val Size = values.size
    val Sum = values.sum
    val avg_dev = Sum/Size
    (key,avg_dev)
})

Step1-2:計算用戶歷史評分的均值

以user為key進行聚合操作包颁,得到的集合是用戶對所有購買過的item的評分情況瞻想。對該評分集合求均值即是用戶的歷史評分

//(user,Setof(prefs))
val RatingPrefs = ratings.map(data => (data._1,data._3)).groupByKey()

//(usertest,itemj)
val TestUser = TestSetRDD.map(line => (line._1,line._2))


//after join:(user,(item,Setof(prefs)))
val JoinedByUser = TestUser.join(RatingPrefs).map(data => {
    val key = (data._1,data._2._1) //(user,item)
    val value = data._2._2 //Setof(prefs)
    (key,value)
})

//key = (user,item)  value = the average rating of user
val RatingByUser = JoinedByUser.map(data => {
    val key = data._1
    val values = data._2
    val Card = values.size
    val SumRatings = values.sum
    val avg_user_rating = SumRatings/Card
    (key,avg_user_rating)
})

Step2:根據(jù)物品間的評分偏差和用戶的歷史評分,預(yù)測用戶對未評分的物品的評分娩嚼。

P_{u,i} = \frac{\sum_{j \in \{N(u)-\{j\}\}}^{ }(R_{u,j}-R(ij))}{card(\{N(u)-\{j\}\})}

其中N(u)是用戶u購買過的物品蘑险,card(\cdot)為集合中的元素個數(shù)

將Step1里的物品間的評分偏差(DevOfTestItemPairs)和用戶的歷史評分(RatingByUser)結(jié)合進行評分的預(yù)測并評估算法。

val predict = DevOfTestItemPairs.join(RatingByUser).map(data => (data._1,data._2._1 + data._2._2))

val joinTestSetRDD = TestSetRDD.map(data => ((data._1,data._2),data._3))
val measure = predict.join(joinTestSetRDD)
val rmseSize = measure.count()
val rmse = measure.map(x => x._2._1-x._2._2).map(y => y*y).reduce(_+_)/rmseSize

MF - Matlab實現(xiàn)
首先矩陣分解的目的是為了填補缺失值岳悟,然后應(yīng)用在推薦系統(tǒng)中的作用便是填補用戶對未購買過的商品進行評分值佃迄,從而根據(jù)預(yù)測值向用戶推薦item。
數(shù)據(jù)預(yù)處理與前兩個算法不同贵少,需要構(gòu)造user-item評分矩陣呵俏,并且原來有評分的位置保持不變,沒有評分的位置置零滔灶。在計算LossFunction時普碎,只需要比較原矩陣有值的rmse,原矩陣沒有值的地方不需要計算誤差录平。

function [rating,prerating]=MF(data,K,alpha)
    [rating,num]=translate_line_to_matrix(data);
    [m,n]=size(rating);
    u=rand(m,K);
    v=rand(K,n);
    e=zeros(m,n);
    distance=100000000;
    while(1)
        for i=1:m
            for j=1:n
                if(rating(i,j)>0)
                    error=0;
                    for k=1:K
                        error=error+u(i,k)*v(k,j);
                    end
                    e(i,j)=rating(i,j)-error;
                    for k=1:K
                        u(i,k)=u(i,k)+2*alpha*e(i,j)*v(k,j);
                        v(k,j)=v(k,j)+2*alpha*e(i,j)*u(i,k);
                    end
                end
            end
        end
        MFLossFunc=sum(sum(e));
        if(distance-MFLossFunc<0.0000000001)
            break;
        else
            distance=MFLossFunc;
        end
    end
    prerating=u*v;
end

Algorithm Comparison and Hyperparameter

Item-based算法的預(yù)測結(jié)果比User-based算法的質(zhì)量要高一點麻车。由于Item-based算法可以預(yù)先計算好物品的相似度,所以在線的預(yù)測性能要比User-based算法的高斗这。
基于用戶之間評級相似性的早期協(xié)作過濾系統(tǒng)(稱user-user協(xié)同過濾)存在以下幾個問題:(1)當(dāng)他們有很多項目但收視率相對較低時动猬,系統(tǒng)表現(xiàn)不佳
(2)計算所有用戶對之間的相似性非常昂貴
(3)用戶配置文件變化很快,整個系統(tǒng)模型必須重新計算

item-based解決了用戶數(shù)量多于項目的系統(tǒng)中的這些問題表箭。 item-item模型使用每個項目的評級分布枣察,而不是每個用戶。 如果用戶數(shù)量多于商品數(shù)量燃逻,則每個商品的評分往往比每位用戶都多序目,因此商品的平均評分通常不會很快發(fā)生變化。 這導(dǎo)致模型中的評級分布更加穩(wěn)定伯襟,所以模型不需要經(jīng)常重建猿涨。 當(dāng)用戶使用并評估某個項目時,該項目的相似項目將從現(xiàn)有系統(tǒng)模型中挑選出來并添加到用戶的個性化推薦中姆怪。

Slope One是項目協(xié)同過濾算法家族中的一員叛赚,旨在減少模型過度擬合問題澡绩。 可以說,它是基于評分的非平凡基于項目的協(xié)同過濾(non-trivial item-based collaborative filtering based on ratings)的最簡單形式俺附。 它們的簡單性使其特別易于有效地實現(xiàn)它們肥卡,而其精度通常與更復(fù)雜且計算量更大的算法相當(dāng), 它也被用作改進其他算法的構(gòu)建塊。
SlopeOne易于實現(xiàn)和維護事镣,可以輕松解釋所有的聚合數(shù)據(jù)步鉴,并且算法易于實現(xiàn)和測試。具有實時性璃哟, 運行時可更新氛琢,新增一個評分項,應(yīng)該對預(yù)測結(jié)果即時產(chǎn)生影響随闪。有高效率的查詢響應(yīng)阳似,快速的執(zhí)行查詢,可能需要付出更多的空間占用作為代價铐伴,對初次訪問者要求少:對于一個評分項目很少的用戶撮奏,也應(yīng)該可以獲得有效的推薦。與最準(zhǔn)確的方法相比当宴,SlopeOne應(yīng)該是有競爭力的畜吊,不僅算法簡單高效,效果也不賴即供。

矩陣分解在推薦系統(tǒng)中的應(yīng)用我覺得非常神奇定拟。隱語義模型(Latent Factor Model于微,LFM)在推薦系統(tǒng)中的應(yīng)用越來越廣泛逗嫡,矩陣分解方法也是基于這個隱語義模型。設(shè)置K為因子factor的數(shù)量株依。矩陣分解的思想簡單來說就是每一個用戶和每一個物品都會有自己的一些特性
(feature/factor)驱证,用矩陣分解的方法可以從評分矩陣中分解出user-factor,factor-item矩陣恋腕,這樣做的好處是得到了用戶的偏好和每件物品的特性抹锄。用戶對電影來舉例子就是:每個用戶看電影的時候都有偏好,這些偏好可以直觀理解成:恐怖荠藤,喜劇伙单,動作,愛情等哈肖。用戶——特性矩陣表示的就是用戶對這些因素的喜歡程度吻育。同樣,每一部電影也可以用這些因素描述淤井,因此特性——物品矩陣表示的就是每一部電影這些因素的含量布疼,也就是電影的類型摊趾。這樣子兩個矩陣相乘就會得到用戶對這個電影的喜歡程度。

mf.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末游两,一起剝皮案震驚了整個濱河市砾层,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贱案,老刑警劉巖肛炮,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異轰坊,居然都是意外死亡铸董,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門肴沫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粟害,“玉大人,你說我怎么就攤上這事颤芬”” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵站蝠,是天一觀的道長汰具。 經(jīng)常有香客問我,道長菱魔,這世上最難降的妖魔是什么留荔? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮澜倦,結(jié)果婚禮上聚蝶,老公的妹妹穿的比我還像新娘。我一直安慰自己藻治,他們只是感情好碘勉,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桩卵,像睡著了一般验靡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雏节,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天胜嗓,我揣著相機與錄音,去河邊找鬼钩乍。 笑死辞州,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的件蚕。 我是一名探鬼主播孙技,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼产禾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了牵啦?” 一聲冷哼從身側(cè)響起亚情,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哈雏,沒想到半個月后楞件,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡裳瘪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年土浸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彭羹。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡黄伊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出派殷,到底是詐尸還是另有隱情还最,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布毡惜,位于F島的核電站拓轻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏经伙。R本人自食惡果不足惜扶叉,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帕膜。 院中可真熱鬧枣氧,春花似錦、人聲如沸泳叠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽危纫。三九已至,卻和暖如春乌庶,著一層夾襖步出監(jiān)牢的瞬間种蝶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工瞒大, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留螃征,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓透敌,卻偏偏與公主長得像盯滚,于是被迫代替她去往敵國和親踢械。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容