背景
度假业务在整个在线旅游市场中占据着非常重要的位置,如何做好做大这块蛋糕是行业内的焦点。与美食或酒店的用户兴趣点明确(比如找某个确定的餐厅或者找某个目的地附近的酒店)不同,旅游场景中的用户兴趣点(比如周末去哪儿好玩)很难确定,而且会随着季节、天气、用户属性等变化而变化。这些特点导致传统的信息检索并不能很好的满足用户需求,我们迫切需要建设旅游推荐系统(本文中度假=旅游)。
旅游推荐系统主要面临以下几点挑战:
针对上述问题我们定制了一套完整的推荐系统框架,包括基于机器学习的召回排序策略,以及从海量大数据的离线计算到高并发在线服务的推荐引擎。
策略迭代
推荐系统的策略主要分为召回和排序两类,召回负责生成推荐的候选集,排序负责将多个召回策略的结果进行个性化排序。下文会分别对召回和排序策略的迭代演进过程进行阐述。
我们从2015年底启动了旅游推荐系统的建设,此时度假业务有独立的周边游频道首页,其中猜你喜欢展位的推荐策略由平台统一负责,不能很好的解决旅游场景中的诸多问题。下文会按时间顺序来阐述如何利用多种召回策略解决这些问题。
旅游推荐第一版的策略主要基于城市热销,不同于基于Deal所在城市统计分城市热销,这一版策略基于用户常驻城市来统计,原因是不同城市的旅游资源分布各异,存在资源缺乏(客源地)、旅游资源丰富(供给地)以及本地人到周边城市游玩的需求。即对于每个城市,都有其对应的“城市圈”Deal库,比如:廊坊没有滑雪场,但常驻城市为廊坊的用户经常购买北京的滑雪场,因此当廊坊用户在当地浏览周边游频道时会推荐出北京的滑雪场。
在具体实现时考虑旅游产品随季节性变化的特性,销量随时间逐渐衰减,假定4周为1个变化周期,Deal得分公式为:deal_score = ∑((count(payorder) * α ^ i),其中count(payorder)指该Deal相应日期的支付订单数,i指该日期距今的天数,取从1到28的整数,α为衰减系数(<1),Deal得分为一定周期内每日销量得分的总和。
根据上述公式对每个城市都能统计Top N热销Deal,再根据Deal关联POI过滤离当前浏览城市200km以外的Deal,比如:在浏览北京时推荐上海迪士尼门票不太好,不符合周边游的定位。
这一阶段还尝试了热门单、低价单、新单策略。新单和低价单比较好理解,就是给这些Deal一定的曝光机会。热门单跟热销单类似,统计的是Deal浏览数据,热门单召回的Deal跟热销策略差异不大。但由于推荐的评估指标是访购率(支付UV/推荐UV),这些策略的效果不及热销,都没有上线。
另外还初步尝试了分时间上下文的推荐,比如:区分工作日/非工作日, 周一至周四过滤周末票、周五至周日过滤平日票,不过随着推荐POI化而下线了。
这一阶段的策略主要有两个创新点:
每个景点下通常会有多个票种,每个票种下通常会有多个Deal,比如:故宫门票的票种有成人票、学生票和老人票,成人票下由于Deal供应商不同会有多个Deal,这些Deal的价格、购买限制可能会有所区别。如果按Deal样式展示,可能故宫成人票、学生票都会被推荐出来,一方面大量重复相似Deal占据了推荐展位,另一方面Deal摘要信息较长,不利于用户决策。因此2016年初启动了推荐POI化,第一版的POI化方案基于Deal关联的POI做推荐,即故宫成人票是热销单,实际推荐展示的故宫POI。 这个方案有两个问题:
因此在2016年Q2上线了基于F值的POI热销策略,F值是美团点评内部的一种埋点追踪方法,可以简单理解为:用户在浏览POI详情页时会在埋点日志的F值记录POI ID,然后这个标记会一直带到订单中,这样就能相对准确计算每个订单的POI归属。
1.0版热销策略的主要问题是只考虑常驻城市的用户在当地的购买偏好,简而言之,只解决了上海人在浏览上海时的推荐问题,北京人在浏览上海时推荐的结果跟上海人推荐的一样。放大看是本异地场景的问题,本异地场景的定义见下表。2.0版热销策略对本异地订单分别统计,当某个用户访问美团时先判断该用户是本地还是异地用户,再分别召回对应的POI,对于取不到常驻城市的用户默认看做是本地请求。从推荐结果看北京本地人爱去欢乐谷,外地人到北京更爱去长城、故宫。
这一版本中继续尝试了分时间上下文的细分推荐,统计一段时间内每天各小时的订单分布,其中有3个鞍点,对应将一天分为早、中、晚3个时间段,分时间段统计POI热销。从召回层面看POI排序对比之前变化比较大,但由于下文中Rerank的作用,对推荐整体的影响并不大。
热销策略虽然能区分本异地用户的差异,但对具体单个用户缺少个性化推荐,因此引入用户历史行为强相关的推荐策略。取用户最近一个月内浏览、收藏未购买的POI,按城市分组,按POI ID去重,越实时权重越高。
上文的策略要么是有大量POI数据,要么是有用户数据,如果用户或POI没有历史行为数据或比较稀疏,上述策略就不能奏效,即所谓的“冷启动”问题。在移动场景下通过设备能实时获取到用户的地理位置,然后根据地理位置做推荐。具体推荐策略分为两类:
地理位置推荐策略需要过滤用户定位城市跟客户端选择城市不一致的情况,比如:定位北京的用户在浏览上海时推荐北京周边POI不太合适。
协同过滤是推荐系统中最经典的算法,相对于历史行为强相关策略,对用户兴趣、POI属性相当于是做了抽象和泛化。协同过滤算法主要分为ItemCF和UserCF两类,我们首先实现了ItemCF,主要原因是:
根据UUID维度的浏览数据来计算POI之间的相似度,浏览行为比下单、支付行为更为稠密。时间窗口取一个月的数据,理论上只要计算计算能力不是瓶颈,时间窗口应该尽可能的长。相似度公式定义如下:
用户对POI的行为表每天离线生产好后更新,相当于只有当天之前的数据,缺少对用户当天实时行为的反馈,因此增加基于用户实时POI行为的协同过滤推荐,复用上文中的POI相似度计算结果。
搜索行为是一种强意图行为,旅游较多订单来源于搜索入口,相当比例的搜索用户没有点击任何POI,基于用户搜索行为的推荐可以作为POI浏览推荐的一种补充。首先构造Query和POI的相似度矩阵,利用用户搜索Query后10分钟内浏览的POI构造对,相似度算法跟POI相似度公式一致。
具体实现时以Query+City为Key,原因是旅游场景中存在部分全国连锁POI,如:欢乐谷、方特,如果只以Query为Key,则跟“欢乐谷”Query最相关的POI可能是“北京欢乐谷”,那用户在深圳搜索“欢乐谷”后会推荐出北京欢乐谷,不符合用户需求。
上述相似度计算公式有两个改进点:一是未考虑用户行为的先后顺序,比如用户先后浏览了POI ,之前会两两计算相似度,实际只用计算A和以及B和C的相似度即可,因为用户是先浏览了A再浏览了B,所以浏览A时可以推荐B,但浏览B时推荐A不一定合适。二是未考虑POI之间的时间序列跨度,理论上A和B的相似度应该高于A和C的相似度。
经过一年的迭代,目前线上在线的召回策略如下图,此外还尝试了基于ALS的矩阵分解,但推荐的结果比较冷门,可解释性较差;另外启动了基于用户标签的推荐,对用户和POI都打上相应的属性标签,可以直接单维度标签进行推荐,比如:给亲子类用户推荐亲子类POI,也可以把标签当做维度,多维度计算用户和POI的相关性。
每类召回策略的结果都需要做过滤,过滤策略主要有几类:
其中前2类过滤策略对所有召回策略是通用的,都需要做,黑名单过滤考虑到数据更新的实时性,在线上处理,其他过滤策略可以在离线数据层统一处理。后3类只有特定召回策略需要,因为依赖用户请求,只能在线上处理,具体规则如下:
每类召回策略都会召回一定的结果,这些结果去重后需要统一做排序。在早期只有热销策略一个时不需要Rerank,直接根据热销得分来排序,加入历史行为强相关和Location-Based策略后也是按固定展位交叉展示的,比如:第1、3、5、7位给历史行为强相关策略,第2、4、6、8位给Location-Based策略。
在2016年Q1初尝试了第一版的Rerank策略,当时推荐样式还是Deal,因此排序对象也是Deal,主要特征是30/180天的销量/评分数据,因为考虑的特征比较少,上线后效果并不明显。
在Q2初由于基本完成了POI化展示,排序对象变成POI,主要特征包括销量、评分、价格、退款数据,上线后效果仍不明显。
因为推荐列表页跟筛选列表页类似,在Q2中期尝试直接接入筛选Rerank,但效果不太理想。随后基于推荐的数据样本重新进行了训练,并新增了一些特征,特征上大致分为以下几类:
从上表看在销量和评价基础上主要新增了上下文特征、距离特征和访购相关特征,注意到HOUR_OF_DAY、DAY_OF_WEEK、CITY_ID并没有采用one-hot编码,在线上实验one-hot编码效果并不优于直接使用原始值。可能的解释是HOUR_OF_DAY离散值可以用于树模型来分类,比如:0~11点可以表示上午、12点~18点可以表示下午、19点~23点表示夜晚;同理DAY_OF_WEEK周一到周四可以认为是平日,周五到周日认为是周末;CITY_ID可能的解释是ID越小,越是开站较早的城市,也是更热门的城市。
模型上取最后一个点击前的样本为候选样本集,以支付为正样本,其他为负样本,正负样本采样比为1:10。如果不做样本采样,假设每100人访问只有1个支付,每次访问列表页假设用户平均能看到10个POI,即正负样本比例大约为1:1000,样本分布极不均衡,容易导致过拟合。模型训练上采用XGBoost算法,上线后点击率和访购率均明显正向,证明了Rerank的有效性。
在上述基础上后续又逐步丰富了上下文特征,比如:召回可能触发周边城市圈的POI,因此增加POI是否本城市的特征,另外热销召回策略拆分了本异地,Rerank也对应增加了用户请求是否本异地特征;增加了User-POI组合特征:User 7天内是否浏览/收藏过POI、实时特征、基于协同过滤的User-POI相关性等,跟历史行为强相关、协同过滤的召回策略能相呼应;增加了POI静态属性特征,如:星级,另外把POI的销量也按本异地进行了拆分。这些特征上线后效果基本都正向,符合预期。
模型上尝试了短周期模型+长周期模型的融合,短周期为近期一个月数据,长周期为近期三个月数据。从线上结果看直接用短周期模型效果最好,这可能跟旅游应季变化快有关。除了上述特征外,后续还可以增加User个性化特征、天气上下文特征、POI特征CTR/CVR可以拆分本异地等。
推荐的离线训练流程跟搜索、筛选排序保持一致,流程图如下:
整个训练集的构造过程由Scala编写在Spark集群上运行,而由于XGBoost的Spark版本效果不太稳定,在最后的模型训练与评估中使用的XGBoost的单机版本,模型的训练参数(迭代次数、树的深度等)一般选取经验值,训练集选一个月的数据,测试集一般选训练集日期后的若干天,离线评估指标主要参考AUC,离线效果有提升就会上线ABTest实验,逐步迭代。
工程架构设计
推荐系统的整体工程架构如下图,从下至上包括离线计算层、核心数据层、推荐服务层和应用场景层,另外是后台配置管理系统和数据调度服务。
离线计算层除了Rerank需要的特征和训练日志外,主要包括基础数据和应用数据两类。基础数据中最重要的是Deal和POI的数据,为了保证数据的准确性和实时性,Deal和POI的数据直接从旅游产品中心去取,通过定时全量拉取并辅以消息队列实时更新。应用数据按生产方式又可以分为三类:
抽象出核心数据层的一个重要原因是需要离线计算工程和线上服务工程复用DataSet,从供线上使用的存储方式看可以分为三类:
推荐上下游的架构图如下图,客户端向API发起调用,API调用推荐服务拿到推荐的ID再添加供App展示用的相关字段传会给App。推荐和搜索没有整合成一个服务的重要原因是推荐的召回策略复杂多样,每次请求可能命中多个召回策略,而搜索单次请求的意图一般比较单一,通常只有一个召回策略。另外推荐服务重点在召回和过滤,Rerank调用独立的rank服务,原因是推荐Rerank和搜索筛选Rerank在特征上有很多是可以复用的,比如:用户特征、POI特征等。
推荐服务向下从数十个数据源中获取数据,经过业务逻辑处理后向上支持数十个应用场景,整个调用流程如下:
Handler是整个流程的核心,其调用流程如下:
核心的对象模型如下图:
监控分为离线监控和实时监控两部分,离线监控使用Falcon来监控以下几类指标:
实时监控接入公司统一的实时数据统计平台,可以分时、分多粒度统计各Booth的请求次数和响应时间。
降级主要通过Hystrix来实现,比如:调用Rerank服务在一定时间内响应时间超过设定的阈值,则直接熔断不请求Rerank服务。
推荐服务开发了Debug工具,输入支持城市、展位、UUID、经纬度等参数,输出展示了POI/Deal的头图、标题、和用户的距离、召回排序策略与得分等。方便PM和RD测试、定位追查Case。
应用场景
推荐系统支持了美团/点评共20个应用场景,主要场景是周边游频道首页猜你喜欢,其召回策略在上文中已有阐述,这里重点阐述其他几类推荐场景:
跟团游Deal一般会绑定多个景点,不适合按POI样式展现,因此采用Deal形式展现,召回策略跟热销POI策略类似,区分本异地,从结果看北京本地人会推荐“古北水镇一日游”,外地人浏览北京时会推荐“故宫、长城一日游”。
用户在筛选酒店时会先选择入住城市再筛选该城市的酒店POI,而周边游存在客源地旅游资源不丰富的问题,筛选时需要突破选择城市限制,能够推荐出周边城市的热门POI,筛选异地召回上线后增加了一定比例的订单,是对本地召回的有效补充。
即为POI打标签,用户可以用这些标签进行筛选,比如:附近热门、近郊周边、周末去哪、亲子同乐、夜场休闲。每个标签都可以定义一套挖掘方法,比如:“亲子同乐”有以下几类方法:
上述挖掘方法偏规则,后续希望能通过半/无监督方法,挖掘POI描述和评论,自动为POI打标。
搜索少结果推荐是指当搜索结果POI类聚结果数=1时,为丰富页面内容给用户提供推荐信息。这里重点利用搜索的POI结果根据POI CF触发推荐,以及利用搜索POI的品类进行同城市同品类推荐。
搜索无结果推荐可以直接统计搜索Query后一定时间内用户浏览的POI做推荐,但这个策略的覆盖面有限,进一步可以计算一段时间内的Query CF,然后做协同推荐;另一方面可以通过意图识别判断Query中是否有品类词,触发同品类推荐。
目前只实现了酒店和旅游之间的交叉推荐,当用户在酒店频道搜索时先判断Query是否旅游意图,其中重点分析两类意图:一是景点POI意图,推荐该景点几公里范围内的POI;二是品类意图,比如:温泉、滑雪,会推荐用户定位附近该品类的热销POI。
在酒店POI详情页会获取酒店POI的地理位置,推荐酒店附近的景点。对于异地用户浏览酒店时都会触发景点推荐,对于本地用户只有在浏览郊区酒店时会触发旅游推荐,这是假设本地用户在浏览市区酒店时旅游度假的意图可能不明显。
除了在各类推荐场景的应用,这些策略在运营上也有应用尝试,比如:用户浏览或购买过POI后根据POI CF给用户PUSH相似的POI,实验证明推荐策略的PUSH点击率要高于平均水平。
未来的挑战
经过一年多的迭代优化,周边游频道内相当比例的订单来自推荐,线上支持了20个左右的推荐场景,很多推荐策略被作为特征加入搜索、筛选Rerank,有明显正向效果,在用户运营上也有了初步的探索。基于目前的推荐系统本身还有不少优化点:
跳出目前单一的以POI/Deal列表为主体的推荐形态看,可以从用户、场景、内容、触达方式四个方面看如何做好旅游推荐:
首先考虑用户是谁?要满足用户的什么需求?这里可以利用美团/点评的数亿用户,打“人群标签”,是一二线城市高端品质女用户、勤俭住宿的中年大叔还是三线城市实惠型年轻妈妈。然后分析这些人群背后的需求,是本地休闲用户、差旅用户还是高频度假用户,不同用户的需求是不一样的。
当知道用户后需要知道用户的场景是什么?可以从四个维度定义场景:时间、位置、行为、渠道。
时间很好理解,当用户在周四周五搜索“滑雪场”,会被认为是休闲度假周末用户,可以协同推荐北京郊区的滑雪场。
地理位置是核心要素,要根据用户的常驻城市和客户端选择城市来判断是本地还是异地需求,对于异地的差旅用户可以推荐商务型的酒店。
行为是用户需求最直接的反应,比如:用户搜索“古北水镇”,不管用户后续是否有浏览行为,都可以推荐古北水镇相关的酒店和景点门票。
渠道包括美团/点评双平台App、i版、PC等多个终端,以美团App为例,周边游、酒店、机票/火车票频道的用户特征都不一样,比如:大交通频道最常见的是差旅用户、周边游频道更多是本地度假休闲的人群。
知道了用户是谁以及处于什么场景,要考虑提供什么样的内容产品?对于美团来说核心是交易,内容不是最核心的目标,但内容是一个非常好的引流措施。以本地场景为例,可以加强场景建设,比如:亲子、团建、温泉等;异地行前场景可以加强目的地、点评游记攻略、酒店交通行程安排等内容建设。
除了目前的搜索推荐外,还可以增加定向投放、内容引导、广告植入、活动运营等多种触达方式。
总之旅游推荐问题复杂多样,需要从度假出行六要素:吃、住、行、游、购、娱综合考虑和规划,对产品形态、业务策略、技术架构都还有很大的挑战和机遇。