本文讲述了airbnb如何对用户和房源进行embedding,并应用在搜索排序和相似房源推荐中进行实时个性化,这两个业务在airbnb中贡献了99%的转化。文中的embedding 模型针对airbnb的业务进行了很多优化,能捕捉用户的短期和长期兴趣。
在airbnb,为房东和房客优化搜索结果,意味着给定带有地点和入住日期的查询,平台需要将位置、价格、风格、评论等信息对房客有吸引力的房源排在前面,同时,也要很好地匹配房东对旅游时长和lead days的偏好。而且,平台需要检测出那些可能会拒绝房客的房源,比如说负面评论、宠物、停留时长、团队大小或者其他因素,并把这些房源靠后排。
为了实现这些目标,airbnb借助于learning to rank技术。具体来说,作者将问题建模为pairwise regression,其中正例是预订,负例是拒绝。并通过 改良版Lambda Rank 来优化双方的排序。
因为房客在预订之前一般会搜索多次,在一个search session中,点击不止一个房源,联系不止一个房东,我们可以使用这些in-session 信号,比如点击、联系房东等行为进行实时的个性化,目的是给用户呈现那些和我们认为他们喜欢的房源相似的房源,同时,用一些负反馈的信号,比如跳过了高排名的房源,少给用户呈现那些我们认为他们不喜欢的房源。为了计算用户交互过的房源和待排序的候选房源之间的相似性,作者提出对房源进行embedding。作者用这些相似性作为个性化特征输入给搜索排序模型和相似房源推荐。
除了用即时的用户行为,比如点击作为用户短期兴趣的表示来进行实时个性化,作者也引入了另外一种通过预订来训练的embedding,这样可以捕捉用户的长期兴趣。由于旅游的特点,一般用户一年也就1-2次,预订是一个很稀疏的信号,很多长尾用户只有一个预订。为了解决这个问题,作者提出在用户类型的层面上来训练embedding,而不是一个具体的用户id。其中,用户类型则是利用已知的用户属性,使用多对一 的规则进行划分。同时,作者在用户类型embedding的相同空间上学习房源类型embedding,这样就可以计算用户的用户类型embedding和房源的房源类型embedding的相似性。
本文的贡献:
1.Real-time Personalization
之前的embedding都是线下计算好线上拿来用,
2.针对旅游业务中存在的聚集特点对训练过程 进行改造
旅游平台的搜索都有聚集属性,即用户搜索时通常都聚集在一个市场,比如巴黎,很少跨越多个市场,作者在负采样时考虑了这一点,可以更好得学习到一个市场内的listing的相似性;
3.把转化作为全局上下文特征
作者意识到以转化结束的session的重要性,在学习房源embedding时,作者将预订的房源作为全局上下文,随着窗口在session中移动时,被预订房源一直会被预测
4.用户类型embedding
之前学习用户embedding来捕捉长期兴趣的方法都是每个用户一个embedding。当样本比较稀疏的时候,一般没有足够的数据来为每个用户训练一个比较好的embedding。另外,在线上为每个用户存储一个embedding来完成计算也需要很大的内存。基于此,作者提出在用户类型的层面训练embedding,同一类型的用户共享embedding。
5.拒绝作为显式的负样本
为了降低最终被拒绝的推荐,作者通过将房东拒绝的房源作为负样本,这样就可以把房东的偏好信息也编码到用户和房源类型的embedding中。
为了训练短期用户兴趣,作者在8亿点击session上训练房源embedding。广泛的线上线下实验表明将embedding加入排序模型可以带来显著的订单收益。除了搜索排序算法,房源embedding也用来进行相似房源推荐,比已有的算法ctr 提升20%。
为了训练长期用户兴趣,作者在5000万用户的预订房源序列上训练用户类型和房源类型embedding。两种类型的embedding是在同一个向量空间完成的,这样我们就可以计算用户类型和需要排序的房源的房源类型embedding的相似度。将这个相似度加入到排序模型中也带来了收益。
略
介绍了两种方法:
1.房源embedding,用于短期实时个性化
2.用户类型和房源类型embedding,用于长期个性化
假设给定一个从N个用户收集的包含S个点击session的集合S,每个session s = ( 1 1 , . . . , l M ) ∈ S s=(1_1, ...,l_M)\in S s=(11,...,lM)∈S 定义为该用户点击过的连续的包含M个房源ID的列表。 只要用户的两个连续的点击之间间隔超过30min,那么就看做 一个新的session 。给定该数据集,目标是学习每个房源I的d-维 实数向量表示 v ∈ R d v\in R^d v∈Rd。这样 相似房源在embedding空间中的位置会比较相近。
更正式一点,模型目标是使用skip-gram 模型学习房源表示,通过最大化session数据集上的目标函数L来进行优化
P ( l i + j ∣ l i ) P(l_{i+j}|l_i) P(li+j∣li) 表示 在点击房源 l i l_i li的邻近 观测到房源 l i + j l_{i+j} li+j的概率,该概率定义如下:
v l v_l vl和 v l ′ v_l' vl′ 表示房源l的输入和输出的embedding表示。m是窗口大小。
V是数据集中房源id的集合,即字典。
从上面两个表达式可以看出,该方法对房源的点击序列的上下文进行建模,上下文相似的房源的表示更比较相似。
计算损失函数的梯度 时间复杂度和词典大小有关。当词典很大时,到达百万量级时,就不可行了。
为了减小计算量,作者使用negative sampling ,该方法操作如下:
1.生成包含正样本对(l,c)的数据集Dp
其中,l是点击房源,c是l的上下文,即同一个用户在点击房源l 前后大小为m的窗口内点击的房源。
2.生成包含负样本对(l,c)的数据集Dn
负样本是从整个数据集词典V中随机采样的n个房源。
优化目标变成:
该公式其实是label为+1/-1的LR, 正样本对的label为+1,负样本对的label为-1。
优化通过sgd完成。
我们可以把session分为两类,一是预订的session,即以预订结束的session,二是探索性session,即不以预订结束的session,用户只是随意浏览。两种session都可以捕捉上下文相似性,但是booked session可以用来调整优化过程,将每步的优化不仅预测邻近的点击房源,还预估最终预订的房源。这个调整可以通过将预订房源作为全局上下文来实现。这样不论该预订房源是否在窗口内,他都会被预测到。这样,对于预订的session,更新变成:
v l b v_{lb} vlb 是预订房源 l b l_b lb的embedding向量。探索性session的更新和之前一样
下图即从预订session中学习embedding的过程,在整个过程中维护一个长度为2m+1的滑动窗口,从第一个点击房源到最后一个预订房源。在窗口滑动过程中,预订房源一直在窗口内。
Adapting Training for Congregated Search
在线旅游预订网站一般只在用户想要去的地方进行搜索,所以很有可能Dp 属于同一地域 ,另外,由于负样本随机采样,Dn 很可能包含和Dp不属于同一地域的样本。作者发现这种不一致会导致学习到的同一地域的房源相似性不是最优的,为了 解决这个问题,作者提出 将一组随机负样本 D m n D_{m_n} Dmn,从中心房源l的区域中采样得到。损失函数如下:
为新房源生成embedding。
房源生成时房东会提高一些基本信息,比如位置,价格,房源类型,作者使用3个地址位置最近的且有embedding、房源类型相同、属于同一价格区间的房源的embedding的平均向量作为新房源的embedding。使用这个方法可以覆盖98%的新房源。
Examining Listing Embeddings
为了检测embedding都学习到了房源的哪些特征,作者对使用公式5,在8亿click session数据上训练出来的32维embedding向量进行测试。
1.对embedding向量进行k均值聚类,看地理位置相似性是否被捕捉到。
下图是California 的100个聚类中心,可以看到,距离近的房源聚类到了一起。同时,聚类结果对重新评估旅游区域的定义很有帮助。
2.检验LA 不同类型的房源和不同价格的房源之间的平均余弦相似度。
从表1、2可以看到相同类型和价格区间的房源的相似度远高于不相同的房源。
所以可以认为价格和类型这两个特点也被学习到了。
虽然有些特征不需要学习,比如价格这种特征可以从源数据中提取,但是有些特征,比如建筑风格、感觉等特征很难提取,为了验证这些特征是否被学习到,我们可以找到具有独特建筑风格 的房源的 embedding的k近邻,下图即一个例子。
前面学习到的embedding适合给用户进行session内的短期的推荐, 主要是呈现和session内点击的房源相似的房源。但是根据用户的长期历史行为进行推荐也是有用的。比如用户在LA 预订,之前在New York和London预订过,那么推荐和之前预订过的相似度的房源也是有用的。
虽然跨市场的房源相似性也能通过使用点击数据训练的房源的embedding学到,但是更好的方式是通过用户历史上预订的房源构成的session中学习。假设给定一个预订房源的session集合 S b S_b Sb,该集合从N个用户获得,每个session s b = ( l b 1 , . . . , l b M ) s_b =(l_{b1},...,l_{bM}) sb=(lb1,...,lbM)包含用户按时间顺序预订的房源列表 。从这种数据中试图为每个房源学习一个embedding会有很多挑战:
1.预订数据集会很小,数据稀疏;
2.很多用户只预订过一个房源,我们没办法从这种session中学习;
3.如果要从上下文信息中为任何房源学习到一个有意义的embedding,那么这个房源在数据中至少出现5-10次。但是有很多房源的出现次数少于5-10次。
4.一个用户的两个连续的预订之间可能间隔了很长时间。在这段时间内用户的偏好,比如价格等会发生变化。
为了解决上述问题,作者提出在房源类型的维度来学习embedding。给定一个房源的基础性信息,比如位置,价格,房源类型,容量,被子数量等等,作者使用一个基于规则的映射来定义房源的类型,映射规则如下表:
比如 一套US 的整套家庭房源可以容纳两人,1床被子,1个卧室&洗手间,平均价格一晚$60.8,人均价格每晚29.3,5个评论,全5星,100%新客接受率,那它的房源类型为
listing_type = US_lt1_pn3_pд3_r3_5s4_c2_b1_bd2_bt2_nu3。
分桶是基于数据驱动来确定的,为了最大化每个房源类型的覆盖率。 多个房源会映射到同一个房源类型。
为了解决用户时刻变化的偏好,作者提出在房源类型相同的特征空间内学习用户类型embedding。用户类型使用类似于房源类型的方法来进行划分,利用用户的基础信息和历史预订信息,如下表。
for a user from San Francisco with MacBook laptop, English language settings, full profile with user photo, 83.4% average Guest 5 star rating from hosts, who has made 3 bookings in the past, where the average statistics of booked listings were $52.52 Price Per Night, $31.85 Price Per Night Per Guest, 2.33 Capacity, 8.24 Reviews and 76.1% Listing 5 star rating, the resulting user _type is SF_lg1_dt1_fp1_pp1_nb1_ppn2_ppд3_c2_nr3_l5s3_g5s3.
生成预订session时 根据最新的booking 来计算用户类型。没有历史预订的用户只使用前5行。
这样的话,根据前5行生成的用户类型的embedding 可以用来为未登录用户和没有历史订单的用户进行冷启动。
为了在同一空间内学习用户类型和房源类型,作者将用户类型加入到预订session中。具体来说,作者构造了一个集合 S b S_b Sb,包含了来自N个用户的 N b N_b Nb个预订session, s b = ( u t y p e 1 l t y p e 1 , . . . , u t y p e M l t y p e M ) ∈ S b s_b = (u_{type1}l_{type1},...,u_{typeM}l_{typeM}) ∈ S_b sb=(utype1ltype1,...,utypeMltypeM)∈Sb是一个预订session,其中的预订事件 (user_type, listing_type)按时间排序。注意,每个session包含同一个user_id的预订,但是同一个user_id的用户类型可能随时间发生变化。类似地,同一个房源的房源类型也会随着预订数越来越多而发生变化。
优化目标中,要更新的中心item不再是房源,而是用户类型或者房源类型,取决于滑动窗口中命中了哪个。假设要更新的中心item是 u s e r t y p e ( u t ) user_type(u_t) usertype(ut),那么优化目标是:
D b o o k D_book Dbook 包含用户历史中的用户类型和房源类型,用户历史指当前中心item的时间戳 往前一段时间和往后一段时间的预订信息。 D n e g D_{neg} Dneg 包含随机抽样的用户类型和房源类型作为负样本。
类似地,如果中心item是一个房源类型 l t l_t lt,目标函数是:
模型结构图如下a:
由于预订session 很可能包含不同区域的房源,这里就没有必要从相同区域进行负采样。
不像点击仅反应客户的偏好,预订也反应了房东的偏好。因为房东可以选择接受或者拒绝用户的预订。房东拒绝的原因可以是租客星级较低,不完整或者空白的用户信息,没有头像等等。
房东拒绝信息可以加入到训练中,这样可以反应一些房东偏好信息。加入这个信息的最要原因是有些房源类型对没有预定、信息不完整、星级低于平均值的用户类型没有那么敏感。我们希望这样的房源类型和用户类型在embedding空间中比较接近。这样推荐的时候就可能降低拒绝率,同时提高预定成功率。
作者将拒绝作为显式的负样本。具体方式如下:
除了 D b o o k D_{book} Dbook和 D n e g D_{neg} Dneg外,作者生成了一个 D r e j D_{rej} Drej集合,包含了拒绝事件中的用户类型和房源类型对 ( u t , l t ) (u_t,l_t) (ut,lt),
模型图如图5b所示。
当中心item是用户类型时:
中心item是房源类型时:
学习到了用户类型和房源类型的embedding后,我们可以根据用户当前的用户类型的embedding和候选房源的房源类型embedding的余弦相似度来进行推荐。
以下标为例,该表为一个用户类型和几个房源类型的相似度。
可以看到,最符合用户偏好的那些房源的余弦相似度更高。不符合的相似度更低。
作者从登录用户中收集了8亿点击session,按用户id分组,并按时间排序。然后把一个有序的点击session按照30分钟不活跃则视为不同session的规则 划分为多个session。然后,将停留时间少于30s的点击去掉,同时,只保留包含至少两个点击的session。最后,session都经过匿名处理,丢掉了user_id字段。同时,根据离线评估结果,作者在训练数据中将预定session 过采样五倍,这样可以取得效果最好的embedding 。
训练数据使用过去数月的数据进行天级别更新,最新一天的搜索session加入到数据集中,最旧一天的搜索session从数据集中删除。为每个房源学习embedding,训练之前随机初始化,每次使用相同的随机数种子,作者发现与增量训练相比,每天重头开始训练embedding可以取得更好地线下效果,由于作者使用embedding向量之间的余弦相似度作为特征,而不是embedding向量本身,所以每天训练出来的embedding向量不同并不会有什么影响。甚至虽然向量随时间变化,但是余弦相似度的含义和取值范围并没有变化。
向量的维度为32, 这是为了权衡线下效果和线上的内存。窗口大小为5,训练集上的迭代次数为10,为了实现聚集搜索对算法的修改,作者更新了word2vec的代码。使用mapreduce 进行训练, 300个mapper 读数据,一个reducer 使用多线程的方式进行训练, 使用airflow 来实现端到端的天级别数据生成和训练流程。
为了快速决定 不同的目标函数,训练集构造和超参数,需要可以快速比较embedding效果的方法。
一种评估方法是根据用户最近点击的房源,看对用户最终预定的房源的推荐有多好。具体来说,就是给定历史点击房源和用于排序的候选房源,候选房源中包含用户最终预定的房源,通过计算点击房源和候选房源的余弦相似度,我们可以对候选房源进行排序,最后看被预定的房源的排序位置。
预订房源的排序 是考虑了所有和预订相关的历史点击,从预订前的1个点击到预订前的17个点击。值越低说明排序越高。
可以看到
1.点击越多效果越好,
2.根据embedding相似度进行re-ranking是有效的,尤其是在搜索的早期阶段。
3.d32 book + neg 效果最好
这种图也用来参数选择和评估数据构建。
每个airbnb的房源页面都包含一组相似房源的轮播,在测试阶段,相似房源轮播使用的算法是Search Ranking model,同时会过滤是否可用,价格区间和房源类型。
作者比较了现有算法和基于embedding 的推荐,即找到k个embedding相似度最高的房源。计算是在线计算,且并行计算,每个机器存储部分embedding。
A/B实验显示 基于embedding的相似房源轮播 ctr 提高21%,输入日期的房源页面提高了23%, 没有输入日期的页面提高了20%,用户最终在相似房源推荐中形成预定的比例提高了4.9%,最终,基于embedding的相似房源推荐部署到线上。
backgroud
Search Ranking Model:
给定搜索数据集 D s = ( x i , y i ) , i = 1... K Ds =(xi,yi),i = 1...K Ds=(xi,yi),i=1...K, K是这次搜素返回的房源数量, x i x_i xi是房源i的特征向量,yi ∈ {0, 0.01, 0.25, 1, −0.4} 是第i个房源的label。为了确定搜索结果中的房源的label,作者会等待一星期看最终的预定结果。如果预定了则label为1, 如果用户联系了房东但是没有预定则label为0.25,如果房东拒绝了用户,label为-0.4,如果点击了则label为0.01, 如果只是看了但是没有点击则label为0。 等待1星期后,Ds 也减少了,只保留用户最后一次点击之前的结果。
为了构造最终的数据集 D = U s = 1 N Ð s D =U _{s=1}^N Ð_s D=Us=1NÐs,作者只保留至少包含一个预定的 D s D_s Ds.每次训练一个新的模型的时候使用最近30天的数据。
第i个房源的特征向量x_i 包含房源特征、用户特征、查询特征和交叉特征。
房源特征包括每晚的价格,房源类型,房间数量,被拒绝率等。查询特征包括房客数量,停留天数,lead days 是啥,用户特征包括平均预定价格,访客星级。交叉特征是从房源、用户、查询三者之间交叉得到的,比如query listing distance,即查询地点和房源地点之间的距离,capacity fit,即查询的房客数量和房源容量之间的差值,价格差,即房源价格和用户历史预定房源的平均价格的差值。拒绝概率,即房东拒绝查询中的参数的概率,点击占比,即实时跟踪用户点击该房源的比例。模型使用了大约100个特征。
作者将问题形式化为一个pairwise 回归问题,用训练数据训练一个GBDT模型,基于https://github.com/yarny/gbdt 开发,该包支持lambda rank。在线下评估时 使用ndcg 指标,在搜索session的验证集上进行验证。80%的数据做训练,20%的做验证。
训练完成后,模型在线上搜索时对房源实时打分,房源的特征向量是并行计算的,基于一种分片的架构。得到所有分数后,房源按分数降序排列。
向排序模型中添加embedding特征的第一步就是将450万房源的embedding特征加载到线上以便实时计算。
接下来,作者引入了几种用户短期的历史房源id集合,包括了用户过去两周的行为数据,这些数据时实时更新的,只要有新的行为产生,这些数据就会被更新。整个逻辑使用kafka实现。具体来说,对于每个user_id,维护和维护(定期更新)如下的房源id集合:
1.Hc clicked listing_ids:即过去两周内用户点击的房间。
2.Hlc long-clicked listing_ids,用户点击并且停留超过60s的房间。
3.Hs skipped listing_ids,用户点击了位置更靠后的房源id,并且跳过了排名靠前的房源。
4.Hw wishlisted listing_ids,过去两周内用户加入心愿单的房源;
5.Hi inquired listing_ids,即咨询过但是最终并未预定的房源;
6.Hb booked listing_ids 过去两周内用户预定的房源id。
作者对上述短期历史集合按市场进行划分。比如,用户点击了纽约和洛杉矶的房源,那么这个用户的Hc 集合则会被划分为Hc(NY)和Hc(LA)。
最后,根据上述这些集合和房源的embedding 来对每个候选房源计算得分。特征总结见图6.
下面描述EmbClickSim 是怎么用Hc 计算得到的。其他特征也是使用对应的集合以同样的方式计算得到的。
为了对候选房源Hl 计算EmbClickSim,需要计算候选房源Hi和Hc 集合中的房源的embedding的余弦距离。
首先,计算Hc中market-level centroid embeddings。举个例子,加入Hc 包含5个NY的房源,3个LA的房源,这样我们就需要计算两个市场级别的中心embedding。一个是Ny的,一个是LA的,方法就是对各自市场内的embedding进行平均。
最后,计算房源和Hc中两个market level的centroid embedding之间的相似度, 取 较大的那个做为EmbClickSim距离。
可以形式化为:
M是用户点击过的market的集合。
除了和所有用户点击的相似度,作者还计算了和最后一个长点击的相似度EmbLastLongClickSim。
对于一个候选房源 l i l_i li, 计算它的embedding v l i v_{li} vli 和 属于 H l c H_{lc} Hlc的最新的长点击 l l a s t l_{last} llast 的embedding 的相似度:
E m b L a s t L o n g C l i c k S i m ( l i , H l c ) = c o s ( v l i , v l l a s t ) . EmbLastLongClickSim(l_i,Hlc ) = cos(v_{li}, vl_{last} ). EmbLastLongClickSim(li,Hlc)=cos(vli,vllast).
这一部分使用同样的思路引入基于用户类型和房源类型的embedding 特征。
使用5000w用户预定session 为50w 用户类型和50w 房源类型训练embedding。
embedding维度32, 窗口大小为5.两种类型的embedding也是load 到内存中。
为了给候选房源 l i l_i li计算UserTypeListingTypeSim 特征,只需要计算所属的房源类型和用户类型之间的余弦相似度。
表6的特征会记录30天,这样可以加入到搜索排序训练集D,
特征的覆盖率表示数据集D中含有该特征的比例,具体如下表。
可以看出,根据用户点击和跳过的房源计算的特征的覆盖率最高。
最后,加入了上述的embedding特征训练一个新的GBDT 搜索排序模型,embedding特征的重要性见表7. 比较高的排序特征是和用户点击房源的相似度特征EmbClickSim(在所有特征中排第5),和用户跳过的房源的相似度特征EmbSkipSim( 在所有特征中排第8)。有5个特征排前20.
和预期一致, 长期特征UserTypeListingTypeSim (使用过去所有的用户预定),比短期特征EmbBookSim(只考虑2周内的预定)排名要好。
这也说明,基于历史的预定房源进行推荐,使用历史预定session训练的embedding 比使用历史点击session进行训练要好 。
这句话啥意思??
为了评估模型是否是按照我们认为的方式使用特征,作者 对3个embedding特征EmbClickSim, EmbSkipSim and UserTypeListTypeSim 画了部分依赖图。从这些图可以看出如果我们只修改我们要检验的那个特征的值 ,其余特征固定,房源的排序分数将发生什么变化。
在左图,可以看出EmbClickSim 值比较大的时候表明房源和用户最近点击的房源比较相似,此时模型得分较高。中间的图表明EmbSkipSim 值比较大的时候,房源和用户跳过的房源比较相似,此时模型得分较低。右图表明 UserTypeListingTypeSim 值较大时,说明用户类型和房源类型比较相似,此时模型得分较高。
这个图是固定其他值,只变换当前特征,可以看出当前特征和最终的预估值之间的额关系。
作者做了线下和线上的实验
首先,使用相同的数据,分别比较加上和不加上embedding特征的两个模型。
下表总结了每个指标(impression, click, rejection and booking)单独的DCU(Discounted Cumulative Utility)和总体的NDCU((Normalized Discounted Cumulative Utility),可以看出,加上embedding 之后, NDCU 增加了2.27% ,预定DCU 增加了2.58%,表明在验证集中预定房源排名更高。没有增加拒绝率(DCU -0.4 was flat),表明和没用embedding特征的模型相比,被拒绝的房源并没有排得更高。
表格8的结论和表7中embedding特征在GBDT重要性中排名靠前和图7 看到的特征行为和我们的直觉一致,足以让我们推进线上实验。在线上实验中我们看到预定显著增长。数月后我们进行了一个 反向测试(back test),即将embedding特征去掉,结果预定开始下降。这也表明实时的embedding特征是有效的。
提出了实时个性化的新方法。该方法从用户的点击和预定对话中的上下文共现情况 学习房源和用户的低维表示。为了更好地利用可用的搜索上下文,作者引入全局上下文和显式的负面信息加入到训练中,并在相似房源推荐和搜索排序中进行评估,在实时搜索流量中测试成功后 两种embedding方法都被部署到线上。