用隐语义模型来进行协同过滤的目标
通过矩阵分解进行降维分析
隐语义模型的实例
假设用户物品评分矩阵为 R,现在有 m 个用户,n 个物品
我们想要发现 k 个隐类,我们的任务就是找到两个矩阵 P 和 Q,使这两个矩阵的乘积近似等于 R,即将用户物品评分矩阵 R 分解成为两个低维矩阵相乘:
我们可以认为,用户之所以给电影打出这样的分数,是有内在原因的,我们可以挖掘出影响用户打分的隐藏因素,进而根据未评分电影与这些隐藏因素的关联度,决定此未评分电影的预测评分
应该有一些隐藏的因素,影响用户的打分,比如电影:演员、题材、年代…甚至不一定是人直接可以理解的隐藏因子
找到隐藏因子,可以对 user 和 item 进行关联(找到是由于什么使得 user 喜欢/不喜欢此 item,什么会决定 user 喜欢/不喜欢此 item),就可以推测用户是否会喜欢某一部未看过的电影
对于用户看过的电影,会有相应的打分,但一个用户不可能看过所有电影,对于用户没有看过的电影是没有评分的,因此用户评分矩阵大部分项都是空的,是一个稀疏矩阵
如果我们能够根据用户给已有电影的打分推测出用户会给没有看过的电影的打分,那么就可以根据预测结果给用户推荐他可能打高分的电影
现在,矩阵因子分解的问题已经转化成了一个标准的优化问题,需要求解 P、Q,使目标损失函数取最小值
最小化过程的求解,一般采用随机梯度下降算法或者交替最小二乘法来实现
交替最小二乘法(Alternating Least Squares,ALS)
ALS的思想是,由于两个矩阵P和Q都未知,且通过矩阵乘法耦合在一起,为了使它们解耦,可以先固定Q,把P当作变量,通过损失函数最小化求出P,这就是一个经典的最小二乘问题;再反过来固定求得的P,把Q当作变量,求解出Q:如此交替执行,直到误差满足阈值条件,或者到达迭代上限
ALS算法具体过程如下:
# LFM隐语义模型梯度下降算法实现
### 0.引入依赖
import numpy as np
import pandas as pd
### 1.数据准备
# 评分矩阵R
R = np.array([[4,0,2,0,1],
[0,2,3,0,0],
[1,0,2,4,0],
[5,0,0,3,1],
[0,0,1,5,1],
[0,3,2,4,1],])
len(R[0])
### 2.算法实现
'''
@输入参数:
R:N*N 的评分矩阵
K:隐特征的向量维度
max_iter: 最大迭代次数
alpha:步长
lamda:正则化系数
@输出:
分解之后的P,Q
P:初始化用户特征矩阵M*K
Q:初始化物品特征矩阵N*K
'''
# 给定超参数
K = 5
max_iter = 5000
alpha = 0.0002
lamda = 0.004
#核心算法
def LFM_grad_desc(R, K, max_iter, alpha, lamda):
# 基本维度参数定义
M = len(R)
N = len(R[0])
# P,Q初始值,随机生成
P = np.random.rand(M, K)
Q = np.random.rand(N, K)
Q = Q.T
# 开始迭代
for step in range(max_iter):
# 对所有用户u、物品i做遍历,对应的特征向量Pu/Qi按照推导的公式进行梯度下降
for u in range(M):
for i in range(N):
# 对每一个评分大于0的评分,求出预测评分误差
if R[u][i] > 0 :
eui = np.dot(P[u,:],Q[:,i]) - R[u][i]
# 代入公式,按照梯度下降算法更新当前的Pu、Qi
for k in range(K):
P[u][k] = P[u][k] - alpha * (2 * eui * Q[k][i] + 2 * lamda * P[u][k])
Q[k][i] = Q[k][i] - alpha * (2 * eui * P[u][k] + 2 * lamda * Q[k][i])
#u,i遍历完成,所有特征向量更新完毕,可以得到P、Q,可以计算预测评分矩阵
predR = np.dot(P,Q)
# 计算当前损失函数
cost = 0
for u in range(M):
for i in range(N):
if(R[u][i]) > 0:
cost += (np.dot(P[u,:], Q[:,i]) - R[u][i]) ** 2
# 加上正则化项
for k in range(K):
cost += lamda * (P[u][k] ** 2 + Q[k][i] ** 2)
if cost < 0.0001:
break
return P , Q.T, cost
### 3.测试
P,Q,cost = LFM_grad_desc(R, K, max_iter, alpha, lamda)
print(P)
print(Q)
print(cost)
predR = P.dot(Q.T)
print(R)
predR