【Datawhale】推荐系统-矩阵分解和FM

一、矩阵分解

矩阵分解模型是在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品,挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。

它的核心思想是通过隐含特征(latent factor)联系用户兴趣和物品(item), 基于用户的行为找出潜在的主题和分类, 然后对item进行自动聚类,划分到不同类别/主题(用户的兴趣)。

我们下面拿一个音乐评分的例子来具体看一下隐特征矩阵的含义。

假设每个用户都有自己的听歌偏好, 比如A喜欢带有小清新的, 吉他伴奏的, 王菲的歌曲,如果一首歌正好是王菲唱的, 并且是吉他伴奏的小清新, 那么就可以将这首歌推荐给这个用户。 也就是说是小清新, 吉他伴奏, 王菲这些元素连接起了用户和歌曲。

原理
矩阵分解算法将 m* n 维的共享矩阵 R 分解成 m * k 维的用户矩阵 U 和 k * n维的物品矩阵 V 相乘的形式。 其中 m 是用户数量, n 是物品数量, k 是隐向量维度, 也就是隐含特征个数, 只不过这里的隐含特征要模型自己去学。 k 的大小决定了隐向量表达能力的强弱, k 越大, 表达信息就越强。

有了用户矩阵和物品矩阵就可以计算用户对物品的评分。

矩阵分解, 最常用的方法是特征值分解(EVD)或者奇异值分解(SVD)。EVD要求分解的矩阵是方阵, 显然用户-物品矩阵不满足这个要求。传统的SVD分解, 会要求原始矩阵是稠密的, 而我们这里的这种矩阵一般情况下是非常稀疏的, 如果想用奇异值分解, 就必须对缺失的元素进行填充, 而一旦补全, 空间复杂度就会非常高, 且补的不一定对。 然后就是SVD分解计算复杂度非常高, 而我们的用户-物品矩阵非常大, 所以基本上无法使用。

Basic SVD
Funk-SVD的思想很简单: 把求解上面两个矩阵的参数问题转换成一个最优化问题, 可以通过训练集里面的观察值利用最小化来学习用户矩阵和物品矩阵。

二、编程

class SVD():
	def __init__(self, rating_data, F=5, alpha=0.1, lmbda=0.1, max_iter=100):
		self.F = F # 这个表示隐向量的维度
		self.P = dict() # 用户矩阵P 大小是[users_num, F]
		self.Q = dict() # 物品矩阵Q 大小是[item_nums, F]
		self.bu = dict() # 用户偏差系数
		self.bi = dict() # 物品偏差系数
		self.mu = 0.0 # 全局偏差系数
		self.alpha = alpha # 学习率
		self.lmbda = lmbda # 正则项系数
		self.max_iter = max_iter # 最大迭代次数
		self.rating_data = rating_data # 评分矩阵
		
		# 初始化矩阵P和Q, 方法很多, 一般用随机数填充, 但随机数大小有讲究, 根据经验, 随机数需要和1/sqrt(F)成正比
		cnt = 0 # 统计总的打分数, 初始化mu用
		for user, items in self.rating_data.items():
			self.P[user] = [random.random() / math.sqrt(self.F) for x in range(0, F)]
			self.bu[user] = 0
			cnt += len(items)
			for item, rating in items.items():
				if item not in self.Q:
					self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0, F)]
					self.bi[item] = 0
		self.mu /= cnt

# 有了矩阵之后, 就可以进行训练, 这里使用随机梯度下降的方式训练参数P和Q
	def train(self):
		for step in range(self.max_iter):
			for user, items in self.rating_data.items():
				for item, rui in items.items():
					rhat_ui = self.predict(user, item) # 得到预测评分
					# 计算误差
					e_ui = rui - rhat_ui
					self.bu[user] += self.alpha * (e_ui - self.lmbda * self.bu[user])
					self.bi[item] += self.alpha * (e_ui - self.lmbda * self.bi[item])
					# 随机梯度下降更新梯度
					for k in range(0, self.F):
						self.P[user][k] += self.alpha * (e_ui*self.Q[item][k] - self.lmbda * self.P[user][k])
						self.Q[item][k] += self.alpha * (e_ui*self.P[user][k] - self.lmbda * self.Q[item][k])
			self.alpha *= 0.1 # 每次迭代步长要逐步缩小

	# 预测user对item的评分, 这里没有使用向量的形式
	def predict(self, user, item):
		return sum(self.P[user][f] * self.Q[item][f] for f in range(0, self.F)) + self.bu[user] + self.bi[item] + self.mu

def loadData():
	rating_data={1: {'A': 5, 'B': 3, 'C': 4, 'D': 4},
		2: {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3},
		3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},
		4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},
		5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}
	}
	return rating_data

rating_data = loadData()
basicsvd = SVD(rating_data, F=10)
basicsvd.train()
for item in ['E']:
	print(item, basicsvd.predict(1, item))

优点:

泛化能力强: 一定程度上解决了稀疏问题
空间复杂度低: 由于用户和物品都用隐向量的形式存放, 少了用户和物品相似度矩阵, 空间复杂度由降到了(n+m)*f
更好的扩展性和灵活性:矩阵分解的最终产物是用户和物品隐向量, 这个深度学习的embedding思想不谋而合, 因此矩阵分解的结果非常便于与其他特征进行组合和拼接, 并可以与深度学习无缝结合。
但是, 矩阵分解算法依然是只用到了评分矩阵, 没有考虑到用户特征, 物品特征和上下文特征, 这使得矩阵分解丧失了利用很多有效信息的机会, 同时在缺乏用户历史行为的时候, 无法进行有效的推荐。 所以为了解决这个问题,逻辑回归模型及后续的因子分解机模型, 凭借其天然的融合不同特征的能力, 逐渐在推荐系统领域得到了更广泛的应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值