【DW推荐系统论文组队task1–NCF】
DW推荐系统论文组队task1--NCF
本文的创新点
总体原理:NCF模型基于神经网络可以学习任意函数的原理,用多层感知机来学习一个用户-项目的交互函数,代替传统CF任务中用来计算用户对目标项目预测分数的内积的方法(inner product)
- 通过神经网络的结构来建模用户和项目的潜在特征,并将其用于CF任务
- 将传统的方法MF和NCF联系起来,将MF方法变成了NCF的一个特例(注:MF即矩阵因子分解,通过用户、项目的ID信息构建embedding,然后对用户-项目交互矩阵做矩阵分解,通过这种方式得到潜在特征,但是得到的是低维特征),NCF使用多层感知机来发掘用户-项目之间的高维特征
- 将多层感知机学习到的信息作为传统MF低维信息的一个补充,最终为用户、项目学习到一个更好的embedding,其中蕴含了更多的潜在特征
NOTE:本文附录的代码引用自Datawhale推荐系统论文组队学习task1中的代码实现
一、INTRODUCTION
文章首先介绍了在大规模使用神经网络之前最受欢迎的MF(matrix factorization) 技术,其主要原理是:
将用户和项目的ID信息映射到一个共享的潜在空间(latent space)里面(本质就是将一个矩阵分解成若干矩阵,因为矩阵代表的就是线性空间中的线性变换),然后在这个空间里面使用一个向量来表示用户和项目,然后使用向量内积的方式来表示用户和项目之间的交互情况。
虽然有一些模型使用了深度神经网络DNN来进行推荐任务的研究,但是这些模型只是建模了一些辅助信息,在衡量用户-项目的潜在特征的关系时还是基于MF的内积的方式,于是本文为了解决这些问题,提出了NCF模型来学习用户-项目之间的潜在关系,代替了MF中的内积的方式。
二、PRELIMINARIE
2.1 从隐式数据中学习
定义Y是一个M*N的用户-项目交互矩阵,矩阵中的元素根据用户和项目之间是否存在交互关系来定义,即:
y
u
i
=
{
1
,
if interaction ( user
u
,
item
i
) is observed
0
,
otherwise.
y_{u i}=left{begin{array}{ll} 1, & text { if interaction ( user } u, text { item } i text { ) is observed } \ 0, & text { otherwise. } end{array}right.
yui={1,0, if interaction ( user u, item i ) is observed otherwise.
用一个具体例子来演示:
需要注意的是,
y
ui
y_{text {ui }}
yui =1不代表用户真的喜欢这个物品,反之
y
ui
y_{text {ui }}
yui =0也不代表用户不喜欢目标物品,可能只是单纯的缺失数据。这也是隐式数据的一个特点,即数据的缺失(或者说用户单纯还不知道这个物品)导致了负反馈的缺失。
所以一个基于隐式反馈的推荐问题就可以被定义为对这些和用户没有交集的(即
y
ui
y_{text {ui }}
yui =0)的项目的评分预测
2.2 矩阵因子分解(MF)
基本原理:
将每个用户和物品和一个潜在特征的实值向量联系起来,分别定义为
p
u
mathbf{p}_{u}
pu和
q
i
mathbf{q}_{i}
qi,然后使用内积的方式来估算一个用户对一个项目的喜爱程度(可以理解为评分):
y
^
u
i
=
f
(
u
,
i
∣
p
u
,
q
i
)
=
p
u
T
q
i
=
∑
k
=
1
K
p
u
k
q
i
k
hat{y}_{u i}=fleft(u, i mid mathbf{p}_{u}, mathbf{q}_{i}right)=mathbf{p}_{u}^{T} mathbf{q}_{i}=sum_{k=1}^{K} p_{u k} q_{i k}
y^ui=f(u,i∣pu,qi)=puTqi=k=1∑Kpukqik
其中
y
^
u
i
hat{y}_{u i}
y^ui代表预测评分,K是隐式空间的维度
下图是根据上一章节使用的用户-项目交互矩阵的例子,生成的一个用户的隐式空间:
(所谓隐式空间的本质就是个线性空间)
MF方法的局限性:
在低维的隐式空间中建模用户、项目的embedding,而且使用简单、不够灵活的内积的方式来估计复杂的用户-项目之间的关系(内积的方式是对用户、项目向量中元素的简单的线性组合)。虽然可以通过增加隐式空间的维度来提升推荐效果,但是在数据稀疏的情况下,会导致过拟合。
三、NCF Model
用文中的模型图来概括NCF模型:
上图的模型最终得到的预测结果可以用公式表示:
y
^
u
i
=
f
(
P
T
v
u
U
,
Q
T
v
i
I
∣
P
,
Q
,
Θ
f
)
hat{y}_{u i}=fleft(mathbf{P}^{T} mathbf{v}_{u}^{U}, mathbf{Q}^{T} mathbf{v}_{i}^{I} mid mathbf{P}, mathbf{Q}, Theta_{f}right)
y^ui=f(PTvuU,QTviI∣P,Q,Θf)
其中
P
∈
R
M
×
K
mathbf{P} in mathbb{R}^{M times K}
P∈RM×K和
Q
∈
R
N
×
K
mathbf{Q} in mathbb{R}^{N times K}
Q∈RN×K各自代表将用户和项目映射到隐式空间的矩阵,
Θ
f
Theta_{f}
Θf代表预测函数f的中的可学习的参数。
其中
f
(
P
T
v
u
U
,
Q
T
v
i
I
)
fleft(mathbf{P}^{T} mathbf{v}_{u}^{U}, mathbf{Q}^{T} mathbf{v}_{i}^{I}right)
f(PTvuU,QTviI)可以定义如下:
f
(
P
T
v
u
U
,
Q
T
v
i
I
)
=
ϕ
out
(
ϕ
X
(
…
ϕ
2
(
ϕ
1
(
P
T
v
u
U
,
Q
T
v
i
I
)
)
…
)
)
fleft(mathbf{P}^{T} mathbf{v}_{u}^{U}, mathbf{Q}^{T} mathbf{v}_{i}^{I}right)=phi_{text {out }}left(phi_{X}left(ldots phi_{2}left(phi_{1}left(mathbf{P}^{T} mathbf{v}_{u}^{U}, mathbf{Q}^{T} mathbf{v}_{i}^{I}right)right) ldotsright)right)
f(PTvuU,QTviI)=ϕout (ϕX(…ϕ2(ϕ1(PTvuU,QTviI))…))
其中
ϕ
out
phi_{text {out }}
ϕout 和
ϕ
x
phi_{text {x }}
ϕx 分别代表最后的输出层的激活函数和第x层的全连接层。
接下来详细介绍一下子模型的各层:
3.1 Input layer
输入层是根据用户、项目的ID顺序得到的独热编码,作为输入向量进行模型
3.2 Embedding layer
利用上述公式中的
P
∈
R
M
×
K
mathbf{P} in mathbb{R}^{M times K}
P∈RM×K和
Q
∈
R
N
×
K
mathbf{Q} in mathbb{R}^{N times K}
Q∈RN×K将输入层的独热编码进行映射得到一个初始的用户和项目的embedding,P,Q矩阵都是可以通过反向传播进行学习的
3.3 Neural CF layer 和 output layer
本质就是通过若干全连接层来逼近训练数据的真实概率分布(即最终得到的模型),层数不易过深,不然就会导致过拟合,其中的传播函数为:
z
1
=
ϕ
1
(
p
u
,
q
i
)
=
[
p
u
q
i
]
,
ϕ
2
(
z
1
)
=
a
2
(
W
2
T
z
1
+
b
2
)
,
⋯
⋯
ϕ
L
(
z
L
−
1
)
=
a
L
(
W
L
T
z
L
−
1
+
b
L
)
,
y
^
u
i
=
σ
(
h
T
ϕ
L
(
z
L
−
1
)
)
,
begin{aligned} mathbf{z}_{1} &=phi_{1}left(mathbf{p}_{u}, mathbf{q}_{i}right)=left[begin{array}{l} mathbf{p}_{u} \ mathbf{q}_{i} end{array}right], \ phi_{2}left(mathbf{z}_{1}right) &=a_{2}left(mathbf{W}_{2}^{T} mathbf{z}_{1}+mathbf{b}_{2}right), \ & cdots cdots \ phi_{L}left(mathbf{z}_{L-1}right) &=a_{L}left(mathbf{W}_{L}^{T} mathbf{z}_{L-1}+mathbf{b}_{L}right), \ hat{y}_{u i} &=sigmaleft(mathbf{h}^{T} phi_{L}left(mathbf{z}_{L-1}right)right), end{aligned}
z1ϕ2(z1)ϕL(zL−1)y^ui=ϕ1(pu,qi)=[puqi],=a2(W2Tz1+b2),⋯⋯=aL(WLTzL−1+bL),=σ(hTϕL(zL−1)),
主要过程就是先将初始生成的embedding做拼接,然后再输入到MLP中学习用户-项目的高维度关系,然后直接得到最终的预测分数,而不是通过内积的方式,其中
y
^
u
i
hat{y}_{u i}
y^ui就是最终的预测函数
四、GMF模型
本小节在整篇文章中的意义是证明NCF模型可以泛化到传统的MF模型中。
用
p
u
mathbf{p}_{u}
pu和
q
i
mathbf{q}_{i}
qi分别代替NCF传播公式中的
P
T
v
u
U
,
Q
T
v
i
I
mathbf{P}^{T} mathbf{v}_{u}^{U}, mathbf{Q}^{T} mathbf{v}_{i}^{I}
PTvuU,QTviI,然后设计一个GMF模型:
定义第一层CF层为:
ϕ
1
(
p
u
,
q
i
)
=
p
u
⊙
q
i
phi_{1}left(mathbf{p}_{u}, mathbf{q}_{i}right)=mathbf{p}_{u} odot mathbf{q}_{i}
ϕ1(pu,qi)=pu⊙qi
最终预测评分的公式为:
y
^
u
i
=
a
o
u
t
(
h
T
(
p
u
⊙
q
i
)
)
hat{y}_{u i}=a_{o u t}left(mathbf{h}^{T}left(mathbf{p}_{u} odot mathbf{q}_{i}right)right)
y^ui=aout(hT(pu⊙qi))
而传统的MF计算最终预测评分的公式为:
y
^
u
i
=
f
(
u
,
i
∣
p
u
,
q
i
)
=
p
u
T
q
i
=
∑
k
=
1
K
p
u
k
q
i
k
hat{y}_{u i}=fleft(u, i mid mathbf{p}_{u}, mathbf{q}_{i}right)=mathbf{p}_{u}^{T} mathbf{q}_{i}=sum_{k=1}^{K} p_{u k} q_{i k}
y^ui=f(u,i∣pu,qi)=puTqi=k=1∑Kpukqik
所以从上面两个式子对比可以看到,如果将GMF模型的预测函数中的
a
o
u
t
a_{o u t}
aout设置为等值函数(f(x) = x),将
h
T
mathbf{h}^{T}
hT设置为单位向量(即元素都为1),那么GMF模型就会退化变成MF模型,所以NCF模型可以泛化MF模型,MF模型是NCF模型基于线性关系的一个特例。
五、损失函数
本模型使用的损失函数是BPR损失函数,这种损失函数的核心思想是尽可能的扩大正样本(用户有过交互历史的)和负样本(无交互历史的)之间的评分差距,因为其认为用户曾经有过交互的项目评分应该比没有交互的评分要高
Loss
=
∑
(
u
,
i
,
j
)
∈
O
−
ln
σ
(
y
(
u
,
i
)
−
y
(
u
,
j
)
)
+
β
⋅
∥
Θ
∥
2
text { Loss }=sum_{(u, i, j) in O}-ln sigma(y(u, i)-y(u, j))+beta cdot|Theta|^{2}
Loss =(u,i,j)∈O∑−lnσ(y(u,i)−y(u,j))+β⋅∥Θ∥2
其中y(u,i)和y(u,j)各自代表用户对交互过的项目的预测评分,和没有交互过的项目的预测评分,
θ
theta
θ代表模型中的所有参数,
β
beta
β是L2正则化参数
六、CONCULSION
NCF模型之所以能够取得性能上的提升,主要思想是在MF将用户和项目的独热编码映射到隐式空间得到初始embedding之后,利用多层感知机能够逼近任意函数的原理去逼近训练数据的真实分布,从而学习到用户和项目的embedding之间的关系,然后取代了MF中使用的内积的方法,直接得出了用户对某一项目的分数预测结果。
本模型将MF的线性以及MLP的非线性进行了结合,通过MLP对高维特征的学习能力来弥补MF在高维隐式空间中学习能力不足的问题,本文是神经网络引入到CF任务中的一篇里程碑式的论文,对后续的NGCF,LightGCN等经典的基于图神经网络的CF模型都有着启发意义。
附录(NCF模型基于paddle的代码实现)
下面展示一些 内联代码片
。
class NCF(paddle.nn.Layer):
#模型初始化
def __init__(self,
embedding_dim = 16,#用户,物品的embedding维度,即向量的维度
vocab_map = None,
loss_fun = 'nn.BCELoss()'):#选用的损失函数
super(NCF, self).__init__()#继承nn父类
self.embedding_dim = embedding_dim
self.vocab_map = vocab_map#映射表
self.loss_fun = eval(loss_fun) # self.loss_fun = paddle.nn.BCELoss()
self.user_emb_layer = nn.Embedding(self.vocab_map['user_id'],
self.embedding_dim)#模型图中的用户embedding生成层
self.item_emb_layer = nn.Embedding(self.vocab_map['item_id'],
self.embedding_dim)#模型图中的项目embedding生成层
"""
模型结构中的mlp层
"""
self.mlp = nn.Sequential(
nn.Linear(2*self.embedding_dim,self.embedding_dim),#一个全连接层,linear(a,b),a是输入层维度,b是输出层维度
nn.ReLU(), #非线性激活函数Relu
nn.BatchNorm1D(self.embedding_dim), #批归一化层,可以加快模型的收敛速度和稳定性
nn.Linear(self.embedding_dim,1),#又是一个全连接层,输出维度是1,代表这个全连接层是最后的输出层
nn.Sigmoid() #sigmod激活函数,把全连接层的结果压缩到(0,1)区间之内,得出一个概率值
)
"""
向前传播函数
"""
def forward(self,data):
user_emb = self.user_emb_layer(data['user_id']) # [batch,emb] ,生成了用户的输入embedding
item_emb = self.item_emb_layer(data['item_id']) # [batch,emb], 生成了项目的输入embedding
mlp_input = paddle.concat([user_emb, item_emb],axis=-1).squeeze(1)#经过全连接层生成最后的输出
y_pred = self.mlp(mlp_input) #最终的预测值
if 'label' in data.keys():
loss = self.loss_fun(y_pred.squeeze(),data['label'])
output_dict = {'pred':y_pred,'loss':loss}
else:
output_dict = {'pred':y_pred}
return output_dict