为什么需要全文搜索
一般的网站都会包含搜索功能,它能帮助用户发现没有找到想要的东西,甚至能帮助用户挖掘到兴趣,这对提升用户对网站的黏性和用户体验有非常大的帮助。举个豆瓣的例子,用户可以在主站的搜索里面找到电影、书籍、音乐、用户、小站、小组、游戏等相关内容。
传统的数据库系统设计成可进行「增删改查」等操作,我们都知道,需要存储到数据库的内容都是经过深思熟虑的,如果用户规模和数据很大,多增加一个字段就意味着要增加很多额外的空间、多个索引,并影响到执行效率,这意味着我们无法把全部数据都存进数据库。举个例子,写一篇日记,可以把作者、发表时间、标题等字段存入数据库,但是日记正文无法也放进去,太占空间了。
现在的一个最佳实践是把访问比较集中的、频繁的数据直接放到内存和键值数据库中。但是再进行搜索,就要对不同的存储内容整理,并根据一定的算法把符合的内容排序后返回。而且这些数据非常有可能是由不同的开发团队来维护和开发的,首先这些产品间的网络通信就是一笔不小的开销,还要从不同的产品获取到结果之后再排序。这显然不合理。
全文搜索软件如Elasticsearch则有如下特点:
- 查询速度。全文搜索的数据存取方式只考虑快速读取,相比数据库的查询,要快的多得多。
- 支持复杂的查询表达式。数据库系统的查询通常只支持AND/OR等有限的模式,全文检索支持多得多的查询方式。
- 灵活排序。数据库系统一般按照内置的排序规则来排序,有什么字段并且有对应的索引才可能按什么字段排序。而全文搜索除了能够支持数据库的排序规则外,还支持按照结果的相关度排序,比如Elasticsearch内置了文本相关性、衰减、分词等高级特性,对搜索的效果有非常大的帮助。虽然Elasticsearch自带的中文分词不好,但是提供良好的插件机制,我们可以安装第三方中文分词插件,可以达到非常好的中文搜索效果。
ElasticSearch(简称ES)是一个提供Restful API的、实时的分布式搜索和分析引擎,稳定,可靠,快速,功能强大。
我最早关注到ElasticSearch是因为Github抛弃了Solr,采取ElasticSearch来做PB级的搜索。而现在ES已经是变得非常流行了。除此之外,基于ELK(ElasticSearch,
Logstash, Kibana)的实时日志分析平台应用也非常广泛。
在上篇文章中我们已经抓取了需要的Live数据。我们先感受一条:
1 |
|
其实对于我这个需求,算是把ES当做NoSQL数据库来用了。
假如用户要搜索Python相关的Live,也就是需要从ES中各个需要覆盖到的字段中找Python这个词,最后把符合的文档按照相关度等因素排好序返回给我们。
排序规则
默认的ES会根据文档的相关度进行排序的,多字段查询的写法如下:
1 |
|
排在前面的结果获得的分数要更高,也就是被ES认为更相关。
但对于很多场景这是有问题的,比如上面这个文档,不同字段应该要不同的权重。举个例子,2个文档包含Python关键词的数量一致,但是第一个topic_names是Python,另外一个topic_names是Ruby,显然第一个文档要排在之前。因为知乎话题(Topic)是比较可信的一种划分,而相对的description里面\的文本就算有Python也不一定说明这个一个Python相关的Live,
有可能只是作者提自己曾经学习过Python而已。所以不同字段我们需要设计不同的权重,我不是算法工程师,只是按自己的理解来调整下:
1 |
|
可以看到,这次评分获得了很大的提升,是因为我给subject的权重 5,outline 2……
注意,topic_name的权重成了之前的10倍,这样很容易岔开不同Live的得分。
还有一点,我使用了Q,这和上面的区别不大,只是随着本文越来越深入,用Q来包装接口会让代码更好理解。
得分衰减
有了权重还是不够,因为我们还要考虑给新的Live机会。如果一个大V在很久之前举办了一场Live,文本相关度很高、收入很多、评价也很好。那么是不是这个Live就应该一直排在前面甚至第一呢?这对用户的积极性有很多的伤害,因为后来的人很难有机会有足够的曝光。所以当一段「保护期」之后,这个Live的得分会随着时间不断的减低。
ES内置了衰减函数(Decay
Function)的支持。对于数值、日期和地理位置类型,可以设置一个理想的值,如果实际的值越偏离这个理想值(无论是增大还是减小),就越不符合期望,分数就越低。
它支持如下参数:
- origin:原点,该字段最理想的值,这个值可以得到满分(1.0)
- offset:偏移量,与原点相差在偏移量之内的值也可以得到满分
- scale:衰减规模,当值超出了原点到偏移量这段范围,它所得的分数就开始进行衰减了,衰减规模决定了这个分数衰减速度的快慢
- decay:衰减值,该字段可以被接受的值(默认为0.5),相当于一个分界点,具体的效果与衰减的模式有关
衰减函数可以指定三种不同的模式:线性函数(linear)、以e为底的指数函数(Exp)和高斯函数(gauss),它们拥有不同的衰减曲线,我盗用官方的图:
可以感受到:
- linear直线衰减,在0分外的值都是0分
- exp衰减速度先快后慢
- gauss衰减速度先慢后快再慢
对于Live搜索和排序的需求,我选择了gauss,希望对live开始的前后7天以外的时间点都让这个live的得分变低。
我们通过一个例子,来感受下时间衰减对得分的影响。
1 |
|
我的Live在sf的60天偏移量范围内,但是不在sf2的7天范围内。现在感受下随着时间衰减对得分的影响:
1 |
|
用boost_mode可以指定计算后的分数与原始的_score如何合并,有以下选项:
- multiply:将结果乘以_score
- sum:将结果加上_score
- min:取结果与_score的较小值
- max:取结果与_score的较大值
- replace:使结果替换掉_score
看看使用sum的效果:
1 |
|
由于我们对不同的字段做了比较大的权重的加成,使用相乘更有意义。
数据归一化
相信知乎Live在排序的时候也考虑了Live的收入(也就是单价*购买人数)这个因素,所谓「市场经济下价格基本准确反映供需关系」,一个Live是不是受到大家的欢迎从收入以及单价上市可以体现出来的。但是就算我们对文本相关性做了权重的提高,得分也不外乎几十到几百。如果直接和收入相乘,显然是有问题的:小众优秀的主题由于受众小,票价上不去,得分会很低。同理一个低质量的Live由于大家一股脑被吸引进来由于收入较高而排在前面。顺便说,知乎Live的年度精选就有多个3星、三星半的Live的存在,有种想不开的感觉。对我这种长期受豆瓣评分体系价值观的影响,无法理解这种评分怎么入选的…
我看Live少的一场才收入2-3k, 而有的一场10几w。怎么对这个指标做标准化处理,让指标之间更具有可比性呢?
这就是数据归一化,也就是把原始数据经过数据标准化处理后,各指标处于同一数量级,适合进行综合对比评价。
归一化的方法很多,我这里为了简单实用,选择了「对数函数转换」。通过Jupyter Notebook+Matplotlib+Numpy看一下转换的效果:
这样2k被转化为3.3,10w会被转化成5。收入会有影响,但是被极大的缩小了。
再验证下,被归一化之前:
1 |
|
这个得分,我想静静..
ES内置了脚本语言painless和groovy,我们能直接在ES内部进行一些简单的编程。当然也支持Python但是需要额外安装了。我觉得完全用不到Python,尤其是5.0开始painless成为了默认编程语言。原因是painless有如下特点:
- 支持List、map和array等高级数据结构
- 性能接近Java
- 内置正则表达式
- 支持匿名函数lambda
- 语法简单,学过Java、Python、Javascript的开发者很容易就可以学会
随便提一下,编程脚本的时候尽量不要出现一些受外界影响大的变量,比如用户id这种,因为ES可以缓存脚本,如果使用一些每次查询都不一样的条件去生成脚本会影响性能。
现在我们把log10引入再看看:
1 |
|
这样会合理很多。
今天先讲到这里。下一篇将介绍聚合分析
版权声明:本文由 董伟明 原创,未经作者授权禁止任何微信公众号和向掘金(juejin.im)转载,技术博客转载采用 保留署名-非商业性使用-禁止演绎 4.0-国际许可协议
python