How do We Re-design Search Feature

问题定位
Do we have problems or not?
我们的房源搜索服务的质量到底怎么样?如何衡量好与不好?Are we really sure it behaves as we designed as well as as user expected? 我们对这个问题一直没能给出一个清晰明确的答案。我们知道衡量的维度很多,却无法进行系统性的整理和分析;我们猜测各维度之间相互关联相互影响,却无法勾勒出具体的图像;我们粗略的了解一些指标,却无法往下拆解,也无法向上映射。我们自己用下来体感不好,觉得有很些功能的设计不合理,却无法给出具体的优化方向和解决方案。我们对用户的搜索意图充满了默认和假设,从来没有认真研究过该功能的use case和用户的实际使用情况。
案例分析
Case study
我在某个小区发了一个房子,然后在搜索栏输入该小区所属街道的名称进行搜索,结果完全搜不到这个房子。经过反复尝试,我发现了两个能够搜到这个房子的方法。一个办法是直接输入这个小区名称进行搜索,另一个办法是在输入街道名称后,在drop down list里选择系统给出的与街道名称相关的地点提示进行搜索。只有这两种途径可以搜到这个房子,其它方式都不奏效。
那么问题来了,我们的产品(搜索服务)在上述案例中的behavior正不正常,答案很简单,Yes or No。但有趣的是,产品经理和工程师的回答截然相反。产品经理毫无疑问的认定这是一个bug,而工程师却认为:it just works as expected.
From product managers' point of view: 用户输入的是一个街道的名称,当然应该搜到这个街道下辖的小区的房子,这是一个最基本的requirement,已经不能再简单和直接了。
From engineers' perspective: 服务端接收到的是一个按关键词搜索的请求, 服务确实是按用户输入的这个词(相关街道的名称)到ES里进行全文检索的,而用户发的这个房子的房源信息里并没有包含这个关键词,所以没有召回是正常的 which is expected for sure.
这也就解释了为什么我后面试出来的两种方法可以搜到这个房子:因为我在房源信息里写了是哪个小区(一般情况下房子在哪个小区是必填的,但不是所有用户都会在房源描述里提及小区所属街道的名称,特别是很多名称比较官方的街道,在生活中是不常用的),所以我按小区名称搜可以搜到,按街道名称就搜不到。另外,在drop down list里选择系统提示的与该街道名称(关键词)相关的地点可以搜到是因为房子的描述里虽然没有写街道名称,但房子的地点坐标落在了该街道的multipoligon内,所以能被搜到。
说了这么多,用户不过是想要搜到这个街道的房子而已,什么一会儿搜得到一会搜不到的,技术上上一套一套的,it's just bullshit.
脚踏实地的做事
Start from the users
当我们说整个房源搜索服务是我们研发的,那我们应该很了解用户是如何使用房源搜索这个功能的吧,他们输入最多的高频词有哪些?我们经常这样回答:“这个数据数仓里有,可以拉出来看一看。” Well, greate answer. 换句话说,这个服务跑到现在我们都还没有看过这个数据。那我觉得我们没有脚踏实地的在做事,根本连自己身处何地都不知晓。
我写了一条query到数仓里把过去两个月用户搜索过的关键词按词频数倒排拉取出来,然后花了2个小时把排名前一万的高频词过了一遍,差不多覆盖了所有被用户搜过两次以上的关键词。通过边看边整理归类,我发现了很多意外的case,我们设计的东西在用户那里未必是我们以为的样子。
有的用户直接搜“附近”两个字,有的用户直接对城市的名称进行搜索,还有很多用户搜索了一些看似跟租房不相关的词语,但存在着某种关联。这些搜索返回的结果在当前的实现上都是属于“技术上合理”的,但对用户来说都是垃圾信息,都是无效召回,因为程序根本就不知道用户的真实意图。我们的程序是服务于用户的,那么识别用户意图应该是程序的基本功能,如果做不到这一点,那技术就是很烂。
我们的代码很容易走入一个误区,就是试图 handle 所有的边缘情况以确保逻辑的严密性,但我们从来没有追究过这些严密的逻辑被hit到的次数,很多时候是零次,而经常有每天被hit到的case完全没有考虑到或者考虑得很随意。根本原因就是我们作为产品的研发者一直在假设用户会怎么使用我们的产品,我们永远在逻辑上去cover我们以为会发生的case,而没有真正花几分钟去看一下用户的实际行为,哪怕数据躺在数仓里烂掉。
解决方案要接地气
Resolve the problem effectively first, and then technically
我拉取到数据后手动对数据进行了清洗,再对词汇进行整理、归类,针对每一个的词汇,我查看了用户的行为记录,通过他们在搜索前后的上下文判断其真实意图,再对词汇进行拆解、分层,建立映射,从而形成了初版的预分类字典,这大概又花了我两个小时 。
分层结果请参照 搜索关键词分层。
需要指出的是,整个过程中,并没有引入任何一行代码。接下来,才是把字典运用到原型方案的代码中,实现搜索服务对用户意图的预判,并根据其实际意图引入针对性的召回策略和技术实现,目的是要让召回的结果符合用户的预期,而不是“技术上合理”。你会发现,代码在整个方案落地中所占的比重非常有限,且并非解决问题的关键,我们需要工程师有做事的能力,而不仅仅只有代码能力。比如,如果用户的搜索关键词是“附近”,我们是不是可以直接按用户的当前位置进行搜索,而不是对“附近”两个字进行全文本检索?如果用户输入的是某城市的名称,那么如果这和用户当前所在或所选的城市不符,是否应该提示用户切换城市,而非继续本次搜索?如果用户键入的词汇并非与房源相关,我们是否可以进行稍微细致一些的分类引导,甚至发现新的用户需求?
字典的作用不仅仅是帮助召回,更重要的是分析和判断用户的意图,在产品层面,甚至在更高的层级上帮助用户解决问题。
在对用户意图进行有效的判断之后,也许我们要为用户召回的不是房源。
效果评估和持续迭代
Evaluate and improve
回到搜索服务上,我们的计划是利用初版的分层字典对服务原型引入关键词预分类功能,完成之后,我们要对这个原型方案进行几项基本的测试评估。一是把之前我们期待召回但没有召回的case作为输入,看是否能召回,这是正向验证。二是把之前不应该召回但召回了的case作为输入,看是否不会再被错误的召回,这是反向验证。三是把之前我们期待召回并且能正常召回的case作为输入,看是否依然能正常召回,这是回归验证。
然后,我们知道在这个功能上 precision 和 recall 都会影响到用户体验和我们的营业额,precision在这里就是搜索结果的相关性和排序,直接关系到用户体验,recall 关系到搜索结果的量是否合适,是否能够更好更快的帮助用户进行决策。同时,房源投放能否被精准召回则是 precision 和 recall 的结合,这个关系到投放位的转化率,直接影响到营业额。这些指标都可以通过房源列表页的STF在数据上得到体现。我们可以把这个经过评估的原型发布上线进行A/B测试。原来的线上版本是对照组,引入了意图预判的原型是实验组。我们期待这个STF在一个经过计算的实验周期内会有一个significant的变化,无论是上升还是失败我们都能从中获取到有用的信息,校准或找到新的优化方向,持续的迭代我们的解决方案。