推荐系统的数学模型-从矩阵分解到推荐系统(Scala实现)

简介: 推荐系统的数学模型-从矩阵分解到推荐系统(Scala实现)

词汇:

Matrix Factorization 矩阵分解

Recommendation System 推荐系统

User 用户 Feature 特征 Item 物品

简介:

不论有没有觉察到,互联网的搜索模式在近几年已经发生了颠覆性的变化。如果说是十年前叫做百度模式,那今天可以被称之为头条模式。两者的区别在于,百度模式提供一个入口,让用户按照自己的需求查询关心的内容(各种广告暂不考虑),头条是按照用户的搜索历史及浏览记录,推送与之相似的内容,如此,用户可以投入更少的精力,更大概率的得到符合自己喜好的节目。

这篇文章不讨论两种模式孰优孰劣,或者谁更有发展前景,只是从纯技术的角度,分析实现推荐系统的数学模型。

业务场景:

假设我们在豆瓣上获取了一组用户评分,我们整理了四位用户 的评分信息,整理如下:


第一滴血 风月俏佳人 小萝莉的猴神大叔 海王 愤怒的黄牛 我是山姆 我不是药神 唐人街探案 星际穿越 变态假面
李雷 9.0 - - 8.5 7.0 2.0 6.5 8.0 - -
韩梅梅 - 9.5 9.0 2.0 - 9.0 9.3 - 9.5 -
Jim Green 9.5 2 8.0 9.0 - 7.0 - 8.0 6.0 6.0
Lucy 3.0 10.0 - 4.0 8.5 - 9.5 4.0 - -

这里面 - 表示用户对某部电影没有相应的评分记录,可以理解成用户并没有看过这部电影,这部电影具有潜在推荐价值

面对这样一组数据,你最直接的反应是什么?

我们先对这些电影打一个粗略的标签:

特征 电影
剧情类 唐人街探案、我不是药神、星际穿越、我不是药神
动作类 第一滴血、愤怒的黄牛、海王
情感类 风月俏佳人、我是山姆、小萝莉的猴神大叔、星际穿越
恶趣味类 变态假面

如果我们把李雷和Jim Green分成一组,韩梅梅和Lucy分成一组,会发现同一组的成员对电影的喜好更为一致。那么,我们可不可以这样操作,将李雷看过但是Jim Green没有看过的电影推荐给Jim Green,对其他人也采取相同的推荐方法。

这正是推荐系统要实现的。就好比两个好朋友互相推荐或者相约去电影院一样。唯一不同的地方就是,这里的两个人可能完全不知道对方的存在,但是通过推荐系统,我们帮助他们建立了”品味相近的朋友关系“。

引入特征:

接下来,我们在 用户user 和 物品item(在上面的例子中就是电影)引入特征 feature,并且将 user 对具体 item 的喜好转换成 user 对具有某一类特征的item具有倾向性,也就是说,如果两个 user 对相同的特征具有倾向性,那么他们也大概率会喜欢彼此关注的具有这类feature 的 item。feature可以是各种因素,比如两个 user 都喜欢同一个明星,或者喜欢某一种导演的风格。在我们的分析中,我们不会挖掘究竟 user 是被哪一种 feature 吸引。

此外,我们还假设每一次分析,feature的个数都要小于 user 的个数以及 item 的个数。理由如下:我们不会考虑每一个 user 都只关心一种 feature,而不会和别的 user 共同关注一个feature的情况。否则的话,推荐就毫无意义,因为每一个 user 都对其他 user 关注的 item 毫无兴趣。对于 item 而言,也是一样的道理。

矩阵分解:

假设我们获得了  user-item 矩阵,R: |U|✖|D|, |U| 表示 user 的个数,|D| 表示 item 的个数。元素  表示第i个user对第j个item的评分,如果用户对某一个产品没有评分,设为0。

接下来,我们将矩阵 R 分解成 user-feature 和 feature-item 矩阵的乘积:

P :|U|✖|K|,|K| 表示 feature 的个数,元素  表示第i个user对第k个feature的喜好程度;

Q:|K|✖|D|,|K| 表示 feature 的个数,元素  表示第第j个item对第k个feature的符合程度。item-feature关联矩阵为 ,求出Q之后,一般还要做一次转置或者item-feature矩阵。

对于  中的每一个因子,有

我们希望R 和  尽可能的接近,因为每一个元素的差异可能为正,可能为负,我们采用所有元素差值的平方和作为R 和  差异的表征。

接下来就要用到梯度下降法了,以,  为自变量,求梯度。一个多元函数的梯度表示这个函数值增长最快的方向,因此如果要求这个函数的最小值,只要每次都沿着梯度的反方向移动就行。

计算梯度如下:

每次移动的步长取α = 0.002,如果α 取得太大,可能会越过最小值,结果就是在最小值附近来回跳动。

对于总偏差,有如下公式:

我们在初始化 user item 矩阵时,对于 user 没有评分的 item 直接赋值为 0。这样就会产生一个问题,当矩阵P ✖ Q 不断逼近 R 时,未评分项都会趋近于0。产生的结果就是 user 对这个 item 没有任何兴趣。实际应用中,我们并不会让P Q的乘积和R一模一样。相反,我们只是试图减少已经评分的 user - item 的偏差值。比如我们将所有已经评分的 (user,item,rating) 组成一个集合T(T也是常说的训练数据 training data),我们需要的是对这个集合内的元素偏差 之和 尽可能的小。而对于那些未知的评分,一旦我们通过已知项计算出了矩阵P,Q,自然可以计算出结果。

基于以上分析,我们将偏差 e 的定义域重新现在在集合T上,由此得到偏差的表达式为:

正则化

上面的算法是分解矩阵最基础的算法。还有更多的分解方法,当然这些方法也会更复杂。一个常用的扩展方法是,通过正则化避免过度拟合。这个方法是通过增加参数β和调节平方差实现的:

新参数 β 用来控制空值 user-feature 和 item-feature 向量的量级,这样不需要包含大数字就可以较好的近似R。在实际应用中,β 一般被设置为0.02。通过类似的步骤,更新的平方差公式如下:

Scala 代码实现

只是为了展示推荐算法的原理,代码采用未经过正则化处理的公式。


package pers.machi
import java.io.{File, PrintWriter}import breeze.linalg.DenseMatriximport breeze.linalg._
object RecomendationSystem {  def main(args: Array[String]): Unit = {    val v1 = Array[Double](9.0, 0, 0, 8.5, 7.0, 2.0, 6.5, 8.0, 0, 0)    val v2 = Array[Double](0, 9.5, 9.0, 2.0, 0, 9.0, 9.3, 0, 9.5, 0)    val v3 = Array[Double](9.5, 2, 8.0, 9.0, 0, 7.0, 0, 8.0, 6.0, 6.0)    val v4 = Array[Double](3.0, 10.0, 0, 4.0, 8.5, 0, 9.5, 4.0, 0, 0)    // R是评分矩阵 val R = DenseMatrix(Array(v1, v2, v3, v4): _*)
    val N: Int = R.rows    val M: Int = R.cols        // 假设有三个feature会对user的喜好造成影响    val K: Int = 3
  // 步长    val alpha = 0.002
  // 随机初始化P Q两个矩阵    var P: DenseMatrix[Double] = DenseMatrix.rand(N, K)    var Q: DenseMatrix[Double] = DenseMatrix.rand(K, M)
  // 开始循环    while (true) {      var R_ = P * Q            // 偏差矩阵,如果用户评分为零,表示用户没有看过这个电影,对应的偏差值为0      val E = R.mapPairs((t, v) => {        if (v == 0)          0        else {          v - R_.valueAt(t._1, t._2)        }      }      )        // 求偏差矩阵平方和,平方和小于0.1时,退出循环,打印输出结果      val s = sum(E.map(e => e * e))      if (s < 0.1) {        val writer = new PrintWriter(new File("D:\\saveTextTitle.txt"))        writer.println(R_.toString(100, 1000000))        writer.close()        System.exit(0)      }        // 每次对P进行更新      P = P.mapPairs((t, v) => {        v + alpha * 2 * E(t._1, ::).inner.dot(Q(t._2, ::).inner)      })       // 每次对Q进行更新      Q = Q.mapPairs((t, v) => {        v + alpha * 2 * E(::, t._2).dot(P(::, t._1))      })    }  }}

结果分析


第一滴血 风月俏佳人 小萝莉的猴神大叔 海王 愤怒的黄牛 我是山姆 我不是药神 唐人街探案 星际穿越 变态假面
李雷 9.00 -2.98 **3.55 ** 8.50 7.00 2.00 6.50 8.00 **0.93 ** **4.01 **
韩梅梅 **2.56 ** 9.50 9.00 2.00 8.07 9.00 9.30 2.00 9.50 **4.28 **
Jim Green 9.50 2.00 8.00 9.00 10.57 7.00 **10.72 ** 8.00 6.00 6.00
Lucy 3.00 10.00 **8.27 ** 4.00 8.50 **6.11 ** 9.50 4.00 **7.59 ** **3.77 **

加粗的评分是我们通过算法预计得到。可以抓取几组数据做个简单的说明:

韩梅梅和Lucy具有相近的偏好,李雷和Jim Green具有相近的偏好。因此,Lucy不喜欢第一滴血,预测韩梅梅对第一滴血的评分也较低。对于风月俏佳人,李雷的评分也较低。可见,尽管我们使用的算法很粗糙,但是其结果还具有一定参考价值。

扩展

如果能和Spark流计算整合,某一时间段内所有用户做相互推荐,或许能实现较好的推荐效果。

目录
相关文章
|
搜索推荐 算法 数据挖掘
# 【推荐系统入门到项目实战】(三):矩阵分解和ALS算法
# 【推荐系统入门到项目实战】(三):矩阵分解和ALS算法
# 【推荐系统入门到项目实战】(三):矩阵分解和ALS算法
|
机器学习/深度学习 数据采集 资源调度
【推荐系统】推荐场景为什么不可以使用SVD分解共现矩阵
【推荐系统】推荐场景为什么不可以使用SVD分解共现矩阵
120 0
【推荐系统】推荐场景为什么不可以使用SVD分解共现矩阵
|
机器学习/深度学习 搜索推荐 算法
# 【推荐系统入门到项目实战】(五):SVD矩阵分解 -
# 【推荐系统入门到项目实战】(五):SVD矩阵分解
# 【推荐系统入门到项目实战】(五):SVD矩阵分解 -
|
机器学习/深度学习 存储 搜索推荐
【推荐系统】推荐系统中分解共现矩阵的优点与局限性
【推荐系统】推荐系统中分解共现矩阵的优点与局限性
80 0
|
人工智能 搜索推荐 TensorFlow
【推荐系统】TensorFlow实现FM特征分解机
【推荐系统】TensorFlow实现FM特征分解机
128 0
|
机器学习/深度学习 人工智能 算法
【推荐系统】矩阵分解MF利用BASIC-SVD分解
【推荐系统】矩阵分解MF利用BASIC-SVD分解
111 0
【推荐系统】矩阵分解MF利用BASIC-SVD分解
|
机器学习/深度学习 人工智能 自然语言处理
【推荐系统】隐语义模型(LFD)与矩阵分解(Matrix Factorization)
【推荐系统】隐语义模型(LFD)与矩阵分解(Matrix Factorization)
156 0
【推荐系统】隐语义模型(LFD)与矩阵分解(Matrix Factorization)
|
数据采集 搜索推荐 PyTorch
推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐
推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐
263 0
推荐系统基础:使用PyTorch进行矩阵分解进行动漫的推荐
|
机器学习/深度学习 搜索推荐 算法
推荐系统的PMF - 概率矩阵分解和协同过滤(三)
推荐系统的PMF - 概率矩阵分解和协同过滤(三)
170 0
推荐系统的PMF - 概率矩阵分解和协同过滤(三)
|
监控 搜索推荐 Python
推荐系统的PMF - 概率矩阵分解和协同过滤(二)
推荐系统的PMF - 概率矩阵分解和协同过滤(二)
123 1
推荐系统的PMF - 概率矩阵分解和协同过滤(二)