前言
用户评分矩阵往往十分稀疏,为了对这种稀疏矩阵进行补全,充分利用已有数据,我们可以采用矩阵分解办法。实际上,一旦矩阵补全,相当于预测出用户对未购买物品的评分,基于这些评分,我们就可以对任何用户进行推荐。
本篇博客将对矩阵分解进行介绍,同时介绍两种常用的用来解决矩阵分解问题的算法SGD(随机梯度下降)和ALS(交叉最小二乘)。这两种优化方法还能用来解决两种简单的推荐算法。
我们都将利用movielens数据集,对这些方法进行简单的实战演示。
MF
给定用户评分矩阵 R m × n R_{m\times n} Rm×n,有 m m m个用户给 n n n个物品进行打分。通常,我们遇到的评分矩阵是这样的,
电影1 | 电影2 | 电影3 | |
---|---|---|---|
用户1 | 5 | ||
用户2 | 3 | ||
用户3 | 4 |
可以看到,评分矩阵往往很稀疏,这是因为物品太多,用户不可能购买或者观看所有的物品,只会挑选他最感兴趣的进行消费。
面对这样的评分矩阵 R m × n R_{m\times n} Rm×n,我们用矩阵分解,也就是说,
- 对于每一个用户和物品,我们都会给予一个 k k k维隐向量,这种隐向量代表着用户或者物品在 k k k个隐特征上的取值
- 矩阵分解就是分解出这些 k k k维隐向量的过程
还是用上面的例子,我们假设,对每一个用户和电影,我们给予一个 2 2 2维隐向量,这个维度我们猜测为 动作,爱情。我们将用户的隐向量拼在一起,将电影的矩阵拼在一起,就可以分别得到用户矩阵和电影矩阵,即
- 用户矩阵 P m × k P_{m\times k} Pm×k,对于第一列,我们可以解释为,用户1对于动作这个元素的喜爱为2,对爱情这个元素的喜爱为1
用户1 | 用户2 | 用户3 | |
---|---|---|---|
动作 | 2 | 4 | 5 |
爱情 | 1 | 5 | 3 |
- 电影矩阵 Q n × k Q_{n\times k} Qn×k,对于第一列,我们可以解释为,电影1对于动作这个元素的倾向为4,对于爱情这个元素的倾向为1
电影1 | 电影2 | 电影3 | |
---|---|---|---|
动作 | 4 | 4 | 1 |
爱情 | 1 | 5 | 3 |
有了上面的用户矩阵 P P P和物品矩阵 Q Q Q,我们期望能将评分矩阵 R R R分解成用户矩阵和物品矩阵的乘积,即下式成立 R = P T Q R=P^TQ R=PTQ 这也是矩阵分解名字的由来。
为了求出这样的
P
P
P和
Q
Q
Q(严格等号成立几乎不可能,我们只能尽量让其近似),我们将误差最小
min
P
i
,
Q
j
E
r
r
o
r
=
1
2
∑
(
i
,
j
)
∈
{
R
i
j
非
空
}
(
R
i
j
−
P
i
T
Q
j
)
2
+
λ
2
∑
i
,
j
(
∣
P
i
∣
2
+
∣
Q
j
∣
2
)
\min_{P_i, Q_j}Error=\frac{1}{2}\sum_{(i,j)\in\{R_{ij}非空\}} (R_{ij}-P_i^TQ_j)^2+\frac{\lambda}{2}\sum_{i,j}(|P_i|^2+|Q_j|^2)
Pi,QjminError=21(i,j)∈{Rij非空}∑(Rij−PiTQj)2+2λi,j∑(∣Pi∣2+∣Qj∣2)
其中, P i P_i Pi为用户 i i i的 k k k维隐列向量, Q j Q_j Qj为电影 j j j的 k k k维隐列向量。
对于上述问题,我们有两种方法解决,分别是随机梯度下降(SGD)和交替最小二乘(ALS)。
1. SGD
对误差函数关于
P
i
P_i
Pi和
Q
j
Q_j
Qj求导,我们有
∂
E
r
r
o
r
∂
P
i
=
∑
j
∈
{
R
i
j
非
空
}
(
P
i
T
Q
j
−
R
i
j
)
Q
j
+
λ
P
i
\frac{\partial Error}{\partial P_i}=\sum_{j\in\{R_{ij}非空\}}(P_i^TQ_j-R_{ij})Q_j+\lambda P_i
∂Pi∂Error=j∈{Rij非空}∑(PiTQj−Rij)Qj+λPi
和
∂
E
r
r
o
r
∂
Q
j
=
∑
i
∈
{
R
i
j
非
空
}
(
P
i
T
Q
j
−
R
i
j
)
P
i
+
λ
Q
j
\frac{\partial Error}{\partial Q_j}=\sum_{i\in\{R_{ij}非空\}}(P_i^TQ_j-R_{ij})P_i+\lambda Q_j
∂Qj∂Error=i∈{Rij非空}∑(PiTQj−Rij)Pi+λQj
这样,我们的更新策略为
P
i
←
P
i
−
α
∂
E
r
r
o
r
∂
P
i
P_i\leftarrow P_i-\alpha \frac{\partial Error}{\partial P_i}
Pi←Pi−α∂Pi∂Error
和
Q
j
←
Q
j
−
α
∂
E
r
r
o
r
∂
Q
j
Q_j\leftarrow Q_j-\alpha \frac{\partial Error}{\partial Q_j}
Qj←Qj−α∂Qj∂Error
2. ALS
ALS的思想是,固定其中一个变量,将另一个变量求出来,再固定另一个变量,将开始的那个变量求出来,如此反复迭代,直至满足条件终止。
我们首先固定
Q
Q
Q,对误差函数关于
P
i
P_i
Pi求导,有
∂
E
r
r
o
r
∂
P
i
=
∑
j
∈
{
R
i
j
非
空
}
(
P
i
T
Q
j
−
R
i
j
)
Q
j
+
λ
P
i
=
∑
j
∈
{
R
i
j
非
空
}
Q
j
Q
j
T
P
i
+
λ
P
i
−
∑
j
∈
{
R
i
j
非
空
}
R
i
j
Q
j
=
(
∑
j
∈
{
R
i
j
非
空
}
Q
j
Q
j
T
+
λ
I
)
P
i
−
∑
j
∈
{
R
i
j
非
空
}
R
i
j
Q
j
\begin{array}{lll} \frac{\partial Error}{\partial P_i}&=&\sum_{j\in\{R_{ij}非空\}}(P_i^TQ_j-R_{ij})Q_j+\lambda P_i\\ &=&\sum_{j\in\{R_{ij}非空\}}Q_jQ_j^TP_i+\lambda P_i-\sum_{j\in\{R_{ij}非空\}}R_{ij}Q_j\\ &=& (\sum_{j\in\{R_{ij}非空\}}Q_jQ_j^T+\lambda I)P_i-\sum_{j\in\{R_{ij}非空\}}R_{ij}Q_j \end{array}
∂Pi∂Error===∑j∈{Rij非空}(PiTQj−Rij)Qj+λPi∑j∈{Rij非空}QjQjTPi+λPi−∑j∈{Rij非空}RijQj(∑j∈{Rij非空}QjQjT+λI)Pi−∑j∈{Rij非空}RijQj
令
∂
E
r
r
o
r
∂
P
i
=
0
\frac{\partial Error}{\partial P_i}=0
∂Pi∂Error=0,可以得到
P
i
=
(
∑
j
∈
{
R
i
j
非
空
}
Q
j
Q
j
T
+
λ
I
)
−
1
∑
j
∈
{
R
i
j
非
空
}
R
i
j
Q
j
P_i=(\sum_{j\in\{R_{ij}非空\}}Q_jQ_j^T+\lambda I)^{-1}\sum_{j\in\{R_{ij}非空\}}R_{ij}Q_j
Pi=(j∈{Rij非空}∑QjQjT+λI)−1j∈{Rij非空}∑RijQj
同理,我们固定
P
i
P_i
Pi,计算
Q
j
Q_j
Qj如下,
∂
E
r
r
o
r
∂
Q
j
=
∑
i
∈
{
R
i
j
非
空
}
(
P
i
T
Q
j
−
R
i
j
)
P
i
+
λ
Q
j
=
0
\frac{\partial Error}{\partial Q_j}=\sum_{i\in\{R_{ij}非空\}}(P_i^TQ_j-R_{ij})P_i+\lambda Q_j=0
∂Qj∂Error=i∈{Rij非空}∑(PiTQj−Rij)Pi+λQj=0
得到
Q
j
=
(
∑
i
∈
{
R
i
j
非
空
}
P
i
P
i
T
+
λ
I
)
−
1
∑
i
∈
{
R
i
j
非
空
}
R
i
j
P
i
Q_j=(\sum_{i\in\{R_{ij}非空\}}P_iP_i^T+\lambda I)^{-1}\sum_{i\in\{R_{ij}非空\}}R_{ij}P_i
Qj=(i∈{Rij非空}∑PiPiT+λI)−1i∈{Rij非空}∑RijPi
3. 优化方法的应用
对于上面的SGD和ALS方法,我们还可以用来计算其他推荐算法,比如Baseline算法和Slopeone算法。
3.1 Baseline算法
Baseline算法是基于统计的一种算法,它同样利用用户评分矩阵,预测用户对于未购买产品的评分。
已知用户 u u u对于物品 i i i的评分 R u i R_{ui} Rui,我们假设
- 全体用户有一个评分均值,记为 μ \mu μ
- 用户 u u u的评分偏好 b u b_u bu,意思是,如果 b u > 0 b_u>0 bu>0,则用户 u u u习惯于打高分,比平均分高 b u b_u bu;如果 b u < 0 b_u<0 bu<0,则用户 u u u习惯于打低分,比平均分低 ∣ b u ∣ |b_u| ∣bu∣
- 物品 i i i的评分偏好 b i b_i bi,意思是,如果 b i > 0 b_i>0 bi>0,则这个物品收到的评分偏高,比平均分高 b i b_i bi
这样,我们的误差函数为
min
b
u
,
b
i
E
r
r
o
r
=
∑
R
u
,
i
非
空
(
R
u
,
i
−
μ
−
b
u
−
b
i
)
2
+
λ
(
∑
u
∣
b
u
∣
2
+
∑
i
∣
b
i
∣
2
)
\min_{b_u, b_i}Error=\sum_{R_{u,i}非空}(R_{u, i}-\mu-b_u-b_i)^2+\lambda(\sum_{u}|b_u|^2+\sum_{i}|b_i|^2)
bu,biminError=Ru,i非空∑(Ru,i−μ−bu−bi)2+λ(u∑∣bu∣2+i∑∣bi∣2)
3.2 Slopeone算法
对于slopeone算法,我们有以下步骤:
- 计算出任意两个物品
i
i
i和
j
j
j之间的平均评分差
d
e
v
i
,
j
dev_{i, j}
devi,j,或者说,物品
i
i
i的评分比物品
j
j
j平均要高多少,即
d
e
v
i
,
j
=
1
∣
N
(
i
)
∩
N
(
j
)
∣
∑
u
∈
N
(
i
)
∩
N
(
j
)
(
R
u
,
i
−
R
u
,
j
)
dev_{i,j}=\frac{1}{|N(i)\cap N(j)|}\sum_{u\in{N(i)\cap N(j)}}(R_{u,i}-R_{u,j})
devi,j=∣N(i)∩N(j)∣1u∈N(i)∩N(j)∑(Ru,i−Ru,j)
其中, N ( i ) N(i) N(i)表示给物品 i i i打过分的用户集合 - 对于用户
u
u
u和物品
i
i
i,利用用户打过分的所有物品与物品
i
i
i的评分差,计算用户
u
u
u对物品
i
i
i的评分
p
(
u
,
i
)
p(u, i)
p(u,i),如下
p u , i = 1 ∣ N ( u ) ∣ ∑ j ∈ N ( u ) ( d e v i , j + u j ) p_{u, i}=\frac{1}{|N(u)|}\sum_{j\in N(u)}(dev_{i, j}+u_j) pu,i=∣N(u)∣1j∈N(u)∑(devi,j+uj)
基于surprise库的简单实战
基于surprise库,我们对movielens数据集进行topN预测。
movielens数据集已经上传,各位可以免费下载。
# 第三方库
import numpy as np
import pandas as pd
from surprise import SVD
from surprise import Reader
from surprise import Dataset
# 导入数据
data = pd.read_csv(r'D:\myfile\开课吧\推荐系统\第五节\movielens\ratings.csv')
data.head()
# 丢掉timestampe列
data.drop('timestamp', axis=1, inplace=True)
data.head()
# 将data读入surprise中
# 定义阅读器,给定文本格式
reader = Reader(line_format='user item rating', sep=',')
# 按照阅读器格式读取数据
raw_data = Dataset.load_from_df(data, reader=reader)
# 将这个数据转化为可用数据集
my_data = raw_data.build_full_trainset()
# 用MF进行预测
algo = SVD(biased=False)
algo.fit(my_data)
# 遍历整个数据集
# 创建user_items字典,存储用户购买过的物品集合
# 创建items集合,存储所有的物品集合
user_items = {}
items = set()
for user, item, rating in data.values:
user_items.setdefault(user, set())
user_items[user].add(item)
items.add(item)
# 给定用户u,期望能给他从没有购买的物品中推荐N个物品
def topN(u, N=4):
scores = {}
for i in items:
if i not in user_items[u]:
scores[i] = algo.predict(u, i).est
# 对评分排序
ans = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:N]
return ans
# 测试
topN(1)
# 测试结果
[(94466.0, 4.578501891615852),
(26587.0, 4.507607235916675),
(77658.0, 4.500811875576563),
(7502.0, 4.500239302579578)]