在上一篇 奇异值分解原理浅析中,简单分析了SVD的矩阵分解原理。本文侧重分享下SVD算法及其变种在推荐领域中的应用,SVD算法最初在Netflix竞赛中大放异彩而引起关注,目前SVD及其变种主要包括Funk-SVD、Bias-SVD、SVD++以及timeSVD。
Funk-SVD 算法
首先传统的SVD矩阵分解
R
=
U
Σ
V
T
R = U\Sigma V^{T}
R=UΣVT要求矩阵
R
R
R必须是dense
的,即不能有缺失值。而在实际的推荐系统中,User-Item评分矩阵
R
R
R通常是非常 sparse
的,此处指包含很多缺失值。如下矩阵所示:
R
=
(
1
?
2
?
?
?
?
?
?
4
2
?
4
5
?
?
?
3
?
?
?
1
?
3
?
5
?
?
?
2
)
% <![CDATA[ R= \begin{pmatrix} 1 & \color{#e74c3c}{?} & 2 & \color{#e74c3c}{?} & \color{#e74c3c}{?}\\ \color{#e74c3c}{?} & \color{#e74c3c}{?} & \color{#e74c3c}{?} & \color{#e74c3c}{?} & 4\\ 2 & \color{#e74c3c}{?} & 4 & 5 & \color{#e74c3c}{?}\\ \color{#e74c3c}{?} & \color{#e74c3c}{?} & 3 & \color{#e74c3c}{?} & \color{#e74c3c}{?}\\ \color{#e74c3c}{?} & 1 & \color{#e74c3c}{?} & 3 & \color{#e74c3c}{?}\\ 5 & \color{#e74c3c}{?} & \color{#e74c3c}{?} & \color{#e74c3c}{?} & 2\\ \end{pmatrix} %]]>
R=⎝⎜⎜⎜⎜⎜⎜⎛1?2??5????1?2?43????5?3??4???2⎠⎟⎟⎟⎟⎟⎟⎞ 显然此时是无法直接应用传统SVD的,一种简单的启发式解决方法是先对评分矩阵中的缺失值进行简单的补全,比如用全局平均值或用户物品平均值补全,得到补全后的矩阵,接下来可以用SVD分解并降维。但填充本身会造成很多问题,其一,填充大大增加了数据量,增加了算法复杂度。其二,简单粗暴的数据填充很容易造成数据失真。即使有了上面的补全策略,传统SVD依然难以直接应用于推荐系统中。因为我们的用户数和物品一般都是超级大,随便就成千上万了,这么大一个矩阵做SVD分解是非常耗时的。
这似乎是个死胡同,但方法总比困难多。
我们回顾一下传统SVD的计算过程,首先求出方阵 R T R R^{T}R RTR的特征值和特征向量,将特征向量单位化后得到 v 1 , v 2 , … , v n v_{1}, v_{2}, \dots,v_{n} v1,v2,…,vn,构成 n n n阶正交矩阵 V V V,进而求 Σ \Sigma Σ和 U U U。必须明确SVD只是手段,而非目的。通过数据填充再进行如此耗时的SVD分解是没有任何business value的。
所谓不忘初心,方得始终。那我们的初心和目的是什么呢?是得到分解后的低维矩阵,是矩阵补全,是对缺失值的预测!有了预测,才能推荐,进而提升CTR和CVR,才能产生business value。那能否另辟蹊径绕开对高维User-Item矩阵 R R R的填充而完成矩阵分解呢?
Simon Funk给出了他在Netflix竞赛中Ranking 3的解决方案,因此也被称为FunkSVD。它的出发点是,既然将一个矩阵做SVD分解成3个矩阵很耗时,同时还面临sparse
的问题,那我们能不能避开稀疏问题,同时只分解为两个矩阵呢?也就是,我们期望User-Item矩阵
R
R
R这样分解:
R
m
×
n
=
P
m
×
k
Q
k
×
n
T
R_{m\times n}=P_{m\times k}Q^{T}_{k\times n}
Rm×n=Pm×kQk×nT此处
Σ
\Sigma
Σ矩阵被省掉了,
Σ
\Sigma
Σ在SVD中是对角矩阵,你可以理解为对
P
P
P或
Q
T
Q^{T}
QT做缩放变换,已经包含在分解后的矩阵之中。这种想法联想下SVD的外积展开式其实非常容易理解。
那么如何实现把矩阵
R
R
R分解为
P
P
P和
Q
Q
Q呢?FunkSVD采用线性回归的思想,并且只考虑已有的评分记录。具体来说,FunkSVD的目标是让用户的评分和矩阵乘积得到的评分残差尽可能小,也就是用均方差作为损失函数,来寻找最终的
P
P
P和
Q
Q
Q。对于
R
R
R中某一个用户评分
r
u
i
r_{ui}
rui,如果用FunkSVD进行矩阵分解,则对应的表示为
r
u
i
=
p
u
⋅
q
i
r_{ui} = p_u \cdot q_i
rui=pu⋅qi,如图所示:
采用均方差作为损失函数,则我们期望
(
r
u
i
−
p
u
⋅
q
i
)
2
(r_{ui}-p_u \cdot q_i)^2
(rui−pu⋅qi)2尽可能小,考虑所有的已知的评分,我们期望最小化下式:
min
p
u
,
q
i
∑
r
u
i
∈
R
(
r
u
i
−
p
u
⋅
q
i
)
2
\min_{p_u, q_i}\sum_{r_{ui} \in R} (r_{ui} - p_u \cdot q_i)^2
pu,qiminrui∈R∑(rui−pu⋅qi)2只要我们能够最小化上面的式子,并求出极值所对应的
p
u
p_u
pu和
q
i
q_i
qi,则我们最终可以得到矩阵
P
P
P和
Q
Q
Q,那么对于任意矩阵
R
R
R任意一个空白评分的位置,我们可以通过
p
u
⋅
q
i
p_u \cdot q_i
pu⋅qi计算预测评分,很漂亮的方法!和传统SVD不同的一点是FunkSVD不要求两个矩阵正交,Simon对此给出的答案是限制矩阵为正交矩阵虽然有助于解释原理,但在实际中对于提升预测准确率帮助不大。
这个optimization问题是非凸的,(后悔当年退选了凸优化,说多了都是泪~),但我们可以用经典的随机梯度下降SGD (Stochastic Gradient Descent)来找到近似解。记待最小化的目标函数为:
f
(
p
∗
,
q
∗
)
=
∑
r
u
i
∈
R
(
r
u
i
−
p
u
⋅
q
i
)
2
=
∑
r
u
i
∈
R
f
u
i
(
p
u
,
q
i
)
,
f(p_*, q_*) = \sum_{r_{ui} \in R} (r_{ui} - p_u \cdot q_i)^2 =\sum_{r_{ui} \in R} f_{ui}(p_u, q_i),
f(p∗,q∗)=rui∈R∑(rui−pu⋅qi)2=rui∈R∑fui(pu,qi),分别对
p
u
p_u
pu和
q
i
q_i
qi求偏导:
∂
f
u
i
∂
p
u
=
∂
∂
p
u
(
r
u
i
−
p
u
⋅
q
i
)
2
=
−
2
q
i
(
r
u
i
−
p
u
⋅
q
i
)
∂
f
u
i
∂
q
i
=
∂
∂
q
i
(
r
u
i
−
p
u
⋅
q
i
)
2
=
−
2
p
u
(
r
u
i
−
p
u
⋅
q
i
)
\frac{\partial f_{ui}}{\partial p_u} = \frac{\partial}{\partial p_u} (r_{ui} - p_u \cdot q_i)^2 = - 2 q_i (r_{ui} - p_u \cdot q_i) \\ \frac{\partial f_{ui}}{\partial q_i} = \frac{\partial}{\partial q_i} (r_{ui} - p_u \cdot q_i)^2 = - 2 p_u (r_{ui} - p_u \cdot q_i)
∂pu∂fui=∂pu∂(rui−pu⋅qi)2=−2qi(rui−pu⋅qi)∂qi∂fui=∂qi∂(rui−pu⋅qi)2=−2pu(rui−pu⋅qi)得到通过SGD进行求解的过程如下:
- 随机初始化向量 p u p_u pu和 q i q_i qi
- 在给定次数内,重复如下过程:
- 对所有已知的评分,重复如下过程:
- 计算 ∂ f u i ∂ p u \frac{\partial f_{ui}}{\partial p_u} ∂pu∂fui和 ∂ f u i ∂ q i \frac{\partial f_{ui}}{\partial q_i} ∂qi∂fui
- 按以下规则更新 p u p_u pu和 q i q_i qi: p u ← p u + α ⋅ q i ( r u i − p u ⋅ q i ) p_u \leftarrow p_u + \alpha \cdot q_i (r_{ui} - p_u \cdot q_i) pu←pu+α⋅qi(rui−pu⋅qi) 和 q i ← q i + α ⋅ p u ( r u i − p u ⋅ q i ) q_i \leftarrow q_i + \alpha \cdot p_u (r_{ui} - p_u \cdot q_i) qi←qi+α⋅pu(rui−pu⋅qi),其中 α \alpha α是学习速率。
- 对所有已知的评分,重复如下过程:
在实际应用中, 为了防止过拟合, 加入
L
2
L_2
L2正则化项,因此最终的优化目标函数变成:
J
(
p
,
q
)
=
min
p
u
,
q
i
∑
r
u
i
∈
R
(
r
u
i
−
p
u
⋅
q
i
)
2
+
λ
(
∣
∣
p
u
∣
∣
2
2
+
∣
∣
q
i
∣
∣
2
2
)
J(p,q) = \min_{p_u, q_i}\sum_{r_{ui} \in R} (r_{ui} - p_u \cdot q_i)^2 + \lambda (||p_u||^2_2+||q_i||^2_2)
J(p,q)=pu,qiminrui∈R∑(rui−pu⋅qi)2+λ(∣∣pu∣∣22+∣∣qi∣∣22)说点感触,一个researcher一辈子能创造一个高引或者广为研究的算法或模型,就足慰平生,比如陈天奇的XGBoost,贾杨清的Caffe,当然堆叠几个模块然后冠名圈地自萌的除外。老了都可以把儿子叫过来,唠叨他一顿你老爹我当年如何如何,这种事,是可以腌起来风干,老的时候下酒的。
Bias-SVD 算法
在Funk-SVD算法火爆之后,出现了很多Funk-SVD的改进版算法。其中Bias算是改进的比较成功的一种算法。
Funk-SVD通过学习用户和物品的特征向量进行预测,即用户和物品的交互信息。用户的特征向量表示用户的兴趣,物品的特征向量代表物品的特点,且每一个维度相互对应,两个向量的内积表示用户对物品的喜好程度。但我们观测到的评分数据大部分都是和用户和物品无关的因素产生的效果,也就是说,有很大一部分因素是和用户对物品的喜好无关而是只取决于用户或物品的自身特性。例如,对于乐观的用户来说,他的评分行为普遍偏高,而对批判性用户来说,她的
评分记录普遍偏低,即使他们对同一物品的评分相同,但实际上他们对该物品的喜好程度并不相同。同理,对物品来说,以电影为例,受大众欢迎的电影得到的评分普遍偏高,而一些烂片的评分普遍偏低,这些因素都是独立于用户或物品的因素,而和用户对物品的喜好程度无关。
我们把这些独立于用户或物品的因素成为偏置bias部分,将用户和物品的交互即用户对物品的喜好部分称为个性化部分。事实上,在矩阵分解模型中偏好部分对提高评分预测准确率的作用要大大高于个性化部分所起的作用。以Netflix Prize推荐比赛数据集为例,Yehuda Koren仅使用偏置部分可以将评分误差降低32%,而加入个性化部分能降低42%,也就是只有10%是个性化部分起到的作用,这也充分说明了bias部分所起的作用,剩下的58%的误差Yehuda Koren将之称为模型不可解释部分,包括数据噪音等因素。
偏置部分主要由三个子部分组成,分别是:
- 训练集中所有评分记录的全局平均值 μ \mu μ,表示训练数据的总体评分情况,对于固定的数据集,它是一个常数。
- 用户偏置 b i b_i bi,独立于物品特征的因素,表示某一特定用户的打分习惯。例如批判型用户对于评分比较苛刻,倾向于打低分;而乐观型用户打分比较保守,总体打分要高。
- 物品偏置 b j b_j bj,独立于用户兴趣的因素,表示某一特定物品的打分情况。以电影为例,好片获得的总体评分要高,而烂片获得的评分普遍偏低,物品偏置捕获的就是这种特征。
因此偏置部分表示如下:
b
i
j
=
μ
+
b
i
+
b
j
b_{ij}=\mu+b_i+b_j
bij=μ+bi+bj 则加入了偏置项以后的优化目标函数
J
(
p
,
q
)
J(p,q)
J(p,q)为:
J
(
p
,
q
)
=
min
p
u
,
q
i
∑
r
u
i
∈
R
(
r
u
i
−
p
u
⋅
q
i
−
μ
−
b
i
−
b
j
)
2
+
λ
(
∣
∣
p
u
∣
∣
2
2
+
∣
∣
q
i
∣
∣
2
2
+
∣
∣
b
i
∣
∣
2
2
+
∣
∣
b
j
∣
∣
2
2
)
J(p,q) = \min_{p_u, q_i}\sum_{r_{ui} \in R} (r_{ui} - p_u \cdot q_i - \mu - b_i - b_j)^2 + \lambda (||p_u||^2_2+||q_i||^2_2 + ||b_i||^2_2 + ||b_j||^2_2)
J(p,q)=pu,qiminrui∈R∑(rui−pu⋅qi−μ−bi−bj)2+λ(∣∣pu∣∣22+∣∣qi∣∣22+∣∣bi∣∣22+∣∣bj∣∣22) 这个优化目标函数也可以通过梯度下降法求解,和Funk-SVD不同的是,此时我们多了两个偏置项
b
i
b_i
bi和
b
j
b_j
bj。
p
u
p_u
pu和
q
i
q_i
qi的迭代公式和Funk-SVD类似,只是每一步的梯度导数稍有不同而已。而
b
i
b_i
bi和
b
j
b_j
bj一般可以初始化为零向量,然后参与迭代
b
i
=
b
i
+
α
(
r
i
j
−
μ
−
b
i
−
b
j
−
p
u
⋅
q
i
−
λ
b
i
)
b
j
=
b
j
+
α
(
r
i
j
−
μ
−
b
i
−
b
j
−
p
u
⋅
q
i
−
λ
b
j
)
b_i=b_i+α(r_{ij}−μ−b_i−b_j− p_u \cdot q_i −λb_i) \\ b_j=b_j+α(r_{ij}−μ−b_i−b_j− p_u \cdot q_i −λb_j)
bi=bi+α(rij−μ−bi−bj−pu⋅qi−λbi)bj=bj+α(rij−μ−bi−bj−pu⋅qi−λbj)通过迭代我们最终可以得到
P
P
P和
Q
Q
Q,进而用于推荐。Bias-SVD增加了一些额外因素的考虑,因此在某些场景会比FunkSVD表现好。
SVD++ 算法
SVD++算法在Bias-SVD算法上进一步做了增强,增加了考虑用户的隐式反馈。所谓隐式反馈,是指没有具体的评分/点赞/收藏等,但可能有点击或浏览等行为。详细实现参考Factorization meets the neighborhood: a multifaceted collaborative filtering model
对于一个用户
i
i
i,它提供了隐式反馈的物品集合定义为
N
(
i
)
N(i)
N(i),这个用户对某个物品
j
j
j对应的隐式反馈修正的评分值为
c
i
j
c_{ij}
cij,那么该用户所有的评分修正值为
∑
s
∈
N
(
i
)
c
s
j
\sum_{s\in N(i)} c_{sj}
∑s∈N(i)csj,一般我们将它表示为
q
j
T
y
s
q_j^Ty_s
qjTys形式,则加入了隐式反馈的优化目标函数
J
(
p
,
q
)
J(p,q)
J(p,q)是这样的:
J
(
p
,
q
)
=
min
p
u
,
q
i
∑
r
u
i
∈
R
(
r
u
i
−
p
u
⋅
q
i
−
μ
−
b
i
−
b
j
−
q
j
T
∣
N
(
i
)
∣
−
1
/
2
Σ
s
∈
N
(
i
)
y
s
)
2
+
λ
(
∣
∣
p
u
∣
∣
2
2
+
∣
∣
q
i
∣
∣
2
2
+
∣
∣
b
i
∣
∣
2
2
+
∣
∣
b
j
∣
∣
2
2
+
Σ
s
∈
N
(
i
)
∣
∣
y
s
∣
∣
2
)
J(p,q) = \min_{p_u, q_i}\sum_{r_{ui} \in R} (r_{ui} - p_u \cdot q_i - \mu - b_i - b_j - q_j^T|N(i)|^{-1/2}\Sigma_{s\in N(i)} y_s)^2 + \lambda (||p_u||^2_2+||q_i||^2_2 + ||b_i||^2_2 + ||b_j||^2_2 + \Sigma_{s\in N(i)}||y_s||^2)
J(p,q)=pu,qiminrui∈R∑(rui−pu⋅qi−μ−bi−bj−qjT∣N(i)∣−1/2Σs∈N(i)ys)2+λ(∣∣pu∣∣22+∣∣qi∣∣22+∣∣bi∣∣22+∣∣bj∣∣22+Σs∈N(i)∣∣ys∣∣2) 其中,引入
∣
N
(
i
)
∣
−
1
/
2
|N(i)|^{-1/2}
∣N(i)∣−1/2是为了消除不同
∣
N
(
i
)
∣
|N(i)|
∣N(i)∣个数引起的差异。如果需要考虑用户的隐式反馈时,使用SVD++是个不错的选择。
timeSVD 算法
Koren在2010年发现在Netflix数据集中,个体的兴趣会随着时间转移,论文中称为concept drift - 观念转移。比如大家熟知的《大话西游》,刚上映票房很低,评分也不高,但随着时间流逝,大家给出的评分越来越高,一种说法是星爷拍大话西游的理念太超前了。另外可能有些电影在某些特定的节日或者某天会获得高分,因此作者希望建立模型捕捉到这些。
timeSVD也就是在SVD中加入时间因素,也称作有时序的SVD,timeSVD的假设条件是评分 = 显示兴趣 + 时序兴趣 + 热点 + 偏见。这个模型为了对观念转移建模,从三个角度出发,首先是时间窗口的概念,另外是实例权重,采用时间衰减,最后采用集成学习的概念,引入多个基础模型预估方法。具体实现参考Collaborative filtering with temporal dynamics
矩阵分解的优缺点
矩阵分解方法在推荐系统中主要应用于将高维User-Item评分矩阵映射为两个低维用户和物品矩阵,进而进行评分预测/矩阵补全,解决了数据稀疏性问题。另外矩阵分解具有很好的扩展性,比如WMF也可以用于Top N推荐,SRui通过引入社交信息来解决数据稀疏和冷启动问题,ConvMF则是通过将MF和CNN结合来缓解数据稀疏和冷启动问题。
矩阵分解具有以下优点:
- 容易编程实现,SGD依次迭代即可训练出模型。比较低的时间和空间复杂度,高维矩阵映射为两个低维矩阵节省了存储空间,训练过程比较费时,但是可以离线完成;评分预测一般在线计算,直接使用离线训练得到的参数,可以实时推荐。
- 预测的精度比较高,预测准确率要高于基于领域的协同过滤以及基于内容的过滤等方法。
- 非常好的扩展性,方便在用户特征向量和物品特征向量中添加其他因素,比如添加隐式反馈因素的SVD++;添加时间动态timeSVD++,此方法将偏置部分和用户兴趣表示成一个关于时间的函数,可以很好的捕捉到用户的兴趣漂移。
矩阵分解的不足有:
- 模型训练比较费时,不过可以通过离线训练来弥补这个缺点;
- 推荐结果不具有很好的可解释性,分解出来的用户和物品矩阵的每个维度无法和现实生活中的概念来解释,无法用现实概念给每个维度命名,只能理解为潜在语义空间。
- 实际推荐场景中往往只关心topn结果的准确性,此时考察全局的均方差显然是不准确的。
Reference
- http://yougth.top
- https://plushunter.github.io/
- http://nicolas-hug.com/blog/matrix_facto_3