手机wap网站开发,导视设计原则,网站建设的流程,网站去哪做深度学习推荐系统(八)AFM模型及其在Criteo数据集上的应用
1 AFM模型原理及其实现 沿着特征工程自动化的思路#xff0c;深度学习模型从 PNN ⼀路⾛来#xff0c;经过了Wide#xff06;Deep、Deep#xff06;Cross、FNN、DeepFM、NFM等模型#xff0c;进⾏了大量的、基于不…深度学习推荐系统(八)AFM模型及其在Criteo数据集上的应用
1 AFM模型原理及其实现 沿着特征工程自动化的思路深度学习模型从 PNN ⼀路⾛来经过了WideDeep、DeepCross、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 partdef __init__(self, hidden_units, dropout0.):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(pdropout)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)# 为dim1进行softmax转换 shape为(batch_size, (field_num*(field_num-1))/2, 1)att_weight F.softmax(att_scores, dim1)# 得到最终权重(广播机制) * bi_interaction然后把dim1压缩行的相同位置相加、去掉dim1# 即(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, dim1)return att_outclass AFM(nn.Module):def __init__(self, feature_info, mode, hidden_units, embed_dim8, att_vector8, dropout0.5, useDNNFalse):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_embeddingsval, embedding_dimembed_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, dim1) # (batch_size, embed_dim)elif self.mode avg:att_out torch.mean(bi_interaction, dim1) # (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), dtypetorch.float32)feature_info [[I1, I2], # 连续性特征[C1, C2, C3], # 离散型特征即field_num3{C1: 20,C2: 20,C3: 20}]# 建立模型hidden_units [128, 64, 32]mode attnet 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_features8, out_features8, biasTrue)(att_dense): Linear(in_features8, out_features1, biasTrue))(nn_final_linear): Linear(in_features10, out_features1, biasTrue)
)
tensor([[0.5879],[0.6086]], grad_fnSigmoidBackward0)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(columnsLabel).values).float(),# 标签信息torch.tensor(train_set[Label].values).float())dl_val_dataset TensorDataset(# 特征信息torch.tensor(val_set.drop(columnsLabel).values).float(),# 标签信息torch.tensor(val_set[Label].values).float())dl_train DataLoader(dl_train_dataset, shuffleTrue, batch_size16)dl_vaild DataLoader(dl_val_dataset, shuffleTrue, batch_size16)return feature_info,dl_train,dl_vaild,test_setfile_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, dropoutdnn_dropout, useDNNTrue)# 测试一下模型
for feature, label in iter(dl_train):out net(feature)print(feature.shape)print(out.shape)print(out)break3.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(i0):if torch.cuda.device_count() i 1:return torch.device(fcuda:{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(paramsnet.parameters(), lrlr)animator Animator(xlabelepoch, 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(ffinal: 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())