协同过滤算法:
特点:
- 仅利用了用户与物品的交互信息就可以实现推荐,是一个可解释性很强, 非常直观的模型
问题:
- 处理稀疏矩阵的能力比较弱
解决:
- 为了使得协同过滤更好处理稀疏矩阵问题, 增强泛化能力, 从协同过滤中衍生出矩阵分解模型(Matrix Factorization,MF)或者叫隐语义模型
理解:
- 在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征
矩阵分解
协同过滤算法的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与物品的交互信息就可以实现推荐,是一个可解释性很强, 非常直观的模型, 但是也存在一些问题:
- 处理稀疏矩阵的能力比较弱, 所以为了使得协同过滤更好处理稀疏矩阵问题, 增强泛化能力, 从协同过滤中衍生出矩阵分解模型(Matrix Factorization,MF)或者叫隐语义模型, 两者差不多说的一个意思
- 在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。
最常用的方法是:
- 特征值分解(EVD):分解的矩阵是方阵
- 奇异值分解(SVD:要求原始矩阵是稠密的,分解计算复杂度非常高
矩阵分解算法的原理
在矩阵分解的算法框架下, 我们就可以通过分解协同过滤的共现矩阵来得到用户和物品的隐向量, 就是上面的用户矩阵Q和物品矩阵P.
矩阵分解算法将
m
×
n
m\times n
m×n维的共享矩阵
R
R
R分解成
m
×
k
m \times k
m×k维的用户矩阵
U
U
U和
k
×
n
k \times n
k×n维的物品矩阵
V
V
V相乘的形式。
矩阵分解算法的求解
EVD
- 它要求分解的矩阵是方阵, 显然用户-物品矩阵不满足这个要求, 而传统的SVD分解, 会要求原始矩阵是稠密的, 而我们这里的这种矩阵一般情况下是非常稀疏的.
奇异值分解
- 必须对缺失的元素进行填充, 而一旦补全, 空间复杂度就会非常高, 且补的不一定对。 然后就是SVD分解计算复杂度非常高, 而我们的用户-物品矩阵非常大, 所以基本上无法使用。
Basic SVD
求解两个矩阵的参数问题转换成一个最优化问题, 可以通过训练集里面的观察值利用最小化来学习用户矩阵和物品矩阵。
目标函数:
min
q
,
p
∑
(
u
,
i
)
∈
K
(
r
u
i
−
p
u
T
q
i
)
2
\min {\boldsymbol{q}^{}, \boldsymbol{p}^{}} \sum{(u, i) \in K}\left(\boldsymbol{r}{\mathrm{ui}}-p{u}^{T} q_{i}\right)^{2}
minq,p∑(u,i)∈K(rui−puTqi)2
梯度:
b
u
=
b
u
+
η
(
e
u
i
−
λ
b
u
)
b
i
=
b
i
+
η
(
e
u
i
−
λ
b
i
)
\begin{aligned} \boldsymbol{b}{u}&=\boldsymbol{b}{\boldsymbol{u}}+\boldsymbol{\eta}\left(\boldsymbol{e}{u i}-\lambda \boldsymbol{b}{\boldsymbol{u}}\right) \ \boldsymbol{b}{\boldsymbol{i}} &=\boldsymbol{b}{\boldsymbol{i}}+\boldsymbol{\eta}\left(\boldsymbol{e}{\boldsymbol{u} i}-\lambda \boldsymbol{b}{\boldsymbol{i}}\right) \end{aligned}
bu=bu+η(eui−λbu) bi=bi+η(eui−λbi) 而对于
p
u
,
k
p_{u,k}
pu,k和
p
k
,
i
p_{k,i}
pk,i, 导数没有变化, 更新公式也没有变化。
编程实现
预测Alice对物品5的评分:
任务就是根据这个评分矩阵, 猜测Alice对物品5的打分。
代码如下:
import numpy as np
import random
import math
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
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用
# print(rating_data.items())
for user, items in self.rating_data.items():
self.P[user] = [random.random() / math.sqrt(self.F) for x in range(0, F)] # 随机数需要和1/sqrt(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
# print('Q',self.Q)
# print('BI',self.bi)
# print('P',self.P)
# print('BU',self.bu)
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
# 接下来就是训练和预测77
rating_data = loadData()
basicsvd = SVD(rating_data, F=10)
basicsvd.train()
for item in 'E':
print(item,basicsvd.predict(1,item))
>>>
E 3.0561098719733346
通过这个方式, 得到的预测评分是3.25, 这个和隐向量的维度, 训练次数和训练方式有关。
补充
-
矩阵分解算法后续有哪些改进呢?针对这些改进,是为了解决什么的问题呢?
-
RSVD,ASVD,SVD++
-
消除用户和物品打分偏差等。
-
矩阵分解的优缺点分析
优点:
- 泛化能力强: 一定程度上解决了稀疏问题
- 空间复杂度低: 由于用户和物品都用隐向量的形式存放, 少了用户和物品相似度矩阵, 空间复杂度由 n 2 n^2 n2降到了 ( n + m ) ∗ f (n+m)*f (n+m)∗f
- 更好的扩展性和灵活性:矩阵分解的最终产物是用户和物品隐向量, 这个深度学习的embedding思想不谋而合, 因此矩阵分解的结果非常便于与其他特征进行组合和拼接, 并可以与深度学习无缝结合
矩阵分解算法依然是只用到了评分矩阵, 没有考虑到用户特征, 物品特征和上下文特征, 这使得矩阵分解丧失了利用很多有效信息的机会, 同时在缺乏用户历史行为的时候, 无法进行有效的推荐。