深度学习 - 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

相关文章

Python xmltodict实现简化XML数据处理

《Pythonxmltodict实现简化XML数据处理》Python社区为提供了xmltodict库,它专为简化XML与Python数据结构的转换而设计,本文主要来为大家介绍一下如何使用xmltod... 目录一、引言二、XMLtodict介绍设计理念适用场景三、功能参数与属性1、parse函数2、unpa

C#实现获得某个枚举的所有名称

《C#实现获得某个枚举的所有名称》这篇文章主要为大家详细介绍了C#如何实现获得某个枚举的所有名称,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... C#中获得某个枚举的所有名称using System;using System.Collections.Generic;usi

Go语言实现将中文转化为拼音功能

《Go语言实现将中文转化为拼音功能》这篇文章主要为大家详细介绍了Go语言中如何实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 有这么一个需求:新用户入职 创建一系列账号比较麻烦,打算通过接口传入姓名进行初始化。想把姓名转化成拼音。因为有些账号即需要中文也需要英

C# 读写ini文件操作实现

《C#读写ini文件操作实现》本文主要介绍了C#读写ini文件操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、INI文件结构二、读取INI文件中的数据在C#应用程序中,常将INI文件作为配置文件,用于存储应用程序的

C#实现获取电脑中的端口号和硬件信息

《C#实现获取电脑中的端口号和硬件信息》这篇文章主要为大家详细介绍了C#实现获取电脑中的端口号和硬件信息的相关方法,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 我们经常在使用一个串口软件的时候,发现软件中的端口号并不是普通的COM1,而是带有硬件信息的。那么如果我们使用C#编写软件时候,如

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

基于WinForm+Halcon实现图像缩放与交互功能

《基于WinForm+Halcon实现图像缩放与交互功能》本文主要讲述在WinForm中结合Halcon实现图像缩放、平移及实时显示灰度值等交互功能,包括初始化窗口的不同方式,以及通过特定事件添加相应... 目录前言初始化窗口添加图像缩放功能添加图像平移功能添加实时显示灰度值功能示例代码总结最后前言本文将

Redis延迟队列的实现示例

《Redis延迟队列的实现示例》Redis延迟队列是一种使用Redis实现的消息队列,本文主要介绍了Redis延迟队列的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录一、什么是 Redis 延迟队列二、实现原理三、Java 代码示例四、注意事项五、使用 Redi

C#实现WinForm控件焦点的获取与失去

《C#实现WinForm控件焦点的获取与失去》在一个数据输入表单中,当用户从一个文本框切换到另一个文本框时,需要准确地判断焦点的转移,以便进行数据验证、提示信息显示等操作,本文将探讨Winform控件... 目录前言获取焦点改变TabIndex属性值调用Focus方法失去焦点总结最后前言在一个数据输入表单