DeepFM理论与实践

理论背景

DeepFM模型是2017年哈工大深圳与华为诺亚方舟联合实验室提出的,论文名称《DeepFM: A Factorization-Machine based Neural Network for CTR Prediction》,DFM模型是在W&D模型上的改进,W&D模型理论参照上一篇笔记,在W&D的基础上,将Wide部分替换为FM模型,不再需要人工特征工程,同时巧妙地在FM的二阶部分和神经网络的Embedding层共享权重,减少了大量参数,极大的提高了训练速度。

在CTR预估任务中,业界常用的方法有人工特征工程+逻辑回归、梯度增强决策树(GBDT)+逻辑回归,FM(Factorization Machine)和FFM(Field-aware Factorization Machine)模型,在这些模型中FM、FFM模型表现突出。

FM模型

在学习DFM之前,先简单的学习一下FM模型和FFM模型,FM模型是由Konstanz 大学 Steffen Rendle于2010年提出,旨在解决稀疏数据下的特征组合问题。下面我们用一个例子引入FM模型,假设我们要处理一个广告问题,根据用户和广告类型特征,预测用户是否点击了广告。

Clicked?CountryDayAd_type
1USA26/11/15Movie
0China1/7/14Game
1China19/2/15Game

其中**Clicked?**是标签label,CountryDayAd_type是特征。由于三种特征都是类别类型,我们需要通过One-Hot编码将他们转换成数值型特征。

Clicked?Country=USACountry=ChinaDay=26/11/15Day=1/7/14Day=19/2/15Ad_type=MovieAd_type=Game
11010010
00101001
10100101

经过One-Hot编码之后,产生的样本数据大部分都是比较稀疏的,每个样本具有7维特征,但平均仅有3维特征具有非零值。在真实场景中,这样的情况也是普遍存在的,例如我们扩展上述数据的特征,加入用户的性别、职业、教育水平,商品品类等,经过One-Hot编码转换后都会导致样本数据的稀疏性。特别是商品品类这种类型的特征,如商品的品类约有550个,采用One-Hot编码生成550个数值特征,但是每个样本的550个特征中,有且仅有一个是有效的(非零)。由此可见,数据的稀疏性,在真实场景中是不可避免地挑战。

另外,我们也可以发现,经过One-Hot编码之后,特征空间陡然增大。例如,商品品类有550维特征,且是一个类别特征引起的。

通过观察大量样本数据可以发现,某些特征经过关联之后,与label之间的相关性就会提高,例如,“USA”与“Thanksgiving”、“China”与“Chinese New Year”这样的关联特征。对用户的点击行为有着正向的影响。同样,“化妆品”类商品与“女性”,“球类运动”类商品与“男”性,可见这种关联特征也是普遍存在的,因此进行特征的组合是非常有意义的。

在众多模型中,多项式模型是包含特征组合的最直观的模型。在多项式模型中,特征 x i x_i xi x j x_j xj的组合我们采用 x i x j x_ix_j xixj表示,即 x i x_i xi x j x_j xj都非零时,组合特征 x i x j x_ix_j xixj才有意义,在这里我们只讨论二阶多项式模型。表达式如下:
y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n w i j x i x j y(x)=w_0+\sum^{n}_{i=1}{w_ix_i}+\sum^{n}_{i=1}\sum^{n}_{j=i+1}{w_{ij}x_ix_j} y(x)=w0+i=1nwixi+i=1nj=i+1nwijxixj
其中, n n n代表样本的特征数量, x i x_i xi是第 i i i个特征的值, w 0 w_0 w0 w i w_i wi w i j w_{ij} wij是模型参数。

在多项式模型中,组合特征的参数一共有 n ( n − 1 ) 2 {n(n-1)\over{2} } 2n(n1)个,且任意两个参数都是独立的。但是因为每个参数 w i j w_{ij} wij的训练都需要大量的 x i x_i xi x j x_j xj都非零的样本,在数据稀疏性普遍存在的实际场景中,这样的训练是非常困难的,参数 w i j w_{ij} wij的不准确将严重影响模型的性能。

那么,我们如何解决二次项参数的训练问题呢?我们借助矩阵分解的思想,将所有二次项参数 w i j w_{ij} wij组成一个对称矩阵 W W W,这个矩阵可以分解为 W = V T V W=V^TV W=VTV V V V的第 j j j列便是第 j j j维特征的隐向量,即 w i j = ⟨ v i , v j ⟩ w_{ij}=\langle{v_i,v_j}\rangle wij=vi,vj,这就是FM模型的核心思想。因此二阶FM模型的方程可以表述为:
y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n ⟨ v i , v j ⟩ x i x j y(x)=w_0+\sum^{n}_{i=1}{w_ix_i}+\sum^{n}_{i=1}\sum^{n}_{j=i+1}{\langle{v_i,v_j}\rangle}{x_ix_j} y(x)=w0+i=1nwixi+i=1nj=i+1nvi,vjxixj
其中, v i v_i vi是第 i i i维特征的隐向量, ⟨ ⋅ , ⋅ ⟩ {\langle{\cdot,\cdot}\rangle} ,表示向量点积。

FFM模型

FFM模型(Field-aware Factorization Machine)来源于Yu-Chin Juan与其比赛队员,他们借鉴了Michael Jahrer的论文中的field概念,提出了FM的升级版模型。FFM把相同性质的特征归于同一个Field。以上面的广告点击案例为例,“Day=26/11/15”、“Day=1/7/14”、“Day=19/2/15” 这三个特征都是代表日期的,可以放到同一个 Field 中。同理,550个商品类别特征也可以放到一个Field中。即同一个类别特征经过One-Hot编码生成的数值特征都可以放到同一个Field中。在FFM中,每一维特征 x i x_i xi,针对其它特征的每一个Field记为 f j f_j fj,都会学习一个隐向量 v i , f j v_{i,f_j} vi,fj。因此,隐向量不仅与特征相关,也与Field相关。也就是说,“Day=26/11/15” 这个特征与 “Country” 特征和 “Ad_type” 特征进行关联的时候使用不同的隐向量,这与 “Country” 和 “Ad_type” 的内在差异相符,也是 FFM 中 “field-aware” 的由来。

假设样本的 n n n个特征属于 f f f个 field,那么 FFM 的二次项有 n f nf nf个隐向量。而在 FM 模型中,每一维特征的隐向量只有一个。FM 可以看作 FFM 的特例,是把所有特征都归属到一个 field 时的 FFM 模型。根据 FFM 的 field 敏感特性,可以导出其模型方程。如下:
y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n ⟨ v i , f j , v j , f i ⟩ x i x j y(x)=w_0+\sum^{n}_{i=1}{w_ix_i}+\sum^{n}_{i=1}\sum^{n}_{j=i+1}{\langle{v_{i,f_j},v_{j,f_i}}\rangle}{x_ix_j} y(x)=w0+i=1nwixi+i=1nj=i+1nvi,fj,vj,fixixj

DFM模型

FM模型和FFM模型都在特征组合方面进行了探索,FM通过对每一维特征的隐变量内积来提取特征组合,虽然也能取得非常好的结果,但是由于计算复杂度的原因,往往只能局限在二阶特征组合进行建模。如果要使用FFM模型,所有的特征必须要转换成"field_id:feat_id:value"的格式,field_id表示特征所属field的编号,feat_id代表特征编号,value是特征的值。

对于高阶的特征组合来说,我们通常会想到通过DNN去解决,但是正如我们之前介绍的那样,对于类别型特征,我们使用One-Hot编码的方式处理,但是将One-Hot编码输入到DNN中,会使网络参数陡增。

image-20210322193403055

为了解决参数量过大的问题,我们可以借助FFM的思想,将特征分为不同的Field,避免全连接,分而治之。

image-20210322193423532

然后通过增加全连接层就可以实现高阶的特征组合,如下:

image-20210322193448474

但是这里仍然缺少低阶的特征组合,我们使用FM把低阶特征组合单独建模。

image-20210322193551205

然后把低阶特征组合模型插入到网络结构中

image-20210322193622982

网络融合的结构

串行结构

image-20210322193658105

并行结构

image-20210322194746626

Wide&Deep模型就是典型的并行结构。

DeepFM模型结构和原理

我们先来看下DeepFM模型的结构:

image-20210322193940693

可以发现模型结构由FM与DNN两部分组成,FM负责低阶特征的组合提取,DNN负责高阶特征的组合提取,这两部分共享同一的输入,预测结果是对FM和DNN的结果一起做Embedding,可以如下表示:
y ^ = s i g m o i d ( y F M + y D N N ) \hat{y}=sigmoid(y_{FM}+y_{DNN}) y^=sigmoid(yFM+yDNN)

FM 部分

image-20210322194036684 $$ \hat{y}_{FM}(x)=w_0+\sum^{N}_{i=1}{w_ix_i}+\sum^N_{i=1}\sum^N_{j=i+1}v^T_iv_jx_ix_j $$ 由上图和公式大致可以看出FM部分是由一阶特征和二阶特征Concatenate到一起再经过一个Sigmoid得到logits,所以在实现的时候要分别考虑线性相加部分和FM交叉特征部分。

Deep部分

image-20210322194136996

在第一层隐藏层之前,引入一个Embedding层将高维稀疏向量转为低维稠密向量,以解决参数爆炸问题。Embedding层的结构如下图所示:

image-20210322194205481

这里需要注意的是尽管Field的输入长度不同,但是Embedding之后向量的长度均为K。另外FM里面得到的隐变量$ V_{ik} 作为 E m b e d d i n g 层网络的权重。 E m b e d d i n g 层的输出是将所有 i d 类特征对应 e m b e d d i n g 向量 c o n c a t 到一起输入到 D N N 中。其中 作为Embedding层网络的权重。Embedding层的输出是将所有id类特征对应embedding向量concat到一起输入到DNN中。其中 作为Embedding层网络的权重。Embedding层的输出是将所有id类特征对应embedding向量concat到一起输入到DNN中。其中v_i 表示第 表示第 表示第i 个 f i e l d 的 e m b e d d i n g , 个field的embedding, fieldembeddingm$是field的数量。我们得到第一层的输入:
z 1 = [ v 1 , v 2 , ⋯   , v m ] z_1=[v_1,v_2,\cdots,v_m] z1=[v1,v2,,vm]
把上一层的输出作为下一层的输入,得到:
z L = σ ( W L − 1 z L − 1 + b L − 1 ) z_L=\sigma(W_{L-1}z_{L-1}+b_{L-1}) zL=σ(WL1zL1+bL1)
其中 σ \sigma σ是激活函数, z z z表示该层的输入, W W W表示该层的权重, b b b表示偏置。

DNN部分输出如下:
y ^ D N N = σ ( W L α L + b L ) \hat{y}_{DNN}=\sigma{(W^L\alpha^L+b^L)} y^DNN=σ(WLαL+bL)

总结

DeepFM模型大致由两部分组成,分别为FM和DNN,而FM部分又由一阶特征部分和二阶特征交叉部分组成,所以模型大概可以拆成三部分,分别为FM一阶特征Linear部分,二阶特征交叉部分和DNN的高阶特征交叉部分。

两个思考题

  • 如果对于FM采用随机梯度下降SGD训练模型参数,请写出模型各个参数的梯度和FM参数训练的复杂度

    FM的模型方程为:
    y ( x ) = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ j = i + 1 n w i j x i x j y(x)=w_0+\sum^{n}_{i=1}{w_ix_i}+\sum^{n}_{i=1}\sum^{n}_{j=i+1}{w_{ij}x_ix_j} y(x)=w0+i=1nwixi+i=1nj=i+1nwijxixj
    如果直接计算,复杂度为 O ( n ˉ 2 ) O(\bar{n}^2) O(nˉ2),其中 ∑ i = 1 n ∑ j = i + 1 n w i j x i x j \sum^{n}_{i=1}\sum^{n}_{j=i+1}{w_{ij}x_ix_j} i=1nj=i+1nwijxixj可化简为,复杂度为 O ( n ˉ ) O(\bar{n}) O(nˉ).
    ∑ i = 1 n ∑ j = i + 1 n w i j x i x j = ∑ i = 1 n ∑ j = i + 1 n ∑ k = 1 n v i k v j k x i x j = 1 2 ∑ k = 1 n ( ( ∑ i = 1 n v i k x i ) 2 − ∑ i = 1 n v i k x i 2 ) \begin{equation} \begin{aligned} \sum^{n}_{i=1}\sum^{n}_{j=i+1}{w_{ij}x_ix_j} &=\sum^{n}_{i=1}\sum^{n}_{j=i+1}\sum_{k=1}^{n}{v_{ik}v_{jk} }x_ix_j\\ &={1\over2}\sum_{k=1}^{n}((\sum_{i=1}^{n}v_{ik}x_i)^2-\sum_{i=1}^{n}{v_{ik}x_i^2}) \end{aligned} \end{equation} i=1nj=i+1nwijxixj=i=1nj=i+1nk=1nvikvjkxixj=21k=1n((i=1nvikxi)2i=1nvikxi2)

    利用SGD(Stochastic Gradient Descent)训练模型,模型各个参数的梯度如下:
    KaTeX parse error: Unknown column alignment: * at position 70: …{\begin{array}{*̲{20}{c} } {1,if…

  • 对于下图所示,根据你的理解Sparse Feature中的不同颜色节点分别表示什么意思?

    image-20210322194851954

    不同的颜色对应的是稀疏特征向量中不同维度不同的值。浅蓝色表示对应的类别特征的onehot向量,黄色的点表示有效值(1),浅蓝色的点表示无效值(0)。

代码实现

我们还是按照代码的执行顺序来学习DeepFM的实现逻辑,使用的数据集还是criteo。首先是main函数:

if __name__ == "__main__":
    # 读取数据
    data = pd.read_csv('./data/criteo_sample.txt')

    # 划分dense和sparse特征
    columns = data.columns.values
    dense_features = [feat for feat in columns if 'I' in feat]
    sparse_features = [feat for feat in columns if 'C' in feat]

    # 简单的数据预处理
    train_data = data_process(data, dense_features, sparse_features)
    train_data['label'] = data['label']

    # 将特征分组,分成linear部分和dnn部分(根据实际场景进行选择),并将分组之后的特征做标记(使用DenseFeat, SparseFeat)
    linear_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
                            for i,feat in enumerate(sparse_features)] + [DenseFeat(feat, 1,)
                            for feat in dense_features]

    dnn_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
                            for i,feat in enumerate(sparse_features)] + [DenseFeat(feat, 1,)
                            for feat in dense_features]

    # 构建DeepFM模型
    history = DeepFM(linear_feature_columns, dnn_feature_columns)
    history.summary()
    history.compile(optimizer="adam", 
                loss="binary_crossentropy", 
                metrics=["binary_crossentropy", tf.keras.metrics.AUC(name='auc')])

    # 将输入数据转化成字典的形式输入
    train_model_input = {name: data[name] for name in dense_features + sparse_features}
    # 模型训练
    history.fit(train_model_input, train_data['label'].values,
            batch_size=64, epochs=5, validation_split=0.2, )

数据预处理部分和特征分组部分在前两部分有所解释,这里不再赘述。FM部分实现如下:

def DeepFM(linear_feature_columns, dnn_feature_columns):
    # 构建输入层,即所有特征对应的Input()层,这里使用字典的形式返回,方便后续构建模型
    dense_input_dict, sparse_input_dict = build_input_layers(linear_feature_columns + dnn_feature_columns)

    # 将linear部分的特征中sparse特征筛选出来,后面用来做1维的embedding
    linear_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), linear_feature_columns))

    # 构建模型的输入层,模型的输入层不能是字典的形式,应该将字典的形式转换成列表的形式
    # 注意:这里实际的输入与Input()层的对应,是通过模型输入时候的字典数据的key与对应name的Input层
    input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())

    # linear_logits由两部分组成,分别是dense特征的logits和sparse特征的logits
    linear_logits = get_linear_logits(dense_input_dict, sparse_input_dict, linear_sparse_feature_columns)

    # 构建维度为k的embedding层,这里使用字典的形式返回,方便后面搭建模型
    # embedding层用户构建FM交叉部分和DNN的输入部分
    embedding_layers = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)

    # 将输入到dnn中的所有sparse特征筛选出来
    dnn_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), dnn_feature_columns))

    fm_logits = get_fm_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers) # 只考虑二阶项

    # 将所有的Embedding都拼起来,一起输入到dnn中
    dnn_logits = get_dnn_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers)
    
    # 将linear,FM,dnn的logits相加作为最终的logits
    output_logits = Add()([linear_logits, fm_logits, dnn_logits])

    # 这里的激活函数使用sigmoid
    output_layers = Activation("sigmoid")(output_logits)

    model = Model(input_layers, output_layers)
    return model

参考文献

  1. 深入 FFM 原理与实践
  2. 推荐系统遇上深度学习 (三)–DeepFM 模型理论和实践
  3. 深度神经网络
  4. 深度推荐模型之DeepFM
  5. 详解 Wide & Deep 结构背后的动机
  6. FM算法公式推导
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UPTOLIMIT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值