前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Field的DeepFM稀疏化实现

基于Field的DeepFM稀疏化实现

原创
作者头像
kelvincai
发布2018-06-15 16:33:22
2.4K1
发布2018-06-15 16:33:22
举报
文章被收录于专栏:Deep Learning in Ads

一、 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
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示意图
Field-Avg-Pooling示意图

2.2 Field-Avg-Pooling具体实现

1. 生成Field-Selector矩阵

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

代码语言:javascript
复制
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。

代码语言:javascript
复制
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的。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 DeepFM介绍
  • 二、 基于Field的DeepFM稀疏化实现
    • 2.2 网络结构图
      • 2.2 Field-Avg-Pooling原理
        • 2.2 Field-Avg-Pooling具体实现
          • 2.3 效果对比
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档