最近有一批业务需要用到推荐算法,因此,准备认真整理一下自己学习的推荐算法的思路,以便于正确地应用和对比。
首先应该需要了解推荐算法的一些基本思路,我之前整理过一篇《(一)推荐算法概述——以协同过滤为主》。
简单来说,推荐系统分为两种: CF-Based(协同过滤)、Content-Based(基于内容的推荐)
推荐系统可以看成是一个search ranking问题,根据query得到items候选列表,然后对items通过ranking算法排序,得到最终的推荐列表。(我们接下来的例子均是以论文的背景为例,论文的例子是给Google应用商店的用户推荐APP)
假如实现上面的推荐系统,针对这一次Query,来给出推荐列表。最简单,最暴力的做法是什么呢?
给数据库中所有的APP都打一个分数,然后按照分数从高到低返回前N个(比如说前100个)
但是有个问题,数据库中的APP实在是太多了,为了保证响应时间,这样做不具备可行性。Retrieval就是用来解决这个问题的(这就是大名鼎鼎的召回,其实应该是找回)。它会利用机器学习模型和一些人为定义的规则,来返回最匹配当前Query的一个小的items集合,这个集合就是最终的推荐列表的候选集。
我们在Retrieval(可以理解成粗排)的基础上再进行Ranking(可以理解成精排)就可以输出推荐的内容。
总的来说, 推荐系统 = Retrieval + Ranking
当然,今天的主角Wide&Deep Model就是用来做Ranking的。不出意外,近期我的博客内容也会重点关注Ranking。
在之前的大规模的在线推荐系统中,Ranking部分logistic regression应用非常广泛,因为其简单、易扩展、可解释性。LR的输入多半是二值化后的one-hot稀疏特征和一些交叉特征。
在稀疏特征上cross-product transformations来实现Memorization(这个词汇下面会介绍),比如:AND(user_installed_app=netflix, impression_app=pandora),当特征user_installed_app=netflix,和特征impression_app=pandora取值都为1的时候,组合特征AND(user_installed_app=netflix, impression_app=pandora)的取值才为1,否则为0。
我们可以看到,如果仅仅使用线性模型:无法学习到训练集中没有的query-item特征组合。
Embedding-based Model可以解决这个问题。(Embedding的内容可以参考我之前的博客,这方面我介绍了很多)
FM和DNN都算是这样的模型,可以在很少的特征工程情况下,通过学习一个低纬度的embedding vector来学习训练集中从未见过的组合特征。
FM和DNN的缺点在于:当query-item矩阵是稀疏并且是high-rank的时候(比如user有特殊的爱好,或item比较小众),很难非常效率的学习出低维度的表示。这种情况下,大部分的query-item都没有什么关系。但是dense embedding会导致几乎所有的query-item预测值都是非0的,这就导致了推荐过度泛化,会推荐一些不那么相关的物品。
相反,linear model却可以通过cross-product transformation来记住这些exception rules,而且仅仅使用了非常少的参数。
总结一下:
因此,结合这两者的优缺点,一个很自然的想法是为什么我们不能将linear model和DNN model结合起来呢,于是Wide & Deep模型应运而生。
Wide&Deep整篇论文的思想其实就是基于这两个词。
人类的大脑很复杂,它可以记忆(memorize)下每天发生的事情(麻雀可以飞,鸽子可以飞)然后泛化(generalize)这些知识到之前没有看到过的东西(有翅膀的动物都能飞)。
但是泛化的规则有时候不是特别的准,有时候会出错(有翅膀的动物都能飞吗)。那怎么办那,没关系,记忆(memorization)可以修正泛化的规则(generalized rules),叫做特例(企鹅有翅膀,但是不能飞)。
这就是Memorization和Generalization的来由或者说含义。
Wide&Deep Mode就是希望计算机可以像人脑一样,可以同时发挥memorization和generalization的作用。—Heng-Tze Cheng(Wide&Deep作者)
其实,简单来说,Wide就是使用了一种线性模型,当然也可以认为是一种特殊的神经网络,它的输入直接和输出相连,属于广义线性模型的范畴。Deep就是指Deep Neural Network,这个很好理解。如图所示,左侧是Wide-only,右侧是Deep-only,中间是Wide & Deep:
参考我们在1.3和2.1节的内容,可以这么说:
Wide Linear Model用于memorization;Deep Neural Network用于generalization。
总结一下:Memorization趋向于更加保守,推荐用户之前有过行为的items。相比之下,Generalization更加趋向于提高推荐系统的多样性(diversity)。
而Wide & Deep的两部门(线性模型 + DNN)就是平衡了memorization和generalizatio。
结论:综合memorization和generalizatio的优点,服务于推荐系统。相比于wide-only和deep-only的模型,wide & deep提升显著。(原论文实验中的结论)
有了上面的背景知识,我们接下来介绍一下Wide & Deep 是如何为用户推荐 Apps的。这里的内容基于论文的结构来介绍,具体参考了参考文献【2】。
我们打开Google APP store,首页展示给我们一些APP,我们点击或者下载或者购买了其中一个APP。在这样一个流程中,推荐系统是如何工作的那?
我们对比上面的图来说:
Query: 当我们打开APP Store的时候,就产生了一次Query,它包含两部分的特征:User features, contextual features。UserFeatures包括性别、年龄等人口统计特征,ContextualFeatures包括设备、时间等上下文特征。
Items: APP store接着展示给我们一系列的app,这些app就是推荐系统针对我们的Query给出的推荐。这个也被叫做impressions。
User Actions: 针对推荐给你的任何一个APP,我们都可以点击、下载、购买等操作。也就是说推荐给你的APP,你产生了某种行为。
Logs: Logs = Query + Impression + UserAction 查询、展示列表、操作会被记录到logs中作为训练数据给Learner来学习。
Retrieval:即找回。从Database中粗排一个集合。
Ranking: Wide & Deep模型,也就是接下来介绍的内容。当然也可以是其它的排序模型。
Wide Part其实是一个广义的线性模型,使用特征包括:
接下来我们用同一个例子来说明:你给model一个query(你想吃的美食),model返回给你一个美食,然后你购买/消费了这个推荐。 也就是说,推荐系统其实要学习的是这样一个条件概率: P(consumption | query, item)
Wide Part可以对一些特例进行memorization。比如AND(query=”fried chicken”, item=”chicken fried rice”)虽然从字符角度来看很接近,但是实际上完全不同的东西,那么Wide就可以记住这个组合是不好的(负样本),是一个特例,下次当你再点炸鸡的时候,就不会推荐给你鸡肉炒米饭了。
Deep Part通过学习一个低维度的embedding vector,对于每一个query和item,来泛化给你推荐一些字符上看起来不那么相关,但是你可能也是需要的。比如说:你想要炸鸡,Embedding Space中,炸鸡和汉堡很接近,所以也会给你推荐汉堡。
Embedding vectors被随机初始化,并根据最终的loss来反向训练更新。这些低维度的dense embedding vectors被作为第一个隐藏层的输入。隐藏层的激活函数通常使用ReLU。
原始的稀疏特征,在两个组件中都会用到,比如query=“fried chicken” item=“chicken fried rice”(即query的特征,如打开设备的时间,查询的内容等;和item的特征,如app的一些特征):
在训练的时候,根据最终的loss计算出gradient,反向传播到Wide和Deep两部分中,分别训练自己的参数。也就是说,两个模块是一起训练的,注意这不是model ensemble。
Wide模块通过组合特征可以很效率的学习一些特定的组合,但是这也导致了并不能学习到训练集中没有出现的组合特征。所幸,Deep模块弥补了这个缺点。
另外,因为是一起训练的,wide和deep的size都减小了。wide组件只需要填补deep组件的不足就行了,所以需要比较少的cross-product feature transformations,而不是full-size wide Model。
论文中的实现:
训练方法是用mini-batch stochastic optimization。
Wide组件是用FTRL(Follow-the-regularized-leader) + L1正则化学习。
Deep组件是用AdaGrad来学习。
User Data和App Impression Data共同组成了Training Data。标签就是目标,如果目标是点击率,但是点了就是1,不点就是0;如果目标是下载率,那么下载了就是1,不下载就是0。
论文中重点介绍了一些特征处理的方法,如:过滤掉出现次数少于设定阈值的离散特征取值,然后把这些取值多的特征进行one-hot或者哈希处理。对于连续特征,通过分位数规范化到[0,1],即先把所有的值分成n份,那么属于第i部分的值规范化之后的值为 (i - 1)/(n - 1)。
连续特征
Embedding后的离散特征,Item特征(Embedding的维度不固定,一般建议 k n 4 k \sqrt[4]{n} k4n, n n n是原始维度上特征不同取值的个数; k k k是一个常数,通常小于10.)
Wide部分使用的特征:
当然,这不是固定的套路,具体如何使用很灵活。
每当有新的数据到达的时候,就要重新训练。如果每次都从头开始会非常耗时,Google给出的解决办法是:实现了warm-starting system, 它可以用之前模型的embeddings 和 线性模型的weights来初始化新的模型。
模型被部署之后。每一次请求,服务器会收到一系列的app候选集(从app retrival system输出的)以及user features(用于为每一个app打分)。然后,模型会把APP按照score排序,并展示给user,按照这个顺序展示。score就是对于wide & deep模型的一次 forward pass。为了控制每一次request响应时间在10ms内,引入了并行化技术。将app候选集分成多个小的batches,并行化预测score。
适用范围:Wide & Deep Model适用于输入非常稀疏的大规模分类或回归问题。比如推荐系统、search、ranking问题。输入稀疏通常是由离散特征有非常非常多个可能的取值造成的,one-hot之后维度非常大。
优缺点:缺点:Wide部分还是需要人为的特征工程;优点:实现了对memorization和generalization的统一建模。
在tensorflow中实现了Wide & Deep,因此对于我们来说,想要应用Wide & Deep是比较简单的。
在tensorflow中,Wide & Deep的API叫DNNLinearCombinedClassifier,在这里可以详细看到英文版和中文版的说明。
【1】Wide & Deep learning for Recommender System
【2】计算广告CTR预估系列(四)–Wide&Deep理论与实践
【3】DNNLinear组合分类器的使用