深度学习推荐系统(七)NFM模型及其在Criteo数据集上的应用

news2024/9/24 13:19:19

深度学习推荐系统(七)NFM模型及其在Criteo数据集上的应用

1 NFM模型原理及其实现

1.1 NFM模型原理

无论是 FM,还是其改进模型FFM,归根结底是⼀个⼆阶特征交叉的模型。受组合爆炸问题的困扰,FM 几乎不可能扩展到三阶以上,这就不可避免地限制了FM模型的表达能力。

新加坡国立大学学者利用神经网络的非线性和强表达能力来改进一下FM模型,得到一个增强版的FM模型,即NFM模型。

如下图,在数学形式上,NFM 模型的主要思路是用⼀个表达能力更强的函数替代原FM中二阶隐向量内积的部分。

在这里插入图片描述

这个表达能力更强的函数就是神经网络,因为神经网络理论上可以拟合任何复杂能力的函数, 所以作者把这个f(x)换成了一个神经网络,当然不是一个简单的DNN, 而是依然底层考虑了交叉,然后高层使用的DNN网络, 这个也就是NFM网络。

1.1.1 NFM的深度网络部分模型结构图

  • NFM 网络架构的特点非常明显,就是在 Embedding 层和多层神经网络之间加入特征交叉池化层(Bi-Interaction Pooling Layer)

  • 所示的 NFM架构图省略了其⼀阶部分。如果把 NFM的⼀阶部分视为⼀个线性模型,那么NFM的架构也可以视为Wide&Deep模型的进化。相比原始的 Wide&Deep 模型,NFM 模型对其 Deep 部分加入了特征交叉池化层,加强了特征交叉。

在这里插入图片描述

1.1.2 特征交叉池化层

在这里插入图片描述

  • 在进行两两Embedding向量的元素积操作后,对交叉特征向量取和,得到池化层的输出向量。

  • 再把该向量输入上层的多层全连接神经网络(DNN),进行进⼀步的交叉。

1.2 NFM模型的实现

NFM模型的实现在于特征交叉池化层,对原始的池化层公式进行化简:

在这里插入图片描述

import torch.nn as nn
import torch.nn.functional as F
import torch

class Dnn(nn.Module):
    """
    Dnn 网络
    """
    def __init__(self, hidden_units, dropout=0.):
        """
        hidden_units: 列表, 每个元素表示每一层的神经单元个数, 、
                      比如[256, 128, 64], 两层网络, 第一层神经单元128, 第二层64, 第一个维度是输入维度
        dropout: 失活率
        """
        super(Dnn, self).__init__()

        self.dnn_network = nn.ModuleList(
            [nn.Linear(layer[0], layer[1]) for layer in list(zip(hidden_units[:-1], hidden_units[1:]))])

        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x):
        for linear in self.dnn_network:
            x = linear(x)
            x = F.relu(x)

        x = self.dropout(x)
        return x



class NFM(nn.Module):

    def __init__(self, feature_info, hidden_units, embed_dim=8):
        """
               DeepCrossing:
                   feature_info: 特征信息(数值特征, 类别特征, 类别特征embedding映射)
                   hidden_units: 列表, 隐藏单元
                   dropout: Dropout层的失活比例
                   embed_dim: embedding维度
               """
        super(NFM, self).__init__()

        self.dense_features, self.sparse_features, self.sparse_features_map = feature_info

        # embedding层, 这里需要一个列表的形式, 因为每个类别特征都需要embedding
        self.embed_layers = nn.ModuleDict(
            {
                'embed_' + str(key): nn.Embedding(num_embeddings=val, embedding_dim=embed_dim)
                for key, val in self.sparse_features_map.items()
            }
        )

        # 注意 这里的总维度  = 数值型特征的维度 + 离散型变量每个特征要embedding的维度
        dim_sum = len(self.dense_features) + embed_dim
        hidden_units.insert(0, dim_sum)

        # bn
        self.bn = nn.BatchNorm1d(dim_sum)

        # dnn网络
        self.dnn_network = Dnn(hidden_units)

        # dnn的线性层
        self.dnn_final_linear = nn.Linear(hidden_units[-1], 1)

    def forward(self, x):
        # 1、先把输入向量x分成两部分处理、因为数值型和类别型的处理方式不一样
        dense_input, sparse_inputs = x[:, :len(self.dense_features)], x[:, len(self.dense_features):]
        # 2、转换为long形
        sparse_inputs = sparse_inputs.long()

        # 2、不同的类别特征分别embedding  [(batch_size, embed_dim)]
        sparse_embeds = [
            self.embed_layers['embed_' + key](sparse_inputs[:, i]) for key, i in
            zip(self.sparse_features_map.keys(), range(sparse_inputs.shape[1]))
        ]
        # 3、embedding进行堆叠
        sparse_embeds = torch.stack(sparse_embeds) # (离散特征数, batch_size, embed_dim)
        sparse_embeds = sparse_embeds.permute((1,0,2))  # (batch_size, 离散特征数, embed_dim)

        # 这里得到embedding向量 sparse_embeds的shape为(batch_size, 离散特征数, embed_dim)
        # 然后就进行特征交叉层,按照特征交叉池化层化简后的公式  其代码如下
        # 注意:
        # 公式中的x_i乘以v_i就是 embedding后的sparse_embeds
        # 通过设置dim=1,把dim=1压缩(行的相同位置相加、去掉dim=1),即进行了特征交叉
        embed_cross = 1 / 2 * (
                torch.pow(torch.sum(sparse_embeds, dim=1), 2) - torch.sum(torch.pow(sparse_embeds, 2), dim=1)
        )  # (batch_size, embed_dim)

        # 4、数值型和类别型特征进行拼接  (batch_size, embed_dim + dense_input维度 )
        x = torch.cat([embed_cross, dense_input], dim=-1)

        x = self.bn(x)

        # Dnn部分,使用全部特征
        dnn_out = self.dnn_final_linear(self.dnn_network(x))

        # out
        outputs = torch.sigmoid(dnn_out)

        return outputs

if __name__ == '__main__':
    x = torch.rand(size=(2, 5), dtype=torch.float32)
    feature_info = [
        ['I1', 'I2'],  # 连续性特征
        ['C1', 'C2', 'C3'],  # 离散型特征
        {
            'C1': 20,
            'C2': 20,
            'C3': 20
        }
    ]
    # 建立模型
    hidden_units = [128, 64, 32]

    net = NFM(feature_info, hidden_units)
    print(net)
    print(net(x))
NFM(
  (embed_layers): ModuleDict(
    (embed_C1): Embedding(20, 8)
    (embed_C2): Embedding(20, 8)
    (embed_C3): Embedding(20, 8)
  )
  (bn): BatchNorm1d(10, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (dnn_network): Dnn(
    (dnn_network): ModuleList(
      (0): Linear(in_features=10, out_features=128, bias=True)
      (1): Linear(in_features=128, out_features=64, bias=True)
      (2): Linear(in_features=64, out_features=32, bias=True)
    )
    (dropout): Dropout(p=0.0, inplace=False)
  )
  (dnn_final_linear): Linear(in_features=32, out_features=1, bias=True)
)
tensor([[0.4627],
        [0.4660]], grad_fn=<SigmoidBackward0>)

2 NFM模型在Criteo数据集上的应用

数据的预处理可以参考

深度学习推荐系统(二)Deep Crossing及其在Criteo数据集上的应用

2.1 准备训练数据

import pandas as pd

import torch
from torch.utils.data import TensorDataset, Dataset, DataLoader

import torch.nn as nn
from sklearn.metrics import auc, roc_auc_score, roc_curve

import warnings
warnings.filterwarnings('ignore')
# 封装为函数
def prepared_data(file_path):
    # 读入训练集,验证集和测试集
    train_set = pd.read_csv(file_path + 'train_set.csv')
    val_set = pd.read_csv(file_path + 'val_set.csv')
    test_set = pd.read_csv(file_path + 'test.csv')

    # 这里需要把特征分成数值型和离散型
    # 因为后面的模型里面离散型的特征需要embedding, 而数值型的特征直接进入了stacking层, 处理方式会不一样
    data_df = pd.concat((train_set, val_set, test_set))

    # 数值型特征直接放入stacking层
    dense_features = ['I' + str(i) for i in range(1, 14)]
    # 离散型特征需要需要进行embedding处理
    sparse_features = ['C' + str(i) for i in range(1, 27)]

    # 定义一个稀疏特征的embedding映射, 字典{key: value},
    # key表示每个稀疏特征, value表示数据集data_df对应列的不同取值个数, 作为embedding输入维度
    sparse_feas_map = {}
    for key in sparse_features:
        sparse_feas_map[key] = data_df[key].nunique()


    feature_info = [dense_features, sparse_features, sparse_feas_map]  # 这里把特征信息进行封装, 建立模型的时候作为参数传入

    # 把数据构建成数据管道
    dl_train_dataset = TensorDataset(
        # 特征信息
        torch.tensor(train_set.drop(columns='Label').values).float(),
        # 标签信息
        torch.tensor(train_set['Label'].values).float()
    )

    dl_val_dataset = TensorDataset(
        # 特征信息
        torch.tensor(val_set.drop(columns='Label').values).float(),
        # 标签信息
        torch.tensor(val_set['Label'].values).float()
    )
    dl_train = DataLoader(dl_train_dataset, shuffle=True, batch_size=16)
    dl_vaild = DataLoader(dl_val_dataset, shuffle=True, batch_size=16)
    return feature_info,dl_train,dl_vaild,test_set
file_path = './preprocessed_data/'

feature_info,dl_train,dl_vaild,test_set = prepared_data(file_path)

2.2 建立NFM模型

from _01_nfm import NFM

hidden_units = [128, 64, 32]
net = NFM(feature_info, hidden_units)
# 测试一下模型
for feature, label in iter(dl_train):
    out = net(feature)
    print(feature.shape)
    print(out.shape)
    print(out)
    break

3.3 模型的训练

from AnimatorClass import Animator
from TimerClass import Timer


# 模型的相关设置
def metric_func(y_pred, y_true):
    pred = y_pred.data
    y = y_true.data
    return roc_auc_score(y, pred)


def try_gpu(i=0):
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')


def train_ch(net, dl_train, dl_vaild, num_epochs, lr, device):
    """⽤GPU训练模型"""
    print('training on', device)
    net.to(device)
    # 二值交叉熵损失
    loss_func = nn.BCELoss()
    optimizer = torch.optim.Adam(params=net.parameters(), lr=lr)

    animator = Animator(xlabel='epoch', xlim=[1, num_epochs],legend=['train loss', 'train auc', 'val loss', 'val auc']
                        ,figsize=(8.0, 6.0))
    timer, num_batches = Timer(), len(dl_train)
    log_step_freq = 10

    for epoch in range(1, num_epochs + 1):
        # 训练阶段
        net.train()
        loss_sum = 0.0
        metric_sum = 0.0

        for step, (features, labels) in enumerate(dl_train, 1):
            timer.start()
            # 梯度清零
            optimizer.zero_grad()

            # 正向传播
            predictions = net(features)
            loss = loss_func(predictions, labels.unsqueeze(1) )
            try:          # 这里就是如果当前批次里面的y只有一个类别, 跳过去
                metric = metric_func(predictions, labels)
            except ValueError:
                pass

            # 反向传播求梯度
            loss.backward()
            optimizer.step()
            timer.stop()

            # 打印batch级别日志
            loss_sum += loss.item()
            metric_sum += metric.item()

            if step % log_step_freq == 0:
                animator.add(epoch + step / num_batches,(loss_sum/step, metric_sum/step, None, None))

        # 验证阶段
        net.eval()
        val_loss_sum = 0.0
        val_metric_sum = 0.0


        for val_step, (features, labels) in enumerate(dl_vaild, 1):
            with torch.no_grad():
                predictions = net(features)
                val_loss = loss_func(predictions, labels.unsqueeze(1))
                try:
                    val_metric = metric_func(predictions, labels)
                except ValueError:
                    pass

            val_loss_sum += val_loss.item()
            val_metric_sum += val_metric.item()

            if val_step % log_step_freq == 0:
                animator.add(epoch + val_step / num_batches, (None,None,val_loss_sum / val_step , val_metric_sum / val_step))

        print(f'final: loss {loss_sum/len(dl_train):.3f}, auc {metric_sum/len(dl_train):.3f},'
              f' val loss {val_loss_sum/len(dl_vaild):.3f}, val auc {val_metric_sum/len(dl_vaild):.3f}')
        print(f'{num_batches * num_epochs / timer.sum():.1f} examples/sec on {str(device)}')
lr, num_epochs = 0.001, 10
train_ch(net, dl_train, dl_vaild, num_epochs, lr, try_gpu())

在这里插入图片描述

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

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

相关文章

sonarqube的基本使用

操作截图 下载一个中文插件。 插件安装成功&#xff0c;提示需要重启sonarqube。 通过maven的命令对代码进行测试 找到maven。 修改apache-maven-3.6.1\setting.xml。 通过以下命令对当前代码进行质量检测。 检测完毕。 回到sonarqube&#xff0c;看到刚刚检测的结果…

【Unity3D赛车游戏优化篇】【十】汽车粒子特效和引擎咆哮打造极速漂移

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

Nginx 学习(十)高可用中间件的配置与实现

一 Keepalived热备 1 概述 调度器出现单点故障&#xff0c;如何解决?Keepalived实现了高可用集群Keepalived最初是为LVS设计的&#xff0c;专门监控各服务器节点的状态Keepalived后来加入了VRRP功能&#xff0c;防止单点故障 2 运行原理 Keepalived检测每个服务器节点状…

湖南省副省长秦国文一行调研考察亚信科技

9月5日&#xff0c;湖南省人民政府党组成员、副省长秦国文一行到亚信科技调研考察&#xff0c;亚信科技高级副总裁陈武主持接待。 图&#xff1a;双方合影 在亚信科技创新展示中心&#xff0c;秦国文了解了亚信科技在5G、算力网络、人工智能、大数据等前沿领域的创新探索&…

LeetCode 865. Smallest Subtree with all the Deepest Nodes【树,DFS,BFS,哈希表】1534

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

AVLTree模拟实现

一、常用的搜索逻辑 1、暴力搜索 O(N) 2、二分搜索 前提是有序&#xff0c;可以先用O(NlogN)排序一次&#xff0c;后续每次查找都是logN。 缺点&#xff1a;快排需要容器有随机访问功能&#xff0c;即为顺序表等。 如果不仅要搜索&#xff0c;还要插入删除&#xff0c;此时…

修复 ChatGPT 发生错误的问题

目录 ChatGPT 发生错误&#xff1f;请参阅如何修复连接错误&#xff01; 修复 ChatGPT 发生错误的问题 基本故障排除技巧 检查 ChatGPT 的服务器状态 检查 API 限制 检查输入格式 清除浏览数据 香港DSE是什么&#xff1f; 台湾指考是什么&#xff1f; 王湘浩 生平 …

如何安装安卓(Android 7.0+)CA根证书

简介 写这个教程时&#xff0c;已经是2023年&#xff0c;现在最新的安卓系已经是Android 13 。从Android7.0以后系统不再信任用户的证书&#xff0c;导致我们在使用一些网络调试工具时非常不便&#xff0c;为了解决这个问题&#xff0c;本教程将教你如何一步步操作&#xff0c…

分类预测 | MATLAB实现PCA-LSTM(主成分长短期记忆神经网络)分类预测

分类预测 | MATLAB实现PCA-LSTM(主成分长短期记忆神经网络)分类预测 目录 分类预测 | MATLAB实现PCA-LSTM(主成分长短期记忆神经网络)分类预测预测效果基本介绍程序设计参考资料致谢 预测效果 基本介绍 MATLAB实现PCA-LSTM(主成分长短期记忆神经网络)分类预测。Matlab实现基于P…

Unity中Shader的屏幕坐标

文章目录 前言一、屏幕坐标1、屏幕像素的坐标2、屏幕坐标归一化 二、在Unity中获取 当前屏幕像素 和 总像素1、获取屏幕总像素,使用_ScreenParams参数2、获取当前片段上的像素怎么使用:在片元着色器传入参数时使用 前言 Unity中Shader的屏幕坐标 一、屏幕坐标 1、屏幕像素的坐…

YOLOV7改进-添加CoordConv(坐标卷积)

坐标卷积link 只替换FPN1x1卷积层和检测头中的第一卷积层 1、复制到common文件夹最后 2、做改进对比实验时&#xff0c;不要载入与训练权重 3、yolo.py解析模型里面 4、对yolov7.yaml的配置文件修改 5、对fpn1x1卷积替换

HCIA自学笔记01-冲突域

共享式网络&#xff08;用同一根同轴电缆通信&#xff09;中可能会出现信号冲突现象。 如图是一个10BASE5以太网&#xff0c;每个主机都是用同一根同轴电缆来与其它主机进行通信&#xff0c;因此&#xff0c;这里的同轴电缆又被称为共享介质&#xff0c;相应的网络被称为共享介…

使用PHPStudy在本地快速建立网站并实现局域网外访问(无公网IP)

文章目录 使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点&#xff0c;测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中&#xff0c;查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2.2 映…

前端面试题JS篇(1)

JS 的各种位置&#xff0c;比如 clientHeight,scrollHeight,offsetHeight ,以及 scrollTop, offsetTop,clientTop 的区别 clientHeight&#xff1a;表示的是可视区域的高度&#xff0c;不包含 border 和滚动条offsetHeight&#xff1a;表示可视区域的高度&#xff0c;包含了 b…

React 状态管理 - Redux 进阶(下)提升开发体验

目录 扩展学习资料 Reselect【数据持久化】&Immutable Data【不变数据】方案【解决某些场景重复渲染&#xff0c;重复计算的问题】 /src/reducer/index.js Reselect【 可缓存的筛选项&#xff0c;当数据量大的时候&#xff0c;可以节省diff时间&#xff0c;提升渲染效率…

[B题]2023 年全国大学生数学建模比赛思路、代码更新中.....

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

力扣刷题总结一:模拟+哈希表+双指针+前缀和+滑动窗口

模拟 根据题目意思&#xff0c;捋清整个流程&#xff0c;使用代码去模拟整个流程 例题 种花问题 class Solution:def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:# 模拟&#xff0c;在数组前后各添加1个0&#xff0c;从第1个元素到倒数第2个元素&…

CMake输出编译时间信息

使用CMake进行编译的代码&#xff0c;有时候会希望获得当前代码编译的时间信息&#xff0c;使用CMake提供的函数和宏等功能可以实现这个功能&#xff0c;下面是实现这个功能的一段代码 原文链接&#xff1a; CMake输出编译时间信息

YOLOV7改进-最新的可变形卷积V3

DCNv3 1、先编译这个文件,放到models文件夹下,再编译 2、编译命令 3、复制到common文件最后 4、源文件用的是LN,由于这里用的都是LN,所以也改了 5、原文也是没接激活函数的,但是按照yolo格式来说,会接一个 6、不加激活函数就把act去掉 7、DCNv3只在gpu上运行,所以下…

记一次时间序列算法的自回归预测--ARAutoreg

背景 最近公司给客户要做一些数据的预测&#xff0c;但是客户不清楚哪些做起来比较符合他们的&#xff0c;于是在经过与业务方的沟通&#xff0c;瞄准了两个方面的数据 1.工程数据&#xff1a;对工程数据做评估&#xff0c;然后做预警&#xff0c;这个想法是好的&#xff0c;…