深度推荐模型之Wide & Deep
1 背景
在CTR预估任务中, 线性模型仍占有半壁江山。利用手工构造的交叉组合特征来使线性模型具有“记忆性”,使模型记住共现频率较高的特征组合,往往也能达到一个不错的baseline,而且可解释性强。但这种方式有着较为明显的缺点:首先,特征工程需要耗费太多精力。其次,因为模型是强行记住这些组合特征的,所以对于未曾出现过的特征组合,权重系数为0,无法进行泛化。
为了加强模型的泛化能力,研究者引入了DNN结构,将高维稀疏特征编码为低维稠密的Embedding vector,这种基于Embedding的方式减轻了特征工程的负担,而且能够有效提高模型的泛化能力。但是,基于Embedding的方式可能因为数据长尾分布,导致长尾的一些特征值无法被充分学习,其对应的嵌入向量是不准确的,这便会造成模型泛化过度,当基础query-item矩阵稀疏且评分较高时,例如具有特定偏好的用户或具有狭窄吸引力的商品,很难学习有效的query和item的低维表示形式。在这种情况下,大多数query-item对之间不应存在任何交互,但是密集的嵌入向量将导致所有query-item组合的预测都不为零,因此可能泛化过度,做出的推荐的相关性也比较小。
2016年,Google提出Wide&Deep模型,将线性模型与DNN很好的结合起来,在提高模型泛化能力的同时,兼顾模型的记忆性。Wide&Deep这种线性模型与DNN的并行连接模式,后来成为推荐领域的经典模式。
2 模型结构及原理
图中最左边是模型的Wide部分,这个部分可以使用广义线性模型来替代,如LR便是最简单的一种。模型的Deep部分是一个简单的基于Embedding的全连接网络,结构与FNN一致。
2.1 Wide部分
这部分是一个广义线性模型 ,其中
是
维特征向量,特征集合包括原始输入特征和转换后的特征。最常用的特征转换函数便是特征交叉函数
, 当且仅当
是第
个特征变换的一部分时,
。否则为0。例如,对于一个二值特征,特征交叉函数为
,当且仅当构成特征中
和
时该函数值为1,否则为0,即同时具有这两个特征,那么转换后的新特征才为1。这捕获了二元特征之间的相互作用,并为广义线性模型增加了非线性。
Wide部分只能学习这些模式的权重,做一些筛选,而不能自己发现新的模式,需要根据人工经验、业务背景,来将我们认为有价值的、显而易见的特征及特征组合喂入Wide部分。
2.2 Deep部分
Deep部分是全连接网络: 。输入的特征分为两类:一类是数值特征(可直接输入DNN);一类是类别特征(需要经过Embedding之后才能输入到DNN中)。通过增加模型的层数,使要素发生更深层次的交互,提高模型的泛化能力。
2.3 Wide与Deep的结合
将两部分的输出结合起来联合训练,将Wide和Deep部分的输出使用逻辑回归模型做最终的预测: 。 需要注意的是,因为Wide侧的数据是高维稀疏的,所以作者使用了FTRL算法优化,而Deep侧使用的是Adagrad。
3 思考
- 适用于Wide部分的特征:高维稀疏特征、人工手动交叉特征;适用于Deep部分的特征:数值类特征、类别特征等
- 使用L1 FTRL是应对稀疏性一种很好的方法,方法非常注重稀疏性,采用L1 FTRL可以让Wide部分更加稀疏。
- Deep部分的输入要么是数值型特征,要么是嵌入向量,不存在严重的稀疏性问题,所以不用特别考虑Deep部分的稀疏性问题。
4 代码实现
模型的实现与模型结构类似由deep和wide两部分组成,这两部分结构所需要的特征在上面已经说过了,针对当前数据集实现,我们在wide部分加入了所有可能的一阶特征,包括数值特征和类别特征的onehot都加进去了,其实也可以加入一些与wide&deep原论文中类似交叉特征。只要能够发现高频、常见模式的特征都可以放在wide侧,对于Deep部分,在本数据中放入了数值特征和类别特征的embedding特征,实际应用也需要根据需求进行选择。
# Wide&Deep 模型的wide部分及Deep部分的特征选择,应该根据实际的业务场景去确定哪些特征应该放在Wide部分,哪些特征应该放在Deep部分
def WideNDeep(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())
# Wide&Deep模型论文中Wide部分使用的特征比较简单,并且得到的特征非常的稀疏,所以使用了FTRL优化Wide部分(这里没有实现FTRL)
# 但是是根据他们业务进行选择的,我们这里将所有可能用到的特征都输入到Wide部分,具体的细节可以根据需求进行修改
linear_logits = get_linear_logits(dense_input_dict, sparse_input_dict, linear_sparse_feature_columns)
# 构建维度为k的embedding层,这里使用字典的形式返回,方便后面搭建模型
embedding_layers = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)
dnn_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), dnn_feature_columns))
# 在Wide&Deep模型中,deep部分的输入是将dense特征和embedding特征拼在一起输入到dnn中
dnn_logits = get_dnn_logits(dense_input_dict, sparse_input_dict, dnn_sparse_feature_columns, embedding_layers)
# 将linear,dnn的logits相加作为最终的logits
output_logits = Add()([linear_logits, dnn_logits])
# 这里的激活函数使用sigmoid
output_layer = Activation("sigmoid")(output_logits)
model = Model(input_layers, output_layer)
return model
参考资料
- Cheng H T, Koc L, Harmsen J, et al. Wide & deep learning for recommender systems[C]//Proceedings of the 1st workshop on deep learning for recommender systems. 2016: 7-10.
- DataWhale https://github.com/datawhalechina/team-learning-rs/blob/master/DeepRecommendationModel/Wide%26Deep.md
- 见微知著,你真的搞懂Google的Wide&Deep模型了吗?
- 推荐系统系列(六):Wide&Deep理论与实践
