推荐系统(七)xDeepFM模型

推荐系统(七)xDeepFM模型

推荐系统系列博客:

  1. 推荐系统(一)推荐系统整体概览
  2. 推荐系统(二)GBDT+LR模型
  3. 推荐系统(三)Factorization Machines(FM)
  4. 推荐系统(四)Field-aware Factorization Machines(FFM)
  5. 推荐系统(五)wide&deep
  6. 推荐系统(六)Deep & Cross Network(DCN)

这篇文章是中科大、北邮和微软合作发表在KDD’18上的文章,从论文写作手法上来看充满了浓浓的学院风。言归正传,乍一看论文标题xDeepFM还以为是对DeepFM的改进,实际上不是,xDeepFM是对上一篇博客中介绍的DCN(deep&cross network)的改进,这也是为什么这篇博客会介绍xDeepFM的原因。本篇博客将会从一下几个方面来介绍xDeepFM:

  1. 论文动机
  2. xDeepFM模型整体结构
  3. xDeepFM中CIN网络结构
  4. 联合训练目标函数
  5. 总结

一、论文动机

这篇文章diss的一个重点就是DCN网络中cross network部分,因此,个人强烈建议在看xDeepFM之前,请先移步去本人的上一篇博客看看DCN。我们先来看看xDeepFM给出的论据:

  1. 这篇文章认为DCN网络中cross部分只是对 x 0 x_0 x0(输入向量)某种特殊的放缩(交叉)。
  2. 这篇文章认为DCN网络中cross部分的交叉是bit-wise的而不是像鼻祖FM那样是vector-wise的。这里简单描述bit-wise和vector-wise的区别,假设有两个特征 A , B A,B A,B,embedding维度为4,所以 A ( a 1 , a 2 , a 3 , a 4 ) A(a_1,a_2,a_3,a_4) A(a1,a2,a3,a4) B ( b 1 , b 2 , b 3 , b 4 ) B(b_1,b_2,b_3,b_4) B(b1,b2,b3,b4),那么bit-wise的交互则为 ( w a , b a 1 b 1 , w a , b a 2 b 2 , w a , b a 3 b 3 , w a , b a 4 b 4 ) (w_{a,b}a_1b_1,w_{a,b}a_2b_2,w_{a,b}a_3b_3,w_{a,b}a_4b_4) (wa,ba1b1,wa,ba2b2,wa,ba3b3,wa,ba4b4),而vector-wise交互则为 w a , b ( a 1 b 1 , a 2 b 2 , a 3 b 3 , a 4 b 4 ) w_{a,b}(a_1b_1,a_2b_2,a_3b_3,a_4b_4) wa,b(a1b1,a2b2,a3b3,a4b4)。通俗说bit-wise交互的粒度为每个特征embedding向量的每个bit上,而vector-wise交互粒度为特征与特征之间。

上面两个点,第二个点没啥好说的,重点来看看第一个点。xDeepFM这篇文章中给出了公式证明,先来看看DCN中cross部分的交叉公式:
x k = x 0 x k − 1 T w k + b k + x k − 1 (1) x_k = x_0x^T_{k-1}w_k + b_k + x_{k-1} \tag{1} xk=x0xk1Twk+bk+xk1(1)
再来看看xDeepFM中的证明:
x 1 = x 0 ( x 0 T w 1 ) + x 0 = x 0 ( x 0 T w 1 + 1 ) = α 1 x 0 (2) \begin{aligned} x_1 &= x_0(x_0^Tw_1) + x_0 \\ &= x_0(x_0^Tw_1 + 1) \\ &=\alpha^1x_0 \tag{2} \end{aligned} x1=x0(x0Tw1)+x0=x0(x0Tw1+1)=α1x0(2)
其中, α 1 = x 0 T w 1 + 1 \alpha^1=x_0^Tw_1 + 1 α1=x0Tw1+1
以此类推,
x i + 1 = α i + 1 x 0 = α i ( x 0 T w i + 1 + 1 ) x 0 = α i − 1 ( x 0 T w i + 1 ) ( x 0 T w i + 1 + 1 ) x 0 = α i − 2 ( x 0 T w i − 1 + 1 ) ( x 0 T w i + 1 ) ( x 0 T w i + 1 + 1 ) x 0 = . . . = ( x 0 T w 1 + 1 ) . . . ( x 0 T w i − 1 + 1 ) ( x 0 T w i + 1 ) ( x 0 T w i + 1 + 1 ) x 0 (3) \begin{aligned} x_{i+1} &= \alpha^{i+1}x_0 \\ &= \alpha^{i}(x_0^Tw_{i+1} + 1)x_0 \\ &= \alpha^{i-1}(x_0^Tw_{i} + 1)(x_0^Tw_{i+1} + 1)x_0 \\ &= \alpha^{i-2}(x_0^Tw_{i-1} + 1)(x_0^Tw_{i} + 1)(x_0^Tw_{i+1} + 1)x_0 \\ &=... \\ &= (x_0^Tw_1 + 1)...(x_0^Tw_{i-1} + 1)(x_0^Tw_{i} + 1)(x_0^Tw_{i+1}+ 1)x_0 \tag{3} \end{aligned} xi+1=αi+1x0=αi(x0Twi+1+1)x0=αi1(x0Twi+1)(x0Twi+1+1)x0=αi2(x0Twi1+1)(x0Twi+1)(x0Twi+1+1)x0=...=(x0Tw1+1)...(x0Twi1+1)(x0Twi+1)(x0Twi+1+1)x0(3)
xDeepFM认为这种交叉是一种对 x 0 x_0 x0特殊的交叉,但其实吧,我个人认为DCN中这种交叉是没什么问题的,直接拿我在DCN(deep&cross network)这篇博客里的例子来看:
假设样本只有两个特征,为了简单,假设每个特征的embedding维度为1,偏置 b b b直接忽略,设 x 0 = [ x 0 , 1 x 0 , 2 ] x_0=\begin{bmatrix} x_{0,1} \\ x_{0,2} \\ \end{bmatrix} x0=[x0,1x0,2],那么,则有:
x 1 = x 0 ⊙ ( w 1 x 1 ) + x 0 = [ x 0 , 1 x 0 , 2 ] ⊙ [ w 1 , 1 x 0 , 1 + w 1 , 2 x 0 , 2 w 1 , 1 x 0 , 1 + w 1 , 2 x 0 , 2 ] + [ x 0 , 1 x 0 , 2 ] = [ x 0 , 1 ( w 1 , 1 x 0 , 1 + w 1 , 2 x 0 , 2 ) + x 0 , 1 x 0 , 2 ( w 1 , 1 x 0 , 1 + w 1 , 2 x 0 , 2 ) + x 0 , 2 ] = [ w 1 , 1 x 0 , 1 2 + w 1 , 2 x 0 , 2 x 0 , 1 + x 0 , 1 w 1 , 1 x 0 , 1 x 0 , 2 + w 1 , 2 x 0 , 2 2 + x 0 , 2 ] (4) \begin{aligned} x_1&=x_0\odot(w_1x_1) + x_0 \\ &=\begin{bmatrix} x_{0,1} \\ x_{0,2} \\ \end{bmatrix}\odot\begin{bmatrix} w_{1,1}x_{0,1} +w_{1,2}x_{0,2} \\ w_{1,1}x_{0,1} +w_{1,2}x_{0,2} \\ \end{bmatrix} + \begin{bmatrix} x_{0,1} \\ x_{0,2} \\ \end{bmatrix} \\\\ &=\begin{bmatrix} x_{0,1}(w_{1,1}x_{0,1} +w_{1,2}x_{0,2}) + x_{0,1} \\ x_{0,2}(w_{1,1}x_{0,1} +w_{1,2}x_{0,2}) + x_{0,2}\\ \end{bmatrix} \\\\ &=\begin{bmatrix} w_{1,1}x_{0,1}^2 +w_{1,2}x_{0,2}x_{0,1} + x_{0,1} \\ w_{1,1}x_{0,1} x_{0,2} +w_{1,2}x_{0,2}^2 + x_{0,2}\\ \end{bmatrix} \end{aligned} \tag{4} x1=x0(w1x1)+x0=[x0,1x0,2][w1,1x0,1+w1,2x0,2w1,1x0,1+w1,2x0,2]+[x0,1x0,2]=[x0,1(w1,1x0,1+w1,2x0,2)+x0,1x0,2(w1,1x0,1+w1,2x0,2)+x0,2]=[w1,1x0,12+w1,2x0,2x0,1+x0,1w1,1x0,1x0,2+w1,2x0,22+x0,2](4)
从上面的例子能够看出,DCN中cross部分交叉项包含了 x 0 , 1 2 , x 0 , 2 x 0 , 1 , . . . x_{0,1}^2,x_{0,2}x_{0,1},... x0,12,x0,2x0,1,...等交叉项,所以DCN中这种cross的设计是没什么问题的。所以xDeepFM是否在真实的场景中效果比DCN好,这一点我是持怀疑态度的,但有一点可以肯定的是xDeepFM网络结构相对复杂,参数量相对大的多,计算复杂度高,在工业界大规模数据场景下,能否顺利落地是个问题。

二、xDeepFM模型整体结构

关于xDeepFM的整天网络结构,如下图所示(图片直接摘自原论文):
推荐系统(七)xDeepFM模型_第1张图片
从上图可以看出,xDeepFM依然延续了wide&deep,DCN这种混合网络结构模式,左手画个xxx网络用于构造高阶交叉特征,右手画个普通DNN用于学习隐式的交叉特征,最终一起联合训练。xDeepFM这里包含三部分:普通的线性部分、CIN部分用于构造高阶交叉特征、普通的DNN部分用于学习隐式特征。
这里核心部分显然在于CIN结果,本篇博客会重点讲解这个部分。

三、xDeepFM中CIN网络结构

CIN全称Compressed Interaction Network,我们照着论文中给出的图逐一讲解,先上图:
推荐系统(七)xDeepFM模型_第2张图片

3.1 图(a)

推荐系统(七)xDeepFM模型_第3张图片

x 0 x^0 x0的维度为 m ∗ D m * D mD,m为特征个数,D为特征embedding向量维度。 x k x^k xk的维度为 H k ∗ D H_k*D HkD H k H_k Hk表示第 k k k层特征向量个数。它这里的做法是: x k x^k xk的第 i i i列向量即 x ∗ i k x^k_{*i} xik x 0 x^0 x0的第 i i i列向量的转置即 ( x ∗ i 0 ) T (x^0_{*i})^T (xi0)T做外积,因此得到一个 H k ∗ m H_k*m Hkm大小的矩阵,因为总共有D个这些向量,所以最终结果也就是 Z k + 1 Z^{k+1} Zk+1为一个 H k ∗ m ∗ D H_k*m*D HkmD的三维矩阵。
用个例子来说明上图,假设 x ∗ 1 k = [ a 1 , a 2 , a 3 ] x^k_{*1}=[a_1,a_2,a_3] x1k=[a1,a2,a3] x ∗ 1 0 = [ b 1 , b 2 , b 3 , b 4 ] x^0_{*1}=[b_1,b_2,b_3,b_4] x10=[b1,b2,b3,b4],则有:
x ∗ 1 k ⊗ ( x ∗ 1 0 ) T = [ a 1 a 2 a 3 ] ⊗ [ b 1 b 2 b 3 b 4 ] = [ a 1 b 1 a 1 b 2 a 1 b 3 a 1 b 4 a 2 b 1 a 2 b 2 a 2 b 3 a 2 b 4 a 3 b 1 a 3 b 2 a 3 b 3 a 3 b 4 ] \begin{aligned} x^k_{*1} \otimes (x^0_{*1})^T=\begin{bmatrix} a_1& a_2& a_3 \\ \end{bmatrix} \otimes \begin{bmatrix} b_1 \\ b_2 \\ b_3 \\ b_4 \end{bmatrix}=\begin{bmatrix} a_1b_1& a_1b_2& a_1b_3&a_1b_4 \\ a_2b_1& a_2b_2& a_2b_3&a_2b_4 \\ a_3b_1& a_3b_2& a_3b_3&a_3b_4 \\ \end{bmatrix} \end{aligned} x1k(x10)T=[a1a2a3]b1b2b3b4=a1b1a2b1a3b1a1b2a2b2a3b2a1b3a2b3a3b3a1b4a2b4a3b4
以此类推, x ∗ 2 k ⊗ x ∗ 2 0 x^k_{*2} \otimes x^0_{*2} x2kx20 x ∗ 3 k ⊗ x ∗ 3 0 x^k_{*3} \otimes x^0_{*3} x3kx30,…, x ∗ D k ⊗ x ∗ D 0 x^k_{*D} \otimes x^0_{*D} xDkxD0均会得到一个 H k ∗ m H_k*m Hkm的矩阵,合在一起变为一个 H k ∗ m ∗ D H_k*m*D HkmD的三维矩阵,也就是上图中的 Z k + 1 Z^{k+1} Zk+1

3.2 图(b)

推荐系统(七)xDeepFM模型_第4张图片
经过图(a)中的外积操作,我们得到一个 H k ∗ m ∗ D H_k*m*D HkmD的三维矩阵 Z k + 1 Z^{k+1} Zk+1图(b)要做的事情就是把一个 H k ∗ m ∗ D H_k*m*D HkmD的矩阵压缩成一个 H k ∗ D H_k*D HkD的矩阵,这也是CIN中C的来源,Compressed
说到矩阵维度的压缩,自然就想到了CNN中的卷积操作,论文也借鉴了卷积的操作思想,我们来看看是怎么操作的:它这里使用了 H k + 1 H_{k+1} Hk+1个大小为 ​ H k ∗ m ​H_k*m Hkm的向量(矩阵)对 Z k + 1 Z^{k+1} Zk+1做了一波点乘内积,此时得到是 H k + 1 H_{k+1} Hk+1个向量的第一个元素,因为 Z k + 1 Z^{k+1} Zk+1的channel为D,因此最终得到的 x k + 1 x^{k+1} xk+1 H k + 1 H_{k+1} Hk+1个D维向量,因此 x k + 1 x^{k+1} xk+1的维度为 H k ∗ D H_k*D HkD。这里需要理解的是 ​ H k ∗ m ​H_k*m Hkm向量(filter)对 Z k + 1 Z^{k+1} Zk+1做点乘内积,得到的是一个值,即向量的某个元素。在原论文里对应 H k H_k Hk W k , h W^{k,h} Wk,h

3.3 图(c)

推荐系统(七)xDeepFM模型_第5张图片
图(c)这里把整个CIN部分做了个概览,原论文中把这个步骤往RNN的思想上靠了一波。 x k x^k xk的产生由它的上一层 x k − 1 x^{k-1} xk1和初始的 x 0 x^0 x0共同决定,论文中给出了一个形式化的公式:
X h , ∗ k = ∑ i = 1 H k − 1 ∑ j = 1 m W i j k , h ( X i , ∗ k − 1 ∘ X j , ∗ 0 ) X_{h,*}^k=\sum_{i=1}^{H_{k-1}}\sum_{j=1}^{m}W_{ij}^{k,h}(X_{i,*}^{k-1} \circ X_{j,*}^0) Xh,k=i=1Hk1j=1mWijk,h(Xi,k1Xj,0)
x 2 x^2 x2为例来看下全过程, x 0 x^0 x0 x 1 x^1 x1先经过图(a)中的外积操作得到一个 H 1 ∗ m ∗ D H_1*m*D H1mD的矩阵 Z 2 Z^{2} Z2,然后 Z 2 Z^{2} Z2经过图(b)中的压缩(卷积)操作,得到维度为 H 2 ∗ D H_2*D H2D的矩阵 x 2 x^2 x2,该矩阵包含 H 2 H_2 H2个D维的向量。然后对这 H 2 H_2 H2个D维向量分别做sum pooling操作,如图(c)所示,因此, x 1 x_1 x1最终产生一个 H 1 ∗ 1 H_1*1 H11维向量, x 2 x_2 x2产生一个 H 2 ∗ 1 H_2*1 H21维向量,…, x k x_k xk产生一个 H k ∗ 1 H_k*1 Hk1维向量,最后合并成一个维度为 ∑ i = 1 k H i \sum_{i=1}^kH_i i=1kHi维度的向量,与DNN向量再做联合训练。论文中形式化的定义为,假设 X k X^k Xk经过sum pooling后得到的向量为 p k p^k pk,则 p k = [ p 1 k , p 2 k , . . . , p H k k ] p^k=[p_1^k,p_2^k,...,p_{H_k}^k] pk=[p1k,p2k,...,pHkk],则整个CIN最终产生的向量为 p + = [ p 1 , p 2 , . . . . , p K ] p^+=[p^1,p^2,....,p^K] p+=[p1,p2,....,pK] p + p^+ p+的维度为 ∑ i = 1 K H i \sum_{i=1}^KH_i i=1KHi

3.4 代码实现

paddle官方给出的cin部分的实现代码为(我增加了tensor的维度注释,方便大家理解):

    def forward(self, feat_embeddings):
        # [shape=[5, 39, 9]]
        Xs = [feat_embeddings]
        last_s = self.num_field
        # layer_sizes_cin=[128, 32]
        # cnn_layers= [Conv2D(1521, 128, kernel_size=[1, 1], data_format=NCHW), 
        # Conv2D(4992, 32, kernel_size=[1, 1], data_format=NCHW)]
        for s, _conv in zip(self.layer_sizes_cin, self.cnn_layers):
            # 做外积,对应图(a)
            # Tensor(shape=[5, 9, 39, 1])
            X_0 = paddle.reshape(
                # shape=[5,9,39]
                x=paddle.transpose(Xs[0], [0, 2, 1]),
                shape=[-1, self.sparse_feature_dim, self.num_field,
                       1])  # None, embedding_size, num_field, 1
            # shape=[5, 9, 1, 39]
            X_k = paddle.reshape(
                x=paddle.transpose(Xs[-1], [0, 2, 1]),
                shape=[-1, self.sparse_feature_dim, 1,
                       last_s])  # None, embedding_size, 1, last_s
            # shape=[5, 9, 39, 39]
            Z_k_1 = paddle.matmul(
                x=X_0, y=X_k)  # None, embedding_size, num_field, last_s
            # 卷积操作,对应图(b)
            # compresses Z^(k+1) to X^(k+1)
            # shape=[5, 9, 1521]
            Z_k_1 = paddle.reshape(
                x=Z_k_1,
                shape=[-1, self.sparse_feature_dim, last_s * self.num_field
                       ])  # None, embedding_size, last_s*num_field
            # shape=[5, 1521, 9]
            Z_k_1 = paddle.transpose(
                Z_k_1, [0, 2, 1])  # None, s*num_field, embedding_size
            # shape=[5, 1521, 1, 9]
            Z_k_1 = paddle.reshape(
                x=Z_k_1,
                shape=[
                    -1, last_s * self.num_field, 1, self.sparse_feature_dim
                ]
            )  # None, last_s*num_field, 1, embedding_size  (None, channal_in, h, w)
            # shape=[5, 128, 1, 9]
            X_k_1 = _conv(Z_k_1)
            # shape=[5, 128, 9]
            X_k_1 = paddle.reshape(
                x=X_k_1,
                shape=[-1, s,
                       self.sparse_feature_dim])  # None, s, embedding_size
            #X_k_1 = m(X_k_1)
            Xs.append(X_k_1)
            last_s = s
        # 池化,图(c)
        # sum pooling
        # Xs-->Tensor(shape=[5, 39, 9]), Tensor(shape=[5, 128, 9]), Tensor(shape=[5, 32, 9]]
        # shape=[5, 160, 9]
        y_cin = paddle.concat(
            x=Xs[1:], axis=1)  # None, (num_field++), embedding_size
        # shape=[5, 160]
        y_cin = paddle.sum(x=y_cin, axis=-1)  # None, (num_field++)i
        tmp_sum = sum(self.layer_sizes_cin)
        # shape=[5, 1]
        y_cin = self.cin_linear(y_cin)
        # shape=[5, 1]
        return y_cin

四、联合训练目标函数

xDeepFM整体包含三部分:线性部分、CIN部分、DNN部分。论文给出了联合训练的交叉熵损失函数:
L = − 1 N ∑ i = 1 N y i l o g y i ^ + ( 1 − y i ) l o g ( 1 − y i ^ ) L=-\frac{1}{N}\sum_{i=1}^Ny_ilog\hat{y_i} + (1-y_i)log(1-\hat{y_i}) L=N1i=1Nyilogyi^+(1yi)log(1yi^)
其中,预测输出 y i ^ = σ ( W l i n e a r T a + W d n n T x d n n k + W c i n T p + + b ) \hat{y_i}=\sigma(W^T_{linear}a + W^T_{dnn}x^k_{dnn} + W^T_{cin}p^+ +b) yi^=σ(WlinearTa+WdnnTxdnnk+WcinTp++b),即把三部分想加然后做sigmoid。

五、总结

个人认为xDeepFM参数量较大,计算复杂度应该相对较高,在真实的场景下性能可能会成为一个问题。至于效果,在不同的数据集下与其他模型可能会呈现出不同的结果,例如DCN V2论文里给出的实验结果,xDeepFM在AUC指标上在一些数据集上效果还不如DCN。有兴趣的可以直接去看DCN V2这篇论文的实验比较部分,DCN V2论文地址:DCN V2。





参考文献
  1. Lian J, Zhou X, Zhang F, et al. xdeepfm: Combining explicit and implicit feature interactions for recommender systems[C]//Proceedings of the 24th ACM SIGKDD international conference on knowledge discovery & data mining. 2018: 1754-1763.
  2. Wang R, Shivanna R, Cheng D, et al. DCN V2: Improved deep & cross network and practical lessons for web-scale learning to rank systems[C]//Proceedings of the Web Conference 2021. 2021: 1785-1797.

你可能感兴趣的:(推荐系统,机器学习&深度学习,推荐系统,xDeepFM,CIN,DCN)