推荐算法实战项目:DCN 原理以及案例实战(附完整 Python 代码)

news2025/1/12 1:10:55

本文要介绍的是由斯坦福大学联合Google的研究人员发表的论文《Deep & Cross Network for Ad Click Predictions》中提出的Deep&Cross模型,简称DCN。

DCN模型是Wide&Deep的改进版本,其中Deep部分的设计思路与Wide&Deep没有发生本质的变化,DCN主要是重新设计了Cross部分以增加特征之间的交互力度,使用了多层特征交叉层对输入向量进行特征交叉,以获得更加强壮的特征表达能力。

完整源码&技术交流

技术要学会分享、交流,不建议闭门造车。一个人走的很快、一堆人可以走的更远。

文章中的完整源码、资料、数据、技术交流提升, 均可加知识星球交流群获取,群友已超过2000人,添加时切记的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、添加微信号:mlc2060,备注:来自 获取推荐资料
方式②、微信搜索公众号:机器学习社区,后台回复:推荐资料

摘要

点击率(CTR)预估任务是一个大规模的问题,尤其是对于价值数百亿美元的在线广告业务。在广告界,广告商向发布商付款,以便在发布商的网站上展示其广告。较为主流的付款方式是根据每次点击付款(cost-per-click),即当用户点击了一次广告之后,发布商就可以向广告商索取费用。

因此,对于发布商而言,其预测广告点击率的能力直接决定了其营收。做出较为准确的点击率预测的关键是要识别出经常预测的特征,并且同时挖掘出不常见的交叉特征信息。

然而,用于Web级推荐系统的数据通常是离散的,且大多是类别数据,这就导致了一个非常庞大且稀疏的特征空间,对特征探索提出了巨大的挑战。这也使得大多数大型系统受限于使用简单的线性模型,比如逻辑回归。线性模型很简单,解释性很强,并且计算快速。

然而,这也限制了它的表达能力。交叉特征已经被证明对提高模型的表达能力很有效。然而它一般需要手工特征工程或者纷繁复杂的搜索才能找到;此外,它也很难推广到那些看不见的特征交互上。

作者在这篇论文中引入了一个新型的神经网络结构,即DCN。它避免了传统的针对特定任务的特征工程,依靠神经网络强大的学习能力,在一定程度上实现自动学习交叉特征组合。

Deep&Cross 网络模型

Deep&Cross模型,简称DCN。DCN模型的输入是稠密和稀疏向量,可以自动进行特征学习,有效捕捉有限度的特征交互,学习高度非线性的特征之间的交互,无需复杂繁琐的特征工程和详细的特征搜索,并且具备较低的计算成本。模型的整体结构图如下:

DCN模型

DCN模型整体结构比较简单,下面分别进行描述。

Embedding和Stack层

输入数据包含稠密和稀疏向量。在Web级的推荐系统任务中,比如点击率预估,其输入大都是类别向量,比如“国家=中国”。这样的特征通常被编码成one-hot向量,比如这样“[0,1,0]”;但是如果稀疏向量很多,且类别较大,那么这将导致一个超高维的特征空间,同时造成空间浪费以及计算复杂度高。

为了减少维度,通常采用embedding处理(即上图红色矩形框部分),将这些二值化特征转化为包含实数的稠密向量,也叫做嵌入向量(embedding vector)。转换公式如下:

Cross网络

Cross网络是DCN模型中最关键的部分,它以一种高效的方式来进行特征交叉。Cross网络包含若干个cross层,每一层都通过以下公式计算:


cross层的工作示意图如下:

复杂度计算

我们再来观察一下cross层的计算迭代公式:

实际代码调试的时候,两种方法都实验了,在我的电脑上测试发现,使用优化过的计算方式大概可以将速度提升6倍左右。

Deep网络

Deep层比较简单,就是一个全连接前向神经网络,每一层的计算方式如下:

复杂度计算

在这里插入图片描述

聚合层

聚合的作用是将Deep和Cross网络的输出聚合到一个向量中,并且通过一个标准的逻辑层。计算公式如下:

在这里插入图片描述

主要贡献

DCN的主要贡献主要包含以下几点:

  • 提出一种新型的交叉网络结构,可以用来提取交叉组合特征,并不需要人为设计的特征工程
  • 这种网络结构足够简单同时也很有效,可以获得随网络层数增加而增加的多项式阶(polynomial degree)交叉特征
  • 十分节约内存(依赖于正确地实现),并且易于使用
  • 实验结果表明,DCN相比于其他模型有更出色的效果,与DNN模型相比,较少的参数却取得了较好的效果

代码实践

模型部分代码,主要包含了Deep和Cross,以及DeepCross模型实现,特别要注意的就是Cross模型中的矩阵计算的时候,有两种方式,默认用的是优化过后的方法。

import torch
import torch.nn as nn
from BaseModel.basemodel import BaseModel

class Deep(nn.Module):
    def __init__(self, input_dim, deep_layers):
        super(Deep, self).__init__()

        deep_layers.insert(0, input_dim)
        deep_ayer_list = []
        for layer in list(zip(deep_layers[:-1], deep_layers[1:])):
            deep_ayer_list.append(nn.Linear(layer[0], layer[1]))
            deep_ayer_list.append(nn.BatchNorm1d(layer[1], affine=False))
            deep_ayer_list.append(nn.ReLU(inplace=True))
        self._deep = nn.Sequential(*deep_ayer_list)

    def forward(self, x):
        out = self._deep(x)
        return out

class Cross(nn.Module):
    """
    the operation is this module is x_0 * x_l^T * w_l + x_l + b_l for each layer, and x_0 is the init input
    """
    def __init__(self, input_dim, num_cross_layers):
        super(Cross, self).__init__()
        
        self.num_cross_layers = num_cross_layers
        weight_w = []
        weight_b = []
        batchnorm = []
        for i in range(num_cross_layers):
            weight_w.append(nn.Parameter(torch.nn.init.normal_(torch.empty(input_dim))))
            weight_b.append(nn.Parameter(torch.nn.init.normal_(torch.empty(input_dim))))
            batchnorm.append(nn.BatchNorm1d(input_dim, affine=False))

        self.weight_w = nn.ParameterList(weight_w)
        self.weight_b = nn.ParameterList(weight_b)
        self.bn = nn.ModuleList(batchnorm)

    def forward(self, x):
        out = x
        x = x.reshape(x.shape[0], -1, 1)
        for i in range(self.num_cross_layers):
            # this compute mode is time-consuming.
            # out = torch.matmul(torch.bmm(x, torch.transpose(out.reshape(out.shape[0], -1, 1), 1, 2)), self.weight_w[i]) + self.weight_b[i] + out

            # use this compute mode to speed up calculation
            xxTw = torch.matmul(x, torch.matmul(torch.transpose(out.reshape(out.shape[0], -1, 1), 1, 2), self.weight_w[i].reshape(1, -1, 1)))
            xxTw = xxTw.reshape(xxTw.shape[0], -1)
            out = xxTw + self.weight_b[i] + out

            out = self.bn[i](out)
        return out

class DeepCross(BaseModel):
    def __init__(self, config, dense_features_cols, sparse_features_cols):
        super(DeepCross, self).__init__(config)
        self._config = config
        # 稠密和稀疏特征的数量
        self._num_of_dense_feature = dense_features_cols.__len__()
        self._num_of_sparse_feature = sparse_features_cols.__len__()

        # For categorical features, we embed the features in dense vectors of dimension of 6 * category cardinality^1/4
        # calculate all the embedding dimension of all the sparse features
        self.embedding_dims = list(map(lambda x: int(6 * pow(x, 0.25)), sparse_features_cols))
        # create embedding layers for all the sparse features
        self.embedding_layers = nn.ModuleList([
            nn.Embedding(num_embeddings=e[0], embedding_dim=e[1], scale_grad_by_freq=True) for e in list(zip(sparse_features_cols, self.embedding_dims))
        ])

        self._input_dim = self._num_of_dense_feature + sum(self.embedding_dims)

        self._deepNet = Deep(self._input_dim, self._config['deep_layers'])
        self._crossNet = Cross(self._input_dim, self._config['num_cross_layers'])

        self._final_dim = self._input_dim + self._config['deep_layers'][-1]
        self._final_linear = nn.Linear(self._final_dim, 1)

    def forward(self, x):
        # 先区分出稀疏特征和稠密特征,这里是按照列来划分的,即所有的行都要进行筛选
        dense_input, sparse_inputs = x[:, :self._num_of_dense_feature], x[:, self._num_of_dense_feature:]
        sparse_inputs = sparse_inputs.long()

        # 求出稀疏特征的隐向量
        sparse_embeds = [self.embedding_layers[i](sparse_inputs[:, i]) for i in range(sparse_inputs.shape[1])]
        sparse_embeds = torch.cat(sparse_embeds, axis=-1)

        # 将dense特征和sparse特征聚合起来
        input = torch.cat([sparse_embeds, dense_input], axis=-1)

        deep_out = self._deepNet(input)
        cross_out = self._crossNet(input)

        final_input = torch.cat([deep_out, cross_out], dim=1)
        output = self._final_linear(final_input)
        output = torch.sigmoid(output)
        return output

测试数据是criteo数据集的一个很小的子集,测试代码如下:

import torch
from DeepCross.trainer import Trainer
from DeepCross.network import DeepCross
from Utils.criteo_loader import getTestData, getTrainData
import torch.utils.data as Data

deepcross_config = \
{
    'deep_layers': [256,128,64,32], # 设置Deep模块的隐层大小
    'num_cross_layers': 4, # cross模块的层数
    'num_epoch': 2,
    'batch_size': 32,
    'lr': 1e-3,
    'l2_regularization': 1e-4,
    'device_id': 0,
    'use_cuda': False,
    'train_file': '../Data/criteo/processed_data/train_set.csv',
    'fea_file': '../Data/criteo/processed_data/fea_col.npy',
    'validate_file': '../Data/criteo/processed_data/val_set.csv',
    'test_file': '../Data/criteo/processed_data/test_set.csv',
    'model_name': '../TrainedModels/DeepCross.model'
}

if __name__ == "__main__":
    ####################################################################################
    # DeepCross 模型
    ####################################################################################
    training_data, training_label, dense_features_col, sparse_features_col = getTrainData(deepcross_config['train_file'], deepcross_config['fea_file'])
    train_dataset = Data.TensorDataset(torch.tensor(training_data).float(), torch.tensor(training_label).float())
    test_data = getTestData(deepcross_config['test_file'])
    test_dataset = Data.TensorDataset(torch.tensor(test_data).float())

    deepCross = DeepCross(deepcross_config, dense_features_cols=dense_features_col, sparse_features_cols=sparse_features_col)

    ####################################################################################
    # 模型训练阶段
    ####################################################################################
    # # 实例化模型训练器
    trainer = Trainer(model=deepCross, config=deepcross_config)
    # 训练
    trainer.train(train_dataset)
    # 保存模型
    trainer.save()

    ####################################################################################
    # 模型测试阶段
    ####################################################################################
    deepCross.eval()
    if deepcross_config['use_cuda']:
        deepCross.loadModel(map_location=lambda storage, loc: storage.cuda(deepcross_config['device_id']))
        deepCross = deepCross.cuda()
    else:
        deepCross.loadModel(map_location=torch.device('cpu'))

    y_pred_probs = deepCross(torch.tensor(test_data).float())
    y_pred = torch.where(y_pred_probs>0.5, torch.ones_like(y_pred_probs), torch.zeros_like(y_pred_probs))
    print("Test Data CTR Predict...\n ", y_pred.view(-1))

测试代码就是对criteo的测试集中的每一个样本数据输出对应的点击率预测,0或者1。以下是部分测试结果:

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

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

相关文章

asp.net基于web的校园美食派送配送系统

1.系统登录:系统登录是用户访问系统的路口,设计了系统登录界面,包括用户名、密码和验证码,然后对登录进来的用户判断身份信息,判断是管理员用户还是普通用户。 2.系统用户管理:不管是…

OpenHarmony JS项目开发流程

一、配置OpenHarmony开发环境 1.1软件需求 1)下载并安装好DevEco Studio 2.1 Release及以上版本,下载链接:https://developer.harmonyos.com/cn/develop/deveco-studio#download 2)获取OpenHarmony SDK包并解压,下载…

学历不仅是敲门砖,也是我下不来的高台,更是孔乙己脱不下的长衫

学历不仅是敲门砖,也是我下不来的高台,更是孔乙己脱不下的长衫 鲁迅《孔乙己》是一篇具有深刻思想和感人情感的短篇小说,通过酒肆里的故事反映社会的残酷和人性的悲哀; 故事中的孔乙己是一个身世不明、生活贫困的酒鬼&#xff0c…

OpenCV学习小记

OpenCV学习小记 🎈🎈记在最前🎈🎈图像处理的基本操作✨读取图像✨显示图像✨保存图像✨获取图像属性 🎈🎈像素的操作✨像素🔔获取像素的BGR值🔔修改像素的BGR值 ✨使用NumPy模块操作…

2023年值得关注的20大网络安全趋势

随着围绕所有企业的数字革命,无论大小,企业、组织甚至政府都依赖计算机化系统来管理他们的日常活动,从而使网络安全成为保护数据免受各种在线攻击或任何未经授权访问的主要目标。 随着数据泄露、勒索软件和黑客攻击的新闻成为常态&#xff0…

基于计算机视觉的手势识别技术

一个不知名大学生,江湖人称菜狗 original author: Jacky Li Email : 3435673055qq.com Time of completion:2023.5.2 Last edited: 2023.5.2 手语是一种主要由听力困难或耳聋的人使用的交流方式。这种基于手势的语言可以让人们轻松地表达想法和想法&…

RTT开发之windows 环境配置

1. 安装python 有些文章说支持2.7, 实测3.9环境也是OK的 2. 安装scons组件 其他文章多是下载安装,实际操作麻烦还成功率低, 直接pip安装 pip install scons 然后命令测试 D:\rt-thread-5.0.0\bsp\wch\arm\ch579m>scons scons: Readin…

【最优潮流】直流最优潮流(OPF)课设(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

HJ51 输出单向链表中倒数第k个结点

写在前面: 做题环境如下: 题目渠道:牛客网 HJ51 输出单向链表中倒数第k个结点 华为机试题 编程语言:C 一、题目描述 描述 输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第1个结点为链表的尾指针…

这就是二分查找?(C语言版)

大家好!我又来了,哈哈~今天我要和大家分享一种神奇的算法——二分查找!你可能会问,“二分查找有什么好玩的?”但在我看来它就像一场魔法表演,当你输入一个数,他会在一堆数中快速找到它的位置。找…

day10 TCP是如何实现可靠传输的

TCP最主要的特点 1、TCP是面向连接的运输层协议。( 每一条TCP连接只能有两个端点(endpoint),每一条TCP连接只能是点对点的(一对一)) 2、TCP提供可靠交付的服务。 3、TCP提供全双工通信。 4…

HTTP第一讲——HTTP是什么?

定义: HTTP 就是超文本传输协议,也就是 HyperText TransferProtocol。 HTTP 的名字是“超文本传输协议”,它可以拆成三个部分,分别是:“超文本”、“传输”和“协议”。 首先,HTTP 是一个协议。不过&…

Swagger使用手册

目录 Swagger 的依赖Swagger 的配置Swagger 生成的测试页面地址Swagger 的注解遇到过的问题提示 documentationPluginsBootstrapper 空指针异常 Swagger 的依赖 <!--swagger2--> <dependency><groupId>io.springfox</groupId><artifactId>sprin…

【MATLAB图像处理实用案例详解(23)】——基于形态学处理的焊缝边缘检测算法

目录 一、问题描述二、图像预处理2.1 中值滤波去噪2.2 白平衡处理 三、焊缝边缘检测3.1 Sobel算子边缘检测3.2 Prewitt算子边缘检测3.3 Canny算子边缘检测3.4 形态学处理边缘检测 四、结果分析 一、问题描述 目前很多机械关键部件均为钢焊接结构&#xff0c;钢焊接结构易出现裂…

SSL证书支持IP改成https地址

我们都知道SSL证书能为域名加密&#xff0c;那么IP地址可以实现https加密吗&#xff1f;答案当然是肯定的。为IP地址进行https加密不仅能保护IP服务器与客户端之间数据传输安全&#xff0c;还能对IP服务器进行身份验证&#xff0c;确保用户信息安全&#xff0c;增强用户对IP地址…

Python每日一练(20230502)

目录 1. 被围绕的区域 &#x1f31f;&#x1f31f; 2. 两数之和 II &#x1f31f; 3. 二叉树展开为链表 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1…

【操作系统OS】学习笔记:第一章 操作系统基础【哈工大李治军老师】

基于本人观看学习 哈工大李治军老师主讲的操作系统课程 所做的笔记&#xff0c;仅进行交流分享。 特此鸣谢李治军老师&#xff0c;操作系统的神作&#xff01; 如果本篇笔记帮助到了你&#xff0c;还请点赞 关注 支持一下 ♡>&#x16966;<)!! 主页专栏有更多&#xff0…

【前端知识】Cookie, Session,Token和JWT的发展及区别(中)

【前端知识】Cookie, Session&#xff0c;Token和JWT的发展及区别&#xff08;中&#xff09; 4. Session4.1 Session的背景及定义4.2 Session的特点&#x1f44d;4.2.1 Session的特点&#x1f440;4.2.2 Session保存的位置 4.3 Session的一些重要/常用属性4.4 Session的认证流…

算法之美~分治算法

如何理解分治算法&#xff1f; 分治算法&#xff08;divide and conquer&#xff09;的核心思想其实是&#xff0c;分而治之&#xff0c;也就是将原问题划分成n个规模较小&#xff0c;并且结构与原问题相似的子问题&#xff0c;递归第解决这些子问题&#xff0c;然后再合并其结…

什么是 Docker?它能用来做什么?

文章目录 什么是云计算&#xff1f;什么是 Docker&#xff1f;虚拟化技术演变特点架构镜像&#xff08;Image&#xff09;仓库&#xff08;Registry &#xff09;容器&#xff08;Container&#xff09; 应用场景 什么是云计算&#xff1f; 云计算是一种资源的服务模式&#x…