本文主要是介绍深度学习 - 37.TF x Keras Deep Cross Network DCN 实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
一.引言
二.模型简介
1.Embedding and stacking layer
2.Cross Network
2.1 模型架构分析
2.2 计算逻辑
2.3 W 权重由 Vector 转换为 Matrix
3.Combination output layer
三.模型实践
1.DCN Model
2.代码测试
四.模型思考
1.多项式近似
2.FM 交叉泛化
一.引言
从前面 FM 家族的实现可以看出,特征工程已经成为 CTR 预估的关键,如何实现自动有效的特征交叉并不断挖掘数据潜在的组合成为模型预测效果的关键。DNN 能够自动学习特征交互;然而,它们隐式地生成所有交互,并不是学习所有交叉特征类型的有效方法。深度交叉网络(Deep & Cross Network, DCN) 通过叠加所有类型的输入来实现自动特征学习,同样可以在每一层应用特征交叉,不需要手动的特征工程,并且对 DNN 模型增加的额外复杂性可以忽略不计。实验结果表明,在CTR预测数据集和密集分类数据集上,该算法在模型精度和内存使用方面均优于现有算法。
二.模型简介
DCN 模型从嵌入层和叠加层开始,然后是并行的交叉网络和深度网络,最后将两个网络的输出组合通过 Sigmoid 输出 CTR 预估值。
1.Embedding and stacking layer
这一层主要负责原始的数据处理,将 Dense 特征与 Embedding 化的稀疏特征 Concat 起来,得到一个 None x FeatNum 的输入。
def get_deep_order(feat_index, args):embedding = tf.nn.embedding_lookup(args, feat_index)[:, :, :]embedding_flatten = layers.Flatten()(embedding)return embedding_flatten
这里假定 args 是 Field x EmbedingDim 的参数矩阵,feat_index 为命中的索引 id,首先 lookup 获取对应 embedding,如果是 multi_hot 特征,可以通过 lookup_sparse 将多个 embedding 合在一起,然后通过 Flatten 将所有 embedding concat 在一起,得到模型输入。
2.Cross Network
2.1 模型架构分析
第二层由 Cross Network 和 Deep Network 组成,这里 DNN 不再赘述了,主要分析下 Cross Network:
广义表达式为:
Cross Network 通过一层一层叠加实现特征交叉方式,假设实现常见的 3 层叠加:
这里将 x_1 带入到 x_2 中看看:
DNN 学到的更多的是隐式和高度非线性的特征,而 DCN 则类似多项式一样学习特征的交叉,持续的叠加 layer,多项式的项数也会越来越高。
2.2 计算逻辑
这里计算逻辑也比较清晰,首先叠加几层就会初始化几个 W 和 B:
- 输入 X0 None x Dim x 1
- 转置 xT 为 None x 1 x Dim
- 参数 W 为 Dim x 1
- 偏置 b 为 Dim x 1
则计算时维度变化为:
- None x Dim x 1 + - Dim x 1 + - None x Dim x 1 => None x Dim x 1
所以 Cross Network 一直叠加,但输入输出维度一直保持不变,即 None x Dim x 1。
def get_cross_net(input, layer_num, kernelType, kernels, bias):# None x dim => None x dim x 1x_0 = tf.expand_dims(input, axis=2)x_l = x_0if kernelType == "vector":for i in range(layer_num):# None x 32 x 1 => None x 1 x 32 x 32 x 1 => None x 1 x 1xl_w = tf.tensordot(x_l, kernels[i], axes=(1, 0))# x_0: None x 32 x 1 xl_w: None x 1 x 1dot_ = tf.matmul(x_0, xl_w)# dot: None x 32 x 1 + bias: 32 x 1x_l = dot_ + bias[i] + x_lre = tf.squeeze(x_l, axis=2)return re
这里代码实现中具体标识了维度,由于输入层的维度为 None x Dim,为了参与后续矩阵计算需要 expand_dim 在末尾添加一维,经过 layer_num 次累加后输出 None x Dim x 1,再调用 tf.squeeze 将最后的 1 去掉,得到一个 None x Dim 继续后续的计算。
2.3 W 权重由 Vector 转换为 Matrix
这里初始化了 Dim x 1 的 vector 权重,参考 FwFM 该权重也可以使用 matrix 即 dim x dim 的矩阵形式,具体的计算逻辑会稍有不同:
elif kernelType == "matrix":for i in range(layer_num):# w: (dim, dim) x_l (None, dim, 1) => (None, dim, 1)xl_w = tf.einsum('ij,bjk->bik', kernels[i], x_l) # W * xi (bs, dim, 1)dot_ = xl_w + bias[i] # W * xi + bx_l = x_0 * dot_ + x_l # x0 · (W * xi + b) +xl Hadamard-product
由于矩阵维度为 dim x dim,所以最后一步计算修改为对位相乘即哈达玛积,最后输出的也是 None x Dim x 1,仍然需要使用 tf.squeeze 转换为 None x Dim 供后续计算使用。
3.Combination output layer
概率 P:
这一步主要将 Cross Network 输出的 None x Dim 与 DNN 输出的 None x Dim2 concat 起来然后输出到最终的 Sigmoid 作为预估概率值,和前面提到的 DeepFwFM、DeepFmFM 类似。
损失函数:
损失函数采用 Log Loss,较低的对数损失值意味着更好的预测,同时加入了 L2 正则化系数。
三.模型实践
1.DCN Model
#!/usr/bin/python
# -*- coding: UTF-8 -*-from tensorflow.keras import layers, Model
from tensorflow.keras.layers import Layer, Activation
import tensorflow as tf
from tensorflow.python.keras.backend import relu
from GetTrainAndTestData import genSamples
from Handel import get_deep_orderdef get_cross_net(input, layer_num, kernelType, kernels, bias):# None x dim => None x dim x 1x_0 = tf.expand_dims(input, axis=2)x_l = x_0if kernelType == "vector":for i in range(layer_num):# None x 32 x 1 => None x 1 x 32 x 32 x 1 => None x 1 x 1xl_w = tf.tensordot(x_l, kernels[i], axes=(1, 0))# x_0: None x 32 x 1 xl_w: None x 1 x 1dot_ = tf.matmul(x_0, xl_w)# dot: None x 32 x 1 + bias: 32 x 1x_l = dot_ + bias[i] + x_lelif kernelType == "matrix":for i in range(layer_num):# w: (dim, dim) x_l (None, dim, 1) => (None, dim, 1)xl_w = tf.einsum('ij,bjk->bik', kernels[i], x_l) # W * xi (bs, dim, 1)dot_ = xl_w + bias[i] # W * xi + bx_l = x_0 * dot_ + x_l # x0 · (W * xi + b) +xl Hadamard-productelse:raise ValueError("kernelType should be 'vector' or 'matrix'")re = tf.squeeze(x_l, axis=2)return reclass DCN(Layer):def __init__(self, feature_num, embedding_dim, layer_num=3, dense1_dim=128, dense2_dim=64,kernelType='matrix', **kwargs):self.feature_num = feature_numself.embedding_dim = embedding_dimself.dense1_dim = dense1_dimself.dense2_dim = dense2_dimself.activation = Activation(relu)# DCN 参数形式与层数self.kernelType = kernelTypeself.layer_num = layer_numsuper().__init__(**kwargs)# 定义模型初始化 根据特征数目def build(self, input_shape):# create a trainable weight variable for this layerdim = int(input_shape[-1]) * self.embedding_dimself.embedding = self.add_weight(name="embedding",shape=(self.feature_num, self.embedding_dim),initializer='he_normal',trainable=True)# DNN Dense1self.dense1 = self.add_weight(name='dense1',shape=(input_shape[1] * self.embedding_dim, self.dense1_dim),initializer='he_normal',trainable=True)# DNN Bias1self.bias1 = self.add_weight(name='bias1',shape=(self.dense1_dim,),initializer='he_normal',trainable=True)# DNN Dense2self.dense2 = self.add_weight(name='dense2',shape=(self.dense1_dim, self.dense2_dim),initializer='he_normal',trainable=True)# DNN Bias1self.bias2 = self.add_weight(name='bias2',shape=(self.dense2_dim,),initializer='he_normal',trainable=True)if self.kernelType == 'vector':self.kernels = [self.add_weight(name='kernel' + str(i),shape=(dim, 1),initializer="he_normal",trainable=True) for i in range(self.layer_num)]elif self.kernelType == 'matrix':self.kernels = [self.add_weight(name='kernel' + str(i),shape=(dim, dim),initializer="he_normal",trainable=True) for i in range(self.layer_num)]else: # errorraise ValueError("kernelType should be 'vector' or 'matrix'")self.bias = [self.add_weight(name='bias4dcn' + str(i),shape=(dim, 1),initializer="he_normal",trainable=True) for i in range(self.layer_num)]# Be sure to call this at the endsuper(DCN, self).build(input_shape)def call(self, inputs, **kwargs):# None x 32deep_order = get_deep_order(inputs, self.embedding)# Cross [None x (Filed x K) => None x (Field x K)]cross_order = get_cross_net(deep_order, self.layer_num, self.kernelType, self.kernels, self.bias)# Deep None x Dim2deep_order = self.activation(tf.matmul(deep_order, self.dense1) + self.bias1)deep_order = self.activation(tf.matmul(deep_order, self.dense2) + self.bias2)# Concat Cross + DNNconcat_order = tf.concat([deep_order, cross_order], axis=-1)return concat_order
init 函数主要分两个方面参数,一方面是 DNN 隐层的参数,这里定义 128 和 64 的两个全连接层,另一方面是 DCN 的参数,主要是参数 kernel 核类型与 layer_num 即叠加次数。
2.代码测试
if __name__ == '__main__':train, labels = genSamples()# 构建模型input = layers.Input(shape=4, name='input', dtype='int32')model_layer = DCN(400, 8)(input)output = layers.Dense(1, activation='sigmoid')(model_layer)DCNModel = Model(input, output, name="DCN")# 模型编译DCNModel.compile(optimizer='adam',loss='binary_crossentropy',metrics='accuracy')DCNModel.summary()# 模型训练DCNModel.fit(train, labels, epochs=10, batch_size=128)
模型 Summary:
训练流程:
这里未加入正则化且使用简单随机构造的数据,具体场景大家可以更换数据与 DCN 参数进行适配。
Epoch 1/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6945 - accuracy: 0.5012
Epoch 2/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6926 - accuracy: 0.5160
Epoch 3/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6903 - accuracy: 0.5303
Epoch 4/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6866 - accuracy: 0.5472
Epoch 5/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6812 - accuracy: 0.5634
Epoch 6/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6742 - accuracy: 0.5809
Epoch 7/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6687 - accuracy: 0.5916
Epoch 8/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6638 - accuracy: 0.5983
Epoch 9/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6587 - accuracy: 0.6067
Epoch 10/10
469/469 [==============================] - 1s 2ms/step - loss: 0.6528 - accuracy: 0.6129
四.模型思考
1.多项式近似
通过 Weierstrass 逼近定理,在一定平滑性假设下,任何函数都可以用一个多项式逼近到任意精度。因此,我们从多项式逼近的角度来分析交叉网络。特别是,交叉网络以一种有效的、有表现力的和更广泛地推广到现实数据集的方式逼近相同程度的多项式类。就像我们前面写到的一样,随着迭代次数的加深,x0 的项数也越来越大:
2.FM 交叉泛化
DCN Cross Network 继承了 FM 模型的参数共享精神,并进一步向更深层次的结构扩展。在 FM 模型中,特征 xi 与权值 vector vi 相关联,交叉项 wij 的权值由< vi, vj >计算。在DCN中,xi 与标量 w 相关联,xi xj 的权值是 w 参数的乘积。两个模型中每个特征都学习了一些独立于其他特征的参数,交叉项的权值是相应参数的一定组合。参数共享不仅提高了模型的效率,而且使模型能够泛化到看不见的特征交互,对噪声的鲁棒性更强。FM是一个浅结构,仅限于表示2次的交叉项。相比之下,DCN 可以构造所有的交叉项 , ..., ,交叉阶 α 由 layer_num 决定。因此,交叉网络将参数共享的思想从单层扩展到多层和高度交叉项。注意,与高阶 FMs 不同,交叉网络中的参数数量仅随输入维数线性增长,而传统的 FFM 参数则出现指数增长的情况。
参考:
Deep & Cross Network for Ad Click Predictions
DCN V2: Improved Deep & Cross Network and Practical Lessons for Web-scale Learning to Rank Systems
更多推荐算法相关深度学习:深度学习导读专栏
这篇关于深度学习 - 37.TF x Keras Deep Cross Network DCN 实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!