美团点评旅游搜索召回策略的演进



转载请注明 AIQ - 最专业的机器学习大数据社区  http://www.6aiq.com

AIQ 机器学习大数据 知乎专栏 点击关注

本文内容与 6 月 22 日第 22 期美团点评技术沙龙“美团点评 AI 实践”主题演讲一致,欢迎大家去现场和作者交流。

关注“美团点评技术团队”微信公众号,第一时间获取沙龙最新信息,还可以查阅往期沙龙 PPT/ 视频。

背景

美团点评作为最大的生活服务平台,有丰富的品类可供用户选择,因此搜索这个入口对各业务的重要性不言而喻,除了平台搜索外,业务搜索系统的质量和效果对用户体验、商家曝光、平台交易也有着关键作用。

相对美团点评平台的 O2O 检索,旅游搜索系统主要面临以下几点挑战:

  • 本异地差异大。在本地生活场景中用户的搜索需求往往集中在本城市内,而在旅游场景特别是行前场景用户会先搜索异地的 POI(门店),比如常驻城市为北京的用户在去上海之前可能会先搜索“东方明珠”、“迪士尼”了解相关信息。
  • 搜索意图多样,不同意图的展现形式可能不同。搜“故宫”、“故宫成人票”是景点门票意图,搜“北京”、“云南”是行政区意图,搜“水上乐园”、“滑雪场”是品类意图,搜“上海到南京”、“一日游”是线路游意图。
  • 底层脏数据多。旅游早期由于上单审核不严等原因,会出现“真人 CS”Deal(团购单)下挂在“故宫博物馆”POI 的情况,按照平台的检索策略,搜“真人 CS”时会展现“故宫”的 POI,导致大量误召回。

针对上述问题,我们建设了一套相对完整的搜索系统,包括检索召回、查询分析、智能排序和业务应用几部分,本文将重点介绍搜索召回(检索召回、查询分析)的策略演进过程。

评价指标

我们在 2015 年 Q2 启动了旅游搜索系统的建设,此时旅游业务有独立的周边游频道,其中的搜索策略由平台统一负责,不能很好的解决旅游场景中的诸多问题。为了解决这些问题,我们首先需要确定搜索的评价指标:

  • 访购率:支付用户数 / 搜索访问 UV,这个是评估搜索效果的主指标。美团点评是一家电商公司,营业收入是核心指标,以搜索为例,用户行为链条包括搜索 Query-> 点击搜索结果列表页中的 POI/Deal 等 -> 下单支付 -> 消费,最后计算消费收入。如果只看点击率,关注的链条太短,没有反映交易属性;如果看最终的收入结果,部分因素(消费受产品的购买限制、退款条件等影响,收入又跟商户拓展人员谈单的毛利等相关)非搜索可控。因此以访购率作为搜索的核心指标跟美团点评的业务特点最为匹配。

  • 点击率:点击 PV/ 搜索 PV(Page View)。部分景点由于商户拓展人员没有谈单或者是免费景点等原因导致没有门票或线路游 Deal 可售时,访购率为零,但用户可能需要了解景点相关信息,这时点击率是重要的辅助评价指标。一个相关的指标是有点行为比,以搜索请求量为统计口径。
  • 无结果率:无结果请求数 / 搜索请求数,衡量搜索召回质量的重要指标。
  • 用户满意度:由产品经理定期人工评测,比如取搜索结果的前 20 条,如果是单景点意图,对应的 POI 能排在首位,排序合理,无重复 POI 则为 1 分;搜索结果满足部分用户需求,存在误召回、排序不合理的情况则为 0.5 分;完全不能满足用户旅游需求,搜索结果没有有效信息则为 0 分。

除了可以用指标评估的问题外,还有一些指标外的问题,比如广告运营、直签门票加权等,这些问题可能跟指标负相关或不好量化评估。指标内的问题又分为两类:一类是算法问题,比如查询意图理解、召回检索策略、个性化排序;另一类是产品和业务问题,比如页面改版、源数据清洗,部分产品问题也需要策略协同解决。

策略迭代方法

明确评价指标后需要找到策略优化的方向和思路,不同于推荐(可以参考作者之前的文章《旅游推荐系统的演进》),搜索的 bad case 往往非常明确,因此我们确立了以 case 驱动为主的策略迭代方法。

  1. 质量评估:定义满意度标准和评估体系,定期(月 / 季度)评估搜索满意度,确定评估样本,了解 Query 需求分布、意图识别准召率、召回及排序情况。
  2. 问题分析:对问题进行梳理分类,比如无供给问题、误召回问题、意图识别问题、POI 排序问题、展示问题等,找出主要问题并明确优化方向。
  3. 项目开发:评估项目实施的可行性,制定相应的技术方案,配合产品、客户端等其他技术团队联调、测试。
  4. 实验迭代:上线 A/B Testing 验证优化效果,根据指标评估项目收益,效果正向则扩量,负向则分析调整或下线,并继续迭代优化。

召回策略迭代

全国召回

2015 年 Q2 启动了第一次周边游频道内的搜索质量评估,其中 Query 搜索无结果影响面非常大,除无供给问题外最重要的一个原因是不支持异地搜索。比如在德州搜索“北京故宫”无结果,进一步分析发现在旅游场景中超过 30% 的订单来自于异地请求,即常驻城市为 A 的用户购买了城市 B 的旅游订单。因此在周边游频道内先放开了上单城市的召回限制,当用户搜索 Query 时根据 POI 和 Deal 字段匹配召回全国范围内的结果,比如在北京搜“武侯祠”能召回多个城市的结果,全国召回策略上线后无结果率大幅下降。

模块化展示

除全国召回外,周边游频道搜索当时仍沿用了美团点评平台的展示及召回机制:

  • POI 下挂 Deal 形式展示。
  • 通过 POI 及 POI 下挂的 Deal 信息进行召回。

这些机制主要有两个问题:

  • 以 POI 为主的展现形式不能很好满足用户的线路游需求,比如用户搜“北京一日游”,返回的是故宫、长城等 POI 结果,用户不能方便找到线路游 Deal。
  • 旅游不同于其他品类,Deal 与 POI 不是一一对应关系,尤其是线路游、年票等 Deal 往往关联多个景点,按照平台现有召回策略,会导致大量 POI 被误召回,比如搜“长城”返回“故宫”POI 结果;同时由于上单审核不严等原因,会出现“真人 CS”Deal 下挂在“故宫”POI 的脏数据,也会被误召回。

针对这些问题,在 2015 年 Q3 启动了旅游搜索结果分模块展示的开发,即对用户 Query 进行意图分类,每类意图定制召回策略和展现样式,Query 意图分类如下:

以意图占比为 56% 的景点 POI 为例,当用户搜索“长城”时会展现“长城相关景点”和“长城相关度假产品”两个类聚,景点类聚只在 POI 字段域搜索“长城”,比如 POI 所在城市、名称,这些字段中不包含“故宫”Term,因此不会返回“故宫”POI。度假产品类聚只限定在非门票 Deal 集合内检索 Deal 标题、品类、商圈等字段,返回的都是跟团游、酒景套餐自由行等线路游信息,方便用户决策。

当用户在北京搜“上海”时是行政区意图,会展示“上海目的地”、“上海热门景点”、“北京 - 上海度假产品”、“上海当地度假产品”4 个类聚,其中“目的地”是为城市专门定制的落地页,“北京 - 上海度假产品”是根据出发地为北京、目的地为上海这两个线路游字段来进行检索。

当用户搜索“温泉”时是品类意图,检索策略跟 POI 景点搜索类似,但会增加品类检索字段。

分模块展示上线后一方面改善了用户体验,另一方面打压了旅游 POI 和 Deal 关联的脏数据,访购率和点击率也大幅提升。

同时为了降低无结果率,在一次召回无结果的基础上增加了二次、三次召回,比如增加 POI 商圈字段。如果二次召回也没有结果,会增加门票 Deal 字段进行三次召回,返回门票结果。

综上可知用户 Query 主要包含景点、行政区、品类、线路游 4 类意图,每类意图又可能展现多个类聚,每个类聚的召回检索策略不同。而早期的技术架构在单次请求下只支持单策略检索,同时在多次召回时只能串行执行,因此需要对检索架构进行升级:

  • 由单任务执行变成多任务并行执行,比如搜索“故宫”时需要并发执行 POI 和线路游两个检索策略。
  • 由串行执行变成基于状态机的调度执行,比如 POI 策略下一次召回无结果,会增加商圈字段二次召回,再无结果时会基于门票 Deal 字段进行三次召回。

无结果优化

为了进一步降低无结果率,在 2015 年 Q4 对线上 Query 做了一次无结果分析,其中 32% 原因是 POI 不在线(无供给,POI 没有可售 Deal),27% 是 POI 品类错误(即 POI 品类标签不是旅游),这两类问题策略不好解决,剩下 30% 是由于 Query 表达方式多样导致搜索无结果,这些 case 细分原因如下:

  • 15% 是 Query 包含冗余词,比如搜“东莞的隐贤山庄”无结果,去掉“的”有结果。
  • 7% 是 Query 含有错别字或同义词,比如在北京搜“雁西湖”无结果,用户实际需求是“雁栖湖”。
  • 5% 是 Query 包含多个信息,比如搜“北京动物园海洋馆门票”无结果,分别搜“北京动物园”和“北京海洋馆”有结果。
  • 3% 是 Query 词与商家实际信息不符,比如在北京搜“798 艺术 3D 体验馆”,搜“活的 3D 博物馆”有结果。

丢词 & 查询改写

针对上述问题分别定制了以下几类策略:

  • 丢词策略:通过挖掘 Query 日志,统计其中的高频停用词,比如的、一张、价格、团购、去哪等,对用户输入 Query 直接丢弃其中的停用词,再进行检索召回。
  • Query 纠错 & 同义词改写:统计同一 Session(比如一个小时内)内用户的查询对,选择词频共现比较高的查询对作为候选,再人工审核加入到同义词词典。用户查询,同时用原词和同义词去检索,最后对两者返回的结果取并集。
  • 二次召回:在上文中已有提及,即一次召回无结果时扩大检索字段和检索范围。
  • 无结果推荐:推荐本身并不能降低无结果率,但在无结果时给用户提供了另外的选择。

无合作 POI 召回

上述策略上线后搜索无结果率又有了大幅下降,但仍有一定的优化空间,2016 年 Q2 启动了新一轮的无结果分析,无结果 case 大致可以分为 3 类:

  • 无法追回项目:比如免费景区或全网无售(商户拓展人员无法谈单),这类 case 早期由于评价指标是访购率,搜索并不能召回,但其实对用户体验伤害较大,容易导致用户流失。因此放开一次召回无结果时二次召回无合作 POI,比如搜索“潭柘寺”会返回结果,虽然暂无可售的 Deal,但用户可以浏览 POI 详情页的景区简介、预订须知等。
  • 待追回项目:即目前无供给,但可以反馈给商户拓展人员谈单,针对这类 case 建立了搜索反馈商户拓展人员上单的流程,自动生成任务工单并分派商户拓展人员处理,形成无结果反馈的整体闭环。
  • 线上已有供给:搜索召回策略问题导致的无结果,分析发现通过丢词可以解决大部分 case。之前的丢词是词表丢词,丢词的范围有限,需要在一次词表丢词的基础上增加基于模型的二次丢词,主要方法是对 Query 做 Chunk 分析,为每个 Term 打上 Chunk 标签,人工定义哪些 Chunk 可以丢弃。

上述策略上线后搜索无结果率横向对比美团点评平台和其他业务基本达到了合理正常的水平。

分类意图识别

模块化展示中用到了 Query 意图分类,早期的意图分类使用词表精确匹配的方法,比如搜“大理”和“云南大理”都是行政区意图,其中“云南大理”被切分成“云南”和“大理”,然后分别和省份、城市词表匹配。词表精确匹配的准确率较高,但召回率不高,比如“大理旅游”、“去大理”跟“大理”都是同一个意图,但无法通过词表精确匹配。如果采用宽泛匹配准确率又不会太高,比如“北海公园”、“中山公园”中都包含行政区,但其实是景点意图。基于此,2015 年 Q4 启动了分类意图识别的优化,首先根据 Query 分布定义了 8 类意图:

  • POI:景点、游乐场、度假村等。
  • 行政区:国家、省、市、县、区、镇。
  • 品类:POI 品类体系中的品类词,以及公园、体验馆等指代词。
  • 线路游:一日游、跟团游等。
  • 旅游关键词:旅游同义词如旅行、游玩等。
  • 旅行社。
  • 门票词:门票、套票、成人票等。
  • 非旅游:美食、住宿等外品类词,杂质词(的、一张等)。

可以通过识别 Query 中 Term 的意图来判定整个 Query 的意图,但上述意图分类对 Term 而言粒度较粗,比如“珠海长隆海洋王国门票”会被切分成“珠海 长隆 海洋 王国 门票”,“珠海”是行政区,“门票”是门票词,“长隆 海洋 王国”整体是 POI,但每个 Term 无法对应到上述分类体系,因此需要设计一套更精细的 tag 体系。

其中将行政区细化到国家、省、市、区县、乡镇和地标商圈等 tag,POI 细化为 POI 核心词、品类词、品类修饰词 tag。在上例中“长隆”是 POI 核心词,“海洋 王国”两个 Term 合并是 POI 品类词,“海洋王国”即是一个 Chunk,Chunk 可以认为是一个语义单元,粒度要大于等于 Term 分词粒度。

基于模型的 Chunk 分析

对于 Query 分词后的 Term,问题转化为识别 Chunk 的边界以及为 Chunk 打上何种 tag,即序列标注问题。Chunk 边界可以采用 BMES(Begin、Middle、End、Single)标记方式,比如“海洋 王国”Chunk 中“海洋”标记是 B,“王国”标记是 E。“珠海”是一个单独的 Chunk,所以整体标记为 S- 城市,同理“长隆”整体标记为 S-POI 核心词,“海洋”标记为 B- 品类词。

Chunk 分析转化为序列标注问题后跟其他机器学习问题类似,需要考虑三方面因素:1)算法模型;2)标记语料;3)特征选取。算法模型方面采用 CRF(条件随机场)模型,其结合了最大熵模型和隐马尔可夫模型的特点,近年来在分词、词性标注和命名实体识别等序列标注任务中取得了很好的效果。

标记语料方面采用一段时间内的搜索日志,分词后对每个 Term 进行标注,但全部采用人工标注费时费力,因此采用词表规则标注然后人工校验,其中重点是收集各 tag 的词表,其中行政区、Deal、旅行社等词表比较好收集,POI 核心词、品类词、修饰词可以通过挖掘和模板匹配来实现,这里以 POI 名称为候选词集合,分词后从后向前匹配,定义模板规则,迭代挖掘品类词、修饰词和核心词。

特征选取方面主要包括三类:

  • 边界特征:即可以用于确定 Chunk 边界的特征,包括左右熵、互信息等。
  • tag 特征:即可以用于确定 Chunk tag 类别的特征,包括词长度、Term 的 tag 类别等。
  • 组合特征:左右熵组合,词的组合等。

模型训练时采用 CRF++,需要将标注语料转成 CRF++ 的训练格式,以 Query“珠海 长隆 海洋 王国 门票”为例,训练语料格式如下:
082642e4d8d6451e80c7024c41a47888.png

最后通过离线训练生成模型供线上使用,对用户输入的 Query,模型会输出分词后每个 Term 的 tag。Chunk 分析是一项非常基础的工作,基于分析的结果可以应用于丢词、Term 重要度、意图识别、Query 改写等。

从 Chunk 分析到意图识别

得到 Chunk 的 tag 后可以制定规则输出整个 Query 的意图,意图之间有优先级顺序:线路游 >POI> 品类 > 门票,比如“北京故宫一日游”是线路游意图,“北京故宫”是 POI 意图,“北京动物园”是 POI 意图,“动物园”是品类意图。

分类意图识别对搜索整个流程都意义重大,召回层面可以分意图定制检索字段、相关性计算等检索策略,Rerank 层面可以分意图优化特征,展示层面可以控制不同的展现样式。

粗排序改进

除了 Query 分析、检索策略外,粗排序是搜索召回的另一个核心功能。当搜索结果较多时,如果粗排序不合理,会导致部分优质 POI 或 Deal 无法召回,并且这些 case 不好人工干预。因此我们在 2016 年 Q3 启动了粗排序的改进工作,主要包括:

  • 距离分分段:计算客户端选择城市中心和 POI 的距离,若距离 >=300KM,则距离分为 0,300KM 以内距离越近,得分越高。另外当搜索品类意图时,加大距离分的权重,比如东莞用户更希望去东莞附近的温泉(东莞本地温泉较少),而不是北京的。
  • 综合评价数和评分:早期评价数和评分是线性加权,会出现部分冷门 POI 评价人数较少但评分较高的情况,因此考虑评分的置信度,评价数越多,置信度越高,总体评分越高。
  • 新单销量平滑:新单或新 POI 由于上线时间较短销量一般不高,因此对据当前日期一段时间内上线的产品会赋予默认销量,并考虑时间衰减。
  • 各因子相乘:文本相关性、距离、评价、销量这些因子维度差异较大,线性加权的权重不好设定,改成相乘,会使各因子的影响更为显著。

文本相关性改进

除了数值类因子优化外,我们对文本相关性也进行了一些改进,早期的文本相关性计算基于 TF-IDF,公式可以简化如下:

RQ,D=∑t∈Q(∑f∈Htft,flf∗wf)∗idftRQ,D=∑t∈Q(∑f∈Htft,flf∗wf)∗idftRQ,D=∑t∈Q(∑f∈Htft,flf∗wf)∗idft" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”>

RQ,DRQ,DRQ,D"role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是搜索词和文档的相关性,t 是 QQQ" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 分词后的 Term,HHH"role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是 t 在文档中命中的文本域集合,tft,ftft,ftft,f" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是 t 在某个命中文本域 f 中的出现次数,lflflf"role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是文本域 f 的长度,wfwfwf" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是 f 的权重,比如 POI 名称域的权重一般会高于 Deal 标题域,idftidftidft" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是 Term t 的倒排文档频率。上述公式主要存在如下问题:

  • 文本域长度影响过大,比如搜“庐山”,官方 POI 是“庐山风景名胜区”,分词后包含“庐山”、“风景”、“名胜区”3 个 Term,而“庐山植物园”只包含“庐山”、“植物园”2 个 Term,权重是官方 POI 的 1.5 倍。
  • 多个域计算结果求和,对部分文本域缺失的 POI 不公平,比如搜“欢乐谷”,“天津欢乐谷”POI 的品牌名 (Brand Name) 字段是“欢乐谷”,“北京欢乐谷”POI 的品牌名字段为空,导致“北京欢乐谷”的权重不如“天津欢乐谷”。
  • 没有考虑字段域的动态权重,比如搜“动物园”,细粒度分词会分成“动物”、“园”,“苏州文化园”POI(包含“动物园、文化园一日游”的 Deal)命中了 Term“园”,“万鸟林”POI 的品类字段是“动物园”,由于 POI 名称域的权重高于品类域,导致“苏州文化园”的权重更高。
  • IDF 只体现了 Term 自身的重要程度,不能体现 Term 在 Query 中的重要程度。

基于上述问题对文本相关性计算公式做了如下改进:

RQ,D=∑t∈Qmaxf∈H{tft,f∗(k1+1)tft,f+K∗wf∗if}∗idf′tRQ,D=∑t∈Qmaxf∈H{tft,f∗(k1+1)tft,f+K∗wf∗if}∗idft′RQ,D=∑t∈Qmaxf∈H{tft,f∗(k1+1)tft,f+K∗wf∗if}∗idft′" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”>

K=k1∗(1−b+b∗lfavglf)K=k1∗(1−b+b∗lfavglf)K=k1∗(1−b+b∗lfavglf)" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”>

其中 k1k1k1"role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 和 b 是调节因子,这部分参考了 BM25 的相关性计算,可以降低文本域长度的影响;另外对多个域的计算结果求 max,减小部分字段缺失的影响;ififif" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 是命中域的动态权重,可以根据命中 Term 在 Query 中的比例或权重来设置;idf′tidft′idft′" role=“presentation” style=“padding: 0px; margin: 0px; display: inline; font-style: normal; font-weight: normal; line-height: normal; font-size: 13.92px; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; position: relative;”> 使用的是 Term 在 Query 中的动态权重。

Term 重要度

如何计算 Term 在 Query 中的动态权重呢?实现时采用模型打分方法,以搜索 Query 为原始语料,人工进行标注,重要度共分 4 级:

  • Super important:主要包括 POI 核心词,比如方特、欢乐谷。
  • Required:包括行政区词、品类词等,比如“北京 温泉”中“北京”和“温泉”都很重要。
  • Important:包括品类词、门票等,比如“顺景 温泉”中“温泉”相对没有那么重要,用户搜“顺景”大部分都是温泉的需求。
  • Unimportant:包括线路游、泛需求词、停用词等。

上例中可见“温泉”在不同的 Query 中重要度是不同的,在特征选取方面有 4 类:

  • 文本特征:包括 Query 长度、Term 长度,Term 在 Query 中的偏移量等。
  • 统计特征:包括 PMI、IDF 等。
  • 语言模型特征:整个 query 的语言模型概率 / 去掉该 Term 后的 Query 的语言模型概率。
  • Chunk tag 特征。

模型方面采用 XGBoost 进行训练,离线生成模型后供线上使用。

全字段召回

随着粗排序和 Rerank 的改进优化上线,我们放开了 POI 类聚检索字段的限制,改为使用所有字段做文本匹配,包括 POI 城市、名称、品类、商圈,简化了二次召回的逻辑。

召回策略流程示例

经过一年多的迭代,整个搜索召回的流程大致如下,以搜索“北京著名的温泉”为例:

  1. 对输入的查询进行预处理,比如特殊字符处理、全半角转换。
  2. 查询分词和词性标注,“北京”是地名、“著名”是形容词、“的”是助词、“温泉”是名词。
  3. 基于词表的一次丢词,“的”作为停用词被丢弃。
  4. 同义词改写,对分词的 Term 匹配同义词,如“温泉”和“热泉”是同义词。
  5. 在同义词改写的同时分析 chunk tag,“北京”是城市、“著名”是品类修饰词、“温泉”是品类词。
  6. 基于 Chunk 分析的结果识别 Query 整体为品类意图。
  7. 同时计算 Term 在 Query 中的重要度,“北京”为 0.48、“著名”为 0.39、“温泉”为 0.55。
  8. 基于品类意图确定检索字段和相关性计算的逻辑,比如距离加权。
  9. 由于所有 POI 的文本字段中都不包含“著名”,一次召回无结果,因此扩大 POI 范围,在无合作 POI 集合中进行二次检索。
  10. 由于无合作 POI 的文本字段也不包含“著名”,二次召回也无结果,因此基于 Chunk 丢弃品类修饰词“著名”,然后进行三次检索。
  11. 最终返回搜索结果列表,“顺景温泉”、“九华山庄”等北京著名温泉。

总结

在旅游搜索召回策略的迭代过程中我们并没有采用大开大合的做法,而是参照策略迭代的四步方法论,定期评估搜索质量,对问题分类分析,集中解决主要核心问题,上线实验验证效果,在避免“误召回”和“无召回”之间保持平衡,逐步迭代,为实现更全更准的搜索目标不断改进。

作者简介

郑刚,美团点评高级技术专家。2010 年毕业于中科院计算所,2011 年加入美团,参与美团早期数据平台搭建,先后负责平台、酒旅数据仓库和数据产品建设,目前在酒旅事业群数据研发中心,重点负责酒店旅游场景下的搜索排序推荐、数据挖掘工作,致力于用大数据和机器学习技术解决业务痛点,提升用户体验。

最后发个广告,美团点评搜索团队长期招聘自然语言处理、数据挖掘与机器学习、C++ 与 Java 后台开发等方向的工程师,有兴趣的同学可以发送简历到 zhenggang#meituan.com。

思考题

深度学习近年来非常火热,并且在自然语言处理领域取得了不错的效果。本文中 Chunk 成分分析用到了 CRF 模型,深度学习如何应用于这类序列标注问题?在样本和特征构建上会有哪些不同?进一步,深度学习如何应用于意图识别与文本分类?欢迎一起探讨。


更多高质资源 尽在AIQ 机器学习大数据 知乎专栏 点击关注

转载请注明 AIQ - 最专业的机器学习大数据社区  http://www.6aiq.com