美文网首页推荐算法
特征交叉系列:FM和深度神经网络的结合,DeepFM原理简述和实

特征交叉系列:FM和深度神经网络的结合,DeepFM原理简述和实

作者: xiaogp | 来源:发表于2023-09-12 08:09 被阅读0次

    关键词:FMDNNDeepFM特征交叉推荐算法

    内容摘要

    • 从FM,FFM到DeepFM
    • DeepFM网络结构解析
    • DeepFM在PyTorch下的实践
    • DeepFM和FM,FFM模型效果对比

    从FM,FFM到DeepFM

    在上两节中介绍了FM和FFM

    这两种算法是推荐算法中经典的特征交叉结构,FM将特征交叉分解到底层属性,通过底层属性的点乘来刻画特征交叉的计算,而FFM引入特征域的概念,对不同的特征对所引用的底层属性进行隔离,避免导致多重特征交叉下,底层属性表征产生互相拉扯,导致表达矛盾。
    在深度学习时代之前,FM结构是主流的推荐算法,而随着深度学习的到来,FM逐渐和DNN深度神经网络进行结合,即期望构建模型既可以拥有FM的二阶特征交互的学习能力,也能够像DNN那样能够学习特征间高阶的复杂关系,其中DeepFM是最经典的FM和DNN结合的例子。
    DeepFM提出于2017年,由于网络中只有FM和简单DNN,因此易于快速实现作为业务场景的一个Baseline。


    DeepFM网络结构解析

    DeepFM的网络结构如下


    DeepFM模型结构

    模型结构中左侧部分是FM,右侧部分是DNN,底层的输入从一个业务特征Field转化为稀疏onehot的Sparse Feature特征,模型的前馈传播有三块计算网络:

      1. Sparse Feature特征直接进入左侧FM的一阶线性层,完成一个wx+b的操作返回一阶的结果,只有Sparse Feature有值的位置才会进行权重加和
      1. Sparse Feature特征进入Dense Embedding层进行稠密向量映射,映射后进入左侧FM,映射的结果作为FM的隐向量进行点乘操作得到FM的二阶输出
    • 3.Sparse Feature特征进入Dense Embedding层进行稠密向量映射,映射后进入右侧DNN,所有Field的映射结果进行拼接作为DNN的输入,经过2层DNN隐藏层输出结果

    最终DeepFM的结果是三个计算流程的相加组合,注意FM的二阶和DNN的底层输入是共享的,共用了Dense Embedding层的结果,因此隐藏层的学习不仅要考虑适配FM的交叉,也要适配DNN的高阶复杂关系学习。


    DeepFM在PyTorch下的实践

    本次实践的数据集和上一篇特征交叉系列:完全理解FM因子分解机原理和代码实战一致,采用用户的购买记录流水作为训练数据,用户侧特征是年龄,性别,会员年限等离散特征,商品侧特征采用商品的二级类目,产地,品牌三个离散特征,随机构造负样本,一共有10个特征域,全部是离散特征,对于枚举值过多的特征采用hash分箱,得到一共72个特征。
    PyTorch代码实现如下

    import torch
    import torch.nn as nn
    from torch.utils.data import DataLoader, random_split, TensorDataset
    
    
    class Linear(nn.Module):
        def __init__(self, feat_num):
            super(Linear, self).__init__()
            self.embedding = nn.Embedding(feat_num, 1)
            self.bias = nn.Parameter(torch.zeros(1))
            nn.init.xavier_normal_(self.embedding.weight.data)
    
        def forward(self, x):
            # [None, field_dim] => [None, field, 1] => [None, 1]
            out = self.embedding(x).sum(dim=1) + self.bias
            return out
    
    
    class Embedding(nn.Module):
        def __init__(self, feat_num, k_dim):
            super(Embedding, self).__init__()
            self.embedding = nn.Embedding(feat_num, k_dim)
            nn.init.xavier_uniform_(self.embedding.weight.data)
    
        def forward(self, x):
            return self.embedding(x)
    
    
    class FM(nn.Module):
        def __init__(self):
            super(FM, self).__init__()
    
        def forward(self, x):
            square_of_sum = torch.sum(x, dim=1) ** 2
            sum_of_square = torch.sum(x ** 2, dim=1)
            ix = square_of_sum - sum_of_square
            # [None, 1]
            out = 0.5 * torch.sum(ix, dim=1, keepdim=True)
            return out
    
    
    class DNN(nn.Module):
        def __init__(self, input_dim, fc_dims=(64, 16), dropout=0.1):
            super(DNN, self).__init__()
            layers = list()
            for fc_dim in fc_dims:
                layers.append(nn.Linear(input_dim, fc_dim))
                layers.append(nn.BatchNorm1d(fc_dim))
                layers.append(nn.ReLU())
                layers.append(nn.Dropout(p=dropout))
                input_dim = fc_dim
            layers.append(nn.Linear(input_dim, 1))
            self.mlp = torch.nn.Sequential(*layers)
    
        def forward(self, x):
            return self.mlp(x)
    
    
    class Model(nn.Module):
        def __init__(self, field_num, feat_num, k_dim, fc_dims=(64, 16), dropout=0.1):
            super(Model, self).__init__()
            self.linear = Linear(feat_num=feat_num)
            self.embedding = Embedding(feat_num, k_dim)
            self.fm = FM()
            self.fc_input_dim = field_num * k_dim
            self.dnn = DNN(self.fc_input_dim, fc_dims, dropout)
    
        def forward(self, x):
            linear_out = self.linear(x)
            # [None, feat_size, k_dim]
            emb = self.embedding(x)
            fm_out = self.fm(emb)
            dnn_out = self.dnn(torch.reshape(emb, [-1, self.fc_input_dim]))
            out = torch.sigmoid(linear_out + fm_out + dnn_out)
            return out.squeeze(dim=1)
    

    本例全部是离散分箱变量,所有有值的特征都是1,因此只要输入有值位置的索引即可,一条输入例如

    >>> train_data[0]
    Out[120]: (tensor([ 2, 10, 14, 18, 34, 39, 47, 51, 58, 64]), tensor(0))
    

    其中x的长度10代表10个特征域,每个域的值是特征的全局位置索引,从0到71,一共72个特征。其中FM和DNN共用了Embedding对象。


    DeepFM和FM,FFM模型效果对比

    采用验证集的10次AUC不上升作为早停,FM,FFM,DeepFM的平均验证集AUC如下

    FM FFM DeepFM
    AUC 0.626 0.630 0.631

    DeepFM相比FM增加了DNN结构,AUC提升了0.5个百分点较为明显,而对比FFM,DeepFM也有略微提升,提升0.1个百分点。

    相关文章

      网友评论

        本文标题:特征交叉系列:FM和深度神经网络的结合,DeepFM原理简述和实

        本文链接:https://www.haomeiwen.com/subject/riddvdtx.html