白話推薦系統——從原理到實踐,還有福利贈送!


之前流水賬似的介紹過一篇機器學習入門的文章,大致介紹了如何學習以及機器學習的入門方法並提供了一些博主自己整理的比較有用的資源。這篇就盡量以白話解釋並介紹機器學習在推薦系統中的實踐以及遇到的問題... 也許很多點在行家的眼里都是小菜一碟,但是對於剛剛接觸機器學習來說,還有很多未知等待挑戰。

所以讀者可以把本篇當做是機器學習的玩具即可,如果文中有任何問題,還請不吝指教。

本篇將會以下面的步驟描述機器學習是如何在實踐中應用的:

  • 1 什么是推薦系統?
  • 2 機器學習的作用
  • 3 機器學習是如何使用的?
  • 4 基於Spark MLlib的機器學習實踐
  • 5 推薦資源

翻到最后都是福利啊!

翻到最后都是福利啊!

翻到最后都是福利啊!

問題背景

為什么需要推薦

最開始互聯網興起的時候,是靠分類來組織知識的,最典型的就是hao123;后來隨着搜索引擎的興起,人們主動的獲取知識成為流行趨勢,例如百度、Google。基於搜索人們可以看到想看的電影,搜到想買的衣服。但是這並能滿足所有人的需求,有時候無聊逛一些網站,希望網站能主動發現我的興趣點,並且主動的給我我感興趣的內容 ——這就是推薦。比如各種電商網站和視頻網站,都可以基於用戶搜索的內容和常看的內容,挖掘用戶的興趣,給用戶展現用戶想看卻不知道怎么搜索到的內容。預知用戶的需求,這就是推薦的魅力。

這么神奇的功能是怎么做的?難道每個網站都有專門的狗仔跟蹤每個用戶的需求?這當然是不可能的...

實現推薦的方法有很多,最典型的就是協同過濾。

推薦中的機器學習

協同過濾我就簡單的說一下,因為它現在實在是應用的太廣泛的....

基於物品的協同過濾

舉個例子:

A:前一陣上映的《刺客信條》,我特別喜歡!最近有沒有類似的電影啊?
B:我感覺《加勒比海盜》跟他差不多,都是大片!要不你去看看?
A:好呀!那我去看看!

這就是基於物品的協同過濾,即推薦相似的物品給這個人。

因為A和C物品很相似,因此C用戶喜歡了A物品,那么推測他也會喜歡C物品,因此把C物品推薦給他。

基於人的協同過濾

舉個例子:

A:最近好無聊,你有沒有什么喜歡的電影,介紹一下?
B:我喜歡看《神奇女俠》,要不你去看看?
A:好滴,一般你推薦的電影我都喜歡,那周末我去看看!

這就是基於人的協同過濾,即會依據相似的人來推薦喜歡的內容。

相似,因此就把C喜歡的物品D推薦給了A。

其實推薦就是這么簡單,那么后續我們來看看它的內部原理和實踐吧!

機器學習

數學知識

上面就是典型的協同過濾的場景,要想弄明白如何基於機器學習實現協同過濾,還需要回顧一下數學的基本知識。

很多人都因為數學而不敢深入學習機器學習,其實大家都是上過高數線代概率論的,所以等真正用它的時候,回去翻翻對應的教材,很快就可以撿起來的。如果沒時間也可以在網上看看別人總結的一些公式,最基礎的應該知道高數中的求導和微分、矩陣的運算、概率論中的一些分布等等。剩下的就針對性的查查書籍即可。

之前看過一篇帖子,還是很基礎的,可以看看:

http://www.cnblogs.com/steven-yang/p/6348112.html

理論原理

在協同過濾中,最基礎的是要構建人與物品的評分矩陣,這個評分可能來自於你對物品的操作,比如電上網站中,購買或者收藏物品,瀏覽物品等等都會作為評分的因素進行計算。最終形成人與物品的二維矩陣:

形成上面的矩陣后,就可以進行基於物品或者基於人的推薦了。

因為物品A和物品C很像,因此物品C推薦給還未購買的用戶C

因為用戶A和用戶C比較像,因此會把用戶C購買的物品推給用戶A

如何計算是否相似

我之前總結過相似度的一些算法:

http://www.cnblogs.com/xing901022/p/6927024.html

在協同過濾中,常用的是歐氏距離、夾角余弦、皮爾遜系數以及傑卡德距離,有興趣的可以關注下各個算法的實現。

降維

在真正的電商環境下,往往具有很多的用戶以及很多的商品,每個用戶並不是對所有的商品都有評分的,因此這個矩陣實際上是一個非常稀疏的矩陣。如果想要在計算機中完全的表示這樣一個矩陣,它其實根本無法計算,數據量實在太龐大了(除非你的數據量根本沒那么大,那么可以直接跳過這一部分了)。

在這種二維矩陣中,最常用的降維手段是SVD——矩陣分解。有矩陣基礎的都應該知道,一個MxN的矩陣可以由一個MxK以及KxN的兩個矩陣相乘得出。因此降維的手段就是把這個矩陣分解成兩個矩陣相乘。

比如,一個矩陣形成下面兩個矩陣:

實際在機器學習中,是使用交替最小二乘ALS來求解兩個矩陣的。再說就遠了,可以簡單的理解成,先隨機一個MxK的矩陣,然后用ALS求得另一個矩陣,然后固定這個求得的矩陣,再反過來求第一個矩陣,直到找到近似的最優解。這個最后得到的兩個矩陣,實際上相乘后,原來有的值還在,但是原來的沒有的會預測出來一個分值。基於這個分值,就可以做用戶的推薦了。

系統架構

架構設計

關於機器學習中的系統架構,可以仿照美團很多年前寫的一篇文章,現在看來對於剛開始構建推薦系統,還是很有幫助的。

  • 在構建推薦系統前,首先應該有足夠完善的數倉機制,能拿到想要的底層數據。
  • 基於底層數據,可以進行數據的預處理,比如歸一化、標准化、去除噪聲點離群點等。
  • 數據預處理之后,應該通過一定的評分機制形成評分矩陣。
  • 基於評分矩陣訓練模型,得出模型后就可以進行推薦了。
  • 因為推薦的算法可能有多種,最后還需要把各個結果進行融合去重
  • 去重后的推薦列表需要經過特定的排序算法,展現給用戶。排序的算法一般跟業務相關,比如基於權重、交替顯示、分區顯示,或者是基於LR等算法進行排序。

另外,這里只看到了離線的部分,通常推薦還需要結合實時的部分,比如用戶當前搜索的條件、地理位置、時間季節等,進行實時的跟蹤推薦。

這樣一個推薦系統的架構就完成了。

注意的問題

  • 首先,就是判斷你的數據量是不是需要降維,如果數據量很小,降維后推薦的效果未必好;
  • 其次,數據最初的調查非常重要。比如有多少用戶、有多少商品、多少是合法的數據、清洗的規則等等
  • 另外,各種推薦的算法各有特點,需要針對業務場景進行整合和顯示。

針對第三種情況,可以詳細說下:

  • 如果你是在商品的詳情頁,那么一般用戶最常見的需求,一個是對比同類的產品、另一個是查看關聯度最高的商品。同類的產品可以基於內容來做、關聯度最高的商品可以參考《機器學習實戰》中Apriori以及FPgrowth,最典型的就是啤酒和尿布的案例。
  • 如果是在購物車的頁面,最好是只推薦關聯或者搭配活動的商品,不然用戶本來想買A,你給推薦了同類的商品B,結果用戶反而猶豫不決,不敢下單。
  • 如果是在支付完成的頁面,那么最好推薦搭配的商品,比如用戶剛剛買過螺絲刀,你要是再給推薦一個更便宜的螺絲刀,用戶真實恨不得馬上退貨。如果你再推薦一個扳子或者錘子或者釘子,那感覺就不一樣了。
  • 如果是列表的詳情頁,就復雜了。最好還是基於用戶當前的搜索來挖掘用戶潛在的需求,這樣的推薦才會更精確。比如說,你通過歷史記錄,猜測用戶最喜歡的是襪子;結果用戶登錄網頁,搜索的是牛肉干,如果沒事結合搜索條件,推薦出來的最靠前的可能是襪子,這並不是用戶當前的需求,那效果肯定是不好的。

等等,很多的場景都需要結合業務來設定,上面說的也不是官方的做法,只是個人的想法而已。

代碼實踐

最后就直接基於Spark MLlib,來實踐一下ALS的協同過濾吧!

基於Spark MLlib的協同過濾

代碼和測試數據都是基於Spark官方提供的example包,如果讀者有興趣可以查看官網文檔,各個例子都有描述。

數據也可以在下面的雲盤中下載:
http://pan.baidu.com/s/1dF07bAL

代碼如下,修改下路徑,就可以直接跑的!

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".
         */
        // 為每一行創建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%的數據作為訓練集
    val training = splits(0).cache()
    // 用20%的數據作為測試集
    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) //迭代次數為10
      .setLambda(1) //正則項lambda參數為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())
  }
}

推薦資源——都是福利

數學篇

1 《高等數學·統計大學 第六版》·上冊,需要看一下導數和微分

2 《高等數學·同濟大學 第六版》·下冊,最小二乘在下冊,也可以看下!

3 《線性代數·同濟大學 第五版》

4 《概率論與數理統計·浙江大學 第四版》

機器學習理論篇

5 《統計分析方法學》,里面好多重要的概念,比如正則化、標注等等

6 《機器學習》,權威書籍,這個就不給鏈接了,怕侵權!有想要的私聊吧....

機器學習Spark實戰篇

7 《Spark MLlib機器學習實戰》,這個同上

8 《Spark2.0》

所有圖片來自於網絡,原諒我懶,沒時間自己畫!侵權請聯系,立馬換掉...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM