矩阵分解 (一)SVD及其的几种变体
背景
在矩阵分解算法出现之前,基于协同过滤实现的推荐算法,一般是依靠近邻模型,即存在一个「user-item」的评分矩阵,类似于:
这个矩阵的每一行都表示一个用户(和物品关系)的向量,每一列都表示一个物品(和用户关系)的向量,在推荐时只需要根据向量相似度推荐相似度最高的n个物品(用户)即可,但是这种模型也存在一些问题:
- 向量的维度一般较大,计算复杂度特别高。例如 用户向量的维度是物品总数,物品向量的维度则是用户总数,但是物品之间存在相关性,信息量并不随着向量维度增加而线性增加
- 没有关系的维度上,一般使用0作为缺省值,数据量达到一定大小时,多数情况下一个系统里任意两个user-item对是没有关系的,即一个向量中多数值都是0,对相似度计算的结果影响较大
上述问题都可以在矩阵分解中解决,那矩阵分解具体是怎么做的呢?
实现原理
直观地说,矩阵分解就是在原本的大矩阵中分解出两个小矩阵,在推荐时,不用原先的大矩阵,而使用分解出的两个小矩阵计算
具体做法是,假设原本的user-item矩阵是m维n维的矩阵,即有m个用户,n个物品。选取一个k,这个k比m,n都小很多,可能是若干个数量级。通过一套算法 得到两个矩阵U和V,矩阵U的维度是m k ,矩阵V的维度是n * k。这两个矩阵需要能够通过矩阵乘法复原本来的矩阵,即:
\[ U_{m\times{k}}V_{n\times{k}}^{T} \approx A_{m\times{n}}\]
类似这样的计算方法就是矩阵分解,或者说叫做SVD(奇异值分解),但是SVD不能和矩阵分解(MF,下同)画等号,除了SVD还有很多矩阵分解的方法
SVD及其变体
SVD
从物理意义上解释,矩阵分解就是把用户和物品都映射到一个k维的空间中,这个k维空间不是显式的 每一个维度的值也没有一个很好的解释,每一个维度也没有名字,所以通常称其为“隐因子”,代表藏在直观矩阵下面的隐藏关联。
经过计算,得出两个小矩阵后,每一个用户有一个向量p,p的每个维度上的元素有正有负,代表用户和物品“不可说”的隐藏关联,每一个物品也有一个向量q,同样代表了对用户的隐藏关联,他们的数量和意义是一致的,数量即我们在计算时人为指定的k。
例如,用户u的向量是pu,物品i的向量是qi,那么在计算时,推荐分数直接计算两个向量的点积即可:
\[ \hat{r}_{ui} = p_{u}q_{i}^{T}\]
那么 两个矩阵是如何得到的呢?这是一个机器学习问题,按照机器学习框架,一般考虑两个核心要素:
- 目标函数
- 优化算法
目标函数
\[ \min_{q^{* },p^{* } } \sum_{(u,i) \in \kappa }{(r_{ui} - p_{u}q_{i}^{T})^{2} + \lambda (||q_{i}||^{2} + ||p_{u}||^{2})} \]
\(\sum_{(u,i) \in \kappa }\) 是对所有已知用户-物品评分对的求和
加号左边是误差平方和 用于控制模型偏差导致的欠拟合 \(r_{ui}\)是用户u对物品i的实际评分; \(p_{u}q_{i}^{T}\) 是通过用户向量和物品特征向量的内积得到的预测评分
加号右边是分解后参数的平方 是正则化项 用于控制模型方差导致的过拟合 确保找到的特征向量\(p_{u}\) \(q_{i}\)不会变得过大 常数 \(\lambda\)是正则化参数 用于平衡预测误差与正则化项的权重
在这个目标函数的基础上 需要使用优化算法找到能使它最小的参数 常用的有两个 交替最小二乘法 随机梯度下降。
交替最小二乘法
核心是 找到两个矩阵P和Q 让他们相乘后等于原矩阵
\[ R_{m \times n} = P_{m \times k} \times Q^{T}_{n \times k} \]
难点在于 P Q都是未知的 如果知道一个话 可以用线性代数解法求出另一个
\[ P_{m \times k} = R_{m \times n} \times Q^{-1}_{n \times k}\]
所以 最小二乘法的原理就是
- 初始化随机矩阵Q里面的元素值;
- 把Q矩阵当做已知的,直接用线性代数的方法求得矩阵P;
- 得到了矩阵P后,把P当做已知的,故技重施,回去求解矩阵Q;
- 上面两个过程交替进行,一直到误差可以接受为止。
以下是一个python实现 使用交替最小二乘法实现这个优化
1 | import numpy as np |
梯度下降方法
梯度下降是一个用于寻找函数最小值的迭代优化算法。在机器学习中,它通常用于最小化损失函数,即试图找到能够使损失函数达到最小值的参数。原理如下:
- 初始化:选择一个起始点作为参数的初始估计。
- 计算梯度:在当前点计算目标函数的梯度,梯度指向的是函数上升最快的方向。
- 反向移动:为了最小化函数,需要向梯度的反方向(下降最快的方向)移动一步。步长通常由学习率决定。
- 更新参数:将当前点更新为新点,再次执行梯度计算和更新步骤。
- 迭代:重复步骤2-4直到满足收敛条件,比如迭代次数、梯度大小小于阈值或者损失函数的变化小于某个阈值。
以下是一个python实现实例 使用随机梯度下降方法来实现这个优化
1 | import numpy as np |
带偏置项的SVD (BiasSVD)
基础的SVD看起来很美好,但事实上,真实世界里充满了复杂,举个例子:在一个电影评分平台上,有一些用户会给出偏高的评分,比如标准宽松的用户,另一些用户可能对艺术的要求较为苛刻,那么ta对于所有的电影的评分就会相对更低,对于item(电影),则会由于明星效应,或者迎合了一些社会热点等原因造成评分整体偏高或偏低,所以为了尽量减少这类偏置情况对于整体模型的影响,SVD就有了一个变种:把偏置信息抽出来的SVD
这种算法下一个用户对一个物品的评分会由四部分相加:
\[\hat{r}_{ui} = \mu + b_{i} + b_{u} + p_{u}q_{i}^{T} \]
从左到右分别代表:
- 全局平均分\(\mu\),表现了训练数据的总体评分情况,对于固定的数据集,它是一个常数
- 物品的评分偏置\(b_{i}\),即独立于用户兴趣的因素,大致表示一个物品的总体评分
- 用户的评分偏置\(b_i\),即独立于物品特征的因素,大致表示一个用户的评分习惯
- 用户和物品之间的兴趣偏好$ p_{u}q_{i}^{T}$,即前文提到的预测评分
目标函数
增加了偏置信息的SVD 目标函数稍有改变
\[\min_{q^{* },p^{* } }\sum_{(u, i) \in K} (r_{ui} - \mu - b_u - b_i - q_i^T p_u)^2 + \lambda (\|p_u\|^2 + \|q_i\|^2 + b_u^2 + b_i^2)\]
即在SVD目标函数的基础上,在加号左边的误差平方和中减去偏置项,在加号右边的正则化项中引入偏置项,保持模型的稳定性
SVD++
增加完偏置信息后,SVD在一定程度上可以适应了现实世界的参差,但现实世界的残酷远不止如此(笑。
更贴合现实的情况是,相比起沉默的大多数,主动点评电影或者美食的用户是少数,这就导致了有的用户评分相对较少,也就是说,用户对于物品的隐式反馈要多于显式反馈,那么 该如何利用隐式反馈来弥补缺失的评分数据呢?另外,对于用户的个人属性,比如性别、年龄、地域等属性,是否也可以加入到模型中来弥补冷启动的不足呢?
对于这些需求,有一套专门的模型给出了方案,即在基础的SVD模型中结合用户的隐式反馈和属性变形来的:SVD++
SVD++模型的损失函数与传统的SVD类似,但是它加入了额外的项来表示用户的隐式反馈。用户 \(u\) 的特征向量 \(p_u\) 在 SVD++ 中被调整为基于其显式特征加权和所有其交互过的隐式特征的总和。
先说隐式反馈怎么加入,方法是:除了假设评分矩阵中的物品有一个隐因子向量外,用户有过行为的物品集合也都有一个隐因子向量,维度是一样的。把用户操作过的物品隐因子向量加起来,用来表达用户的兴趣偏好。
类似的,用户属性,全都转换成0-1型的特征后,对每一个特征也假设都存在一个同样维度的隐因子向量,一个用户的所有属性对应的隐因子向量相加,也代表了他的一些偏好。
具体来说,模型定义如下: \[ \hat{r}_{ui} = \mu + b_u + b_i + q_i^T \left(p_u + |N(u)|^{-\frac{1}{2}} \sum_{j \in N(u)} y_j \right) \] 其中:
- $ _{ui} $ 是模型预测用户 $ u $ 对物品 $ i $ 的评分。
- $ $ 是所有评分的全局平均值。
- $ b_u $, $ b_i $ 分别是用户偏置和物品偏置。
- $ q_i $, $ p_u $ 分别是物品 $ i $ 和用户 $ u $ 的特征向量。
- $ y_j $ 是与物品 $ j $ 相关的隐式反馈特征向量。
- $ N(u) $ 是用户 $ u $ 有过隐式反馈的物品集合。
- $ |N(u)| $ 是集合 $ N(u) $ 中元素的个数,用于归一化。
目标函数
SVD++ 的目标函数增加了对隐式反馈特征的正则化,以避免过拟合: \[ \min_{b_u, b_i, p_u, q_i, y_j} \sum_{(u, i) \in K} (r_{ui} - \hat{r}_{ui})^2 + \lambda (\|p_u\|^2 + \|q_i\|^2 + b_u^2 + b_i^2 + \sum_{j \in N(u)} \|y_j\|^2) \]
TimeSVD
人,总是善变的;人对于事物的兴趣也是会随着时间变化的。所以,对于将过去时间内的行为和现在时间的行为用相同的权重计算,显然是不合理的,因为这个计算结果很可能无法代表用户当前的兴趣。所以,在SVD中考虑时间因素,也就成了顺理成章的事情。
具体的,有以下几种做法:
- 对评分按照时间加权,让久远的评分更趋近平均值;
- 对评分时间划分区间,不同的时间区间内分别学习出隐因子向量,使用时按照区间使用对应的隐因子向量来计算;
- 对特殊的期间,如节日、周末等训练对应的隐因子向量。
小结
本篇描述了经典的矩阵分解算法SVD及其的一些变体,那么经过分解后得到的稠密向量该如何使用,如何在庞大的数据中计算推荐项?
除了SVD,还有什么别的方法可以完成矩阵分解呢?他们和SVD有哪些不同?在不同的业务场景下孰优孰劣?请关注接下来的矩阵分解系列文章