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

news2024/10/3 0:21:41

本文要介绍的是由上海交通大学的研究人员提出的PNN(Product-based Neural Networks)模型,该模型包含一个embedding层来学习类别数据的分布式表示,此外还包含product层来捕获字段之间的特征交互模式,最后包含一个全连接层去挖掘更高阶的特征交互。

相比Deep Crossing模型,PNN模型在输入、EMbedding层、多层神经网络、以及最终的输出层并没有什么结构上的不同,唯一的区别在于PNN引入了Product(乘积)层来代替了Deep Crossing中的Stack层,即不同特征的embedding向量不再是简单地拼接在一起,而是使用Product操作来进行两两交互,更有针对性地获取特征之间的交叉信息。

PNN模型出自论文《Product-based Neural Networks for User Response Prediction》。

文章目录

  • 完整源码&技术交流
  • 模型简介
    • 输出层
    • L2隐层
    • L1隐层
    • Product层
      • IPNN
      • OPNN
  • 代码实践

完整源码&技术交流

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

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

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

模型简介

先看一下整体的模型结构图:
在这里插入图片描述

从自顶向下的视角对模型结构逐步分析:

输出层

在这里插入图片描述

L2隐层

在这里插入图片描述

L1隐层

在这里插入图片描述

Product层

PNN模型对深度学习结构的创新主要体现在Product层的引入,下面详细介绍下它们的计算方式。首先定义向量的内积操作:
在这里插入图片描述

IPNN

首先定义向量的内积操作:

在这里插入图片描述

内积操作可以可视化如下:

在这里插入图片描述

OPNN

将特征交叉的方式由内积变为外积,则可得到OPNN的形式。外积的示意图如下:

在这里插入图片描述

代码实践

模型部分包含了InnerProduct、OutterProduct、以及PNN模型,代码如下:

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

class InnerProduct(nn.Module):
    """InnerProduct Layer used in PNN that compute the element-wise
        product or inner product between feature vectors.
          Input shape
            - a list of 3D tensor with shape: ``(batch_size,1,embedding_size)``.
          Output shape
            - 3D tensor with shape: ``(batch_size, N*(N-1)/2 ,1)`` if use reduce_sum. or 3D tensor with shape:
            ``(batch_size, N*(N-1)/2, embedding_size )`` if not use reduce_sum.
          Arguments
            - **reduce_sum**: bool. Whether return inner product or element-wise product
    """
    def __init__(self, reduce_sum=True):
        super(InnerProduct, self).__init__()
        self.reduce_sum = reduce_sum

    def forward(self, inputs):
        embed_list = inputs
        row,col = [], []
        num_inputs = len(embed_list)

        # 这里为了形成n(n-1)/2个下标的组合
        for i in range(num_inputs - 1):
            for j in range(i + 1, num_inputs):
                row.append(i)
                col.append(j)
        p = torch.cat([embed_list[idx] for idx in row], dim=1)  # batch num_pairs k
        q = torch.cat([embed_list[idx] for idx in col], dim=1)
        # inner_product 中包含了 n(n-1)/2 个 embedding size大小的向量,为了减少计算复杂度,将最后的维度求和,即将embedding size大小变为1
        inner_product = p * q
        if self.reduce_sum:
            # 默认打开,将最后一维的数据累加起来,降低计算复杂度
            inner_product = torch.sum(inner_product, dim=2, keepdim=True)
        return inner_product

class OutterProduct(nn.Module):
    """
      Input shape
            - A list of N 3D tensor with shape: ``(batch_size,1,embedding_size)``.
      Output shape
            - 2D tensor with shape:``(batch_size,N*(N-1)/2 )``.
      Arguments
            - **filed_size** : Positive integer, number of feature groups.
            - **kernel_type**: str. The kernel weight matrix type to use,can be mat,vec or num
    """
    def __init__(self, field_size, embedding_size, kernel_type='mat'):
        super(OutterProduct, self).__init__()
        self.kernel_type = kernel_type

        num_inputs = field_size
        num_pairs = int(num_inputs * (num_inputs - 1) / 2)
        embed_size = embedding_size

        if self.kernel_type == 'mat':
            self.kernel = nn.Parameter(torch.Tensor(embed_size, num_pairs, embed_size))
        elif self.kernel_type == 'vec':
            self.kernel = nn.Parameter(torch.Tensor(num_pairs, embed_size))
        elif self.kernel_type == 'num':
            self.kernel = nn.Parameter(torch.Tensor(num_pairs, 1))

        nn.init.xavier_uniform_(self.kernel)

    def forward(self, inputs):
        embed_list = inputs
        row = []
        col = []
        num_inputs = len(embed_list)
        for i in range(num_inputs - 1):
            for j in range(i + 1, num_inputs):
                row.append(i)
                col.append(j)
        p = torch.cat([embed_list[idx] for idx in row], dim=1)  # batch num_pairs k
        q = torch.cat([embed_list[idx] for idx in col], dim=1)

        # -------------------------
        if self.kernel_type == 'mat':
            p.unsqueeze_(dim=1)
            # k     k* pair* k
            # batch * pair
            kp = torch.sum(
                # batch * pair * k
                torch.mul(
                    # batch * pair * k
                    torch.transpose(
                        # batch * k * pair
                        torch.sum(
                            # batch * k * pair * k
                            torch.mul(p, self.kernel), dim=-1), 2, 1),
                    q),
                dim=-1)
        else:
            # 1 * pair * (k or 1)
            k = torch.unsqueeze(self.kernel, 0)
            # batch * pair
            kp = torch.sum(p * q * k, dim=-1)
            # p q # b * p * k
        return kp

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

        # create embedding layers for all the sparse features
        self.embedding_layers = nn.ModuleList([
            # 根据稀疏特征的个数创建对应个数的Embedding层,Embedding输入大小是稀疏特征的类别总数,输出稠密向量的维度由config文件配置
            nn.Embedding(num_embeddings=sparse_features_cols[idx], embedding_dim=config['embed_dim']) for idx in range(self._num_of_sparse_feature)
        ])

        self.use_inner = config['use_inner']
        self.use_outter = config['use_outter']
        self.kernel_type = config['kernel_type']

        if self.kernel_type not in ['mat', 'vec', 'num']:
            raise ValueError("kernel_type must be mat,vec or num")

        num_inputs = self._num_of_sparse_feature
        # 计算两两特征交互的总数
        num_pairs = int(num_inputs * (num_inputs - 1) / 2)

        if self.use_inner:
            self.innerproduct = InnerProduct()
        if self.use_outter:
            self.outterproduct = OutterProduct(num_inputs, config['embed_dim'], kernel_type=config['kernel_type'])

        # 计算L1全连接层的输入维度
        if self.use_outter and self.use_inner:
            product_out_dim = 2*num_pairs + self._num_of_dense_feature + config['embed_dim'] * self._num_of_sparse_feature
        elif self.use_inner or self.use_outter:
            product_out_dim = num_pairs + self._num_of_dense_feature + config['embed_dim'] * self._num_of_sparse_feature
        else:
            raise Exception("you must specify at least one product operation!")

        self.L1 = nn.Sequential(
            nn.Linear(in_features=product_out_dim, out_features=config['L2_dim']),
            nn.ReLU()
        )
        self.L2 = nn.Sequential(
            nn.Linear(in_features=config['L2_dim'], out_features=1, bias=False),
            nn.Sigmoid()
        )

    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])]
        # 线性信号lz
        linear_signal = torch.cat(sparse_embeds, axis=-1)

        sparse_embeds = [e.reshape(e.shape[0], 1, -1) for e in sparse_embeds]
        if self.use_inner:
            inner_product = torch.flatten(self.innerproduct(sparse_embeds), start_dim=1)
            product_layer = torch.cat([linear_signal, inner_product], dim=1)
        if self.use_outter:
            outer_product = self.outterproduct(sparse_embeds)
            product_layer = torch.cat([linear_signal, outer_product], dim=1)
        if self.use_outter and self.use_inner:
            product_layer = torch.cat([linear_signal, inner_product, outer_product], dim=1)

        # 将dense特征和sparse特征聚合起来
        dnn_input = torch.cat([product_layer, dense_input], axis=-1)
        output = self.L1(dnn_input)
        output = self.L2(output)
        return output

上述代码实现的模型与论文中有些许差异,主要在L1层。实际上,PNN模型在经过对特征的线性和乘积操作之后,并没有结果直接送到上层的L1全连接层,而是在乘积层内部又进行了局部全连接层的转换,这其实也并不影响我们理解论文的思想。

测试部分代码:

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

pnn_config = \
{
    'L2_dim': 256, # 设置L2隐层的输入维度
    'embed_dim': 8,
    'kernel_type': 'mat',
    'use_inner': True,
    'use_outter': False,
    'num_epoch': 25,
    '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/pnn.model'
}

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

    pnn = PNN(pnn_config, dense_features_cols=dense_features_col, sparse_features_cols=sparse_features_col)

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

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

    y_pred_probs = pnn(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.5的就认为用户会点击,否则不点击。以下是部分结果,其中’0‘代表预测用户不点击,’1‘代表预测用户点击。

在这里插入图片描述

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

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

相关文章

一个实例讲讲 ChatGPT 推理

吴恩达与 OpenAI 官方联合推出了 1.5 小时的免费视频课:地址: https://learn.deeplearning.ai/chatgpt-prompt-eng/lesson/2/guidelines 今天我学了第四讲,ChatGPT Inferring,即推理 教学中,给的例子是情绪判断。 我很…

手写数字识别基本思路

问题 什么是MNIST?如何使用Pytorch实现手写数字识别?如何进行手写数字对模型进行检验? 方法 mnist数据集 MNIST数据集是美国国家标准与技术研究院收集整理的大型手写数字数据集,包含了60,000个样本的训练集以及10,000个样本的测试集。 使用P…

RIP笔记

目录 RIP路由信息协议——UDP520端口(RIPNG521端口) RIP使用的算法——贝尔曼福特算法 RIP的版本 RIP的数据包 RIP的工作过程 RIP的计时器 周期更新计时器——默认30s 失效计时器——默认180s 垃圾回收计时器——默认120s RIP的环路问题 解决方法: RIP的…

12种接口优化的通用方案

一、背景 针对老项目,去年做了许多降本增效的事情,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 二、接口优化方案总结 1.批处理 批量思想:批量操作数据库…

Item冷启优化

Item冷启动的目标: 1.精准推荐。 2.激励发布。 3.挖掘高潜。 Item冷启动优化措施: 1.优化全链路(召回和排序) 2.流量调控(新老物品的流量分配) 评价指标: 作者侧: 发布渗透率&a…

【基于Ubuntu18.04+Melodic的realsense D435安装】

【基于Ubuntu18.04Melodic的realsense D435安装】 1. RealSense SDK安装1.1 克隆SDK1. 2 安装相关依赖1.3 安装权限脚本1. 4 进行编译与安装1.5 测试安装是否成功 2. D435i 安装ROS接口2.1 方法一realsense—ros源码2.2 方法二安装相机库 3. 总结 1. RealSense SDK安装 系统硬…

C++:分治算法之选择问题的选择第k小元素问题

目录 3.2.6 选择问题 分析过程: 解法一: 算法代码: 【单组数据】 【多组数据】 运行结果: 解法二 代码: 运行结果: 解法三: 3.2.6 选择问题 ¢ 对于给定的 n 个元素的数组 a[0 …

DAY 53 Haproxy负载均衡集群

常见的Web集群调度器 目前常见的Web集群调度器分为软件和硬件: 软件通常使用开源的LVS、Haproxy、 Nginx LVS性能最好,但是搭建相对复杂;Nginx 的upstream模块支持群集功能,但是对群集节点健康检查功能不强,高并发性能…

第一章 Linux是什么

Linux是一套操作系统,如同下图所示,Linux就是核心与系统调用接口那两层。至于应用程序不算Linux。 1.1 Linux当前应用的角色 由于Linux kernel实在是非常的小巧精致,可以在很多强调省电以及较低硬件资源的环境下面执行; 此外&…

【Elasticsearch】NLP简单应用

文章目录 NLP简介ES中的自然语言处理(NLP)NLP演示将opennlp插件放在ESplugins路径中下载NER模型配置opennlp重启ES、验证 NLP简介 NLP代表自然语言处理,是计算机科学和人工智能领域的一个分支。它涉及使用计算机来处理、分析和生成自然语言,例如英语、中…

企业对网络安全的重视度开始降低

近日,英国科学技术部发布了《2023年企业网络安全合规调查报告》( Cyber Security Breaches Survey ),对英国所有企业和社会性组织目前的网络威胁态势和合规建设进行研究,同时也就如何提升新一代网络应用的合规性给出专…

02-管理员登录与维护 尚筹网

一、管理员登陆 需要做的: 对存入数据库的密码进行MD5加密在登录界面登录失败时的处理抽取后台页面的公共部分检查登录状态,防止未登录时访问受保护资源的情况 具体操作如下: 1)、MD5加密 ​ 使用到的CrowdConstant类中的一些…

人的全面发展评价指标体系—基于相关-主成分分析构建

本文先从经济、社会、生活质量和人口素质四个方面海选了众多人的全面发展评价指标,然后根据可观测性原则剔除无法获得的指标进行了初步筛选,再利用相关性分析删除相关系数大的指标,以及通过主成分分析删除因子负载小的指标,完成了…

CCD视觉检测设备如何选择光源

CCD视觉检测设备的机器视觉系统对光源的要求很高,光源是决定图像质量的一个重要因素。那么,我们就来看看CCD图像加网设备和机器视觉系统光源的选择点——CCD图像加网设备。 CCD视觉检测设备机器视觉系统光源选择要点: 1. 对比度:…

最新VUE面试题

前言 本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性、框架的原理以问题的形式进行整理汇总,意在帮助作者及读者自测下 Vue 掌握的程度。 本文章节结构以从易到难进行组织,建议读者按章节顺序进行阅读,当然大佬级别的…

P1915 [NOI2010] 成长快乐

此题为世纪难题 题目提供者 洛谷 难度 NOI/NOI/CTSC 输入输出样例 输入 #1 5 1 6 0 0 1 5 2 2 0 0 输出 #1 1 5 5 2 2 1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~此题非常难,小白就不用想着独自完成了 题解: #…

如何在 Windows 11 启用 Hyper-V

准备在本机玩一下k8s,需要先启用 Hyper-V,谁知道这一打开,没有 Hyper-V选项: 1、查看功能截图: 2、以下文件保存记事本,然后重命名为*.bat pushd "%~dp0" dir /b %SystemRoot%\servicing\Packa…

常用的MySQL 优化方法

数据库优化一方面是找出系统的瓶颈,提高MySQL数据库的整体性能,而另一方面需要合理的结构设计和参数调整,以提高用户的相应速度,同时还要尽可能的节约系统资源,以便让系统提供更大的负荷。   本文我们来谈谈项目中常用…

maven中的 type ,scope的作用

dependency为什么会有type为pom,默认的值是什么? dependency中type默认为jar即引入一个特定的jar包。那么为什么还会有type为pom呢?当我们需要引入很多jar包的时候会导致pom.xml过大,我们可以想到的一种解决方…

Linux指令-2

文章目录 一、 m a n man man [选项] 命令1、功能:2、常用选项:3、运用实例 二、 c p cp cp [选项] 源文件/目录 目标文件/目录1、功能:2、常用选项:3、运用实例 三、 m v mv mv [选项] 源文件/目录 目标文件/目录1、功能…