赞
踩
奇异值分解(SVD)可以将任意矩阵分解成两个方阵和一个对角矩阵的乘积。借助于SVD,我们可以将推荐系统中的用户-评分矩阵进行分解,通过推广的SVD方法(FunkSVD,BiasSVD和SVD++),我们还可以进一步做评分矩阵的补全。根据补全的评分矩阵,我们将可以给用户推荐他没有购买过的产品。因此,SVD方法在推荐系统中具有重要的应用价值。
下面,我们将逐个介绍 原始SVD,FunkSVD,BiasSVD以及SVD++。介绍完这些算法之后,我们将在movielens数据集上,用第三方库surprise进行简单的评分预测。
对于任意
m
×
n
m\times n
m×n矩阵
A
A
A,通过奇异值分解,有严格等式
A
=
P
Σ
Q
T
A=P\Sigma Q^T
A=PΣQT
其中, P P P和 Q Q Q是方阵, Σ \Sigma Σ是对角矩阵。
进一步的, P P P的列向量是 m × m m\times m m×m方阵 A A T AA^T AAT的特征向量; Q Q Q的列向量是 n × n n\times n n×n方阵 A T A A^TA ATA的特征向量; m × n m\times n m×n矩阵 Σ \Sigma Σ的对角线元素是 A A A的特征值。
通过上面的奇异值分解,我们可以将用户-评分矩阵 R R R分解为三个矩阵的乘积。
虽然SVD能够将评分矩阵 R R R分解,但是存在以下问题
实际上,原始SVD实际上更加适合于数据降维,可以参考我的博客 用SVD做图像降维。
用户-评分矩阵 R R R通常是很稀疏的,原始SVD无法使用,因此,我们提出一个近似的矩阵分解算法,FunkSVD。
与MF类似,对于
m
×
n
m\times n
m×n评分矩阵
R
R
R,我们假设由两个矩阵
P
k
×
m
P_{k\times m}
Pk×m和
Q
k
×
n
Q_{k\times n}
Qk×n的乘积近似得到,这就意味着下式的成立
R
≈
P
T
Q
R\approx P^TQ
R≈PTQ
对于上面的式子,我们可以这样理解
我们想得到这样的 P P P和 Q Q Q,并使得 P T Q P^TQ PTQ尽量接近 R R R。注意, R R R中仅有少量位置存在值,我们求的 P T Q P^TQ PTQ就是尽量接近这些值。
假设
R
R
R中非空位置集合为
K
K
K,我们有如下最优化问题
min
P
,
Q
L
=
1
2
∑
(
i
,
j
)
∈
K
(
R
i
,
j
−
p
i
T
q
j
)
2
+
λ
2
(
∑
i
=
1
m
∣
p
i
∣
2
+
∑
j
=
1
n
∣
q
j
∣
2
)
\min_{P,Q}L= \frac{1}{2}\sum_{(i, j)\in K}(R_{i, j}-p_i^Tq_j)^2+\frac{\lambda}{2}(\sum_{i=1}^m\lvert p_i \rvert^2+\sum_{j=1}^n|q_j|^2)
P,QminL=21(i,j)∈K∑(Ri,j−piTqj)2+2λ(i=1∑m∣pi∣2+j=1∑n∣qj∣2)
直接对损失函数求导,我们有
∂
L
∂
p
i
=
∑
j
(
p
i
T
q
j
−
R
i
,
j
)
q
j
+
λ
p
i
=
(
∑
j
q
j
q
j
T
+
λ
I
)
p
i
−
∑
j
R
i
,
j
q
j
和
∂
L
∂
q
j
=
∑
i
(
p
i
T
q
j
−
R
i
,
j
)
p
i
+
λ
q
j
=
(
∑
i
p
i
p
i
T
+
λ
I
)
q
j
−
∑
i
R
i
,
j
p
i
因此,有更新策略
p
i
←
p
i
−
α
∂
L
∂
p
i
p_i\leftarrow p_i-\alpha\frac{\partial L}{\partial p_i}
pi←pi−α∂pi∂L
q j ← q j − α ∂ L ∂ q j q_j\leftarrow q_j-\alpha\frac{\partial L}{\partial q_j} qj←qj−α∂qj∂L
其中, α \alpha α是学习率。
通过以上迭代更新,可以得到 P P P和 Q Q Q,从而得到近似的评分矩阵 P T Q P^TQ PTQ,从而补全评分矩阵。
在FunkSVD的基础上,我们进一步考虑用户偏好和商品偏好。
比如,对于某些用户而言,他们打分一向比较高,而对于较为苛刻的用户,他们打分又偏低;对于某些高质量商品而言,给它们的评分一般偏高,比如泰坦尼克电影评分4.9,可能这部电影没有那么好,被高估了。
基于以上观察,我们有,不同用户有自己的打分习惯,或者偏高或者偏低;不同电影也有自己的分数倾向,或者倾向于低分或者倾向于高分。我们需要在模型中加以体现。
令
μ
\mu
μ为评分的平均值,
b
i
b_i
bi为用户
i
i
i的偏好带来的评分偏置,
b
j
b_j
bj为商品
j
j
j的质量带来的评分偏置。这样,用户
i
i
i对商品
j
j
j的评分可以写为
μ
+
b
i
+
b
j
+
p
i
T
q
j
\mu+b_i+b_j+p_i^Tq_j
μ+bi+bj+piTqj
因此,我们的目标函数可以写为
min
P
,
Q
,
b
i
,
b
j
L
=
1
2
∑
(
i
,
j
)
∈
K
(
R
i
,
j
−
μ
−
b
i
−
b
j
−
p
i
T
q
j
)
2
+
λ
2
(
∑
i
=
1
m
∣
b
i
∣
2
+
∑
j
=
1
n
∣
b
j
∣
2
+
∑
i
=
1
m
∣
p
i
∣
2
+
∑
j
=
1
n
∣
q
j
∣
2
)
类似的,对目标函数求导,可以写出更新策略,这里略过。
在BiasSVD的基础上,我们进一步考虑用户隐式反馈的影响。
用户除了对商品有评分这一显式反馈之外,还有诸如浏览、点击等隐式反馈。一个用户可能对许多商品有隐式反馈,我们将用户 i i i有过隐式反馈的商品集合记为 N ( i ) N(i) N(i),每一次对于特定商品 s ∈ N ( i ) s\in N(i) s∈N(i) 的点击或者浏览,都带来对于用户特征 p i p_i pi的某些偏置 y s y_s ys。
这样,对于用户
i
i
i,最终他的特征
p
i
p_i
pi可以写为
p
i
+
∑
s
∈
N
(
i
)
y
s
p_i+\sum_{s\in N(i)}y_s
pi+s∈N(i)∑ys
因此,用户
i
i
i对商品
j
j
j的评分可以写为
μ
+
b
i
+
b
j
+
q
j
T
(
p
i
+
∑
s
∈
N
(
i
)
y
s
)
\mu+b_i+b_j+q_j^T(p_i+\sum_{s\in N(i)}y_s)
μ+bi+bj+qjT(pi+s∈N(i)∑ys)
这样, 我们的目标函数可以写为
min
P
,
Q
,
b
i
,
b
j
,
y
s
L
=
1
2
∑
(
i
,
j
)
∈
K
(
R
i
,
j
−
μ
−
b
i
−
b
j
−
q
j
T
(
p
i
+
∑
s
∈
N
(
i
)
y
s
)
)
2
+
λ
2
(
∑
i
=
1
m
∣
b
i
∣
2
+
∑
j
=
1
n
∣
b
j
∣
2
+
∑
i
=
1
m
∣
p
i
∣
2
+
∑
j
=
1
n
∣
q
j
∣
2
+
∑
i
=
1
m
∑
s
∈
N
(
i
)
∣
y
s
∣
2
)
同样用梯度下降方法,我们可以对变量进行迭代。
这里,我们选用movielens的ratings数据集,利用第三方库surprise进行简单的TopN预测。
movielens数据集在本人的免费资源里面,请自取。
# 第三方库
import pandas as pd
from surprise import SVD, SVDpp
from surprise import Dataset, Reader
from surprise import accuracy
from surprise.model_selection import KFold, train_test_split
# 数据读取
# 注意:surprise读取数据需要自己的阅读器和格式,直接使用pandas不行的
# 定义阅读器框架
reader = Reader(line_format='user item rating')
# 利用pandas读取数据
df_data = pd.read_csv(r'D:\myfile\开课吧\推荐系统\第六节\movielens\ratings.csv', usecols=['userId', 'movieId', 'rating'])
# 将数据转化为surprise特定数据集
data = Dataset.load_from_df(df_data, reader)
print(data)
# 比较funkSVD,BiasSVD和SVDpp的预测效果,指标为rmse
# funkSVD
algo = SVD(n_factors=30, biased=False)
kf = KFold(n_splits=5)
error = []
for trainset, testset in kf.split(data):
algo.fit(trainset) # 训练模型
predictions = algo.test(testset) # 预测
cur_error = accuracy.rmse(predictions) # 计算误差
error.append(cur_error)
mean_error = sum(error) / len(error)
print('funkSVD的平均误差为', mean_error)
# funkSVD误差
RMSE: 0.8478
RMSE: 0.8509
RMSE: 0.8495
RMSE: 0.8487
RMSE: 0.8495
funkSVD的平均误差为 0.8492727275931049
# biasSVD
algo = SVD(n_factors=30, biased=True)
kf = KFold(n_splits=5)
error = []
for trainset, testset in kf.split(data):
algo.fit(trainset) # 训练模型
predictions = algo.test(testset) # 预测
cur_error = accuracy.rmse(predictions) # 计算误差
error.append(cur_error)
mean_error = sum(error) / len(error)
print('biasSVD的平均误差为', mean_error)
# biasSVD误差
RMSE: 0.8321
RMSE: 0.8327
RMSE: 0.8312
RMSE: 0.8323
RMSE: 0.8320
biasSVD的平均误差为 0.8320637234052113
# SVD++
algo = SVDpp(n_factors=30)
kf = KFold(n_splits=5)
error = []
for trainset, testset in kf.split(data):
algo.fit(trainset) # 训练模型
predictions = algo.test(testset) # 预测
cur_error = accuracy.rmse(predictions) # 计算误差
error.append(cur_error)
mean_error = sum(error) / len(error)
print('SVD++的平均误差为', mean_error)
这里SVD++算法相对复杂,需要运行较长时间,这里我就没有等到运算出结果了,可以预期到误差应该更低的。
这里,我们的思路是
# 创立字典user_items,记录user用过哪些movie user_items = {} # 创建userID和itemID,分别记录所有的user和item userID, itemID = [], [] # 将data数据集变成操作的数据集 data = data.build_full_trainset() # 遍历数据集data,生成user_items列表 for user, item, rating in data.all_ratings(): user_items.setdefault(user, []) user_items[user].append(item) if item not in itemID: itemID.append(item) userID = list(user_items.keys())
# TopN推荐
# 遍历数据集,找出user没有购买过的商品
# 用user_newitems_ratings列表记录对没有购买过的商品的预估评分
user_newitems_ratings = {}
# biasSVD
alg = SVD(n_factors=30)
alg.fit(data) # 训练算法
# 遍历所有可能的用户-物品对,找出用户没有购买过的商品
for user in userID:
user_newitems_ratings.setdefault(user, {})
for item in itemID:
if item not in user_items[user]: # user没有购买过的商品
# 预测用户评分
user_newitems_ratings[user][item] = alg.predict(user, item, verbose=False).est
# 对用户评分进行排序
for user in user_newitems_ratings.keys():
user_newitems_ratings[user] = sorted(user_newitems_ratings[user].items(), key=lambda x: x[1], reverse=True)
# 获取user的topN推荐
def get_topN(user, N):
return user_newitems_ratings[user][:N]
# 例子:user=0,N=4
get_topN(0, 4)
# 预测结果
[(318, 4.460564726496344),
(6271, 4.427441820009547),
(3090, 4.416865860322598),
(7502, 4.409934931201513)]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。