推荐算法(五)——谷歌经典 Deep&Cross Network原理及代码实践

1 介绍

DCN 是 2017 年由谷歌和斯坦福大学联合出品的 CTR 预估模型。
在这里插入图片描述论文传送门:Deep & Cross Network

代码传送门:DCN 模型复现

DCN 是基于 Wide&Deep 的改进版,它把 wide 侧的 LR 换成了 cross layer,可显式的构造有限阶特征组合,并且具有较低的复杂度。

2 原理

在这里插入图片描述

2.1 Embedding and stacking layer

在这里插入图片描述在这里插入图片描述在这里插入图片描述
该层为嵌入层,对于稠密的数值特征(Dense feature)保持不变,对于稀疏的类别特征(Sparse feature)进行嵌入,即乘上一个嵌入矩阵映射到低维,得到低维稠密向量(Embedding vec),然后将类别特征、数值特征拼接即为该层的输出,然后作为后面两部分的共享输入。

2.2 Cross Network

在这里插入图片描述
特征交叉的数学表达式:
在这里插入图片描述
图形表达式:
在这里插入图片描述
对于第 l l l 层的输出 X l X_{l} Xl,每次都会乘上原始的输入 X 0 X_{0} X0,向量中的元素两两相乘,以此来构造更高一阶的特征组合;

然后乘上 W W W 进行线性转换并加上一个偏置 b b b,相当于对特征组合进行了一次线性转换 f ( x l , w l , b l ) f(x_{l},w_{l}, b_{l}) f(xl,wl,bl)

最后会加上该层的输出 X l X_{l} Xl,如果前一项的线性转换值接近 0,则这种计算方式相当于引入了残差连接,能够有效缓解梯度消失问题,可累加多层来构造高阶特征;

Tips:

1.每层的特征交叉不会影响 X X X 的维度, 所以 cross layer 每层的输出与最终的输出都跟输入维度保持一致;

2.每层的可学习参数为向量 W W W 与向量 b b b,所以对于 l l l 层的 cross layer,可训练的参数量为 2 ∗ d ∗ l 2*d*l 2dl ,其中 d d d 为两向量的维度,也是输入 X 0 X_{0} X0 的维度,所以交叉层的参数量随着层数 l l l 与输入的特征数 d d d 线性增长;

3.cross layer 的特征交叉为元素级交叉,所以每一层都含有所以阶数的特征组合,比如 X l X_{l} Xl 含有0 到 l + 1 l+1 l+1 阶的所有特征组合。

2.3 Deep network

在这里插入图片描述
在这里插入图片描述
标准的全连接层,论文图中每层的维度保持一致,实际中可自己设定每个隐层的维度。

2.4 Combination layer

在这里插入图片描述
加粗样式
将来自 cross layer 的输出与 deep layer 的输出 concat 到一起,然后乘上矩阵 W l o g i t s W_{logits} Wlogits 将其映射到 1 维,经过 sigmoid 得到概率输出。
在这里插入图片描述DCN 的损失函数为交叉熵损失,并加入了正则项(每一层权重向量 W l W_{l} Wl 的 l2 正则),降低模型过拟合风险。

3 总结

优点:

1 引入 cross layer 显示的构造有限阶特征组合,无需特征工程,可端到端训练;

2 cross layer 具有线性复杂度,可累加多层构造高阶特征交互,并且因为其类似残差连接的计算方式,使其累加多层也不会产生梯度消失问题;

3 跟 deepfm 相同,两个分支共享输入,可更精确的训练学习。

缺点:

1 cross layer 是以 bit-wise 方式构造特征组合的,最小粒度是特征向量中的每个元素,这样导致 DCN 不会考虑域的概念,属于同一特征的各个元素应同等对待;

扩展:

DCN 有一个改进版,叫做 DCN-matrix,以上介绍的 DCN 也叫作 DCN-vector。

DCN-matrix 只是将 cross layer 中做线性映射的向量 W W W 替换成了矩阵,并且为了不引入过多的参数量,作者又将 W W W 矩阵分解成了两个高瘦矩阵的乘积,并且在第一个矩阵映射后还可以加一个激活函数进行非线性转换,以此来提高模型的表达能力,这就是 DCN-matrix 相对于 DCN-vector 的不同之处。

DCN-matrix 也提出了一种串行拼接方式,就是将 cross layer 最后一层的输出作为 deep layer 的输入,串行的拼接两部分。串行与并行拼接方式在不同场景中表现不同,说不上孰好孰坏。

这就是 DCN 的改进版本DCN-matrix。

4 代码实践

Layer 搭建:

import tensorflow as tf
from tensorflow.keras.layers import Layer
from tensorflow.keras.layers import Input, Dense

class Dense_layer(Layer):
    def __init__(self, hidden_units, output_dim, activation):
        super().__init__()
        self.hidden_layer = [Dense(x, activation=activation) for x in hidden_units]
        self.output_layer = Dense(output_dim, activation=None)

    def call(self, inputs, **kwargs):
        x = inputs
        for layer in self.hidden_layer:
            x = layer(x)
        output = self.output_layer(x)
        return output

class Cross_layer(Layer):
    def __init__(self, layer_num, reg_w=1e-4, reg_b=1e-4):
        super().__init__()
        self.layer_num = layer_num
        self.reg_w = reg_w
        self.reg_b = reg_b

    def build(self, input_shape):
        self.cross_weight = [
            self.add_weight(name='w'+str(i),
                            shape=(input_shape[1], 1), # 跟输入维度相同的向量
                            initializer=tf.random_normal_initializer(),
                            regularizer=tf.keras.regularizers.l2(self.reg_w),
                            trainable=True)
            for i in range(self.layer_num)] 		   # 每层对应不同的w
        self.cross_bias = [
            self.add_weight(name='b'+str(i),
                            shape=(input_shape[1], 1), # 跟输入维度相同的向量
                            initializer=tf.zeros_initializer(),
                            regularizer=tf.keras.regularizers.l2(self.reg_b),
                            trainable=True)
            for i in range(self.layer_num)]			   # 每层对应不同的b

    def call(self, inputs, **kwargs):
        x0 = tf.expand_dims(inputs, axis=2)  # (None, dim, 1)
        xl = x0  							 # (None, dim, 1)
        for i in range(self.layer_num):
            # 先乘后两项得到标量,便于计算
            xl_w = tf.matmul(tf.transpose(xl, [0, 2, 1]), self.cross_weight[i]) # (None, 1, 1)
            # 再乘上x0,加上b、xl
            xl = tf.matmul(x0, xl_w) + self.cross_bias[i] + xl  # (None, dim, 1)

        output = tf.squeeze(xl, axis=2)  # (None, dim)
        return output

Model 搭建:

from layer import Dense_layer, Cross_layer

import tensorflow as tf
from tensorflow.keras.layers import Dense, Embedding
from tensorflow.keras import Model

class DCN(Model):
    def __init__(self, feature_columns, hidden_units, output_dim, activation, layer_num, reg_w=1e-4, reg_b=1e-4):
        super().__init__()
        self.dense_feature_columns, self.sparse_feature_columns = feature_columns
        self.embed_layers = {
            'embed_' + str(i): Embedding(feat['feat_onehot_dim'], feat['embed_dim'])
             for i, feat in enumerate(self.sparse_feature_columns)
        }
        self.dense_layer = Dense_layer(hidden_units, output_dim, activation)
        self.cross_layer = Cross_layer(layer_num, reg_w=reg_w, reg_b=reg_b)
        self.output_layer = Dense(1, activation=None)

    def call(self, inputs):
        dense_inputs, sparse_inputs = inputs[:, :13], inputs[:, 13:]
        
        # embedding
        sparse_embed = tf.concat([self.embed_layers['embed_{}'.format(i)](sparse_inputs[:, i])
                                  for i in range(sparse_inputs.shape[1])], axis=1)
        x = tf.concat([dense_inputs, sparse_embed], axis=1)

        # Crossing layer
        cross_output = self.cross_layer(x)
        
        # Dense layer
        dnn_output = self.dense_layer(x)

        x = tf.concat([cross_output, dnn_output], axis=1)
        output = tf.nn.sigmoid(self.output_layer(x))
        return output

完整的可训练代码可在文末仓库中查看。

写在最后

下一篇预告:推荐算法(六)——xDeepFM 原理详解及代码实战

完整的推荐算法复现代码可参考仓库: Recommend-System-tf2.0

希望看完此文的你能够有所收获…

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值