ElasticSearch进阶之停用词处理
我想停用词(Stop Words)这个概念对大家来说并不陌生,通常来说,停用词是在一个语言中出现的频率很高的词语(和文本或者语言相关,同样的词在不同的语言中出现的频率可能完全不同),比如我们常见的the, is, and等等,这些词很难进行精确的搜索,也很容易影响搜索的性能。所以在ElasticSearch中可以对他们进行特殊处理,当然ElasticSearch早期对他们进行处理的另外一个主要原因是为了节省磁盘和memory的空间,但是随着时间的推移,单纯从存储空间来看,其实是否要特殊处理可能已经不是很重要了(当然准确的说在位置和偏移信息的保存中,它还是会占用很多存储空间的)。
去除停用词
英语中默认的停用词列表如下:

一般来讲,哪怕我们把这些单词忽略了,对搜索的结果影响也不大,但世间万物其存在必有其存在的道理,比如说假如我们把not去掉了之后就没有办法区分happy和not happy两者的不同, 也没法搜索一些名言比如”to be or not to be”。
但是从另一个方面来看,去除停用词也有其好处,一个比较常见的优点就是可以提升搜索的性能。举例来说,我们现在有很多文本,比如说几百万的文本,其中有20个文本有dog这个单词,现在我们搜索”the dog”,在去除停用词之后,我们很快就可以过滤出这20个文本,然后计算他们相关性,最后进行排序返回结果。但是不去除停用词,the这个单词可能会在一百万的文本中出现,这样一来就意味着会有一百万的文本符合我们搜索的要求,我们需要对这一百万文本进行算分,最后再进行排序。很显然对20文本计算分数和对一百万文本计算分数,两者的性能不可能一样。
我们下面先来看一下常见的几种去除停用词的方法:
Stopwords加标准分词器
第一种去除停用词的方法就是在标准分词器中设置stopwords的值,如下所示。

举例来看,假如我们传入的是”The quick and the dead”,它的结果如下所示:

和我们的预期是一样的,这个句子中的the, and都被去除掉了。另外一个有趣的现象是假如你足够细心,你会发现position的结果并没有随着停用词的去除而改变,这一点很重要,就像我们在《ElasticSearch进阶之多域的搜索》中所讲的一样,这个位置的信息其实对匹配的结果是有很重要的影响的,我们需要保留他们原本的位置信息。
使用语言自带的停用词
我们除了可以像上面那样给一个stopwords的list,还可以直接使用_lang_这样的符号来去除各种语言默认的停用词,比如下面就是使用英语的默认停用词。

我们甚至可以使用一个文本来配置支持的停用词。每个停用词在文本中占一行,如下所示:

停用词的更新
对于停用词的更新,ElasticSearch有以下几种方法。假如你是通过stopwords list来进行设置停用词的话,唯一的更新办法就是关闭index,然后通过更新index设置来更新这个list,再打开index。
但是假如你是通过stopwords_path来进行设置停用词的话,你可以更新文本,然后通过下面任意一种方法来使之生效:
- 关闭再打开index。
- 一个一个重启cluster中的所有节点。
性能处理
我们之前有简单说过我们去除停用词的一个重要原因就是性能问题,其实说白了这个性能问题的根源是”the quick brown fox”会被解析成 the OR quick OR brown OR fox,这样一来在查询the的匹配结果的时候,就会有很多结果需要进行计算。那么假如我们不去除停用词,有没有办法解决这个性能问题呢?答案也是显然的,我们来看下面这几种方法:
And操作
一个最简单的方法就是把OR变成And的操作,也就算是说把“the quick brown fox”解析成“the AND quick AND brown AND fox”,这样一来整个query就变成了下面这样:

它能匹配的内容其实就要求这四个词都存在,从而解决了我们上面说的性能问题,但是这种方法的缺点也很明显,它的限制太死了,需要四个都匹配才行。
Minimum_should_match
这个时候一个新的方法就自然而然出现了,也就是说我们设置最小匹配的比例,如下所示:

这就意味着四个词里面至少有三个是匹配的才会返回,这样一来既解决了性能的问题,也解决了要求太过严格的问题,算是一个很好的折中方案。但是有没有更好的解决方法呢?
权重分配
上面的minimum_should_match的一个比较大的问题就是the和别的词比如fox的权重是一样的。而一个优秀的搜索显然是希望fox能够有更高的权重,我们可以使用下面这个来实现:

这个cutoff_frequency就表示当一个词出现的概率大于1%的时候我们认为它是一个高频的词,从而会把这个词和别的词进行区分处理,对于低频的词会使用must,就是必须匹配,而对于高频词则使用should,所以它会被重写成下面这样:

这个时候你会说我们能不能把权重分配和最少匹配结合起来呢,答案是肯定的,你可以写成下面这样:

这个75%会仅仅应用到低频词的must选项里面,所以它会重写成这样:

这里有一个特殊的情况大家需要注意,假如你的搜索都是高频词,比如我们上面提到的”to be, or not to be”,按照上面的方法进行重写它就会都放到should里面了,显然这个不是很合理,所以针对全部都是高频词的情况,我们会重写成所有的高频词都放到must中的形式,如下所示:

总结
至此本文就把ElasticSearch中的停用词相关概念都介绍完成了,希望能够解决你遇到的相关问题。
Recent Comments