本文的创新在于,使用神经网络框架来代替MF中的内积,将MF和MLP的线性以及非线性特点相结合,使用预训练参数来初始化模型,进一步提升模型性能。本文思路清晰,逻辑严谨,细节说明很到位,实验对比完整且比较有说服力,很值得学习。因此我将笔记整理出来分享一下,如果有不对的地方,多多包涵,敬请批评指出。
本文致力于使用基于神经网络的技术来解决推荐中的关键问题–协同过滤-基于隐式反馈(保持原评分-显式反馈,将原评分大于0的置1-隐式反馈)。本文提出了一种简称NCF(Neural network based Collaborative Filtering)的通用神经框架来代替内积,以从数据中学习任意的函数。为了更好地建模非线性关系,本文提出利用多层感知机来学习user-item交互函数。
J ( A , B ) = ∣ A ∩ B ∣ ∣ A ∪ B ∣ = ∣ A ∩ B ∣ ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ J(A, B)=\frac{|A \cap B|}{|A \cup B|}=\frac{|A \cap B|}{|A|+|B|-|A \cap B|} J(A,B)=∣A∪B∣∣A∩B∣=∣A∣+∣B∣−∣A∩B∣∣A∩B∣
d j ( A , B ) = 1 − J ( A , B ) = ∣ A ∪ B ∣ − ∣ A ∩ B ∣ ∣ A ∪ B ∣ = A Δ B ∣ A ∪ B ∣ d_{j}(A, B)=1-J(A, B)=\frac{|A \cup B|-|A \cap B|}{|A \cup B|}=\frac{A \Delta B}{|A \cup B|} dj(A,B)=1−J(A,B)=∣A∪B∣∣A∪B∣−∣A∩B∣=∣A∪B∣AΔB
Jaccard 距离越大,样本相似度越低
推荐->矩阵分解->矩阵分解扩展方法->矩阵分解的不足之处(内积)->神经网络的表现
本文研究基于隐式反馈:
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.
1(a)采用Jaccard coefficient计算相似度,为了解决这个限制,本文通过DNNs来学习这个交互函数。
输入是user和item的one-hot向量,然后再通过Embedding层得到user和item的潜在特征向量,然后再后续操作。
而本文提出了一种新的神经矩阵分解模型,该模型集成了NCF框架下的MF和MLP,结合了MF的线性能力以及MLP的非线性能力。
本文采用的是point-wise,未来工作可能会使用pair-wise。
L s q r = ∑ ( u , i ) ∈ Y ∪ Y − w u i ( y u i − y ^ u i ) 2 L_{s q r}=\sum_{(u, i) \in \mathcal{Y} \cup \mathcal{Y}^{-}} w_{u i}\left(y_{u i}-\hat{y}_{u i}\right)^{2} Lsqr=(u,i)∈Y∪Y−∑wui(yui−y^ui)2
其中 w u i w_{u i} wui表示样本权重,通常MF中 w u i = 1 w_{u i}=1 wui=1
似然函数
p ( Y , Y − ∣ P , Q , Θ f ) = ∏ ( u , i ) ∈ Y y ^ u i ∏ ( u , j ) ∈ Y − ( 1 − y ^ u j ) p\left(\mathcal{Y}, \mathcal{Y}^{-} \mid \mathbf{P}, \mathbf{Q}, \Theta_{f}\right)=\prod_{(u, i) \in \mathcal{Y}} \hat{y}_{u i} \prod_{(u, j) \in \mathcal{Y}^{-}}\left(1-\hat{y}_{u j}\right) p(Y,Y−∣P,Q,Θf)=(u,i)∈Y∏y^ui(u,j)∈Y−∏(1−y^uj)
取概率的负对数
L = − ∑ ( u , i ) ∈ Y log y ^ u i − ∑ ( u , j ) ∈ Y − log ( 1 − y ^ u j ) = − ∑ ( u , i ) ∈ Y ∪ Y − y u i log y ^ u i + ( 1 − y u i ) log ( 1 − y ^ u i ) \begin{aligned} L &=-\sum_{(u, i) \in \mathcal{Y}} \log \hat{y}_{u i}-\sum_{(u, j) \in \mathcal{Y}^{-}} \log \left(1-\hat{y}_{u j}\right) \\ &=-\sum_{(u, i) \in \mathcal{Y} \cup \mathcal{Y}^{-}} y_{u i} \log \hat{y}_{u i}+\left(1-y_{u i}\right) \log \left(1-\hat{y}_{u i}\right) \end{aligned} L=−(u,i)∈Y∑logy^ui−(u,j)∈Y−∑log(1−y^uj)=−(u,i)∈Y∪Y−∑yuilogy^ui+(1−yui)log(1−y^ui)
其实这就是二分类任务中常用的交叉熵,也常被成为log loss
y ^ u i = a out ( h T ( p u ⊙ q i ) ) \hat{y}_{u i}=a_{\text {out}}\left(\mathbf{h}^{T}\left(\mathbf{p}_{u} \odot \mathbf{q}_{i}\right)\right) y^ui=aout(hT(pu⊙qi))
a o u t a_{out} aout是激活函数,本文使用sigmoid,如果将 a o u t a_{out} aout取自己, h T {h}^{T} hT是一个全为1的向量,那么这就是MF
为了更好地建模user和item,不能只是简单地将两个向量进行拼接,因为这样并不能起到user和item之间的特征交互作用,为了解决这个问题,在拼接后的向量添加全连接层,因此使用多层感知机结构。
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}{c} \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) \\ & \ldots \ldots \\ \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} &=\sigma\left(\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))
隐层激活函数可选:sigmoid,tanh,relu
sigmoid :sigmoid将数值规约到[0, 1],但容易遇到饱和问题,当神经元的输出值接近0或者1的时候就会停止学习
tanh:虽然tanh被大量使用,但它也只是一定程度上缓解了sigmoid的问题,因为它可以被视为另一种重新调整的sigmoid( t a n h ( x / 2 ) = 2 σ ( x ) − 1 ) tanh(x/2) = 2σ(x) − 1) tanh(x/2)=2σ(x)−1))
relu:我们更倾向于选择relu作为激活函数,relu从生物学角度来说是可行的,并且被证明是不饱和的
至于隐层神经元个数设置,本文采用金字塔结构,即 [32, 16, 8, 4, 2, 1]
一种直接的方法是GMF和MLP共享Embedding,但这样可能会限制融合的作用,因此本文采用了不用大小的Embedding size,模型输出为:
ϕ G M F = p u G ⊙ q i G ϕ M L P = a L ( W L T ( a L − 1 ( … a 2 ( W 2 T [ p u M ] + b 2 ) … ) ) + b L ) y ^ u i = σ ( h T [ ϕ G M F ϕ M L P ] ) \begin{aligned} \phi^{G M F} &=\mathbf{p}_{u}^{G} \odot \mathbf{q}_{i}^{G} \\ \phi^{M L P} &=a_{L}\left(\mathbf{W}_{L}^{T}\left(a_{L-1}\left(\ldots a_{2}\left(\mathbf{W}_{2}^{T}\left[\mathbf{p}_{u}^{M}\right]+\mathbf{b}_{2}\right) \ldots\right)\right)+\mathbf{b}_{L}\right) \\ \hat{y}_{u i} &=\sigma\left(\mathbf{h}^{T}\left[\begin{array}{c} \phi^{G M F} \\ \phi^{M L P} \end{array}\right]\right) \end{aligned} ϕGMFϕMLPy^ui=puG⊙qiG=aL(WLT(aL−1(…a2(W2T[puM]+b2)…))+bL)=σ(hT[ϕGMFϕMLP])
由于NeuMF的目标函数具有非凸的性质,使用梯度下降求解只能找到局部最优解,因此参数初始化显得格外重要;因为NeuMF集成了GMF和MLP模型,因此本文提出使用GMF和MLP模型的参数作为NeuMF模型的初始化参数。
首先使用随机初始化的方式生成GMF和MLP的参数,然后训练直至收敛,使用Adam(它通过对频繁的参数执行较小的更新而对不频繁的参数进行较大的更新来调整每个参数的学习率,更快的收敛)优化。初始化NeuMF时使用GMF和MLP中相应部分的参数来初始化即可,不过初始化参数h时使用以下方法:
h ← [ α h G M F ( 1 − α ) h M L P ] \mathbf{h} \leftarrow\left[\begin{array}{c} \alpha \mathbf{h}^{G M F} \\ (1-\alpha) \mathbf{h}^{M L P} \end{array}\right] h←[αhGMF(1−α)hMLP]
其中参数 α = 0.5 \alpha=0.5 α=0.5,使用预训练的初始化权重之后,NeuMF使用SGD进行参数优化。
MovieLens:http://grouplens.org/datasets/movielens/1m/
Pinterest:https://sites.google.com/site/xueatalphabeta/ academic-projects
HR( H i t R a t i o Hit Ratio HitRatio)
NDCG( N o r m a l i z e d D i s c o u n t e d C u m u l a t i c e G a i n Normalized Discounted Cumulatice Gain NormalizedDiscountedCumulaticeGain)
https://github.com/hexiangnan/neural_collaborative_filtering
python GMF.py --dataset ml-1m --epochs 20 --batch_size 256 --num_factors 8 --regs [0,0] --num_neg 4 --lr 0.001 --learner adam --verbose 1 --out 1
python MLP.py --dataset ml-1m --epochs 20 --batch_size 256 --layers [64,32,16,8] --reg_layers [0,0,0,0] --num_neg 4 --lr 0.001 --learner adam --verbose 1 --out 1
python NeuMF.py --dataset ml-1m --epochs 20 --batch_size 256 --num_factors 8 --layers [64,32,16,8] --num_neg 4 --lr 0.001 --learner adam --verbose 1 --out 1 --mf_pretrain Pretrain/ml-1m_GMF_8_1501651698.h5 --mlp_pretrain Pretrain/ml-1m_MLP_[64,32,16,8]_1501652038.h5
python NeuMF.py --dataset ml-1m --epochs 20 --batch_size 256 --num_factors 8 --layers [64,32,16,8] --reg_mf 0 --reg_layers [0,0,0,0] --num_neg 4 --lr 0.001 --learner adam --verbose 1 --out 1