前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解推荐系统:微软xDeepFM原理与实践

深入理解推荐系统:微软xDeepFM原理与实践

作者头像
Coggle数据科学
发布2022-08-31 15:11:29
7420
发布2022-08-31 15:11:29
举报
文章被收录于专栏:Coggle数据科学Coggle数据科学

背景介绍

传统交叉特征工程主要有三个缺点,以下部分来自paper:

  • 获取高质量特征代价高昂
  • 大规模预测系统(比如:推荐系统),存在大量原始特征(raw features),很难人工抽取所有交叉特征
  • 人工交叉特征不能泛化到在训练数据中未见过的交叉上

上面的所有模型都使用DNN来学习高阶特征交叉。然而,DNN可以以一个隐式的方式建模高阶特征交叉。由DNN学到的最终函数可以是任意形式,关于特征交叉的最大阶数(maximum degree)没有理论上的结论。另外,DNNs在bit-wise级别建模征交叉,这与FM框架不同(它会在vector-wise级别建模)。这样,在推荐系统的领域,其中DNN是否是用于表示高阶特征交叉的最有效模型,仍然是一个开放问题。在本paper中,我们提供了一个基于NN的模型,以显式、vector-wise的方式来学习特征交叉。我们的方法基于DCN(Deep&Cross Network)之上,该方法能有效捕获有限阶数(bounded degree)的特征交叉。然而,我们会在第2.3节讨论,DCN将带来一种特殊形式的交叉。我们设计了一种新的压缩交叉网络CIN(compressed interaction network)来替换在DCN中的cross network。CIN可以显式地学到特征交叉,交叉的阶数会随着网络depth增长。根据Wide&Deep模型和DeepFM模型的精神,我们会结合显式高阶交叉模块和隐式交叉模型,以及传统的FM模块,并将该联合模型命名为“eXtreme Deep Factorization Machine (xDeepFM)”。这种新模型无需人工特征工程,可以让数据科学家们从无聊的特征搜索中解放出来。总结一下,主要有三个贡献:

  • 提出了一种新模型xDeepFM,可以联合训练显式和隐式高阶特征交叉,无需人工特征工程
  • 设计了CIN来显式学习高阶特征交叉。我们展示了特征交叉的阶(degree)会在每一层增加,特征会在vector-wise级别进行交叉。
  • 我们在三个数据集中进行了实验,结果展示xDeepFM效果好于其它state-of-art模型

准备工作

Embedding Layer

在CV或NLP领域,输入数据通常是图片或文本信号,它们空间相关(spatially correlated)或时序相关(temporally correlated),因而DNN可以被直接应用到dense结构的原始特征上。然而,在推荐系统中,输入特征是sparse、高维、没有明显地空间相关或时序相关。因此,multi-field类别形式被广泛使用。例如,一个输入实例为: [user_id=s02,gender=male,organization=msra,interests=comedy&rock]

通过field-aware one-hot进行编码成高维稀疏特征:

[\underbrace{0, 1, 0, 0, ..., 0}_{userid}] [\underbrace{1, 0}_{gender}] [\underbrace{0, 1, 0, 0, ..., 0}_{organization}] [\underbrace{0, 1, 0, 1, ..., 0}_{interests}] \\
[\underbrace{0, 1, 0, 0, ..., 0}_{userid}] [\underbrace{1, 0}_{gender}] [\underbrace{0, 1, 0, 0, ..., 0}_{organization}] [\underbrace{0, 1, 0, 1, ..., 0}_{interests}] \\

在原始特征输入上使用一个embedding layer,可以将它压缩到一个低维、dense、real-value vector上。如果field是一阶的(univalent),feature embedding被当成field embedding使用。以上述实例为例,特征(male)的embedding被当成field gender的embedding。如果field是多阶的(multivalent),feature embedding的求和被用于field embedding。embedding layer如图1所示。embedding layer的结果是一个wide concatenated vector:

e = [e_1, e_2, ..., e_m] \\
e = [e_1, e_2, ..., e_m] \\

其中,m表示fields的数目,

e_i \in R^D\\
e_i \in R^D\\

表示一个field的embedding。尽管实例的feature长度可以是多变的,它们的embedding具有相同的长度 m x D, 其中D是field embedding的维数。下图中,field embedding layer。本例中embedding的维度是4

隐式高阶交叉

FNN, Deep&Cross,以及Wide&Deep的deep part会使用一个在field embedding vector e上的feed-forward神经网络来学习高阶特征交叉。forward process是:

x^1 = \delta(W^{(1)} e + b^1) \\
x^1 = \delta(W^{(1)} e + b^1) \\
x^k = \delta(W^{(k)} x^{(k-1)} + b^k) \\
x^k = \delta(W^{(k)} x^{(k-1)} + b^k) \\

显式高阶交叉

Cross Network(CrossNet)的结构如下图所示:

CIN模型

CIN

论文设计了一个新的cross network,命名为CIN(Compressed Interaction Network),具有如下注意事项:

  • (1) 交叉是在vector-wise级别上进行,而非bit-wise级别
  • (2) 高阶特征的交叉显式衡量
  • (3) 网络的复杂度不会随着交叉阶数进行指数增长
p_i^k = \sum_{j=1}^D X_{i,j}^k \\
p_i^k = \sum_{j=1}^D X_{i,j}^k \\
y = \frac{1} {1 + exp(p^{+^T} w_o)} \\
y = \frac{1} {1 + exp(p^{+^T} w_o)} \\

其中,

w^o
w^o

是回归参数。

CIN详解

论文对CIN进行分析,研究了模型复杂度以及潜在的效果。

空间复杂度

时间复杂度

多项式近似(Polynomial Approximation)

x_h^2 = \sum_{i \in [m], j \in [m]} W_{i,j}^{2,h} (x_i^1 \circ x_j^0) \\ = \sum_{i \in [m], j \in [m]} \sum_{l \in [m], k \in [m]} W_{i,j}^{2,h} W_{l,k}^{1,i} (x_j^0 \circ x_k^0 \circ x_l^0 \\
x_h^2 = \sum_{i \in [m], j \in [m]} W_{i,j}^{2,h} (x_i^1 \circ x_j^0) \\ = \sum_{i \in [m], j \in [m]} \sum_{l \in [m], k \in [m]} W_{i,j}^{2,h} W_{l,k}^{1,i} (x_j^0 \circ x_k^0 \circ x_l^0 \\

注意,l和k相关的所有计算在前一个hidden layer已经完成。在等式

x_h^2
x_h^2

扩展的因子是为了清晰。可以观察到,在第二层的每个feature map会使用

O(m^2)
O(m^2)

新参数来建模3-way交叉。

一个经典的k阶多项式具有

O(m^k)
O(m^k)

系数。我们展示了CIN会逼近这类型多项式,根据一个feature maps链,只需要

O(k m^3)
O(k m^3)

个参数。通过引入hypothesis,我们可以证明,在第k层的第h个feature map为:

x_h^k = \sum_{i \in [m], j \in [m]} W_{i,j}^{k,h} (x_i^{k-1} \circ x_j^0) \\ = \sum_{i \in [m], j \in [m]} ... \sum_{r \in [m], t \in [m]} \sum_{l \in [m], s\in [m]} W_{i,j}^{k,h} ... W_{l,s}^{1,r} (x_j^0 \circ ... \circ x_s^0 \circ x_l^0) \\
x_h^k = \sum_{i \in [m], j \in [m]} W_{i,j}^{k,h} (x_i^{k-1} \circ x_j^0) \\ = \sum_{i \in [m], j \in [m]} ... \sum_{r \in [m], t \in [m]} \sum_{l \in [m], s\in [m]} W_{i,j}^{k,h} ... W_{l,s}^{1,r} (x_j^0 \circ ... \circ x_s^0 \circ x_l^0) \\

与隐式网络的组合

我们知道plain DNNs可以学到隐式高阶特征交叉。由于CIN和plain DNNs可以互补,一个直观的做法是,将这两种结构进行组合使模型更强。产生的模型与Wide&Deep和DeepFM非常像。结构如图5所示,我们将新模型命名为eXtreme Deep Factorization Machine(xDeepFM),一方面,它同时包含了低阶和高阶特征交叉;另一方面,它包含了隐式特征交叉和显式特征交叉。它产生的output unit如下:

\hat{y} = \sigma(w_{linear}^T a + w_{dnn}^T x_{dnn}^k + w_{cin}^T p^{+} + b) \\
\hat{y} = \sigma(w_{linear}^T a + w_{dnn}^T x_{dnn}^k + w_{cin}^T p^{+} + b) \\
L = - \frac{1}{N} \sum_{i=1}^N y_i log \hat{y}_i + (1-y_i) log(1-\hat{y}_i) \\
L = - \frac{1}{N} \sum_{i=1}^N y_i log \hat{y}_i + (1-y_i) log(1-\hat{y}_i) \\

其中,N是训练实例的总数。Optimization过程是最小化下面的目标函数:

J = L + \lambda_{*} \| \theta \| \\
J = L + \lambda_{*} \| \theta \| \\

与FM和DeepFM的关系

假设所有field是一阶的(univalent)。如上图所示(xDeepFM的结构),当depth和CIN part的feature maps同时设为1时,xDeepFM就是DeepFM的一个泛化,通过为FM layer学习线性回归权重实现(注意,在DeepFM中,FM layer的units直接与output unit相连,没有任何系数)。当我们进一步移去DNN part,并同时为该feature map使用一个constant sum filter(它简单采用输入求和,无需任何参数学习),接着xDeepFM就变成了传统的FM模型。

CIN 源码浅析

详细注释写在了代码中, 其中不太直观的地方有两处, 我写了很简单的测试用例, 可以用于后续的参考: dot_result_m = tf.matmul(split_tensor0, split_tensor, transpose_b=True)

代码语言:javascript
复制
import tensorflow as tf

B = 2
D = 3
m = 2
H = 2 ## 理解为 H_{k-1}
a = tf.reshape(tf.range(B * D * m, dtype=tf.float32),
              (B, m, D))
b = tf.split(a, D * [1], 2)
c = tf.matmul(b, b, transpose_b=True)

with tf.Session() as sess:
    print(sess.run(tf.shape(c))) ## shape 为 [D, B, m, H_{k-1}]

curr_out = tf.nn.conv1d(dot_result, filters=self.filters[idx], stride=1, padding='VALID')

代码语言:javascript
复制
import tensorflow as tf

B = 2
D = 3
E = 4  ## 代表 m * H_{k-1}
F = 5  ## 代表 H_{k}
a = tf.reshape(tf.range(B * D * E, dtype=tf.float32),
              (B, D, E))
b = tf.reshape(tf.range(1 * E * F, dtype=tf.float32),
              (1, E, F))
curr_out = tf.nn.conv1d(
    a, filters=b, stride=1, padding='VALID')

with tf.Session() as sess:
    print(sess.run(tf.shape(curr_out))) ## 结果为 [B, D, H_{k}]

CIN 模块的代码如下:

代码语言:javascript
复制
class CIN(Layer):
    """Compressed Interaction Network used in xDeepFM.This implemention is
    adapted from code that the author of the paper published on https://github.com/Leavingseason/xDeepFM.
      Input shape
        - 3D tensor with shape: ``(batch_size,field_size,embedding_size)``.
      Output shape
        - 2D tensor with shape: ``(batch_size, featuremap_num)`` ``featuremap_num =  sum(self.layer_size[:-1]) // 2 + self.layer_size[-1]`` if ``split_half=True``,else  ``sum(layer_size)`` .
      Arguments
        - **layer_size** : list of int.Feature maps in each layer.
        - **activation** : activation function used on feature maps.
        - **split_half** : bool.if set to False, half of the feature maps in each hidden will connect to output unit.
        - **seed** : A Python integer to use as random seed.
      References
        - [Lian J, Zhou X, Zhang F, et al. xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems[J]. arXiv preprint arXiv:1803.05170, 2018.] (https://arxiv.org/pdf/1803.05170.pdf)
    """

    def __init__(self, layer_size=(128, 128), activation='relu', split_half=True, l2_reg=1e-5, seed=1024, **kwargs):
        if len(layer_size) == 0:
            raise ValueError(
                "layer_size must be a list(tuple) of length greater than 1")
        self.layer_size = layer_size
        self.split_half = split_half
        self.activation = activation
        self.l2_reg = l2_reg
        self.seed = seed
        super(CIN, self).__init__(**kwargs)

    def build(self, input_shape):
        if len(input_shape) != 3:
            raise ValueError(
                "Unexpected inputs dimensions %d, expect to be 3 dimensions" % (len(input_shape)))

        self.field_nums = [int(input_shape[1])]
        self.filters = []
        self.bias = []
        for i, size in enumerate(self.layer_size):
   
   ## layer_size 对应着论文中的 H_{k}, 表示 CIN 每层中 feature map 的个数
   ## self.filters[i] 的 shape 为 [1, m * H_{k-1}, H_{k}]
            self.filters.append(
             self.add_weight(name='filter' + str(i),
                                shape=[1, self.field_nums[-1] * self.field_nums[0], size],
        dtype=tf.float32, initializer=glorot_uniform(seed=self.seed + i),
                                regularizer=l2(self.l2_reg)))
   ## self.bias[i] 的 shape 为 [H_{k}]
            self.bias.append(
             self.add_weight(name='bias' + str(i), 
                 shape=[size], dtype=tf.float32,
                                initializer=tf.keras.initializers.Zeros()))

            if self.split_half:
                if i != len(self.layer_size) - 1 and size % 2 > 0:
                    raise ValueError(
                        "layer_size must be even number except for the last layer when split_half=True")

                self.field_nums.append(size // 2)
            else:
                self.field_nums.append(size)

        self.activation_layers = [activation_layer(
            self.activation) for _ in self.layer_size]

        super(CIN, self).build(input_shape)  # Be sure to call this somewhere!

    def call(self, inputs, **kwargs):
  ## inputs 的 shape 为 [B, m, D], 其中 m 为 Field 的数量,
  ## D 为 embedding size, 我注释的符号尽量和论文中的一样
        if K.ndim(inputs) != 3:
            raise ValueError(
                "Unexpected inputs dimensions %d, expect to be 3 dimensions" % (K.ndim(inputs)))

        dim = int(inputs.get_shape()[-1]) # D
        hidden_nn_layers = [inputs]
        final_result = []
  
  ## split_tensor0 表示 list: [x1, x2, ..., xD], 其中 xi 的 shape 为 [B, m, 1]
        split_tensor0 = tf.split(hidden_nn_layers[0], dim * [1], 2)
        for idx, layer_size in enumerate(self.layer_size):
         ## split_tensor 表示 list: [t1, t2, ..., tH_{k-1}], 即有 H_{k-1} 个向量;
         ## 其中 ti 的 shape 为 [B, H_{k-1}, 1]
            split_tensor = tf.split(hidden_nn_layers[-1], dim * [1], 2)
   
   ## dot_result_m 为一个 tensor, 其 shape 为 [D, B, m, H_{k-1}]
            dot_result_m = tf.matmul(
                split_tensor0, split_tensor, transpose_b=True)

   ## dot_result_o 的 shape 为 [D, B, m * H_{k-1}]
            dot_result_o = tf.reshape(
                dot_result_m, shape=[dim, -1, self.field_nums[0] * self.field_nums[idx]])
   
   ## dot_result 的 shape 为 [B, D, m * H_{k-1}]
            dot_result = tf.transpose(dot_result_o, perm=[1, 0, 2])
   
   ## 牛掰啊, 还可以这样写, 精彩!
   ## self.filters[idx] 的 shape 为 [1, m * H_{k-1}, H_{k}]
   ## 因此 curr_out 的 shape 为 [B, D, H_{k}]
            curr_out = tf.nn.conv1d(
                dot_result, filters=self.filters[idx], stride=1, padding='VALID')
   
   ## self.bias[idx] 的 shape 为 [H_{k}]
   ## 因此 curr_out 的 shape 为 [B, D, H_{k}]
            curr_out = tf.nn.bias_add(curr_out, self.bias[idx])
   
   ## curr_out 的 shape 为 [B, D, H_{k}]
            curr_out = self.activation_layers[idx](curr_out)
   
   ## curr_out 的 shape 为 [B, H_{k}, D]
            curr_out = tf.transpose(curr_out, perm=[0, 2, 1])
   
            if self.split_half:
                if idx != len(self.layer_size) - 1:
                    next_hidden, direct_connect = tf.split(
                        curr_out, 2 * [layer_size // 2], 1)
                else:
                    direct_connect = curr_out
                    next_hidden = 0
            else:
                direct_connect = curr_out
                next_hidden = curr_out

            final_result.append(direct_connect)
            hidden_nn_layers.append(next_hidden)
  
  ## 先假设不走 self.split_half 的逻辑, 此时 result 的
  ## shape 为 [B, sum(H_{k}), D] (k=1 -> T, T 为 CIN 的总层数)
        result = tf.concat(final_result, axis=1)
        ## result 最终的 shape 为 [B, sum(H_{k})]
        result = reduce_sum(result, -1, keep_dims=False)

        return result
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准备工作
    • Embedding Layer
      • 隐式高阶交叉
        • 显式高阶交叉
        • CIN模型
          • CIN
            • CIN详解
              • 空间复杂度
                • 时间复杂度
                  • 多项式近似(Polynomial Approximation)
                    • 与隐式网络的组合
                      • 与FM和DeepFM的关系
                      • CIN 源码浅析
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档