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

news2024/11/25 1:00:18

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

1 AFM模型原理及其实现

  • 沿着特征工程自动化的思路,深度学习模型从 PNN ⼀路⾛来,经过了Wide&Deep、Deep&Cross、FNN、DeepFM、NFM等模型,进⾏了大量的、基于不同特征互操作思路的尝试。

  • 但特征工程的思路走到这里几乎已经穷尽了可能的尝试,模型进⼀步提升的空间非常小,这也是这类模型的局限性所在。

  • 从这之后,越来越多的深度学习推荐模型开始探索更多“结构”上的尝试,诸如注意力机制、序列模型、强化学习等在其他领域大放异彩的模型结构也逐渐进⼊推荐系统领域,并且在推荐模型的效果提升上成果显著。

  • 从 2017年开始,推荐领域也开始尝试将注意力机制引入模型之中,由浙江大学提出的AFM和由阿里巴巴提出的DIN是典型模型。

在这里插入图片描述

1.1 AFM模型原理

  • AFM模型和NFM模型结构上非常相似, 算是NFM模型的一个延伸。在NFM中, 不同特征域的特征embedding向量经过特征交叉池化层的交叉,将各个交叉特征向量进行加和, 然后后面跟了一个DNN网络。 加和池化,它相当于一视同仁地对待所有交叉特征, 没有考虑不同特征对结果的影响程度。

  • 这可能会影响最后的预测效果, 因为不是所有的交互特征都能够对最后的预测起作用。 没有用的交互特征可能会产生噪声。例如:如果应用场景是预测一位男性用户是否购买一款键盘的可能性, 那么“性别=男且购买历史包含鼠标”这个交叉特征, 很可能比“性别=男且用户年龄=30”这一个交叉特征重要。

  • 作者在提出NFM之后,把注意力机制引入到了里面去, 来学习不同交叉特征对于结果的不同影响程度。

  • AFM 是从改进模型结构的⾓度出发进行的⼀次有益尝试。它与具体的应用场景无关。

1.1.1 AFM模型结构

  • AFM模型是通过在特征交叉层和最终的输出层之间加入注意力网络来引入了注意力机制

  • 注意力网络的作用是为每⼀个交叉特征提供权重,也就是注意力得分

该模型的网络架构如下:

在这里插入图片描述

1.1.2 基于注意力机制的池化层

在这里插入图片描述

其中要学习的模型参数就是特征交叉层到注意力网络全连接层的权重矩阵W,偏置向量b,以及全连接层到softmax输出层的权重向量h

注意力网络将与整个模型⼀起参与梯度反向传播的学习过程,得到最终的权重参数。

1.2 AFM模型代码复现

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

class Dnn(nn.Module):
    """
    Dnn part
    """
    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 Attention_layer(nn.Module):
    def __init__(self, att_units):
        """
        :param att_units: [embed_dim, att_vector]
        """
        super(Attention_layer, self).__init__()

        self.att_w = nn.Linear(att_units[0], att_units[1])
        self.att_dense = nn.Linear(att_units[1], 1)

    # bi_interaction (batch_size, (field_num*(field_num-1))/2, embed_dim)
    def forward(self, bi_interaction):
        # a的shape为(batch_size, (field_num*(field_num-1))/2, att_vector)
        a = self.att_w(bi_interaction)
        a = F.relu(a)
        # 得到注意力分数,shape为(batch_size, (field_num*(field_num-1))/2, 1)
        att_scores = self.att_dense(a)
        # 为dim=1进行softmax转换  shape为(batch_size, (field_num*(field_num-1))/2, 1)
        att_weight = F.softmax(att_scores, dim=1)

        # 得到最终权重(广播机制) * bi_interaction,然后把dim=1压缩(行的相同位置相加、去掉dim=1)
        # 即(field_num*(field_num-1))/2,embed_dim)变为embed_dim
        # att_out的shape为(batch_size, embed_dim)
        att_out = torch.sum(att_weight * bi_interaction, dim=1)
        return att_out


class AFM(nn.Module):
    def __init__(self, feature_info, mode, hidden_units, embed_dim=8, att_vector=8, dropout=0.5, useDNN=False):
        """
        AFM:
        :param feature_info: 特征信息(数值特征, 类别特征, 类别特征embedding映射)
        :param mode: A string, 三种模式, 'max': max pooling, 'avg': average pooling 'att', Attention
        :param att_vector: 注意力网络的隐藏层单元个数
        :param hidden_units: DNN网络的隐藏单元个数, 一个列表的形式, 列表的长度代表层数, 每个元素代表每一层神经元个数
        :param dropout: Dropout比率
        :param useDNN: 默认不使用DNN网络
        """
        super(AFM, self).__init__()
        self.dense_features, self.sparse_features, self.sparse_features_map = feature_info
        self.mode = mode
        self.useDNN = useDNN

        # 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()
            }
        )

        # 如果是注意机制的话,这里需要加一个注意力网络
        if self.mode == 'att':
            self.attention = Attention_layer([embed_dim, att_vector])

        # 如果使用DNN的话, 这里需要初始化DNN网络
        if self.useDNN:
            # 注意 这里的总维度  = 数值型特征的维度 + embedding的维度
            self.fea_num = len(self.dense_features) + embed_dim
            hidden_units.insert(0, self.fea_num)

            self.bn = nn.BatchNorm1d(self.fea_num)
            self.dnn_network = Dnn(hidden_units, dropout)
            self.nn_final_linear = nn.Linear(hidden_units[-1], 1)
        else:
            # 注意 这里的总维度  = 数值型特征的维度 + embedding的维度
            self.fea_num = len(self.dense_features) + embed_dim
            self.nn_final_linear = nn.Linear(self.fea_num, 1)

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

        # 2、不同的类别特征分别embedding
        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进行堆叠  fild_num即为离散特征数
        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(batch_size, 离散特征数, embed_dim)
        # 下面进行两两交叉, 注意这时候不能加和了,也就是NFM的那个计算公式不能用, 这里两两交叉的结果要进入Attention
        # 两两交叉embedding之后的结果是一个(batch_size, (field_num*field_num-1)/2, embed_dim)
        # 这里实现的时候采用一个技巧就是组合
        # 比如fild_num有3个的话,那么组合embedding就是[0,1] [0,2],[1,2]位置的embedding乘积操作
        first = []
        second = []
        for f, s in itertools.combinations(range(sparse_embeds.shape[1]), 2):
            first.append(f)
            second.append(s)

        # 取出first位置的embedding  假设field是3的话,就是[0, 0, 1]位置的embedding
        # p的shape为(batch_size, (field_num*(field_num-1))/2, embed_dim)
        p = sparse_embeds[:, first, :]

        # 取出second位置的embedding  假设field是3的话,就是[1, 2, 2]位置的embedding
        # q的shape为(batch_size, (field_num*(field_num-1))/2, embed_dim)
        q = sparse_embeds[:, second, :]

        # 最终得到bi_interaction的shape为(batch_size, (field_num*(field_num-1))/2, embed_dim)
        # p * q ,对应位置相乘
        # 假设field_num为3,即为[batch_size,(0,0,1),embed_dim] 与 [batch_size,(1,2,2),embed_dim]  对应元素相乘
        # 即[0,1] [0,2],[1,2]位置的embedding乘积操作
        bi_interaction = p * q

        if self.mode == 'max':
            att_out = torch.sum(bi_interaction, dim=1)  # (batch_size, embed_dim)
        elif self.mode == 'avg':
            att_out = torch.mean(bi_interaction, dim=1)  # (batch_size, embed_dim)
        else:
            # 注意力网络
            att_out = self.attention(bi_interaction)  # (batch_size, embed_dim)

        # 把离散特征和连续特征进行拼接
        x = torch.cat([att_out, dense_inputs], dim=-1)

        if not self.useDNN:
            outputs = torch.sigmoid(self.nn_final_linear(x))
        else:
            # BatchNormalization
            x = self.bn(x)
            # deep
            dnn_outputs = self.nn_final_linear(self.dnn_network(x))
            outputs = torch.sigmoid(dnn_outputs)

        return outputs


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

    net = AFM(feature_info, mode, hidden_units)
    print(net)
    print(net(x))
AFM(
  (embed_layers): ModuleDict(
    (embed_C1): Embedding(20, 8)
    (embed_C2): Embedding(20, 8)
    (embed_C3): Embedding(20, 8)
  )
  (attention): Attention_layer(
    (att_w): Linear(in_features=8, out_features=8, bias=True)
    (att_dense): Linear(in_features=8, out_features=1, bias=True)
  )
  (nn_final_linear): Linear(in_features=10, out_features=1, bias=True)
)
tensor([[0.5879],
        [0.6086]], grad_fn=<SigmoidBackward0>)

2 AFM模型在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 建立AFM模型

from _01_afm import AFM

hidden_units = [128, 64, 32]
mode = "att"
dnn_dropout = 0.0
net = AFM(feature_info, mode, hidden_units, dropout=dnn_dropout, useDNN=True)
# 测试一下模型
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, 5
train_ch(net, dl_train, dl_vaild, num_epochs, lr, try_gpu())

在这里插入图片描述

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

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

相关文章

【计组】3.5高速缓冲存储器

一、cache基本概念 二、cache—主存 映射方式 全相联映射&#xff08;随即放&#xff09; 主存块号、块内地址&#xff08;即记录cache块大小贮存块大小&#xff09; 有效位&#xff08;记录该cache块内是否转入主存信息&#xff09;、标记&#xff08;采用主存块号进行标记…

OpenVINO2023+Win 11配置

&#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 目录 前言 一、Anaconda 二、OpenVINO 三、PyCharm 前言 OpenVINO™是英特尔…

管理类联考——数学——汇总篇——知识点突破——数据分析——计数原理——排列组合——全能元素

⛲️ 一、考点讲解 1.全能元素特征 全能元素是指一个元素可以同时具备多个属性&#xff0c;在选取时&#xff0c;注意全能元素的归宿问题。 2.全能卡片 若一个卡片上的数字可以变化&#xff0c;则称为全能卡片&#xff0c;其解法是根据全能卡片是否选中来分类讨论。 二、考试解…

c++ 学习 之 类对象作为 类成员 ,构造函数和析构函数的先后顺序

前言 我们要学会用类对象作为 类成员&#xff0c;那让我们来深究一下构造函数和析构函数的先后顺序 正文 看代码 #define CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; // 来学习类对象作为类成员 // c类中的成员可以是另外一个类的对象&#xf…

开机启动应用

windows 建立快捷方式 winr 输入shell:startup 将快捷方式复制进来 就可以了 如果你有ccleaner&#xff0c;也可以看到

【爬虫】8.1. 使用OCR技术识别图形验证码

使用OCR技术识别图形验证码 文章目录 使用OCR技术识别图形验证码1. OCR技术2. 准备工作2.1. tesserocr安装异常 3. 验证码图片爬取4. 无障碍识别测试5. 错误识别6. 识别实战&#xff1a;7. 参数设置 图形验证码是最早出现的验证方式&#xff0c;现在依然很常见&#xff0c;一般…

【uni-app】

准备工作 1.下载hbuilder&#xff0c;插件使用Vue3的uni-app项目 2.需要安装编译器 3.下载微信开发者工具 4.点击运行->微信开发者工具 5.打开微信开发者工具的服务端口 效果图 page.json&#xff08;添加路由&#xff0c;修改底层导航栏&#xff0c;背景色&#xff09…

安达发|APS排程系统解决各类制造业难题方案

APS(Advanced Product Scheduling,先进产品计划)软件是一种基于计算机技术的生产计划和调度系统&#xff0c;广泛应用于汽车制造、电子制造、注塑、化工、纺织等行业。本文将详细介绍APS软件在这些行业的应用场景及其优势。 一、汽车制造 1. 零部件生产计划&#xff1a;APS软件…

QEM网格简化算法学习

《Surface Simplification Using Quadric Error Metrics》这篇论文介绍了一种网格简化的算法&#xff0c;通过“edge contraction”&#xff08;边收缩&#xff09;的方法来简化网格。边收缩的结果就是将两个顶点合成一个顶点&#xff0c;因此可以按照任意的顶点数目去简化网格…

2023国赛C题解题思路代码及图表:蔬菜类商品的自动定价与补货决策

2023国赛C题&#xff1a;蔬菜类商品的自动定价与补货决策 C题表面上看上去似乎很简单&#xff0c;实际上23题非常的难&#xff0c;编程难度非常的大&#xff0c;第二题它是一个典型的动态规划加仿真题目&#xff0c;我们首先要计算出销量与销售价格&#xff0c;批发价格之间的…

MySQL的概述、版本、安装过程

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、MySQL的概述 二、MySQL的版本 三、MySQL的下载与安装 前言 本文将来谈谈MySQL的概述&#xff0c;MySQL的版本&#xff0c;以及它…

浙大MBA提面苏州/上海批周末申请截止:仅剩杭州第五批可选

9月10日一过&#xff0c;2024年浙大MBA提前批面试将正式迎来最后一批申请&#xff01;还没开始申请的伙伴要抓紧时间了&#xff0c;按照惯例&#xff0c;最后一批一般在时间节奏上都是最为紧张的&#xff01; 回顾今年的提前批面试申请历程&#xff0c;虽然在总体人数上…

企业内训课程、在线教育平台付费课程加密防下载的10种方式

企业内训课程、在线教育平台付费课程加密防下载的10种方式&#xff1a; 实例演示&#xff1a;课程视频-第1课状语从句,VRM演示应用 企业内训课程、在线教育平台付费课程&#xff0c;他们的这种视频课程的加密是如何做的&#xff1f;整理了10种思路&#xff0c;供大家参考&…

山西电力市场日前价格预测【2023-09-09】

日前价格预测 预测明日&#xff08;2023-09-09&#xff09;山西电力市场全天平均日前电价为372.85元/MWh。其中&#xff0c;最高日前电价为435.72元/MWh&#xff0c;预计出现在18: 45。最低日前电价为342.46元/MWh&#xff0c;预计出现在04: 00。 价差方向预测 1&#xff1a; 实…

UMA 2 - Unity Multipurpose Avatar☀️二.概念介绍

文章目录 🟥 UMA核心🟧 UMA Data 数据类1️⃣ DNA2️⃣ Slots 插槽Overlays 纹理贴图🟨 Base Recipe 基础人形Recipes🟩 Wardrobe Recipes 服饰Recipes🟥 UMA核心 UMA核心组件是 DynamicCharacterAvatar ,后续我们跟插件交互的API,例如捏脸的参数,都是与之交互完成的…

element-ui switch开关组件二次封装,添加loading效果,点击时调用接口后改变状态

先看效果&#xff1a; element-ui中的switch开关无loading属性&#xff08;在element-plus时加入了&#xff09;&#xff0c;而且点击时开关状态就会切换&#xff0c;这使得在需要调用接口后再改变开关状态变得比较麻烦。 思路&#xff1a;switch开关外包一层div&#xff0c;给…

SAP FI/SD的集成-VKOA科目确定

前言 一、组成部分 二、使用步骤 1.VKOA确定收入科目 1.1定义物料科目分配组 1.2定义客户科目分配组 2.V/08定价过程 3. 库存成本Inventory的自动记账科目配置-OBYC 总结 前言 财务和销售集成的点&#xff0c;也是各种SAP顾问经常遇到的面试问题&#xff0c;实际工作中也会经常…

雅思 《九分达人》阅读练习(二)

目录 雅思阅读练习 《九分达人》test3 paragraph3 1.单词含义要记准确&#xff0c;敏感度要上来。 2.找准定位&#xff0c;之后理解句子大致含义。 说说关于判断题的做题方法 关于“承认”有哪些单词 同替词汇 think 可以用什么其他单词来替换 单词 一些疑问 I have…

项目实战:ES的增加数据和查询数据

文章目录 背景在ES中增加数据新建索引删除索引 在ES中查询数据查询数据总数量 项目具体使用&#xff08;实战&#xff09;引入依赖方式一&#xff1a;使用配置类连接对应的es服务器创建配置类编写业务逻辑----根据关键字查询相关的聊天内容在ES中插入数据 总结提升 背景 最近需…

每日一题(设计循环队列)

每日一题&#xff08;设计循环队列&#xff09; 622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; 1.题意解读 本题只能为队列开辟k个单位空间&#xff0c;并且只能利用这几个空间进行数据的存储。 思路&#xff1a;本题使用数组来实现队列是比较方便的&#xff0c…