传统推荐模型(二)协同过滤的进化——矩阵分解算法
针对协同过滤算法的头部效应较明显、泛化能力较弱的问题,矩阵分解算法被提出。矩阵分解在协同过滤算法中“共现矩阵”的基础上,加人了隐向量的概念,加强了模型处理稀疏矩阵的能力,针对性地解决了协同过滤存在的主要问题。
1、矩阵分解算法原理
协同过滤算法找到用户可能喜欢的视频的方式很直接,即基于用户的观看历史,找到跟目标用户A 看过同样视频的相似用户,然后找到这些相似用户喜欢看的其他视频,推荐给目标用户A。
矩阵分解算法则期望为每一个用户和视频生成一个隐向量,将用户和视频定位到隐向量的表示空间上,距离相近的用户和视频表明兴趣特点接近,在推荐过程中,就应该把距离相近的视频推荐给目标用户
。例如,如果希望为上图中的用户 Dave 推荐视频可以发现离 Dave 的用户向量最近的两个视频向量分别是“Ocean’s 11”和“The Lion King”,那么可以根据向量距离由近到远的顺序生成 Dave 的推荐列表。
在“矩阵分解”的算法框架下,用户和物品的隐向量是通过分解协同过滤生成的共现矩阵得到的。
矩阵分解算法将mxn维的共现矩阵R
分解为mxk维的用户矩阵U
和kxn维的物品矩阵V
相乘的形式。其中 m 是用户数量,n 是物品数量,k是隐向量的维度。k 的大小决定了隐向量表达能力的强弱。
k 的取值越小,隐向量包含的信息越少,模型的泛化程度越高;反之,k 的取值越大,隐向量的表达能力越强但泛化程度相应降低。此外,k 的取值还与矩阵分解的求解复杂度直接相关。
基于用户矩阵U
和物品矩阵V
,用户u对于物品i的预估评分:
2、矩阵分解(MF)
(1) 特征值分解(Eigen Decomposition)
特征分解(Eigen decomposition),又称谱分解(Spectral decomposition)是将矩阵分解为由其特征值和特征向量表示的矩阵之积的方法。
特征值分解可以得到特征值
与特征向量
,特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么
,可以将每一个特征向量理解为一个线性的子空间,我们可以利用这些线性的子空间干很多的事情。不过,特征值分解也有很多的局限,比如说变换的矩阵必须是方阵,那这也是后来又引入了奇异值分解的原因之一。
特征值分解只能作用于方阵,显然不适用于分解用户-物品矩阵。
(2) 奇异值分解(Singular Value Decomposition,SVD)
SVD是一种常用的降维数据压缩方法,核心是矩阵因子分解,即用因子分解的方式近似地表示原始矩阵,这种近似是在平方损失意义下的最优近似。
下面奇异值分解的定义、证明、计算及案例,来源于李航老师的《统计学习方法》。
奇异值分解的定义
奇异值分解例子:
矩阵的奇异值分解不是唯一的。
任意给定一个实矩阵,奇异值分解一定存在。
奇异值分解定理的证明
证明过程:
奇异值分解的计算步骤
1、构造n阶实对称矩阵W
W = (A.T)A
2、计算W的特征值和特征向量
3、求得n阶正交矩阵V(利用上述求得的特征向量
)
4、求对角矩阵(利用上述求得的特征值
)
5、求解m阶正交矩阵(利用上述求得的V和对角矩阵)
奇异值分解的计算案例
1、构造n阶实对称矩阵W
2、计算W的特征值和特征向量
3、求得n阶正交矩阵V(利用上述求得的特征向量
)
4、求对角矩阵(利用上述求得的特征值
)
5、求解m阶正交矩阵(利用上述求得的V和对角矩阵)
上面的算法和例题只是为了说明计算的过程,并不是实际应用中的算法。可以看出,奇异值分解算法关键在于 ATA 的特征值的计算。实际应用的奇异值分解算法是通过求 ATA 的特征值进行,但不直接计算 ATA。按照这个思路产生了许多矩阵奇异值分解的有效算法。
奇异值分解的缺点
1、传统的SVD分解,会要求原始矩阵是密的,而我们这里的这种矩阵一般情况下是非常稀疏的,如果想用奇异值分解,就必须对缺失的元素进行填充,而一旦补全,空间复杂度就会非常高,且补的不一定对。
2、然后就是SVD分解计算复杂度非常高,而我们的用户-物品矩阵非常大,所以基本上无法使用。
传统奇异值分解也不适用于解决大规模稀疏矩阵的矩阵分解问题,因此,梯度下降法成了进行矩阵分解的主要方法。
(3) Basic SVD(LFM、Funk SVD)
Funk-SVD, 也称Latent Factor Model(LFM)。 Funk-SVD的思想很简单: 把求解两个矩阵的参数问题转换成一个【最优化】问题,可以通过训练集里面的观察值利用最小化来学习用户矩阵和物品矩阵。
我们知道,如果有了用户矩阵和物品矩阵的话,我们就知道了如果想计算用户对物品的评分,只需要
我们有真实的r(u,i)
,我们可以随机初始化一个用户矩阵 U
和一个物品矩阵 V
,我们就可以计算一个猜测的r^(u,i)
,那么这个猜测的和真实值之间就会有一个误差,有了误差,我们就可以计算出总的误差平方和,有了损失,我们就可以想办法进行训练,把SSE降到最小,那么我们的两个矩阵参数就可以算出来。所以就把这个问题转成了最优化的的问题。
预测函数
损失函数(误差平方和)
优化目标
梯度下降
求梯度
梯度更新
1、首先先初始化这两个参数矩阵U和V
2、通过两个隐向量乘积得到预测值pred
3、根据label和pred计算损失
4、通过梯度下降的方式,更新两个隐向量的值
5、未评过分的那些样本当做测试集,通过两个隐向量就可以得到测试集的label值这样就填充完了矩阵,下一步就可以进行推荐了
import random
import math
class LFM(object):
def __init__(self, rating_data, F, alpha=0.1, lmbd=0.1, max_iter=500):
"""
:param rating_data: rating_data是[(user,[(item,rate)]]类型
:param F: 隐因子个数
:param alpha: 学习率
:param lmbd: 正则化
:param max_iter:最大迭代次数
"""
self.F = F
self.P = dict() # R=PQ^T,代码中的Q相当于博客中Q的转置
self.Q = dict()
self.alpha = alpha
self.lmbd = lmbd
self.max_iter = max_iter
self.rating_data = rating_data
'''随机初始化矩阵P和Q'''
for user, rates in self.rating_data:
self.P[user] = [random.random() / math.sqrt(self.F)
for x in range(self.F)]
for item, _ in rates:
if item not in self.Q:
self.Q[item] = [random.random() / math.sqrt(self.F)
for x in range(self.F)]
print(self.P)
def train(self):
"""
随机梯度下降法训练参数P和Q
:return:
"""
for step in range(self.max_iter): # 遍历次数max_iter
for user, rates in self.rating_data:
for item, rui in rates:
hat_rui = self.predict(user, item) # 预测得分
err_ui = rui - hat_rui # 计算误差
for f in range(self.F): # 梯度下降更新
self.P[user][f] += self.alpha * (err_ui * self.Q[item][f] - self.lmbd * self.P[user][f])
self.Q[item][f] += self.alpha * (err_ui * self.P[user][f] - self.lmbd * self.Q[item][f])
self.alpha *= 0.9 # 每次迭代步长要逐步缩小
def predict(self, user, item):
"""
:param user:
:param item:
:return:
预测用户user对物品item的评分
"""
return sum(self.P[user][f] * self.Q[item][f] for f in range(self.F))
if __name__ == '__main__':
'''用户有A B C,物品有a b c d'''
rating_data = list()
rate_A = [('a', 2.0), ('b', 1.0)]
rating_data.append(('A', rate_A))
rate_B = [('b', 1.0), ('c', 1.0)]
rating_data.append(('B', rate_B))
rate_C = [('c', 1.0), ('d', 1.0)]
rating_data.append(('C', rate_C))
lfm = LFM(rating_data, 2)
lfm.train()
for item in ['a', 'b', 'c', 'd']:
print(item, lfm.predict('A', item)) # 计算用户A对各个物品的喜好程度
(4) RSVD(增加正则项)
RSVD是在Basic SVD目标函数的基础上,中加入正则化参数(加入惩罚项),以防止过拟合。
预测函数
目标函数(损失函数+正则项)
梯度下降
求梯度
梯度更新
(5) RSVD进一步优化(消除用户和物品打分的偏差)
- 由于不同用户的打分体系不同(比如在 5分为满分的情况下,有的用户认为打 3 分已经是很低的分数了,而有的用户认为打 1 分才是比较差的评价 )
- 不同物品的衡量标准也有所区别(比如电子产品的平均分和日用品的平均分差异有可能比较大 )
为了消除用户和物品打分的偏差(Bias ),常用的做法是在矩阵分解时加入用户和物品的偏差向量。
预测函数
目标函数(损失函数+正则项)
梯度下降
求梯度
梯度更新
3、矩阵分解优缺点
优点
相比协同过滤,矩阵分解有如下非常明显的优点:
(1)泛化能力强,在一定程度上解决了数据稀疏问题。
-
显然,协同过滤在应对稀疏矩阵时,准确率很低。举个例子:在一个共现矩阵中,大部分物品只有少数用户评分了,那么计算相似性的时候,很难找到准确的相似。而在矩阵分解中,比如梯度下降中,我们是不需要关注缺失值的,我们只需要利用已知的评分,去拟合出我们的两个矩阵然后就可以通过这两个矩阵,去预测缺失值
-
协同过滤中,它只利用了
用户自身
跟物品自身
的信息,矩阵分解中,挖掘了用户与物品的潜在信息,可以看作是对用户和物品“打标签”,用户和物品画像都得到了丰宫,提高了泛化能力
(2)空间复杂度低。
协同过滤,需要存储庞大的
[用户-用户 相似度] m×m矩阵,[物品-物品 相似度] n×n矩阵
矩阵分解,只需要存储
[用户隐向量] m×k,[物品隐向量]n×k
(3)更好的扩展性和灵活性
产生的隐向量,便于与其他特征进行组合和拼接便于与深度学习网络进行无缝结合
缺点
-
与协同过滤一样,矩阵分解同样不方便加入用户、物品和上下文相关的特征,这使得矩阵分解丧失了利用很多有收信息的机会
-
同时在缺乏用户历史行为时,无法进行有效的推荐。
为了解决这问题,逻辑回归模型及其后续发展出的因子分解机
等模型,凭借其天然的融合不同特征的能力,逐渐在推荐系统领域得到更广泛的应用。