基于Field的DeepFM稀疏化实现

一、 DeepFM介绍

DeepFM是一个集成了FM和DNN的神经网络框架,思路和Google的Wide&Deep相似,都包括wide和deep两部分。W&D模型的wide部分是广义线性模型,DeepFM的wide部分则是FM模型,两者的deep部分都是深度神经网络。DeepFM神经网络部分,隐含层的激活函数用ReLu和Tanh做信号非线性映射,Sigmoid函数做CTR预估的输出函数。

DeepFM: A Factorization-Machine based Neural Network for CTR Prediction

    上图是原论文中的网络结构图,在大多数人的实现中,都或多或少的忽略了两个问题:

    1. DeepFM的原始特征是非常稀疏的,所以代码实现需要考虑特征的稀疏化运算;

    2. 生产环境中,每一个Field的输入可能是多值,有的实现中,将每一个one-hot特征都看作一个独立的field,这样虽然简单实现DeepFM模型,但是会造成模型的参数爆炸,训练效率和inference效率低下,而更多的实现中则是忽略了这种情况。

    本文的实现方案解决了以上两个问题,使DeepFM可以真正应用于生产环境中。

二、 基于Field的DeepFM稀疏化实现

2.2 网络结构图

网络结构

    如图所示,每一种颜色代表不同Field的特征,我们假设输入是稀疏的维度为N,并且同一个Field的特征可以不相邻,Field的大小为F,FM的embedding维度为D。最终进入神经网络的是F*D维的向量,并且Field多值的情况下,我们进行field-avg-pooling。

    代码地址:https://github.com/ck8275411/deep_rec

2.2 Field-Avg-Pooling原理

    Field-Avg-Pooling最麻烦的地方在于:如何在稀疏化的样本tensor中,找出属于同一个Field的特征,并将这些特征进行avg-pooling。

    我这里设计了一组名为Field-Selector的0-1矩阵,每一个矩阵中仅有属于同一个Field的特征所属的向量值为1,其它特征的向量值为0。具体方法如下:

    1. 将一个Field-Selector与FM embedding矩阵进行element-wise运算,可以得仅与当前Field相关的所有特征的embedding:fm_field_embeddings;

    2. 将Field-Selector与样本的SparseTensor进行点积,可以得到每条样本中该Field的特征个数;

    3. 将fm_field_embeddings与样本的SparseTensor进行点积,可以得到每条样本中该Field的sum-pooling;

    4. sum-pooling值除以特征个数,即得到了avg-pooling。

    示意图如下:

Field-Avg-Pooling示意图

2.2 Field-Avg-Pooling具体实现

1. 生成Field-Selector矩阵

    Field-Selector矩阵主要是从一个Field-特征id的映射字典里得到,字典格式为:第一列为Field_id,第二列为特征id。具体实现如下:

def get_feature_field(self, feature_field_file):
        feature_fields = []
        field_names = {}
        _fields = []

        for line in open(feature_field_file, "r"):
            tokens = line.strip('\r').strip('\n').split(' ')
            #特征id从0开始编码,所以feature_fields数组中的下表就可以表示特征id,值表示特征所属的field
            feature_fields.append(tokens[0])
            #保存去重后的field id
            field_names[tokens[0]] = 1
        for field_name in field_names.keys():
            #遍历所有field
            field = []
            for j in range(self.feature_size):
                '''
                遍历所有特征id
                如果j大于len(feature_fields),则直接置0
                如果第j个特征的field等于当前field,则置1.0
                如果第j个特征的field不等于当前field,则置0.0
                '''
                if j >= len(feature_fields):
                    field.append([0.0])
                elif feature_fields[j] == field_name:
                    field.append([1.0])
                else:
                    field.append([0.0])
            _fields.append(field)
        return _fields, len(field_names)

2. Avg-Pooling实现

    输入是样本SparseTensor,假设样本数为K,则输出为[K,F*D]的样本DenseTensor以及维度K*D。

def get_field_embeddings(self, sparse_features):
        input_layers = []
        k = 0
        with tf.variable_scope("fm_layer", reuse=tf.AUTO_REUSE):
            fm_layer_embeddings = tf.get_variable("weights",
                                                  self.fm_weights_shape)
            for i in range(self.fields_num):
                fm_field_embeddings = tf.multiply(fm_layer_embeddings, self.field_embedding_masks[i])
                # 计算每个field的feature_cnt
                sparse_x_feature_cnt = tf.maximum(tf.sparse_tensor_dense_matmul(sparse_features, self.field_embedding_masks[i]), 1.0)
                sparse_x_field_embedded = tf.sparse_tensor_dense_matmul(sparse_features, fm_field_embeddings)
                # 计算embedding计算的均值
                sparse_x_field_embedded = tf.divide(sparse_x_field_embedded, sparse_x_feature_cnt)
                input_layers.append(sparse_x_field_embedded)
                k += self.embedding_dim
            nn_inputs = tf.concat(input_layers, 1)
        return nn_inputs, k

2.3 效果对比

    跑了一个实际数据集,验证两种deepfm的实现在训练效率以及效果的差异:

1. 将one-hot特征每一维看做一个独立的field的deepfm;

2. 增加field-avg-pooling层的field-deepfm;

batch_size = 1024

sample_num = 1700w

feature_size = 10560

field_size = 29

nn_layer_shape = [128, 64, 8]

learning_rate = 0.01

optimizer = adam

    效果如下:

实验结果

    可以看到,deepfm的auc比field-deepfm好了0.006,但是训练耗时是field-deepfm的9.2倍,考虑到生产环境的训练效率和Inference效率,显然field-deepfm的实现是好于普通的deepfm的。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

Deep Learning in Ads

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏深度学习那些事儿

利用pytorch实现Visualising Image Classification Models and Saliency Maps

saliency map即特征图,可以告诉我们图像中的像素点对图像分类结果的影响。

2204
来自专栏专知

【论文推荐】最新5篇目标检测相关论文——显著目标检测、弱监督One-Shot检测、多框检测器、携带物体检测、假彩色图像检测

【导读】专知内容组整理了最近目标检测相关文章,为大家进行介绍,欢迎查看! 1. MSDNN: Multi-Scale Deep Neural Network f...

4467
来自专栏SnailTyan

ResNet论文翻译——中文版

Deep Residual Learning for Image Recognition 摘要 更深的神经网络更难训练。我们提出了一种残差学习框架来减轻网络训...

3707
来自专栏专知

【论文推荐】最新七篇图像分割相关论文—域适应深度表示学习、循环残差卷积、二值分割、图像合成、无监督跨模态

3875
来自专栏专知

【Python实战】无监督学习—聚类、层次聚类、t-SNE,DBSCAN

【导读】本文主要介绍了无监督学习在Python上的实践,围绕着无监督学习,讲述了当前主流的无监督聚类方法:数据准备,聚类,K-Means Python实现,层次...

2513
来自专栏云时之间

深度学习与神经网络:单层感知机

今天这个文章让我们一起来学习下感知机: ? 一个传统的单层感知机如上图所示,其实理解起来很简单,我们可以直接理解为输入节点接受信号之后直接传输到输出节点,然后得...

4715
来自专栏云时之间

深度学习与神经网络:单层感知机

一个传统的单层感知机如上图所示,其实理解起来很简单,我们可以直接理解为输入节点接受信号之后直接传输到输出节点,然后得到结果y.

3629
来自专栏AILearning

【机器学习实战】第5章 Logistic回归

第5章 Logistic回归 <script type="text/javascript" src="http://cdn.mathjax.org/mat...

2177
来自专栏技术沉淀

KNN算法实现及其交叉验证

2443
来自专栏自然语言处理

逻辑回归模型算法研究与案例分析

回归:假设现在有一些数据点,我们用一条直线对这些点进行拟合(这条直线称为最佳拟合直线),这个拟合的过程就叫做回归。

1K3

扫码关注云+社区