FLEN: 一种时空高效的利用场信息缓解梯度耦合的大规模CTR预测模型

FLEN: 一种时空高效的利用场信息缓解梯度耦合的大规模CTR预测模型

本文是由美图公司,腾讯公司和厦门大学共同发表的一篇文章,题目为FLEN: Leveraging Field for Scalable CTR Prediction

文章提出了field-wise bi-interaction pooling技术,解决了在大规模应用特征field信息时存在的时间复杂度和空间复杂度高的困境,同时提出了一种缓解
梯度耦合问题的方法,dicefactor。该模型已应用于美图的大规模推荐系统中,持续稳定地取得业务效果的全面提升

下面将从论文背景,梯度耦合问题简介,FLEN模型结构以及实验对比几个方面为大家介绍这篇文章~



背景

点击率预估问题中,建模特征交互对于模型的效果起着至关重要的作用。然而,大部分基于因子分解的模型都存在着一个梯度耦合的问题。

本文提出了一个Field-Leveraged Embedding Network (FLEN) 模型,该模型能够以一种时空高效的方法缓解广泛存在的梯度耦合问题,从而得以在生产环境中部署服务。

FLEN使用了一个filed-wise bi-interaction pooling技术。通过利用特征所属的场的信息,filed-wise bi-interaction pooling能够以更少的模型参数量和更短的计算时间消耗来同时捕获inter-filed和intra-filed之间的特征交互。

同时提出一种dropout策略Dicefactor。Dicefactor随机丢弃一定的bi-linear交互的结果(元素级别)来帮助缓解梯度耦合问题。

FLEN也是首次公开提出的能够将不同的浅层模型如FM,MF,FwFM通过一个统一的框架表达的模型。

关于梯度耦合问题

  • 假设有如下表达式 \hat y_{FM}=b+w_{男}+w_{伦敦}+w_{周二}+<v_{男},v_{伦敦}>+<v_{男},v_{周二}>+<v_{伦敦},v_{周二}> 其中 v 为FM模型的隐向量
  • 利用反向传播算法更新隐向量时,关于 v_{男} 的梯度为 \nabla_{v_{男}}\hat y_{FM}=v_{伦敦}+v_{周二}

如果我们假设性别和星期是独立的,那么理想情况下应该有 v_{男} v_{周二} 是正交的,但是上述梯度会持续朝着 v_{周二} 的方向更新 v_{男} ,反过来, v_{周二} 也会朝着 v_{男} 的方向被更新

以上就是我们说的梯度耦合问题,由于FM使用了相同的隐含向量和不同场内的特征进行交互,在一定程度上损失了模型的表达能力,

  • 如何解决这个问题

FFM模型提出了利用特征所属的场的信息来缓解梯度耦合方法,每个特征与不同场下的特征交互时都使用了不同的隐向量。

\hat y_{FFM}=b+w_{男}+w_{伦敦}+w_{周二}+<v_{男(与地点)},v_{伦敦(与性别)}>+<v_{男(与星期)},v_{周二(与性别)}>+<v_{伦敦(与星期)},v_{周二(与地点)}>

\nabla_{v_{男(与地点)}}\hat y_{FFM}=v_{伦敦(与性别)}

\nabla_{v_{男(与星期)}}\hat y_{FFM}=v_{周二(与性别)}

在上式的隐向量更新过程中, v_{男} 的更新被分为了 v_{男(与地点)}v_{男(与星期)}v_{男(与地点)} 不再受到和性别相关的特征的干扰,从而缓解了梯度耦合的问题

FFM及基于FFM的神经网络方法虽然能够缓解梯度耦合问题,但是其带来的参数量的增加和训练时间的延长使得其难以被大规模部署,下面就看看本文提出的FLEN是如何以一种时空高效的方法缓解该问题的。

FLEN



Feature Representation

特征在输入到模型时按照其所属的大类别分别被划分成了user field,item field和context field。

下图只展示了user field和item field


Embedding Layer

将每一个特征 x_n 通过embedding层映射为稠密向量 f_n

与传统的embedding层不同的是,FLEN模型中额外使用了一个filed-wise embedding vector,通过将同一个大类(如user filed或者item field)中的embedding 向量进行求和得到大类对应的embedding向量 e_m

e_m=\sum\limits_{n|F(n)=m}f_n

Field-Wise Bi-Interaction Component

这部分主要分为三个小块,分别为

  • 所有特征线性组合以及全局偏置项
  • 特征大类之间的交叉组合MF
  • 特征大类内部的交叉组合FM

线性部分

第一项可以看作一个简单的lr,输出为所有特征的线性组合加一个偏置项。

MF模块

第二项叫做MF模块,主要用来学习大类特征组(user,item or context)之间的特征组合h_{MF}=\sum_{i=1}^M\sum_{j=i+1}^Me_i\odot e_j*r[i][j]

其中M是大类特征组的个数(若分成user,item,context,则M=3), e_i 是前面提到的filed-wise embedding vectorr[i][j] 为表示大类特征组 i j 之间的相关性强度的参数。

FM模块

第三项叫做FM模块,用来学习每一个大类特征组内部的特征两辆之间的组合。这里使用FM中常用的计算化简技巧,将 O(n^2) 的时间复杂度化简到了 O(n)

h_{FM}=\sum_m(hf_m-ht_m)*r[m][m]

其中

hf_m=e_m\odot e_m=(\sum\limits_{n|F(n)=m}f_n)\odot (\sum\limits_{n|F(n)=m}f_n)

ht_m=\sum\limits_{n|F(n)=m}f_n\odot f_n

r[m][m] 表达的是每个大类特征组自身的相对重要性

Field-Wise Bi-Interaction的输出

MF和FM组件的输出分别是包含了特征组之间和特征组内部特征交叉的 K_e 维的向量。

将两个向量求和与一阶项的结果进行拼接,经过一次非线性变换得到最终的输出

h_{FwBI}=\sigma(W_{FwBI}^T[h_S,h_{MF}+h_{FM}])

得到的 h_{FwBI} 的维度为 K_e+1

FwBI组件之所以能缓解梯度耦合问题,是因为其将传统的所有特征直接进行交互组合的方式变为了分组交互的方式。

在FwBI中,若取MF中 r[i][j]=1 和FM中的 r[m][m]=0.5 ,则FwBI退化成了NFM中的Bi-Interaction Pooling。

理想情况下,相关性弱的特征组之间的 r[i][j] 应该是一个较低的值,这样则可以减少特征向量受到无关特征向量的影响。

MLP component

除了使用FwBI建模二阶交互外,还使用了一个多层全连接网络隐式地学习高阶特征组合。

Prediction Layer

最终将FwBI的输出 h_{FwBi} 和MLP的输出 h_L 进行拼接,经过一次非线性变换得到最终的模型打分 z

z=sigmoid(W^T_Fh_F)

其中 h_F=concat(h_{FwBI,H_L})

Dicefactor:Dropout Technique

在Field-Wise Bi-Interaction中,MF组件通过引入参数 r[i][j] 来缓解大类特征组之间存在的梯度耦合问题。然而,对于每个特征组内部,仍然可能存在该问题。本文使用了一个Dicefactor技巧来缓解特征组内部的梯度耦合问题。


如图,对于两个 K_e 维的embedding vector,两两交互可以形成 K_e 条交互路径(这里每条交互路径是元素级别的,一个embedding vector由 K_e 个元素构成)。 DiceFactor通过随机丢弃部分交互路径来防止 e_ie_j 之间的相互影响。

每条路径被丢弃的概率 p[i]\sim Bernoulli(\beta) ,该部分网络可以看作是 2^{K_e} 个共享权重参数的网络集合。每次前向传播时随机选取集合中的一个进行训练。

\phi_{FM}=[w_0+ \sum\limits_{i=1}^Nw[i]x[i]+\sum\limits_{i=1}^M\sum\limits_{j=1\&i\neq j}^Mpe_i\odot e_j]

为了保证后续网络的输入的期望不变,在预测时需要对该部分的输出结果乘上 \beta ,以补偿训练时由于随机丢弃造成的输出结果减小。

\phi_{FM}=[w_0+ \sum\limits_{i=1}^Nw[i]x[i]+\sum\limits_{i=1}^M\sum\limits_{j=1\&i\neq j}^M \beta e_i\odot e_j]

实验对比

实验环节验证FLEN在离线数据集和线上AB中的有效性,以及FLEN相比于其他利用场信息的模型在参数量级和训练时间上的优势,文章还对比了一些超参数对模型效果的影响。

数据集效果对比

在离线实验中,FLEN相比于同样利用特征场信息的NFFM模型能够达到相近的效果。同时,使用了Dicefactor的FLEN模型能够进一步提升效果。


参数量和训练时间对比

其中 N 是特征个数,M是大类特征组的个数, K_e 是embedding size。

可以看到FLEN相比于利用场信息的FFM和NFFM,参数量缩小了一个数量级。

下图是不同数据集中,模型的每秒能够处理的样本条数和收敛速度,可以看到FLEN模型相比于xDeepFM和NFFM等单位时间内能够处理的样本更多,同时也收敛的更快。


在线实验

在美图生产环境中的7天ab实验中,对比的基准桶为NFM模型。 线上CTR平均提升5.195%。

DeepCTR样例

https://github.com/shenweichen/DeepCTR/github.com
import pandas as pd
from sklearn.metrics import log_loss, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder

from deepctr.inputs import SparseFeat, get_feature_names
from deepctr.models import FLEN

if __name__ == "__main__":
    data = pd.read_csv('./avazu_sample.txt')
    data['day'] = data['hour'].apply(lambda x: str(x)[4:6])
    data['hour'] = data['hour'].apply(lambda x: str(x)[6:])

    sparse_features = ['hour', 'C1', 'banner_pos', 'site_id', 'site_domain',
                       'site_category', 'app_id', 'app_domain', 'app_category', 'device_id',
                       'device_model', 'device_type', 'device_conn_type',  # 'device_ip',
                       'C14',
                       'C15', 'C16', 'C17', 'C18', 'C19', 'C20', 'C21', ]

    data[sparse_features] = data[sparse_features].fillna('-1', )
    target = ['click']

    # 1.Label Encoding for sparse features,and do simple Transformation for dense features
    for feat in sparse_features:
        lbe = LabelEncoder()
        data[feat] = lbe.fit_transform(data[feat])

    # 2.count #unique features for each sparse field,and record dense feature field name
    # 注明每个特征所属的field信息
    field_info = dict(C14='user', C15='user', C16='user', C17='user',
                      C18='user', C19='user', C20='user', C21='user', C1='user',
                      banner_pos='context', site_id='context',
                      site_domain='context', site_category='context',
                      app_id='item', app_domain='item', app_category='item',
                      device_model='user', device_type='user',
                      device_conn_type='context', hour='context',
                      device_id='user'
                      )
    # 在构造特征列的时候指定传入上面的field信息
    fixlen_feature_columns = [
        SparseFeat(name, vocabulary_size=data[name].nunique(), embedding_dim=16, use_hash=False, dtype='int32',
                   group_name=field_info[name]) for name in sparse_features]

    dnn_feature_columns = fixlen_feature_columns
    linear_feature_columns = fixlen_feature_columns

    feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)

    # 3.generate input data for model

    train, test = train_test_split(data, test_size=0.2)
    train_model_input = {name: train[name] for name in feature_names}
    test_model_input = {name: test[name] for name in feature_names}

    # 4.Define Model,train,predict and evaluate
    # 调用FLEN模型即可
    model = FLEN(linear_feature_columns, dnn_feature_columns, task='binary')
    model.compile("adam", "binary_crossentropy",
                  metrics=['binary_crossentropy'], )

    history = model.fit(train_model_input, train[target].values,
                        batch_size=256, epochs=10, verbose=2, validation_split=0.2, )
    pred_ans = model.predict(test_model_input, batch_size=256)
    print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
    print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))


参考资料

https://github.com/aimetrics/jarvisgithub.com

编辑于 03-15

文章被以下专栏收录

    我是一个很懒的人,不定期更新,看心情~ 公众号:浅梦的学习笔记 微信:deepctrbot

    微信公众号:机器学习初学者(ID:ai-start-com) 知识星球ID:92416895 个人主页:http://www.ai-start.com Github:https://github.com/fengdu78, 机器学习爱好者qq群:1003271085