京东、淘宝无疑是中国电商界的传奇,虽然一个主打物流和质量、一个主打品类和价格,但它们都有一个共同的特点,那就是核心流量都来自用户搜索,用户都是通过搜索引擎选购心仪商品并下单购买的;一个好的搜索引擎不单单要关注点击与购买转化率,还要在产品体验上满足用户的要求 ,如缩短用户的查找路径,降低用户在购买商品时的决策时间。
Query下拉推荐是指搜索引擎系统根据用户当前的输入,自动提供一个Query候选列表供用户选择,Query下拉提示(Query suggestion)在搜索引擎和广告竞价平台中已经是标配的产品。Query suggestion可以帮助用户明确搜索意图,减少用户的输入并节约搜索时间,对提高搜索体验有重要作用。各个搜索系统的下拉推荐的处理流程基本相同,下拉推荐不同主要体现在后台查询机制的不同,下面介绍几种京东搜索框效果:
搜索框提示效果
这里面大致包含如下几个功能点:
- 中文文本提示 ,如输入 小米 ,会提示 小米手机
- 拼音输入,如输入 xiaomi ,提示 小米手机
- 中文拼音混合,如输入 小mi,提示 小米手机
- 中文简拼混合,如输入 xm手机,会提示 小米手机;输入 小米sj 同样会提示 小米手机
- 关联商品数量,联想提示词右边会关联该联想词可能命中的商品数量
- 提示纠错,京东会根据历史搜索词条对用户部分搜索关键词进行纠错 如输入 平果 ,会提示 苹果
搜索词推荐
- 热词推荐
-
相近词推荐
image.png
意图识别
当用户输入关键词比较长,或商品库中没有匹配的商品时,京东会对用户输入语句进行意图识别,分析用户最想要的是什么,找到核心关键词并进行相关推荐。
如用户输入 五彩斑斓的苹果球鞋 时,京东会先进行一次精确搜索,当所有词都匹配上时才会返回结果,如果没有结果返回,京东服务端会进行查询改写,将用户输入关键词匹配度降低50%后去商品库再进行一次模糊搜索,如果还找不到匹配的商品时,京东会进行意图识别,对识别的关键词进行推荐;“五彩斑斓的苹果球鞋” 分词后被拆分成 五彩斑斓、苹果、球鞋,显然没有匹配的商品,模糊50%需要同时满足其中任意两个词同时出现,显然也没有对应的商品(这里大家可以试着增加 耐克 或删除 苹果 看下效果),在上述环节都无结果时 最后从用户输入关键词中识别出球鞋,并进行相关商品推荐。
实现
鉴于第一版易企秀商城大搜效果一直被大家吐槽,今天我们就仿造京东来一波优化,通过ES实现 输入框联想词提示 及 对用户输入词进行意图分析。
输入框联想词提示
这里有两种实现方案,一种是通过ES官方自带的算法支持,另外一种我们通过edge n-gram实现。
edge n-gram
什么是edge n-gram ? 假设有一个词hello,普通建索引时,就是把这个词hello放入倒排索引
用户输入h、he时会找不到索引(倒排索引中只有hello),因此匹配失败
而对于输入即搜索这种应用场景,可以使用一种特殊的n-gram,称为边界n-grams (edge n-grams)
所谓的edge n-gram,就是指它会固定从一边开始,进行窗口滑动,每次滑动长度为1,最终的结果取决于 n 的选择长度
以单词hello为例,它的edge n-gram的结果如下:
h
he
hel
hell
hello
因此可以发现到,在使用edge n-gram建索引时,一个单词会生成好几个索引,而这些索引一定是从头开始
这符合了输入即搜索的特性,即是用户打h、he能找到倒排中的索引h、he,而这些索引对应著的数据就是hello
- Mapping
"autocomplete": {
"type": "text",
"analyzer": "autocomplete",
"search_analyzer": "autocomplete_search"
}
- Setting
"analysis": {
"filter": {
"my_pinyin": {
"keep_joined_full_pinyin": "true",
"keep_full_pinyin": "false"
"lowercase": "true",
"keep_original": "true",
"remove_duplicated_term": "true",
"keep_separate_first_letter": "false",
"type": "pinyin",
"limit_first_letter_length": "16"
}
},
"analyzer": {
"autocomplete_search": {
"filter": [
"lowercase" ,
"unique"
],
"tokenizer": "search_py"
},
"autocomplete": {
"filter": [
"lowercase",
"unique",
"my_pinyin"
],
"tokenizer": "autocomplete"
} ,
"tokenizer": {
"autocomplete": {
"min_gram": "1",
"type": "edge_ngram",
"max_gram": "16"
},
"search_py": {
"lowercase": "true",
"keep_joined_full_pinyin": "true",
"keep_original": "true",
"keep_first_letter": "false",
"keep_separate_first_letter": "false",
"type": "pinyin",
"limit_first_letter_length": "16",
"keep_full_pinyin": "false"
}
}
}
需要注意的是将keep_full_pinyin置为false,keep_joined_full_pinyin设为true;如果需要支持简拼提示,可将keep_first_letter设为true;开启keep_first_letter支持的提示词输入场景会增多,但同时也会触发一些长尾词效果问题,如输入韩式婚礼,会在候选提示词列表中出现 红色婚礼,原因是他们的简拼都是hshl。同样这些问题在京东PC站也有存在,如果要开启keep_first_letter 就需要对一些长尾词进行容忍:
- Query
通过前缀查询获取提示词列表,并按照搜索量、关联商品数量及相似度分值进行综合打分排序:
{
"_source": "autocomplete",
"query": {
"script_score": {
"query": {
"match_phrase_prefix":{
"autocomplete":{
"query":"韩式h",
"max_expansions":16
}
}
},
"script": {
"source": " Math.log10(doc['count'].value*params.factor1 + 1) + Math.log10(doc['weight'].value * params.factor2 + 1) + _score*params.factor3",
"params": {
"factor1": 1,
"factor2": 2,
"factor3": 3
}
}
}
}
}
completion suggester
completion 是ES自带的联想词提示功能,它采用了一种支持快速前缀查找的数据结构,并将数据存储在内存中,检索效率非常高,但缺点也很明显,不能对结果进行自定义排序。
- Mapping
参数 | 说明 |
---|---|
analyzer | 指定索引是分析器,这里我们采用自定义 |
search_analyzer | 指定搜索时使用的分词器 ,各别配置的不同直接决定前端效果 |
preserve_separators | 是否保留分隔符,主要考虑中文提示词 这里不保留 |
max_input_length | 最大提示词输入长度 默认是50,这里设置为16 足够了 |
"suggest": {
"type": "completion",
"analyzer": "pinyin_analyzer",
"search_analyzer": "suggest_search",
"max_input_length": 16,
"preserve_separators": false
}
- Setting
这里主要依赖elasticsearch pinyin插件:
"analysis": {
"filter": {
"length_filter" : {
"type" : "length",
"min" : "2"
},
"my_pinyin": {
"keep_joined_full_pinyin": "true",
"lowercase": "true",
"keep_original": "true",
"remove_duplicated_term": "true",
"keep_separate_first_letter": "false",
"type": "pinyin",
"limit_first_letter_length": "16",
"keep_full_pinyin": "false"
}
},
"analyzer": {
"suggest_search": {
"filter": [
"lowercase" ,
"unique"
],
"tokenizer": "suggest_py"
},
"pinyin_analyzer": {
"filter": [
"my_pinyin",
"lowercase",
"unique"
],
"tokenizer": "keyword"
}
},
"tokenizer": {
"suggest_py": {
"keep_joined_full_pinyin": "true",
"keep_none_chinese":"false",
"keep_none_chinese_in_first_letter ": "false",
"lowercase": "true",
"none_chinese_pinyin_tokenize": "false",
"keep_none_chinese_in_joined_full_pinyin": "true",
"keep_original": "true",
"keep_first_letter": "true",
"keep_separate_first_letter": "false",
"type": "pinyin",
"limit_first_letter_length": "16",
"keep_full_pinyin": "false"
}
}
}
需要注意的是 这里需要将keep_first_letter设置为true ,否则无法进行任意位置的简拼提示;但开启简拼后,需要配合一个好的提示词排序算法,否则就会出现 输入 韩式婚礼 提示 红色婚礼。
其它配置:
keep_first_letter:这个参数会将词的第一个字母全部拼起来.例如:刘德华->ldh.默认为:true
keep_separate_first_letter:这个会将第一个字母一个个分开.例如:刘德华->l,d,h.默认为:flase.如果开启,可能导致查询结果太过于模糊,准确率太低.
limit_first_letter_length:设置最大keep_first_letter结果的长度,默认为:16
keep_full_pinyin:如果打开,它将保存词的全拼,并按字分开保存.例如:刘德华> [liu,de,hua],默认为:true
keep_joined_full_pinyin:如果打开将保存词的全拼.例如:刘德华> [liudehua],默认为:false
keep_none_chinese:将非中文字母或数字保留在结果中.默认为:true
keep_none_chinese_together:保证非中文在一起.默认为: true, 例如: DJ音乐家 -> DJ,yin,yue,jia, 如果设置为:false, 例如: DJ音乐家 -> D,J,yin,yue,jia, 注意: keep_none_chinese应该先开启.
keep_none_chinese_in_first_letter:将非中文字母保留在首字母中.例如: 刘德华AT2016->ldhat2016, 默认为:true
keep_none_chinese_in_joined_full_pinyin:将非中文字母保留为完整拼音. 例如: 刘德华2016->liudehua2016, 默认为: false
none_chinese_pinyin_tokenize:如果他们是拼音,切分非中文成单独的拼音项. 默认为:true,例如: liudehuaalibaba13zhuanghan -> liu,de,hua,a,li,ba,ba,13,zhuang,han, 注意: keep_none_chinese和keep_none_chinese_together需要先开启.
keep_original:是否保持原词.默认为:false
lowercase:小写非中文字母.默认为:true
trim_whitespace:去掉空格.默认为:true
remove_duplicated_term:保存索引时删除重复的词语.例如: de的>de, 默认为: false, 注意:开启可能会影响位置相关的查询.
ignore_pinyin_offset:在6.0之后,严格限制偏移量,不允许使用重叠的标记.使用此参数时,忽略偏移量将允许使用重叠的标记.请注意,所有与位置相关的查询或突出显示都将变为错误,您应使用多个字段并为不同的字段指定不同的设置查询目的.如果需要偏移量,请将其设置为false。默认值:true
- Query
completion query根据数据索引时指定的权重值进行排序
{
"suggest" : {
"input": [ "韩式婚礼", "红色婚礼" ],
"weight" : 34
}
}
{
"size": 0,
"_source": ["autocomplete","weight"],
"suggest": {
"word-suggest": {
"prefix": "韩式婚礼",
"completion": {
"size":5,
"field": "suggest"
}
}
}
}
- 真正到线上应用时可以配合策略两种算法搭配使用,当精确提示返回效果不佳或无提示时,可采用开启简拼的方式进行补充。
提示词关联商品数量
- 在词条入库前会在商品索引库上执行一次精确查询,返回的total值就是对应的商品数量
- 为了避免对大搜业务的影响,spark任务会放在凌晨进行
- 词条的索引Id使用词条的Md5值,便于后期更新处理
- 为提升效率,应该使用Elasticsearch的Multi Search接口批量进行count,同时批量更新数据库里建议词的count值
- 在ES 7.x版本想获取total在1w以上的具体值时需 track_total_hits=true
- 关联商品数量过低的词条会被剔除
- 由于商品是实时入库的而关联统计是定期离线执行的,所以在进行相关提示时会显示“约”
搜索词推荐
- 热词推荐
这里根据提示词库中的weight值排序取Top
{
"sort": [
{
"weight": {
"order": "desc"
}
}
],
"query": {
"match_all": {}
}
}
- 相关搜索词推荐
基于用户搜索会话日志构建word2vector算法模型,针对搜索提示词入库前,对每一种词条进行关联度预测,获取每个搜索词的最相近的top
输入词意图分析
基于用户的搜索长句,找到最能表达用户搜索意图的核心关键词组;如 电影院影城复工开业活动宣传 -> 电影院复工宣传
意图识别和纠错识别都是基于现有词条实现的,覆盖率与准度受限于提示词库。
大多数情况下我们都是通过输入关键词来查找满足条件的文档,然而意图识别有点类似于给出一段短文本文档来获取满足条件的搜索关键词,通过ES的percolate query可以实现这种反向输入查询。
首先我们需要为每一个词条定义一种查询方式,这里我们统一使用一种query :
{
"query": {
"span_near": {
"clauses": [
{
"span_term": {
"txt": {
"value": "武汉"
}
}
},
{
"span_term": {
"txt": {
"value": "加油"
}
}
}
],
"slop": 3,
"in_order": true
}
}
}
或者
{
"query" : {
"intervals" : {
"txt" : {
"all_of" : {
"intervals" : [
{
"match" : {
"ordered" : true,
"query" : "高端蓝"
}
}
]
}
}
}
}
}
这里使用的是span_near query,否则 武汉加油 与 加油武汉 都会被 “我们一起为武汉加油” 所召回;具体排序方式以及召回策略可根据具体业务层场景灵活调整,通过slop来调整词的编辑距离 。
同理,分类与品牌词的识别也可以通过这种方式实现,前提是要建立分类与品牌词库。
提示词纠错
用户在PC端通过键盘快速键入关键词搜索时难免输入一些同音或形似错别字,此时如果不作处理的话,用户拿着错误的词很可能检索不到心仪的商品,或者无商品返回,如 黄小明、邀请含等;此时如果能通过搜索提示框帮组用户进行正确的提示,将大大提升用户的查找准度和效率。
{
"_source": false,
"size": 0,
"suggest": {
"term-suggestion": {
"text": "邀请含",
"term": {
"field": "autocomplete",
"analyzer":"keyword",
"min_word_length":2,
"prefix_length":1,
"size":2
}
}
}
}
参数 | 说明 |
---|---|
analyzer | 指定分词器 |
min_word_length | 默认值是4 ,当提示词长度低于4时不作处理,这里改为 2 |
prefix_length | 从什么位置进行错别字识别,这里设置为1 |
size | 返回结果集条目数 |
sort | 需要注意的是返回的结果是按照编辑距离排序 ,也可设置为 frequency 基于文档词频排序 |
效果
写在最后
搜索的调优不能一直关注技术方面,还要关注用户。搜索质量的好坏是一个比较主观的评价,想要了解用户是否满意搜索结果,只能通过监测搜索结果和用户的行为,例如用户重复搜索的频率,翻页的频次等。
如果搜索能返回相关性较高的文档,用户应该会在第一次搜索便得到想要的内容,如果返回相关性不太好的结果,用户可能会来回点击或尝试新的搜索条件。
ES是一个通用的搜索框架,想要根据业务实现一个专业的搜索平台,还需要进行很多优化,不仅仅是DSL方面的改变。