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

深度学习推荐系统(八)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 itertoolsclass 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 xclass 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_outclass 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_infoself.mode = modeself.useDNN = useDNN# embedding层, 这里需要一个列表的形式, 因为每个类别特征都需要embeddingself.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_dimhidden_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_dimself.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、不同的类别特征分别embeddingsparse_embeds = [self.embed_layers['embed_' + key](sparse_inputs[:, i]) for key, i inzip(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 * qif 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:# BatchNormalizationx = self.bn(x)# deepdnn_outputs = self.nn_final_linear(self.dnn_network(x))outputs = torch.sigmoid(dnn_outputs)return outputsif __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 pdimport torch
from torch.utils.data import TensorDataset, Dataset, DataLoaderimport torch.nn as nn
from sklearn.metrics import auc, roc_auc_score, roc_curveimport 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 AFMhidden_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.datay = y_true.datareturn 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 = 10for epoch in range(1, num_epochs + 1):# 训练阶段net.train()loss_sum = 0.0metric_sum = 0.0for 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.0val_metric_sum = 0.0for 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:passval_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.mzph.cn/news/79878.shtml

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

相关文章

idea装载jerbel以及文件上传下载

一、JRebel 1.1 Jrebel介绍 JRebel是一个Java开发工具&#xff0c;它是一款用于实时代码重载的插件。它的主要功能是在不重新启动应用程序的情况下&#xff0c;将修改后的Java代码实时应用到正在运行的应用程序中&#xff0c;从而加快开发周期&#xff0c;提高开发效率。 实…

【实战项目开发技术分享】如何设置机器人禁行区/虚拟墙

文章目录 前言一、代价地图自定义图层1.1 Costmap组成1.2 costmap_2d1.3 实现过程1.3.1 安装插件1.3.2 在costmap_2d中插入障碍物1.3.3 修改launch文件1.3.4 设置障碍物坐标参数二、图像编辑器2.1 安装GIMP2.1.1 命令行方式安装2.1.2 使用图形界面安装GIMP:2.2 实现过程三、ro…

Sqlserver如何调试存储过程

前提&#xff1a;需要使用Sql Server Managerment Studio 工具进行调试。 步骤&#xff1a; 1.选择存储过程&#xff0c;右键选择“执行存储过程”。操作过后&#xff0c;会生成一个调用存储过程的代码块。 2.以编辑模式打开所以需要调试的存储过程。 3.点击调试按钮进行调…

【视觉检测】电源线圈上的导线弯直与否视觉检测系统软硬件方案

 检测内容 线圈上的导线弯直与否检测系统。  检测要求 检测线圈上的导线有无弯曲&#xff0c;弯曲度由客户自己设定。检测速度5K/8H625PCS/H。  视觉可行性分析 对样品进行了光学实验&#xff0c;并进行图像处理&#xff0c;原则上可以使用机器视觉进行测试测量…

数据结构与算法--排序算法复习

目录 1.三种常见的简单排序&#xff1a; 1.1冒泡排序 1.2 选择排序 1.3 插⼊排序 2 常见高级排序算法 2.1 希尔排序 2.2 快速排序 2.3 归并排序 2.4计数排序 先上结论&#xff1a; 1.三种常见的简单排序&#xff1a; 1.1冒泡排序 1.⾸先在未排序数组的⾸位开始&#…

Vue 3的革命性新特性:深入了解Composition API

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

安装深度(Deepin)系统

Deepin系统安装 Deepin是和Ubuntu一样&#xff0c;是一个基于Debian的Linux的发型版本。 Deepin相对于Ubuntu&#xff0c;Deepin更适合中国用户的使用习惯。 一 官网工具制作启动盘 制作启动盘、和安装系统&#xff0c;操作非常简单&#xff0c;nice&#xff01; 官网提供了…

C语言 -- 零基础入门详解

文章目录 引言1. 第一个C语言程序&#xff1a;HelloWorld1.1 编写C语言代码&#xff1a;hello.c1.2 代码分析 2. 数据类型2.1 常量与变量2.1.1 关键字2.1.2 数据类型2.1.3 常量2.1.4 变量2.1.5 使用示例 2.2 整型&#xff1a;int2.2.1 整型变量的定义和输出2.2.2 整型变量的输入…

操作系统的运行机制

1.程序的运行原理&#xff1a; 1.CPU执行指令的过程 C语言代码在编译器上“翻译”&#xff0c;得到二进制的机器指令。一条高级语言的代码翻译过来可能会对应多条机器指令。对于CPU来说&#xff0c;机器指令才是"能看得懂"的语言。程序运行的过程其实就是CPU执行一…

VRTK4⭐二.VRTK4的项目基础配置

文章目录 &#x1f7e5; 硬件基本配置&#x1f7e7; 设置XR Plug-in Management&#x1f7e8; 添加项目Tilia&#x1f7e9; 配置项目Hierarchy &#x1f7e5; 硬件基本配置 解决使用OpenXR,HTC头显正常追踪,但手柄无法使用的问题. 问题如下: 当我们按照官方的标准流程配置完Op…

Apache Kafka 基于 S3 的数据导出、导入、备份、还原、迁移方案

在系统升级或迁移时&#xff0c;用户常常需要将一个 Kafka 集群中的数据导出&#xff08;备份&#xff09;&#xff0c;然后在新集群或另一个集群中再将数据导入&#xff08;还原&#xff09;。通常&#xff0c;Kafka集群间的数据复制和同步多采用 Kafka MirrorMaker&#xff0…

负载均衡-ribbon源码解析

负载均衡-ribbon源码解析 1 LoadBalanced注解 /*** 基于ribbon调用服务及负载均衡* return*/ LoadBalanced Bean public RestTemplate restTemplate(){return new RestTemplate(); }Bean ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(fin…

学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践

文章目录 1.矩阵按键是什么2.矩阵按键的控制原理3.矩阵按键程序的编写将数值转化为键码完整代码&#xff1a;demo.c&#xff1a;key.c:key.h: 密码锁&#xff08;简易版&#xff09;需求分析&#xff1a; 总结课后练习&#xff1a; 1.矩阵按键是什么 这个矩阵按键也是我们这个…

Shell 正则表达式及综合案例及文本处理工具

目录 一、常规匹配 二、常用特殊字符 三、匹配手机号 四、案例之归档文件 五、案例之定时归档文件 六、Shell文本处理工具 1. cut工具 2. awk工具 一、常规匹配 一串不包含特殊字符的正则表达式匹配它自己 例子&#xff0c;比如说想要查看密码包含root字符串的&#x…

Verilog语法之条件编译`ifdef, `ifndef,`else, `elsif, `endif

文章目录 目录 文章目录 概要 整体架构流程 技术名词解释 技术细节 小结 概要 主要分享条件编译语句的用法 整体架构流程 C语言中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑&#xff0c;希望只对其中一部分内容进行编译&#xff0c;此时就需要在程序中加上…

Open3D(C++) 点云旋转的轴角表示法和罗德里格斯公式

目录 一、算法原理1、轴角表示法2、罗德里格斯公式二、代码实现三、结果展示四、相关链接一、算法原理 1、轴角表示法 假设刚体坐标系为B(Oxyz)绕单位向量 ω ⃗ \vec{ω}

点云从入门到精通技术详解100篇-基于 Kinect v2 相机的柑橘点云分割与配准

目录 前言 国内外研究现状 重叠果实分割国内外研究现状 点云配准国内外研究现状

Vue3路由

文章目录 Vue3路由1. 载入vue-router 库2. 实例2.1 Vue.js vue-router 实现单页应用2.2 router-link创建链接2.3 router-view显示与url对应组件2.4 <router-link> 相关属性 Vue3路由 1. 载入vue-router 库 Vue.js 路由需要载入vue-router 库 安装直接下载地址&#xf…

效率工具3-计算机网络工具

查看各个状态的tcp连接情况 netstat -n | awk ‘/^tcp/ {S[$NF]} END {for(a in S) print a, S[a]}’ /^tcp/ 正则表达式匹配 netstat 命令输出的匹配部分&#xff0c;即以 "TCP" 开始的行{S[$NF]} 对于符合条件的每一行&#xff0c;awk 命令将使用数组 S 来计算每…

Redis模块一:缓存简介

目录 缓存的定义 应用 生活案例 程序中的缓存 缓存优点 缓存的定义 缓存是⼀个高速数据交换的存储器&#xff0c;使用它可以快速的访问和操作数据。 应用 1.CPU缓存&#xff1a;CPU缓存是位于CPU和内存之间的临时存储器&#xff0c;它的容量通常远小于内存&#xff0…