论文链接:Real-time Personalization using Embeddings for Search Ranking at Airbnb
这篇论文去年就读过了,当时过分注重算法设计的fancy,在当时来看,基于word2vec衍生而来的embedding似乎并没有什么新奇的地方。尤其是在Bert大放异彩和各种end2end模型论文出世的环境下。重读这篇论文,才发现这篇论文的方法包含了Airbnb团队对搜索推荐业务的深刻理解,正所谓没有最好的算法,只有最合适的算法。Airbnb取得了好效果,为公司带来收益,那就是好算法,这对于我一个即将进入职场,从事搜索推荐算法工作的菜鸟来说还是很有意义的。
Airbnb作为全世界最大的短租网站,提供了一个连接房主(host)挂出的短租房(listing)和主要是以旅游为目的的租客(guest/user)的中介平台。这样一个中介平台的交互方式比较简单,guest输入地点,价位,关键词等等,Airbnb会给出listing的搜索推荐列表。
基于这样的场景,利用几种交互方式产生的数据,Airbnb的search团队要构建一个real time的搜索排序模型。为了捕捉到用户short term以及long term的兴趣,Airbnb并没有把将用户历史数据中的点击房源id序列(clicked listing ids)或者预定房源id序列(booked listing ids)直接输入ranking model,而是先对租客和房源进行了embedding,进而利用embedding的结果构建出诸多feature,作为ranking model的输入。
具体到embedding上,文章通过两种方式生成了两种不同的embedding分别基于用户的short term和long term的兴趣。
Airbnb采用了click session数据对listing进行embedding,其中click session指的是一个用户在一次搜索过程中,点击的listing的序列,这个序列需要满足两个条件。
listing embedding是在经典skip-gram+负采样优化基础上衍生而来的,经典模式如下:
其中,D_p是正样本集合,D_n是负样本集合。
Airbnb把会话点击序列分成两类,一类是最终产生预定行为的的session称为booked session,另一类是没有预定的session称谓exploratory session。
考虑在booked session中,将最终预订的listing增加为global context,无论是否在滑窗中都要参与学习。这样一来,两个listing_id相似,不仅因为所处的点击序列相似,而且还会因为导致预订相同listing而相似。而预订相同listing比点击是一个更强、更有意义的信号,训练得到的embedding对提升“预订率”也更有意义。目标函数变成下面的公式:
论文中的示意图可以清晰表示出这种做法:
之前的做法,负采样是随机抽样,虽然说它大概率是与listing不相关的,是合格的负样本但是,这种信号太弱,可能让模型学习到粗粒度的特征差异,比如“是否同城”,对于同一区域内的listing差异性无法学习到,导致大量的false positive。
因此,需要改进服5采样以更加贴合业务,具体做法是增加一批“同城”做负样本,缓解正样本在"地域"上的bias。
其中,D_mn指和正样本“同城”的负样本集合。
这点Airbnb的做法简单粗暴而实用,如果有新的房源缺失Embedding向量,就找附近的3个同样类型,相似价格的房源向量进行平均得到。
前面基于 click session生成房源Embedding,虽然可以很好的找到相似房源,但是没有包含用户的长期兴趣。比如用户3个月之前预定的房源,在点击session里面就没有包含,从而丢失了用户的长期兴趣。
为了捕捉用户的长期偏好,airbnb在这里使用了booked session序列。比如用户j在过去1年依次book过5个listing,那么其booked session就是 :
具体来讲booked session的数据稀疏问题表现在下面三点上:
为了解决预定序列数据稀疏的问题,Airbnb对用户和房源进行分组,保证相同组的用户有相似的爱好,相同组的房源相似度高。
比如说某个listing的国家是US,类型(listing type)是Ent(bucket 1),每晚的价格(per night)是56-59美金(bucket3),那么就可以用US_lt1_pn3来表示该listing的listing_type。
这相当于对用户/房源根据各自的属性做hash,直接用hash后的属性代替id,可以解决稀疏问题,同时由于hash特性,引入type属性去替代id更利用user/listing的启动。
了用户分组和房源分组,就可以生成新的预定序列。使用 (user_type, listing_type) 替换原来的listing item,预定序列变成 ((u_type1,l_type1),(u_type2,l_type2),…,(u_typeM,l_typeM)),由于用户的属性会发生变化,比如历史预定次数,因此 u_type1,u_type2不一定相同。
有了预定序列,接下来便可以使用Skip-gram训练Embedding,模型如下所示。
上图是中心词是user_type时的模型结构,从图中可以看出,Airbnb在训练user_type Embedding和listing_type Embedding时没有任何区分,把user_type和listing_type当作完全相同的词进行训练,这样可以保证用户Embedding和房源Embedding在相同空间,可以直接计算二者的相似度。
和点击行为只反映用户信息不同,预定行为还包含了房主的信息,有些房主在接到预定请求后,可能选择拒绝,原因可能是用户信息不全,或者用户信誉低。因此,在预定序列中,Airbnb引入了额外的房主拒绝的负样本集合 D_reject,模型结构如下图所示。
做法简而言之就是将host“明确拒绝”也作为一个“negative word”加入word2vec训练集中:
线上开A/B test,是评价算法有效性的终极目标。但是,AB实验的缺点也很突出:
Airbnb的方法是测试通过用户最近的点击来推荐房源,有多大可能最终会产生预定,使用这一方法来评估模型训练出的Embedding效果。具体地,假设获得了用户最近点击的房源和候选房源集合,其中包含用户最终预定的房源,通过计算点击房源和候选房源的相似度,对候选房源进行排序,并观察预定房源在候选房源中的排序位置,如果排序位置越靠前,说明Embedding效果越好。
上图显示了评估结果,横坐标表示最近的17次点击,比如-2表示倒数第二次点击,纵坐标表示预定房源在排序中的位置,d32 reg表示序列中没有加预定房源,d32 book表示序列中引入预定房源,d32 book+neg表示引入了同一地区负样本集合。比如使用d32 book+neg计算倒数第2次点击(-2)和所有候选房源相似度,最终预定房源的平均位置在4.5,而d32 reg计算出的平均位置在4.9,因此book+neg的效果更好。
Airbnb并没有直接把embedding相似度排名当作搜索结果,而是基于embedding的到了不同的用户房源相关特征,然后输入给搜索排序模型,得到最终结果。以下是airbnb基于embedding生成的一些特征:
为了评估上面特征的效果,Airbnb使用GBDT模型生成特征权重,用于评估特征重要度,如下所示。
参考: