推荐算法(三)——Wide&Deep 推荐算法与深度学习的碰撞

目录

    • 1 介绍
    • 2 模型结构
      • 2.1 Wide Models
      • 2.2 Deep Models
    • 3 实验结果
    • 4 总结
    • 5 代码实践
    • 写在最后

1 介绍

Wide&Deep 是 Google 在2016年提出的模型,一个线性模型与深度模型结合的产物。

在此之前,CTR 任务中主要以线性模型+人工特征为主流方法,此类方法缺陷比较明显:线性模型表达能力有限,需要大量人工特征来提升模型效果。随着深度学习的不断火热,深度模型展现了强大的表达能力,并且能自适应的学习特征之间的高阶交互。

因此 Google 取彼之长补己之短,将线性模型与深度模型以并行结构的方式进行融合,线性部分拟提取低阶交互信息,深度部分提取高阶交互信息,提出了 Wide&Deep模型,并在Google Play store中成功落地,收益明显。

2 模型结构

推荐算法(三)——Wide&Deep 推荐算法与深度学习的碰撞_第1张图片
模型结构如图1,中间为 Wide&Deep 总体结构,左边(Wide Models)为拆解出来线性模型,右边(Deep Models)为深度模型。未激活的线性模型输出与深度模型输出相加,再进行激活即得到总体模型的输出。

算法公式如下:

在这里插入图片描述
括号内第一项为线性模型的输出,第二项为深度模型的输出,将两部分输出相加,再加上一个偏置 b 之后输入 sigmoid 进行激活得到预测的概率值。

2.1 Wide Models

在这里插入图片描述

线性部分等同于一个 LR,唯一不同的是在输入上多了 ϕ ( X ) \phi(X) ϕ(X),该项表示的是在原始输入 X X X 上构造出的人工特征,一般为特征之间的二阶交互。也可根据业务场景设计一些强特,以此提升模型表达能力。

线性部分的输出是对输入 [ X , ϕ ( X ) ] [X,\phi(X)] [X,ϕ(X)] 的线性映射,无需激活。

2.2 Deep Models

在这里插入图片描述在这里插入图片描述
该部分为一个多层的全连接网络,第 l l l 层的输出为 a ( l ) a^{(l)} a(l) y y y 为全连接最后一层未进行 sigmoid 激活的输出,与线性部分未激活的输出相加再进行激活即为模型最终输出。

需要注意的是,两部分的输入不同:

Wide 部分:Dense Features + Sparse Features(onehot 处理)+ 特征组合
Deep 部分:Dense Embeddings (Sparse Features 进行 onehot + embedding 处理)

3 实验结果

论文中,Wide 与 Deep 部分共同进行训练,Wide 部分输入的是高维稀疏特征,所以使用 FTRL 算法 + L1 正则进行优化,Deep 部分使用的是 AdaGrad 优化。

A/B test 实验结果如下:

推荐算法(三)——Wide&Deep 推荐算法与深度学习的碰撞_第2张图片

4 总结

优点:

1 结构简单,复杂度低,目前在工业界仍有广泛应用;
2 线性模型与深度模型优势互补,分别提取低阶与高阶特征交互信息,兼顾记忆能力与泛化能力;
3 线性部分为广义线性模型,可灵活替换为其他算法,比如FM,提升wide部分提取信息的能力。

缺点:

1 深度模型可自适应的进行高阶特征交互,但这是隐式的构造特征组合,可解释性差;
2 深度模型仍需要人工特征来提升模型效果,只是需求量没有线性模型大。

5 代码实践

学习算法的最好方式就是用代码实现它。

本文为模型的搭建代码,使用 tf2.0 实现,完整代码(包括数据处理以及模型训练)可参考以下仓库,自行下载数据集到本地运行即可:

Github

Layer 层的搭建:

import tensorflow as tf
from tensorflow.keras.layers import Layer
from tensorflow.keras.layers import Dense

class Wide_layer(Layer):
    def __init__(self):
        super().__init__()

    def build(self, input_shape):
        self.w0 = self.add_weight(name='w0', shape=(1,),
                                  initializer=tf.zeros_initializer(),
                                  trainable=True)
        self.w = self.add_weight(name='w', shape=(input_shape[-1], 1),
                                 initializer=tf.random_normal_initializer(),
                                 trainable=True,
                                 regularizer=tf.keras.regularizers.l2(1e-4))

    def call(self, inputs, **kwargs):   
        x = tf.matmul(inputs, self.w) + self.w0     #shape: (batchsize, 1)
        return x

class Deep_layer(Layer):
    def __init__(self, hidden_units, output_dim, activation):
        super().__init__()
        self.hidden_layer = [Dense(i, activation=activation) for i in hidden_units]
        self.output_layer = Dense(output_dim, activation=None)

    def call(self, inputs, **kwargs):
        x = inputs
        for layer in self.hidden_layer:
            x = layer(x)
        output = self.output_layer(x)
        return output

Model 搭建:

from layer import Wide_layer, Deep_layer

import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.layers import Embedding

class WideDeep(Model):
    def __init__(self, feature_columns, hidden_units, output_dim, activation):
        super().__init__()
        # 模型需要对sparse feature进行embedding,所以需要传入feature_columns,让模型区分dense feature与sparse feature
        self.dense_feature_columns, self.sparse_feature_columns = feature_columns 
        self.hidden_units = hidden_units
        self.output_dim = output_dim
        self.activation = activation
		
        self.embedding_layer = {
     'embed_layer'+str(i): Embedding(feat['feat_onehot_dim'], feat['embed_dim'])
                                for i,feat in enumerate(self.sparse_feature_columns)}

        self.wide = Wide_layer()
        self.deep = Deep_layer(hidden_units, output_dim, activation)

    def call(self, inputs):
        dense_inputs, sparse_inputs = inputs 

        # wide部分
        wide_output = self.wide(inputs)

        # deep部分
        sparse_embed = tf.concat( # 对spare feature进行embedding
        	[self.embedding_layer['embed_layer'+str(i)](sparse_inputs[:, i])
              	for i in range(sparse_inputs.shape[-1])], 
        	axis=-1) 
        deep_output = self.deep(sparse_embed)

        output = tf.nn.sigmoid(0.5*(wide_output + deep_output))
        return output

到此,模型结构的搭建就结束了。

写在最后

下一篇预告:推荐算法(四)——DeepFM 算法的理论及代码实践

其他推荐算法的理论及模型复现代码可参考仓库 Recommend-System-tf2.0

希望看完此文的你能够有所收获…

你可能感兴趣的:(推荐算法也可以很简单,算法,tensorflow,推荐系统,人工智能,深度学习)