全文搜索,与机器学习领域其他大多数问题不同,是一个 Web 程序员在日常工作中经常遇到的问题。客户可能要求你在某个地方提供一个搜索框,然后你会写一个类似 WHERE title LIKE %:query% 的 SQL 语句实现搜索功能。一开始,这是没问题,直到有一天,客户找到你跟你说,“搜索出错啦!”
当然,实际上搜索并没有“出错”,只是搜索的结果并不是客户想要的。一般的用户并不清楚如何做精确匹配,所以得到的搜索结果质量很差。为了解决问题,你决定使用全文搜索。经过一阵枯燥的学习,你开启了 MySQL 的 FULLTEXT 索引,并使用了更高级的查询语法,如 “MATCH() … AGAINST()” 。
好了,问题解决,完结撒花!数据库规模不大的时候是没问题了。
但是当你的数据越来越多时,你会发现你的数据库也越来越慢了。MySQL 不是一个非常好用的全文搜索工具。所以你决定使用 ElasticSearch,重构代码,并部署 Lucene 驱动的全文搜索集群。你会发现它工作的非常好,又快又准确。
这时你不禁会想:为什么 Lucene 这么牛逼呢?
本篇文章(主要介绍 TF-IDF,Okapi BM-25 和普通的相关性评分 )和 下一篇文章 (主要介绍索引)将为你讲述全文搜索背后的基本概念。
相关性
对每一个搜索查询,我们很容易给每个文档定义一个“相关分数”。当用户进行搜索时,我们可以使用相关分数进行排序而不是使用文档出现时间来进行排序。这样,最相关的文档将排在第一个,无论它是多久之前创建的(当然,有的时候和文档的创建时间也是有关的)。
有很多很多种计算文字之间相关性的方法,但是我们要从最简单的、基于统计的方法说起。这种方法不需要理解语言本身,而是通过统计词语的使用、匹配和基于文档中特有词的普及率的权重等情况来决定“相关分数”。
这个算法不关心词语是名词还是动词,也不关心词语的意义。它唯一关心的是哪些是常用词,那些是稀有词。如果一个搜索语句中包括常用词和稀有词,你最好让包含稀有词的文档的评分高一些,同时降低常用词的权重。
这个算法被称为Okapi BM25。它包含两个基本概念 词语频率(term frequency) 简称词频(“TF”) 和 文档频率倒数(inverse document frequency) 简写为(“IDF”). 把它们放到一起,被称为 “TF-IDF”,这是一种统计学测度,用来表示一个词语 (term) 在文档中有多重要。
TF-IDF
词语频率( Term Frequency), 简称 “TF”, 是一个很简单的度量标准:一个特定的词语在文档出现的次数。你可以把这个值除以该文档中词语的总数,得到一个分数。例如文档中有 100 个词, ‘the' 这个词出现了 8 次,那么 'the' 的 TF 为 8 或 8/100 或 8%(取决于你想怎么表示它)。
逆向文件频率(Inverse Document Frequency), 简称 “IDF”,要复杂一些:一个词越稀有,这个值越高。它由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。越是稀有的词,越会产生高的 “IDF”。
如果你将这两个数字乘到一起 (TF*IDF), 你将会得到一个词语在文档中的权重。“权重”的定义是:这个词有多稀有并且在文档中出现的多么频繁?
你可以将这个概念用于文档的搜索查询。在查询中的对于查询中的每个关键字,计算他们的 TF-IDF 分数,并把它们相加。得分最高的就是与查询语句最符合的文档。
很酷吧!
Okapi BM25
上述算法是一个可用的算法,但并不太完美。它给出了一个基于统计学的相关分数算法,我们还可以进一步改进它。
Okapi BM25 是到目前为止被认为最先进的排名算法之一(所以被称为 ElasticSearch )。Okapi BM25 在 TF-IDF 的基础上增加了两个可调参数,k1 和 b,, 分别代表 “词语频率饱和度(term frequency saturation)” 和 “字段长度规约”。这是什么鬼?
为了能直观的理解“词语频率饱和度”,请想象两篇差不多长度的讨论棒球的文章。另外,我们假设所有文档(除去这两篇)并没有多少与棒球相关的内容,因此 “棒球” 这个词将具有很高的 IDF - 它极稀少而且很重要。 这两篇文章都是讨论棒球的,而且都花了大量的篇幅讨论它,但是其中一篇比另一篇更多的使用了“棒球”这个词。那么在这种情况,是否一篇文章真的要比另一篇文章相差很多的分数呢?既然两个两个文档都是大篇幅讨论棒球的,那么“棒球”这个词出现 40 次还是 80 次都是一样的。事实上,30 次就该封顶啦!