之前流水賬似的介紹過一篇機器學習入門的文章,大致介紹了如何學習以及機器學習的入門方法并提供了一些博主自己整理的比較有用的資源。這篇就盡量以白話解釋并介紹機器學習在推薦系統(tǒng)中的實踐以及遇到的問題... 也許很多點在行家的眼里都是小菜一碟,但是對于剛剛接觸機器學習來說,還有很多未知等待挑戰(zhàn)。
所以讀者可以把本篇當做是機器學習的玩具即可,如果文中有任何問題,還請不吝指教。
本篇將會以下面的步驟描述機器學習是如何在實踐中應用的:
問題背景為什么需要推薦
最開始互聯(lián)網(wǎng)興起的時候,是靠分類來組織知識的,最典型的就是;后來隨著搜索引擎的興起,人們主動的獲取知識成為流行趨勢,例如百度、。基于搜索人們可以看到想看的電影,搜到想買的衣服。但是這并能滿足所有人的需求,有時候無聊逛一些網(wǎng)站,希望網(wǎng)站能主動發(fā)現(xiàn)我的興趣點,并且主動的給我我感興趣的內容 ——這就是推薦。比如各種電商網(wǎng)站和視頻網(wǎng)站,都可以基于用戶搜索的內容和常看的內容,挖掘用戶的興趣,給用戶展現(xiàn)用戶想看卻不知道怎么搜索到的內容。預知用戶的需求,這就是推薦的魅力。
這么神奇的功能是怎么做的?難道每個網(wǎng)站都有專門的狗仔跟蹤每個用戶的需求?這當然是不可能的...
實現(xiàn)推薦的方法有很多,最典型的就是協(xié)同過濾。
推薦中的機器學習
協(xié)同過濾我就簡單的說一下,因為它現(xiàn)在實在是應用的太廣泛的....
基于物品的協(xié)同過濾
舉個例子:
A:前一陣上映的《刺客信條》,我特別喜歡!最近有沒有類似的電影啊?
B:我感覺《加勒比海盜》跟他差不多,都是大片!要不你去看看?
A:好呀!那我去看看!
這就是基于物品的協(xié)同過濾,即推薦相似的物品給這個人。
因為A和C物品很相似,因此C用戶喜歡了A物品,那么推測他也會喜歡C物品,因此把C物品推薦給他。
基于人的協(xié)同過濾
舉個例子:
A:最近好無聊,你有沒有什么喜歡的電影,介紹一下?
B:我喜歡看《神奇女俠》,要不你去看看?
A:好滴,一般你推薦的電影我都喜歡,那周末我去看看!
這就是基于人的協(xié)同過濾協(xié)同過濾推薦系統(tǒng)ppt,即會依據(jù)相似的人來推薦喜歡的內容。
相似,因此就把C喜歡的物品D推薦給了A。
其實推薦就是這么簡單,那么后續(xù)我們來看看它的內部原理和實踐吧!
機器學習數(shù)學知識
上面就是典型的協(xié)同過濾的場景,要想弄明白如何基于機器學習實現(xiàn)協(xié)同過濾,還需要回顧一下數(shù)學的基本知識。
很多人都因為數(shù)學而不敢深入學習機器學習,其實大家都是上過高數(shù)線代概率論的,所以等真正用它的時候,回去翻翻對應的教材,很快就可以撿起來的。如果沒時間也可以在網(wǎng)上看看別人總結的一些公式,最基礎的應該知道高數(shù)中的求導和微分、矩陣的運算、概率論中的一些分布等等。剩下的就針對性的查查書籍即可。
之前看過一篇帖子,還是很基礎的,可以看看:
理論原理
在協(xié)同過濾中,最基礎的是要構建人與物品的評分矩陣,這個評分可能來自于你對物品的操作,比如電上網(wǎng)站中,購買或者收藏物品,瀏覽物品等等都會作為評分的因素進行計算。最終形成人與物品的二維矩陣:
形成上面的矩陣后,就可以進行基于物品或者基于人的推薦了。
因為物品A和物品C很像,因此物品C推薦給還未購買的用戶C
因為用戶A和用戶C比較像,因此會把用戶C購買的物品推給用戶A
如何計算是否相似
我之前總結過相似度的一些算法:
在協(xié)同過濾中,常用的是歐氏距離、夾角余弦、皮爾遜系數(shù)以及杰卡德距離,有興趣的可以關注下各個算法的實現(xiàn)。
降維
在真正的電商環(huán)境下,往往具有很多的用戶以及很多的商品,每個用戶并不是對所有的商品都有評分的,因此這個矩陣實際上是一個非常稀疏的矩陣。如果想要在計算機中完全的表示這樣一個矩陣,它其實根本無法計算,數(shù)據(jù)量實在太龐大了(除非你的數(shù)據(jù)量根本沒那么大,那么可以直接跳過這一部分了)。
在這種二維矩陣中,最常用的降維手段是SVD——矩陣分解。有矩陣基礎的都應該知道,一個MxN的矩陣可以由一個MxK以及KxN的兩個矩陣相乘得出。因此降維的手段就是把這個矩陣分解成兩個矩陣相乘。
比如,一個矩陣形成下面兩個矩陣:
實際在機器學習中,是使用交替最小二乘ALS來求解兩個矩陣的。再說就遠了,可以簡單的理解成,先隨機一個MxK的矩陣,然后用ALS求得另一個矩陣,然后固定這個求得的矩陣,再反過來求第一個矩陣,直到找到近似的最優(yōu)解。這個最后得到的兩個矩陣,實際上相乘后,原來有的值還在,但是原來的沒有的會預測出來一個分值。基于這個分值,就可以做用戶的推薦了。
系統(tǒng)架構架構設計
關于機器學習中的系統(tǒng)架構,可以仿照美團很多年前寫的一篇文章,現(xiàn)在看來對于剛開始構建推薦系統(tǒng),還是很有幫助的。
另外,這里只看到了離線的部分,通常推薦還需要結合實時的部分,比如用戶當前搜索的條件、地理位置、時間季節(jié)等,進行實時的跟蹤推薦。
這樣一個推薦系統(tǒng)的架構就完成了。
注意的問題
針對第三種情況,可以詳細說下:
等等,很多的場景都需要結合業(yè)務來設定,上面說的也不是官方的做法,只是個人的想法而已。
代碼實踐
最后就直接基于Spark MLlib,來實踐一下ALS的協(xié)同過濾吧!
基于Spark MLlib的協(xié)同過濾
代碼和測試數(shù)據(jù)都是基于Spark官方提供的包,如果讀者有興趣可以查看官網(wǎng)文檔,各個例子都有描述。
數(shù)據(jù)也可以在下面的云盤中下載:
?????
代碼如下,修改下路徑協(xié)同過濾推薦系統(tǒng)ppt,就可以直接跑的!
package xingoo.mllib
import org.apache.spark.mllib.recommendation.{ALS, MatrixFactorizationModel, Rating}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Created by xinghailong on 2017/6/9.
*/
object MovieLensALSTest {
val implicitPrefs: Boolean = true
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("MovieLensALS-Test").setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
// 讀取評分矩陣
val ratings = sc.textFile("C:\\Users\\xingoo\\Documents\\workspace\\my\\Spark-MLlib-Learning\\resouce\\sample_movielens_ratings.txt")
.map { line =>
val fields = line.split("::")
// 是否有負的評分
if (implicitPrefs) {
/*
* MovieLens ratings are on a scale of 1-5:
* 5: Must see
* 4: Will enjoy
* 3: It's okay
* 2: Fairly bad
* 1: Awful
* So we should not recommend a movie if the predicted rating is less than 3.
* To map ratings to confidence scores, we use
* 5 -> 2.5, 4 -> 1.5, 3 -> 0.5, 2 -> -0.5, 1 -> -1.5. This mappings means unobserved
* entries are generally between It's okay and Fairly bad.
* The semantics of 0 in this expanded world of non-positive weights
* are "the same as never having interacted at all".
*/
// 為每一行創(chuàng)建Rating
Rating(fields(0).toInt, fields(1).toInt, fields(2).toDouble - 2.5)
} else {
Rating(fields(0).toInt, fields(1).toInt, fields(2).toDouble)
}
}.cache()
val numRatings = ratings.count()
val numUsers = ratings.map(_.user).distinct().count()
val numMovies = ratings.map(_.product).distinct().count()
println(s"Got $numRatings ratings from $numUsers users on $numMovies movies.")
// 按照權重切分rdd
val splits = ratings.randomSplit(Array(0.8, 0.2))
// 用80%的數(shù)據(jù)作為訓練集
val training = splits(0).cache()
// 用20%的數(shù)據(jù)作為測試集
val test = if (implicitPrefs) {
/*
* 0 means "don't know" and positive values mean "confident that the prediction should be 1".
* Negative values means "confident that the prediction should be 0".
* We have in this case used some kind of weighted RMSE. The weight is the absolute value of
* the confidence. The error is the difference between prediction and either 1 or 0,
* depending on whether r is positive or negative.
*/
splits(1).map(x => Rating(x.user, x.product, if (x.rating > 0) 1.0 else 0.0))
} else {
splits(1)
}.cache()
val numTraining = training.count()
val numTest = test.count()
println(s"Training: $numTraining, test: $numTest.")
ratings.unpersist(blocking = false)
val model = new ALS()
.setRank(10) //矩陣分解的隱含分類為10
.setIterations(10) //迭代次數(shù)為10
.setLambda(1) //正則項lambda參數(shù)為1
.setImplicitPrefs(implicitPrefs)
.run(training)
// 計算模型的準確度
val rmse1 = computeRmse(model, training, implicitPrefs)
val rmse = computeRmse(model, test, implicitPrefs)
println(s"Test RMSE = $rmse1.")
println(s"Test RMSE = $rmse.")
sc.stop()
}
/** Compute RMSE (Root Mean Squared Error). */
def computeRmse(model: MatrixFactorizationModel, data: RDD[Rating], implicitPrefs: Boolean)
: Double = {
def mapPredictedRating(r: Double): Double = {
if (implicitPrefs) math.max(math.min(r, 1.0), 0.0) else r
}
val predictions: RDD[Rating] = model.predict(data.map(x => (x.user, x.product)))
val predictionsAndRatings = predictions.map{ x =>
((x.user, x.product), mapPredictedRating(x.rating))
}.join(data.map(x => ((x.user, x.product), x.rating))).values
math.sqrt(predictionsAndRatings.map(x => (x._1 - x._2) * (x._1 - x._2)).mean())
}
}