深度学习 - 37.TF x Keras Deep Cross Network DCN 实现

2023-10-14 12:50

本文主要是介绍深度学习 - 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:

x_1=x_0x_0^Tw_{c,0}+b_{c,0}+x_0

广义表达式为:

x_{l+1}=x_0x_l^Tw_{l}+b_{l}+x_l=f(x_l,w_l,b_l) +x_l

Cross Network 通过一层一层叠加实现特征交叉方式,假设实现常见的 3 层叠加:

\\ x_1=x_0x_0^Tw_{0}+b_{0}+x_0 \\ x_2=x_0x_1^Tw_{1}+b_{1}+x_1 \\ x_3=x_0x_2^Tw_{2}+b_{2}+x_2

这里将 x_1 带入到 x_2 中看看:

x_2=w_0w_1x_0^2x_0^T+w_1x_0^2+w_0x_0x_0^T+(w_0+w_1+1)b_1+x_0+b_0

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

则计算时维度变化为:

x_0x^Tw - None x Dim x 1 + b - Dim x 1 + x - 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 的项数也越来越大:

x_2=w_0w_1x_0^2x_0^T+w_1x_0^2+w_0x_0x_0^T+(w_0+w_1+1)b_1+x_0+b_0

2.FM 交叉泛化

DCN Cross Network 继承了 FM 模型的参数共享精神,并进一步向更深层次的结构扩展。在 FM 模型中,特征 xi 与权值 vector vi 相关联,交叉项 wij 的权值由< vi, vj >计算。在DCN中,xi 与标量 w 相关联,xi xj 的权值是 w 参数的乘积。两个模型中每个特征都学习了一些独立于其他特征的参数,交叉项的权值是相应参数的一定组合。参数共享不仅提高了模型的效率,而且使模型能够泛化到看不见的特征交互,对噪声的鲁棒性更强。FM是一个浅结构,仅限于表示2次的交叉项。相比之下,DCN 可以构造所有的交叉项 x_1^{\alpha _1},x_2^{\alpha _2} ..., x_d^{\alpha _d},交叉阶 α 由 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 实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/210636

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import