基于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 条评论
登录 后参与评论

相关文章

来自专栏张耀琦的专栏

【机器学习入门系列05】分类、概率生成模型

本文通过将神奇宝贝数值化的过程介绍了分类模型、先验概率以及高斯分布的应用;最大似然估计的方法;推导后验概率等

6280
来自专栏MelonTeam专栏

机器学习入门系列05,classification: probabilistic generative model(分类:概率生成模型)

引用课程:http://speech.ee.ntu.edu.tw/~tlkagk/courses_ml16.html 先看这里,可能由于你正在查看这个平台行...

2555
来自专栏烂笔头

机器学习笔记—KNN算法

目录[-] 前言 分类(Classification)是数据挖掘领域中的一种重要技术,它从一组已分类的训练样本中发现分类模型,将这个分类模型应用到待分类的样...

45410
来自专栏数据派THU

推导和实现:全面解析高斯过程中的函数最优化(附代码&公式)

2484
来自专栏YoungGy

LinearAlgebra_1

方程组的几何解释 linear equation row picture column picture 矩阵计算的两种方法 some questions 需要思...

23710
来自专栏机器之心

从数学到实现,全面回顾高斯过程中的函数最优化

27910
来自专栏大数据挖掘DT机器学习

基于贝叶斯算法的文本分类算法

1、基本定义: 分类是把一个事物分到某个类别中。一个事物具有很多属性,把它的众多属性看作一个向量,即x=(x1,x2,x3,…,xn),用x这个向量来代表这个...

2864
来自专栏kevindroid

mahout学习之聚类(1)——向量的引入与距离测度

1474
来自专栏ATYUN订阅号

机器学习入门——使用python进行监督学习

? 什么是监督学习? 在监督学习中,我们首先要导入包含训练特征和目标特征的数据集。监督式学习算法会学习训练样本与其相关的目标变量之间的关系,并应用学到的关系对...

40410
来自专栏机器学习原理

机器学习(5)——KNNKNNKD Tree

前言:KNN算法以一种“物以类聚”为思想的方法,它不同于前面提到的回归算法,没有损失函数,通过判断预测值离的远近来预测结果。主要分为KNN算法和KD-Tree来...

3335

扫码关注云+社区