特征交叉系列:DeepCross(DCN-V2)理论和实践

news2024/12/27 1:45:28
DCN之前FM系列特征交叉思路总结

在之前的推荐算法特征交叉系列中,已经介绍了从FM,FFM,到PNN,DeepFM,NFM,AFM这一系列围绕特征交叉展开的算法,这些算法都是以FM为基石,在FM的基础上优化其二阶交互表达能力的策略,主要有三种形式:

    1. 引入DNN优化FM的非线性能力:PNN,DeepFM,NFM都是将DNN和FM结合,其中PNN,NFM采用串联结构,DeepFM采用并联结构
    1. 改变隐向量之间的交互方式:以PNN为代表,它改变了FM单一的内积方式,尝试外积,内外融合来增强二阶交叉的表达
    1. 改变交差结果聚合的计算方式:在FM中一对隐向量逐位相乘再相加为一个标量,所有标量相加作为最终的二阶表征,而在NFM中只做逐位相乘但不相加,在IPNN中虽然也是隐向量逐位相乘再相加,但是所有标量没有直接相加而是平铺开来作为二阶表征,在AFM中更是采用注意力进行加权求和。

针对第一点,之前章节的代码实践都能证明引入DNN相比于FM有显著提升,DNN弥补了FM的非线性能力隐式高阶交叉能力。针对第二点外积和内外融合相比于内积有提升但是会带来更多参数的学习,详情见[特征交叉系列:PNN向量积模型理论和实践,FM和DNN的串联],而第三点不论怎么聚合,隐向量的逐位相乘都保留了下来,因此逐位相乘是FM特征交叉的精髓,是能够让两特征进行交叉表达的核心。

在DCN出来之前,以上算法都是没有改变FM这种范式底层结构,仅仅是对FM的某些细节做调整,而不论怎么调整也只能完成二阶交叉,对于高阶交叉只能寄希望于DNN在FM二阶的基础上能够隐式的学习到,因此对于高阶交叉这系列算法的表达能力是不充分的,引出本节介绍的算法DCN-V2,它彻底摆脱了FM的范式,通过一个递归的设计实现了任意有限阶的显示高阶交叉


DCN-V2模型结构介绍

DCN(Deep & Cross Network,深度交叉网络),有两个版本分别时DCN V1和DCN V2,DCN V1也叫DCN-V发表于2017年,DCN V2也叫DCN-M发表于2020年,两篇论文都来自Google公司,其中DCN-V1存在理论缺陷本节不做介绍,直接进入DCN-V2,两者的核心区别是前者使用向量Vector作为交叉层的学习权重,而后者采用矩阵Matrix,这也是命名DCN-V和DCN-M的由来。
DCN-V2的网络结构如下

DCN-V2

左边Stack和右边Parallel分别是DCN中交叉网络和DNN组合的策略,Stack代表串联,Parallel代表并联,相当于肯定了之前基于FM和DNN并联或者串联的算法,认为这两种形式都可以,具体用那种需要根据数据学习情况而定。
最底层是输入层,采用连续变量和稀疏变量的embedding拼接的方式,拼接的结果为一个向量,该向量作为一个整体进入Cross Network交叉层,交叉层每做一层交叉会输出一个新的向量,该新向量和输入的向量维度相同,与此同时每次交叉操作都需要引入一次原始输入,由于输入的向量维度和输入相同,因此这个操作可以无限递归下去,即Cross Network可以完成任意阶数的交叉操作。
Cross Network输出的结果作为高阶交叉的最终表征,如果是Stack策略则输入给DNN继续学习,如果是Parallel策略则和DNN的输出进行合并,最终映射到标签y上进行损失迭代学习。


DCN-V2的交叉网络为什么可以完成高阶交叉

上面简述了DCN-V2的整体结构,除了Cross Network其他和NFM,DeepFM没啥本质区别,现在开始深入Cross Network部分,它的计算公式如下

交叉计算公式

论文中的可视化表达地更加容易理解

交叉计算公式可视化

前面也提到DCN-V2的交叉部分是个可以无限递归的过程,i表示第i阶(本层),i+1表示第i+1阶(下一层),则下一层交叉的结果等于本层的结果经过一个M矩阵和b向量偏置,和原始输入的逐位相乘,再加上本层的结果,下面对于等式右边的元素,按照从左到右的位置分别讲解下它们在这个操作中的作用

    1. X0原始输入:它代表特征的一阶结果,在交叉网络中每一层都会引用它,每引用一次本层就会再这基础上再上一阶,X0是一个把DCN推上更高阶的增量因子
    1. W变换矩阵:该矩阵和本层的结果xi矩阵相乘,使得Xi每个元素都变成了Xi内所有元素的一个加权求和线性映射的结果,再配合上和X0的哈达马积逐位相乘就实现了类似FM的精髓交叉操作,而W的作用就是挑选出每个Xi位置下需要交叉的内容,将原始的Xi映射为最合适和X0做逐位相乘的形态
    1. b偏置向量:W对每个Xi位置上是个纯线性映射,因此加入偏置项b
    1. Xi本层输入:类似残差连接,是一种兜底策略,随着网络层数的增高使得模型的表达能力至少不弱于上一层,缓解梯度弥散和梯度爆炸的情况。

这个计算公式的核心是权重矩阵W,可以举个简单例子侧面证明W的有效性,比如交叉网络只做一层,这个计算公式能不能复现出FM?举例我们有三个特征,经过embedding之后分别有三个向量w1,w2,w3,在FM中会采用类似for循环的方式形成field_num*(field_num-1)对向量两两逐位相乘,分别是w1×w2,w2×w3,w1×w3,而在DCN-V2中输入被拼接在了一起w1||w2||w3,在一层中是X0和X0进行交叉,因此只要W能把第二个X0变换为w2||w3||w1,就可以复现FM的效果,如图

DCN第一层复现FM

因此只要设计一个矩阵W实现如图所示的把w1||w2||w3转化为w2||w3||w1即可,这个W当然很容易就可以设计出来,W的每一行都设计成一个onehot向量即可,只在红线的开始的那个位置和W矩阵相乘的对应位置处设置为1,其他位置全部为0即可。因此在DCN的第一层,W矩阵可以完全恢复出FM的效果,相当于在DCN-V2的第一层完成了二阶交叉,而往后的每一层都在之前基础上再做一次二阶交叉,因此DCN-V2可以通过这种递归的方式实现任意高阶的交叉。
本质上,DCN-V2采用了一个wx+b操作对每层的表征x做一个变换,使得它更好地和一阶输入x0进行哈达马积,而哈达马积就是在做特征交叉


再回过头来看DCN的高阶交叉思路

在明白交叉层的公式之后,我们回过头来猜测一下作者这么设计的脑回路,毕竟DCN彻底从FM这种field_num*(field_num-1)的for循环中跳出来了,非常具有创新性。
我们能不能把所有特征嵌入向量拼接在一起看成一个整体,让这个整体自己去做各种复杂的交叉操作,而不是采用FM这个手动两两的策略。对这个整体的输入,我们能不能要设计一个网络能对原始输入做任意高阶交叉,能不能设计一个变换函数,使得n+1阶交叉从n阶表达直接变换出来,最好能满足以下两个条件:

    1. 这个变换函数最好有通用固定的结构,每层可以复用,但是内部参数每层可以不同
    1. 每层交叉的结果和原始输入维度相同,因此这种变换可以一直持续延伸下去

如果这两个条件能够满足,就可以写一个for循环把原始输入无限变换再变换到任意高阶交叉,这是多么丝滑的体验。这种思路在深度学习中并不少见,图神经网络GNN也是设计了一个固定的信息传播聚合的公式,使得GNN可以让中心节点聚合任意跳的邻居节点信息作为自身的表征。
前文也提到DCN是将输入的向量拼接成成一个整体,而FM系是做半三角的循环拿到两两向量作为一对,紧接着引出DCN和FM的交叉性质的不同,即DCN是采用bit-wise交叉,而FM系列都是vector-wise的交叉,所谓bit-wise就是输入中不再区分不同特征field的概念,所有embeding的每个元素当成一个特征单元和其他任意元素单元进行交叉,即同一个field下会内部元素进行交叉,而vector-wise是一个field的向量和另一个field的向量进行交叉,field内部元素之间不进行交叉。


DCN-V2在PyTorch下的实践

本次实践的数据集和上一篇[特征交叉系列:完全理解FM因子分解机原理和代码实战]一致,采用用户的购买记录流水作为训练数据,用户侧特征是年龄,性别,会员年限等离散特征,商品侧特征采用商品的二级类目,产地,品牌三个离散特征,随机构造负样本,一共有10个特征域,全部是离散特征,对于枚举值过多的特征采用hash分箱,得到一共72个特征。
DCN-V2的PyTorch代码实现如下

class Embedding(nn.Module):
    def __init__(self, feat_num, emb_num):
        super(Embedding, self).__init__()
        self.embedding = nn.Embedding(feat_num, emb_num)
        nn.init.xavier_normal_(self.embedding.weight.data)

    def forward(self, x):
        # [None, filed_num] => [None, filed_num, emb_num] => [None, filed_num * emb_num]
        return self.embedding(x).flatten(1)


class DNN(nn.Module):
    def __init__(self, input_num, hidden_nums, dropout=0.1):
        super(DNN, self).__init__()
        layers = []
        input_num = input_num
        for hidden_num in hidden_nums:
            layers.append(nn.Linear(input_num, hidden_num))
            layers.append(nn.BatchNorm1d(hidden_num))
            layers.append(nn.ReLU())
            layers.append(nn.Dropout(p=dropout))
            input_num = hidden_num
        self.mlp = nn.Sequential(*layers)
        for layer in self.mlp:
            if isinstance(layer, nn.Linear):
                nn.init.xavier_normal_(layer.weight.data)

    def forward(self, x):
        return self.mlp(x)


class CrossCell(nn.Module):
    """一个交叉单元"""

    def __init__(self, input_num):
        super(CrossCell, self).__init__()
        self.w = nn.Parameter(torch.randn(input_num, input_num))
        self.b = nn.Parameter(torch.randn(input_num, 1))
        nn.init.xavier_normal_(self.w.data)

    def forward(self, x0, xi):
        # [None, emb_num] => [None, emb_num, 1]
        xi = xi.unsqueeze(2)
        x0 = x0.unsqueeze(2)
        # [input_num, input_num] * [None, emb_num, 1] => [None, emb_num, 1] => [None, emb_num, 1] => [None, emb_num, 1]
        xii = (torch.matmul(self.w, xi) + self.b) * x0 + xi
        return xii.squeeze(2)


class CrossNet(nn.Module):
    def __init__(self, order_num, input_num):
        super(CrossNet, self).__init__()
        self.order = order_num
        self.cell_list = nn.ModuleList([CrossCell(input_num) for i in range(order_num)])

    def forward(self, x0):
        xi = x0
        for i in range(self.order):
            xi = self.cell_list[i](x0=x0, xi=xi)
        return xi


class DCN(nn.Module):
    def __init__(self, field_num, feat_dim, emb_num, order_num, dropout=0.1, method='parallel',
                 hidden_nums=(128, 64, 32)):
        super(DCN, self).__init__()
        input_num = field_num * emb_num
        self.embedding = Embedding(feat_num=feat_dim, emb_num=emb_num)
        self.dnn = DNN(input_num=input_num, hidden_nums=hidden_nums, dropout=dropout)
        self.cross_net = CrossNet(order_num=order_num, input_num=input_num)
        if method not in ('parallel', 'stacked'):
            raise ValueError('unknown combine type: ' + method)
        self.method = method
        linear_dim = hidden_nums[-1]
        if self.method == 'parallel':
            linear_dim = linear_dim + input_num
        self.linear = nn.Linear(linear_dim, 1)
        nn.init.xavier_normal_(self.linear.weight.data)

    def forward(self, x):
        emb = self.embedding(x)  # [None, field * emb_num]
        cross_out = self.cross_net(emb)  # [None, input_num]
        if self.method == 'parallel':
            dnn_out = self.dnn(emb)  # [None, input_num]
            out = torch.concat([cross_out, dnn_out], dim=1)
        else:
            out = self.dnn(cross_out)  # [None, input_num]
        out = self.linear(out)
        return torch.sigmoid(out).squeeze(dim=1)

其中CrossCell子模块是一层交叉层,在CrossNet中指定阶数创建多个CrossCell的列表。在DCN主模块中设定了stacked,parallel两种策略。
本例全部是离散分箱变量,所有有值的特征都是1,因此只要输入有值位置的索引即可,一条输入例如

>>> train_data[0]
Out[120]: (tensor([ 2, 10, 14, 18, 34, 39, 47, 51, 58, 64]), tensor(0))

其中x的长度10代表10个特征域,每个域的值是特征的全局位置索引,从0到71,一共72个特征。


DCN-V2调参效果对比

对阶数(order_num)和融合策略(method)这两个参数进行调参,分别尝试1~4层交叉层,stacked和parallel两种策略,采用10次验证集AUC不上升作为早停条件,验证集的平均AUC如下

DCN调参AUC并行parallel串行stacked
1层交叉(2阶)0.63310.6325
2层交叉(3阶)0.63340.6320
3层交叉(4阶)0.63480.6344
4层交叉(5阶)0.63300.6326

结论是不论parallel还是stacked,在当前数据集下DCN使用3层交叉达到最优AUC,其中parallel略高一点,再往上对叠交叉层AUC会下降。
再对比一下之前文章中实践的FM,FFM,PNN等一系列算法,验证集AUC和参数规模如下

算法AUC参数量
FM0.6274361
FFM0.63172953
IPNN0.632215553
OPNN0.632627073
PNN*0.634229953
DeepFM0.632212746
NFM0.632910186
DCN-parallel-30.6348110017
DCN-stacked-30.6344109857

image.png

DCN都取得了目前为止的SOTA结果,其次是PNN*,发现随着模型参数量的增大,模型的预测效果也随着提升,DCN的参数规模原高于其他FM系的算法,在本例中即使只用1层交叉DCN的参数规模也能达到5万(是FM参数量的100倍以上)。从结果看,引入高阶交叉的DCN-V2确实比只有二阶交叉的FM系高出一筹,但是模型复杂度也大幅提升。

最后的最后

感谢你们的阅读和喜欢,我收藏了很多技术干货,可以共享给喜欢我文章的朋友们,如果你肯花时间沉下心去学习,它们一定能帮到你。

因为这个行业不同于其他行业,知识体系实在是过于庞大,知识更新也非常快。作为一个普通人,无法全部学完,所以我们在提升技术的时候,首先需要明确一个目标,然后制定好完整的计划,同时找到好的学习方法,这样才能更快的提升自己。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

五、面试资料

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下。
在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1794204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kali扩容

通过wmware虚拟机–>设置–>添加40G容量的硬盘。 ──(root㉿kali)-[~/桌面] fdisk -lDisk /dev/sda: 40 GiB, 42949672960 bytes, 83886080 sectors …

Meta的开源力作:Lexical框架,富文本的未来

引言 Lexical 是一个由 Facebook(现在称为 Meta)开源的可扩展 JavaScript Web 文本编辑器框架。 这个框架特别强调了三个核心特性:可靠性、可访问性以及高性能。 旨在为开发者创造最优的开发体验。 以下是 Lexical 框架的几个关键特点和能…

STM32F103单片机工程移植到航顺单片机HK32F103注意事项

一、简介 作为国内MCU厂商中前三阵营之一的航顺芯片,建立了世界首创超低功耗7nA物联网、万物互联核心处理器浩瀚天际10X系列平台,接受代理商/设计企业/方案商定制低于自主研发十倍以上成本,接近零风险自主品牌产品,芯片设计完成只…

flask轻松入门,概念讲解

Hello World Flask 是轻量级web框架,仅保留了核心功能: 请求响应处理模板渲染URL路由 文章目录 Hello Worldflask命令模式python命令模式两种模式对比修改入口文件配置flask命令修改python命令修改 修改端口和地址flask命令修改python命令修改 修改 URL …

SQL Developer管理RESTful 服务

RESTful 服务依赖于ORDS(Oracle REST Data Services),所以在进行本实验前,请先确认数据库服务器上的ORDS服务已启动: $ systemctl status ords ● ords.service - Oracle REST Data ServicesLoaded: loaded (/etc/sys…

动态规划7:LCR 166. 珠宝的最高价值

动态规划解题步骤: 1.确定状态表示:dp[i]是什么 2.确定状态转移方程:dp[i]等于什么 3.初始化:确保状态转移方程不越界 4.确定填表顺序:根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接:LCR …

最新版wordpress网创资源美化以及更新自动同步插件

最新更新了美化右侧悬浮图标 底部分类板块,以及文章自动同步插件 1.支持分类替换 将主站同步过来的文章分类进行替换 2.支持本地化文章图片 (使用储存桶可能会导致无法保存图片) 3.支持自定义文章作者(选择多个作者则同步到的…

Python round函数详解

大家好,在 Python 编程中,经常需要对数字进行舍入操作。无论是在金融领域的货币计算,还是科学计算中的数据处理,都可能需要使用到四舍五入功能。为了满足这一需求,Python 提供了一个内置函数 round(),它能够…

Java多线程-初阶1

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:MySQL数据库 JavaEE专栏:JavaEE 关注博主带你了解更多数据结构知识 1. 认识线程(Thread) 1.线程是什么 ⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代…

白酒:茅台镇白酒的精致包装与品牌形象

茅台镇,这个位于中国贵州省的小镇,因其与众不同的自然环境和杰出的酿酒工艺而成为世界著名的白酒产区。作为茅台镇的品牌,云仓酒庄豪迈白酒不仅在品质和口感上追求卓着,更在包装和品牌形象上展现出精致与品味。 云仓酒庄豪迈白酒的…

【vue-admin-template】设置前后端访问地址

最近在使用vue-admin-template模板进行二次开发,GitHub地址: Vue-Admin-Template。 如果要在该项目中设置前后端的访问IP及端口,可以这样做: 前端:在vue.config.js中: 后端:在request.js中&…

Django学习三:views业务层中通过models对实体对象进行的增、删、改、查操作。

文章目录 前言一、Django ORM介绍二、项目快速搭建三、操作1、view.pya、增加操作b、删除操作c、修改操作d、查询操作 2、urls.py 前言 上接博文:Django学习二:配置mysql,创建model实例,自动创建数据库表,对mysql数据…

Day44 代码随想录打卡|二叉树篇---找树左下角的值

题目(leecode T513): 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 方法:本题需要找二叉树左下角的值,因此该节点首先是在最后一行&#xff0…

java——顺序表

前言:顺序表是线性表的一种,它是较于数组更加灵活的一种储存方式。线性表通常是逻辑上是连续的一条直线,但在物理上不是连续的。java中已经实现好了一个顺序表,搭配泛型可以支持各种类型的使用,下面就来介绍该如何使用…

如何修复d3dcompiler43.dll丢失问题,这三种方法可轻松解决

在计算机使用过程中,我们常常会遇到一些错误提示,其中之一就是“计算机缺失d3dcompiler43.dll”。这个问题可能会影响到计算机的正常运行,让我们无法正常使用某些软件或者游戏。那么,究竟什么是d3dcompiler43.dll?为什…

Golang | Leetcode Golang题解之第128题最长连续序列

题目: 题解: func longestConsecutive(nums []int) int {numSet : map[int]bool{}for _, num : range nums {numSet[num] true}longestStreak : 0for num : range numSet {if !numSet[num-1] {currentNum : numcurrentStreak : 1for numSet[currentNum…

构筑数字文创产业生态,推动集群发展

随着数字技术的飞速发展,数字影像文创产业正迎来前所未有的发展机遇。作为西部地区的文创产业高地,成都国际数字影像产业园积极响应时代潮流,致力于推动数字影像文创产业集群的发展,为文创产业的繁荣贡献力量。 成都国际数字影像产…

Vue3:eachars 折线图 数据不联动 和 tooltip: trigger: ‘axis‘ 不生效,不提示数据

问题1: 点击折线图的头部数据(Email、UnionAds等) 下面数据线不联动问题 问题2:下图是没有提示数据的Demo 这是echars官网的提示数据图 3.解决办法 (1)检查是否设置:trigger:axi…

上位机图像处理和嵌入式模块部署(f407 mcu中fatfs中间件使用)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 前面我们已经实现了spi norflash的驱动,理论上这已经可以实现数据的持久化保存了。为什么还需要一个文件系统呢?主要原因还…

神经网络 torch.nn---Linear Layers(nn.Linear)

torch.nn - PyTorch中文文档 (pytorch-cn.readthedocs.io) torch.nn — PyTorch 2.3 documentation nn.Linear torch.nn.Linear(in_features, out_features, biasTrue, deviceNone, dtypeNone) 参数: in_features - 每个输入样本的大小out_features - 每个输出…