Wide&Deep 是 Google 在2016年提出的模型,一个线性模型与深度模型结合的产物。
在此之前,CTR 任务中主要以线性模型+人工特征为主流方法,此类方法缺陷比较明显:线性模型表达能力有限,需要大量人工特征来提升模型效果。随着深度学习的不断火热,深度模型展现了强大的表达能力,并且能自适应的学习特征之间的高阶交互。
因此 Google 取彼之长补己之短,将线性模型与深度模型以并行结构的方式进行融合,线性部分拟提取低阶交互信息,深度部分提取高阶交互信息,提出了 Wide&Deep模型,并在Google Play store中成功落地,收益明显。
模型结构如图1,中间为 Wide&Deep 总体结构,左边(Wide Models)为拆解出来线性模型,右边(Deep Models)为深度模型。未激活的线性模型输出与深度模型输出相加,再进行激活即得到总体模型的输出。
算法公式如下:
括号内第一项为线性模型的输出,第二项为深度模型的输出,将两部分输出相加,再加上一个偏置 b 之后输入 sigmoid 进行激活得到预测的概率值。
线性部分等同于一个 LR,唯一不同的是在输入上多了 ϕ ( X ) \phi(X) ϕ(X),该项表示的是在原始输入 X X X 上构造出的人工特征,一般为特征之间的二阶交互。也可根据业务场景设计一些强特,以此提升模型表达能力。
线性部分的输出是对输入 [ X , ϕ ( X ) ] [X,\phi(X)] [X,ϕ(X)] 的线性映射,无需激活。
该部分为一个多层的全连接网络,第 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 处理)
论文中,Wide 与 Deep 部分共同进行训练,Wide 部分输入的是高维稀疏特征,所以使用 FTRL 算法 + L1 正则进行优化,Deep 部分使用的是 AdaGrad 优化。
A/B test 实验结果如下:
优点:
1 结构简单,复杂度低,目前在工业界仍有广泛应用;
2 线性模型与深度模型优势互补,分别提取低阶与高阶特征交互信息,兼顾记忆能力与泛化能力;
3 线性部分为广义线性模型,可灵活替换为其他算法,比如FM,提升wide部分提取信息的能力。
缺点:
1 深度模型可自适应的进行高阶特征交互,但这是隐式的构造特征组合,可解释性差;
2 深度模型仍需要人工特征来提升模型效果,只是需求量没有线性模型大。
学习算法的最好方式就是用代码实现它。
本文为模型的搭建代码,使用 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
希望看完此文的你能够有所收获…