ElasticSearch 2 (24) - 语言处理系列之停用词:性能与精度

时间:2024-01-19 12:17:20

ElasticSearch 2 (24) - 语言处理系列之停用词:性能与精度

摘要

在信息检索早期,磁盘和内存相较我们今天的使用只是很小的一部分。将索引空间保持在一个较小的水平是至关重要的,节省每个字节都意味着巨大的性能提升。词干提取(参见 缩减单词至词根形式(Reducing Words to Their Root Form))的重要性不仅是因为它让搜索的内容更广泛、让检索的能力更深入,还因为它是压缩索引空间的工具。

要减少索引空间的另一个简单的方法就是索引更少的词。在搜索中,有些词要比其他词更重要,只索引那些更重要的词来可以大大减少索引的空间。

那么哪些词该被忽略呢?我可以粗略的将它们分为两类:

  • 低频词(Low-frequency terms)

    在文档集合中相对出现较少的词,因为它们稀少,所以它们的权重值更高。

  • 高频词(High-frequency terms)

    在索引下的文档集合中出现较多的常用词,比如 theandis。这些词的权重小,对相关度评分影响不大。

小贴士

当然,频率实际上是个可以衡量的标尺而不是非 的标签。我们可以在标尺的任何位置选取一个标准,低于这个标准的属于低频词,高于它的属于高频词。

词项到底是低频或是高频取决于它们所处的文档。单词 and 如果在所有都是中文的文档里可能是个低频词。在关于数据库的文档集合里,单词 database 可能是一个高频词项,它对搜索这个特定集合毫无帮助。

每种语言都存在一些非常常见的单词,它们对搜索没有太大价值。在 Elasticsearch 中,英语默认的听用词为:

a, an, and, are, as, at, be, but, by, for, if, in, into, is, it,
no, not, of, on, or, such, that, the, their, then, there, these,
they, this, to, was, will, with

这些 停用词 通常在索引前就可以被过滤掉,对检索丝毫没有负面影响。但这真的是个好主意吗?

版本

elasticsearch版本: elasticsearch-2.x

内容

停用词的两面(Pros and Cons of Stopwords)

与过去相比,我们现在有更多的磁盘空间,更多的内存空间,更好的压缩算法。将之前的 33 个常见词从索引中移除,每百万文档只能节省 4MB 空间。为了索引空间移除停用词不再是个好的理由。(但这句话有个反例,我们会在 停用词与短语查询(Stopwords and Phrase Queries) 讨论)

在此基础上,从索引里将这些词移除会使我们降低某种类型的搜索能力。将前面这些所列单词移除会让我们难以完成以下事情:

  • 区分 happynot happy高兴不高兴)。
  • 搜索 the 的连用 The The(注:类似 the more the better 这种连用)。
  • 查找莎士比亚的名句 “To be, or not to be” (生存还是毁灭)。
  • 使用挪威的国家代码:no

移除停用词的最主要好处是性能,假设我们在个具有上百万文档的索引中搜索单词 fox。或许 fox 只在其中 20 个文档中出现,也就是说 Elasticsearch 需要计算 20 个文档的相关度评分 _score 从而排出前十。现在我们把搜索条件改为 the OR fox,几乎所有的文件都包含这个词,也就是说 Elasticsearch 需要为所有一百万文档计算评分 _score。第二个查询肯定没有第一个的结果好。

幸运的是,我们有能让常用词查出来的技术,同时也维持较好的性能。首先,我们从如何使用停用词开始谈起。

停用词的使用(Using Stopwords)

移除停用词的工作是由 stop 停用词标记过滤器完成的,可以通过创建自定义的分析器来使用它(参见 使用停用词标记分析器(Using the stop Token Filter))。但是,也有一些自带的分析器预置使用停用词标记分析器:

  • 语言分析器(Language analyzers)

    每个语言分析器默认使用与该语言相适的停用词列表。例如,english 英语分析器使用 _english_ 停用词列表。

  • standard 标准分析器(standard analyzer)

    默认使用空的停用词列表:_none_,实际上是禁用了停用词。

  • pattern 模式分析器(pattern analyzer)

    默认使用空的停用词列表:_none_,与 standard 标准分析器一样。

停用词和标准分析器(Stopwords and the Standard Analyzer)

为了让标准分析器能与自定义停用词表连用,我们要做的只需创建一个分析器的配置好的版本,然后将停用词列表传入:

PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": { #1
"type": "standard", #2
"stopwords": [ "and", "the" ] #3
}
}
}
}
}

#1 自定义分析器,名字为 my_analyzer

#2 这个分析器是一个标准分析器,进行了一些自定义配置。

#3 停用词过滤了 andthe

小贴士

相同的技术也可以被应用于为任何语言分析器进行自定义配置。

保持位置(Maintaining Positions)

analyze API 的输出十分有意思:

GET /my_index/_analyze?analyzer=my_analyzer
The quick and the dead {
"tokens": [
{
"token": "quick",
"start_offset": 4,
"end_offset": 9,
"type": "<ALPHANUM>",
"position": 2 #1
},
{
"token": "dead",
"start_offset": 18,
"end_offset": 22,
"type": "<ALPHANUM>",
"position": 5 #2
}
]
}

#1 #2 注意每个标记的位置 position

停用词如我们期望被过滤掉了,但有趣的是两个词项的位置 position 没有变化:quick 是原句子的第二个词,dead 是第五个。这对短语查询十分重要,因为如果每个词项的位置被调整了,一个短语查询 quick dead 会与以上示例中的文档错误匹配。

指定停用词(Specifying Stopwords)

停用词可以以内联的方式传入,就像我们前面示例一样,为它指定一个数组:

"stopwords": [ "and", "the" ]

为特定语言指定默认的停用词列表可以通过 _lang_ 符号声明:

"stopwords": "_english_"

小贴士

Elasticsearch 中预定义的与语言相关的停用词列表可以在文档 停用词过滤器(stop token filter) 中找到。

停用词可以通过指定一个特殊列表 _none_ 来禁用。例如,使用 english 分析器而不使用停用词,可以通过以下方式做到:

PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_english": {
"type": "english", #1
"stopwords": "_none_" #2
}
}
}
}
}

#1 my_english 分析器是基于 english 分析器的。

#2 但是停用词被禁用了。

最后,关键词也可以使用文件,只要文件中的每个词一行。这个文件必须存于集群中的每个节点,通过参数 stopwords_path 指定读取路径:

PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_english": {
"type": "english",
"stopwords_path": "stopwords/english.txt"
}
}
}
}
}

#1 停用词文件的路径,与 Elasticsearch 配置文件路径相对

使用停用词标记过滤器(Using the stop Token Filter)

停用词标记过滤器可以与标记器和其他标记过滤器联用,只要我们创建自定义分析器。例如,我们想创建一个下面这样的西班牙语分析器:

  • 一个自定义停用词表
  • light_spanish 词干提取器
  • asciifolding 过滤器用以移除变音符号

我们可以通过以下设置完成:

PUT /my_index
{
"settings": {
"analysis": {
"filter": {
"spanish_stop": {
"type": "stop",
"stopwords": [ "si", "esta", "el", "la" ] #1
},
"light_spanish": { #2
"type": "stemmer",
"language": "light_spanish"
}
},
"analyzer": {
"my_spanish": {
"tokenizer": "spanish",
"filter": [ #3
"lowercase",
"asciifolding",
"spanish_stop",
"light_spanish"
]
}
}
}
}
}

#1 与标准分析器一样,停用词标记过滤器同时接收参数 stopwordsstopwords_path

#2 参见 算法提取器(Algorithmic Stemmers)

#3 标记过滤器的顺序非常重要,下面会进行解释。

我将 spanish_stop 过滤器置于 asciifolding 过滤器之后,这就意味着 estaéstaestá 会首先移除变音符号,变成 esta,再以停用词被移除。但如果我们只想移除 estaésta 并保留 está,我们就应该将 spanish_stop 放在 asciifolding 过滤器之前,然后在停用词表中同时指定两者。

更新停用词(Updating Stopwords)

想要更新分析器的停用词列表有多种方式,分析器是在索引创建时初始化的,也可以是在节点重启或重新开启一个已关闭索引时。

如果我们使用 stopwords 参数以内联方式指定停用词,我们只能通过关闭索引,用 API 更新分析器配置,最后再开启索引这样的方式完成更新停用词。

如果以 stopwords_path 参数的方式使用文件里的停用词,更新会较为容易。我们可以只更新每个节点上的文件,然后通过以下步骤重新强制创建分析器:

  • 关闭和重开索引(参见 索引的开与关(open/close index)

  • 一一重启集群下的每个节点。

当然,更新停用词表并不能改变已索引文档的状态。它只会应用于搜索和新建或更新的文档。为了改变已有文档,我们需要重新索引数据。参见 重新索引数据(Reindexing Your Data)

停用词及其性能(Stopwords and Performance)

保留停用词最大的缺点就是它会影响性能。当 Elasticsearch 进行全文搜索时,它需要为所有匹配的文档计算相关度评分 _score 从而返回最匹配的前 10 个文档。

通常大多数词在所有文档出现的概率不超过 0.1%,有些词(如 the)可能会在几乎所有文档出现,假设我们的索引中有一百万个文档,查询 quick brown fox 的匹配文档可能少于 1,000 个,而查询 the quick brown fox 则需要对几乎一百万个文档进行评分和排序,只是为了返回前 10 名最相关的文档!

问题在于 the quick brown fox 实际上是在查询 the OR quick OR brown OR fox,任何文档即使它什么内容都没有而只包含 the 这个词也会被包括在结果集中。因此,我们需要找到一种降低待评分文档数量的方法。

and 操作符(and Operator)

我们想要减少带评分文档的数量,最简单的方式就是在匹配查询时使用 and 操作符,这样可以让所有词都是必须的。

一个 match 查询如下:

{
"match": {
"text": {
"query": "the quick brown fox",
"operator": "and"
}
}
}

它被重写成 bool 查询如下:

{
"bool": {
"must": [
{ "term": { "text": "the" }},
{ "term": { "text": "quick" }},
{ "term": { "text": "brown" }},
{ "term": { "text": "fox" }}
]
}
}

bool 查询会智能的根据较优的顺序依次执行每个 term 查询:它会从最低频的词开始。因为所有词项都必须匹配,只要包含低频词的文档才有可能匹配。使用 and 操作符可以大大提升多词查询的速度。

最少匹配数(minimum_should_match)

控制精度(Controlling Precision) 中,我们讨论过使用 minimum_should_match 操作符去掉结果中次相关的长尾。虽然它只对这个目的奏效,但是也为我们从侧面带来一个好处,它可以提供与 and 操作符相似的性能:

{
"match": {
"text": {
"query": "the quick brown fox",
"minimum_should_match": "75%"
}
}
}

在上面这个示例中,四分之三的词都必须匹配,这意味着我们只需考虑那些包含最低频或次低频词的文档。

相比默认使用 or 操作符的简单查询,这为我们带来了巨大的性能提升。不过我们有办法可以做得更好……

分而治之(Divide and Conquer)

查询字符串中的词项可以被划分成 更重要的(低频)和 次重要的(高频)这两类。只与次重要词项匹配的文档很有可能不太相关。实际上,我们想要文档能尽可能多的匹配那些更重要的词项。

match 查询接受一个参数 cutoff_frequency,从而可以让它将查询字符串里的词项分为低频和高频两组。低频组(更重要的词项)组成 bulk 大量查询条件,而高频组(次重要的词项)只会用来评分,而不参与匹配过程。通过对这两组词的区分处理,我们可以在之前慢查询的基础上获得巨大的速度提升。

领域相关的停用词(Domain-Specific Stopwords)

cutoff_frequency 是我们使用 领域相关 停用词所带来的一个免费收益。例如,一个关于电影(movies)的网站可能会使用 movie(电影)color(色彩)black(黑)white(白) 这些我们认为几乎没有意义的词。通过使用停用词标记过滤器,这些领域相关的词会被手工加入到停用词表中,但是,由于 cutoff_frequency 会查看索引里词项的具体频率,这些词会被自动识别成 高频(high frequency)

以下面查询为例:

{
"match": {
"text": {
"query": "Quick and the dead",
"cutoff_frequency": 0.01 #1
}
}

#1 任意词项只要它在文档从出现的频率超过 1% ,我们就认为它是高频词。cutoff_frequency 可以被指定一个小数(0.01 表示百分比)或一个绝对的数字(5)。

查询首先用 cutoff_frequency 将词项分为 低频组(quickdead)和高频组(andthe)。然后,查询被重写成以下形式的 bool 查询:

{
"bool": {
"must": { #1
"bool": {
"should": [
{ "term": { "text": "quick" }},
{ "term": { "text": "dead" }}
]
}
},
"should": { #2
"bool": {
"should": [
{ "term": { "text": "and" }},
{ "term": { "text": "the" }}
]
}
}
}
}

#1 至少一个低频/重要词 必须 匹配。

#2 高频/次重要词是非必须的。

must 语句的意思是至少有一个低频词(quickdead必须 出现在被匹配文档中,其他的所有文档都会被排除。should 语句会查找高频词 andthe,但也只限于能与 must 语句匹配的集合中。should 语句的唯一工作就是在对如 “Quick and the dead” 和 “The quick but dead” 语句进行评分时,前者得分比后者高。这种方式可以大大减少需要进行评分计算的文档数量。

小贴士

将操作符参数设置成 and 会要求所有低频词都必须匹配,同时对包含所有高频词的文档给予更高评分。但是,在匹配文档时,并不要求文档必须包含所有高频词,如果希望文档包含所有的低频和高频词,我们应该使用一个 bool 来替代。正如我们在 and 操作符(and Operator) 中看到的,它的查询效率已经很高了。

控制精度(Controlling Precision)

minimum_should_match 参数可以与 cutoff_frequency 结合使用,但是它只能应用于低频词。如下:

{
"match": {
"text": {
"query": "Quick and the dead",
"cutoff_frequency": 0.01,
"minimum_should_match": "75%"
}
}

被重写为:

{
"bool": {
"must": {
"bool": {
"should": [
{ "term": { "text": "quick" }},
{ "term": { "text": "dead" }}
],
"minimum_should_match": 1 #1
}
},
"should": { #2
"bool": {
"should": [
{ "term": { "text": "and" }},
{ "term": { "text": "the" }}
]
}
}
}
}

#1 因为这里只有两个词项,原来 75% 的比例会被取整为 1,即必须匹配低频词的两者之一。

#2 高频词仍然是可选的,只在评分时使用。

只有高频词(Only High-Frequency Terms)

当使用 or 查询仅对高频词(如:“To be, or not to be”)进行查询时性能最差。只是为了返回最匹配的前十个结果就对只是包含这些词的所有文档进行评分是盲目的。我们感兴趣的只是那些所有词都同时出现的文档,所以在这种情况下,低频词并不存在,重写后查询要求所有高频词都必须匹配:

{
"bool": {
"must": [
{ "term": { "text": "to" }},
{ "term": { "text": "be" }},
{ "term": { "text": "or" }},
{ "term": { "text": "not" }},
{ "term": { "text": "to" }},
{ "term": { "text": "be" }}
]
}
}

对常用词使用更多控制(More Control with Common Terms)

尽管高频/低频的功能在 match 查询中是有用的,有时我们还希望能对它有更多的控制,想控制它对高频和低频词分组的行为。match 查询针对 common 词项查询提供了一组功能。

例如,我们可以让所有低频词都必须匹配,而只对那些包括超过 75% 的高频词文档进行评分:

{
"common": {
"text": {
"query": "Quick and the dead",
"cutoff_frequency": 0.01,
"low_freq_operator": "and",
"minimum_should_match": {
"high_freq": "75%"
}
}
}
}

更多配置项参见 common 词项查询(common terms query)

停用词与短语查询(Stopwords and Phrase Queries)

在所有查询中,5% 的查询是短语查询(参见 短语查询(Phrase Matching)),它们通常也是慢查询的最大贡献者。短语查询可能性能非常差,特别是当短语包括常用词的时候,如 “To be, or not to be” 被认为是相当不正常的。原因在于必须支持近似匹配的数据量。

停用词的两面(Pros and Cons of Stopwords) 中,我们提到移除停用词只能节省倒排索引中的一小部分空间。这句话只部分正确,一个典型的索引会可能包含部分或所有以下数据:

  • 词项字典(Terms dictionary)

    索引中所有文档内所有词项的有序列表,以及包含该词的文档数量。

  • 倒排表(Postings list)

    包含每个词项的文档(ID)列表。

  • 词频(Term frequency)

    每个词项在每个文档里出现的频率。

  • 位置(Positions)

    每个词项在每个文档里出现的位置,供短语查询或近似查询使用。

  • 偏移(Offsets)

    每个词项在每个文档里开始与结束字符的偏移,供词语高亮使用,默认是禁用的。

  • 规范因子(Norms)

    用来对字段长度进行规范化处理的因子,给较短字段予以更多权重。

将停用词从索引中移除会节省 词项字典倒排表 里的少量空间,但 位置偏移 是另一码事。位置和偏移数据很容易变成索引大小的两倍、三倍、甚至四倍。

位置信息(Positions Data)

位置在 analyzed 字符串字段默认是开启的,所以短语查询能随时使用到它。词项出现的越频繁,用来存储它位置信息的空间就越多。在一个大的文档集合中,对于那些非常常见的词,它们的位置信息可能占用成百上千兆的空间。

运行一个针对高频词( the )的短语查询可能会导致从磁盘读取好几G的数据。这些数据会被存储与文件系统的缓存核心中以提高后续访问的速度,这看似是件好事,但这可能会导致其他数据从缓存中被剔除,进一步使后续查询变慢。

这显然是我们需要解决的问题。

索引选项(Index Options)

我们首先应该问自己:到底是否需要短语查询还是近似查询?

答案通常是:不需要。在很多应用场景下,比如说日志,我们需要知道一个词是否在文档中,这个信息就在倒排表中。或许我们要对一两个字段使用短语查询,但是我们完全可以禁用其他 analyzed 字符串字段的位置信息。

index_options 参数允许我们控制索引里为每个字段存储的信息。可选值如下:

  • docs

    只存储文档及其包含词项的信息。这对 not_analyzed 字符串字段是默认的。

  • freqs

    存储 docs 信息,以及每个词在每个文档里出现的频次。词频需要完整的 TF/IDF 相关度计算,但如果只想知道一个文档是否包含某个特定词项,则无需使用它。

  • positions

    存储 docsfreqs,以及每个词项在每个文档里出现的位置。这对 analyzed 字符串字段是默认的,但当不需使用短语或近似匹配时,可以将其禁用。

  • offsets

    存储 docsfreqspositions,以及每个词在原始字符串中开始与结束字符的偏移信息。这个信息被用以高亮搜索结果,但它默认是禁用的。

我们可以在索引创建的时候为字段设置 index_options 选项,或者在使用 put-mapping API 新增字段映射的时候设置。我们无法修改已有字段的这个设置。

PUT /my_index
{
"mappings": {
"my_type": {
"properties": {
"title": { #1
"type": "string"
},
"content": { #2
"type": "string",
"index_options": "freqs"
}
}
}
}

#1 title 字段使用默认的位置设置,所以它适于短语或近似查询。

#2 content 字段的位置设置是禁用的,所以它无法用于短语或近似查询。

停用词(Stopwords)

删除停用词是能使位置信息所占空间大大降低的一种方式。一个被删除停用词的索引仍然可以被用以短语查询,因为剩下的词的原始位置仍然被保存着,这正如 保持位置(Maintaining Positions) 中看见的那样。尽管如此,将词项从索引中排除终究会降低搜索能力,这使我们难以区分 Man in the moonMan on the moon 这两个短语。

幸运的是,鱼与熊掌是可以兼得的:common_grams 标记过滤器。

common_grams 标记过滤器(common_grams Token Filter)

common_grams 标记过滤器是针对短语查询能更高效的使用停用词而设计的。它与 shingles 标记过滤器类似(参见 查找相关词(Finding Associated Words)),为每个相邻词对生成 bigrams。用示例解释更为容易。

common_grams 标记过滤器根据 query_mode 设置的不同而生成不同输出结果:false(为索引使用) 或 true(为搜索使用),所以我们必须创建两个独立的分析器:

PUT /my_index
{
"settings": {
"analysis": {
"filter": {
"index_filter": { #1
"type": "common_grams",
"common_words": "_english_" #2
},
"search_filter": { #3
"type": "common_grams",
"common_words": "_english_", #4
"query_mode": true
}
},
"analyzer": {
"index_grams": { #5
"tokenizer": "standard",
"filter": [ "lowercase", "index_filter" ]
},
"search_grams": { #6
"tokenizer": "standard",
"filter": [ "lowercase", "search_filter" ]
}
}
}
}
}

#1 #3 首先我们基于 common_grams 标记过滤器创建两个标记过滤器:index_filter 在索引时使用(此时 query_mode 的默认设置是 false),search_filter 在查询时使用(此时 query_mode 的默认设置是 true)。

#2 #4 common_words 参数可以接受与 stopwords 参数同样的选项(参见 指定停用词(Specifying Stopwords))。这个过滤器还可以接受参数 common_words_path,使用存于文件里的常用词。

#5 #6 然后我们使用过滤器各创建一个索引时分析器和查询时分析器。

有了自定义分析器,我们可以创建一个字段在索引时使用 index_grams 分析器:

PUT /my_index/_mapping/my_type
{
"properties": {
"text": {
"type": "string",
"index_analyzer": "index_grams", #1
"search_analyzer": "standard" #2
}
}
}

#1 #2 text 字段索引时使用 index_grams 分析器,但是在搜索时默认使用 standard 分析器,稍后我们会解释其原因。

索引时(At Index Time)

如果我们对短语 quick and brown fox with shingles 进行分享,它会生成如下词项:

Pos 1: the_quick
Pos 2: quick_and
Pos 3: and_brown
Pos 4: brown_fox

新的 index_grams 分析器生成以下词项:

Pos 1: the, the_quick
Pos 2: quick, quick_and
Pos 3: and, and_brown
Pos 4: brown
Pos 5: fox

所有的词项都是以 unigrams 形式输出的(thequick 等等),但是如果一个词本身是常用词或者跟随着常用词,那么它同时还会在 unigram 同样的位置以 bigram 形式输出:the_quickquick_andand_brown

Unigram 查询(Unigram Queries)

因为索引包含 unigrams ,可以使用与其他字段相同的技术进行查询,例如:

GET /my_index/_search
{
"query": {
"match": {
"text": {
"query": "the quick and brown fox",
"cutoff_frequency": 0.01
}
}
}
}

上面这个查询字符串是通过为文本字段配置的 search_analyzer 分析器(本例中使用的是标准分析器)进行分析的, 它生成的词项为:thequickandbrownfox

因为文本字段的索引中包含与标准分析去生成的一样的 unigrams,搜索对于任何普通字段都能正常工作。

Bigram 短语查询(Bigram Phrase Queries)

但是当我们使用短语查询时,我们可以用专门的 search_grams 分析器让整个过程变得更高效:

GET /my_index/_search
{
"query": {
"match_phrase": {
"text": {
"query": "The quick and brown fox",
"analyzer": "search_grams" #1
}
}
}
}

#1 对于短语查询,我们重写了默认的 search_analyzer 分析器,而使用 search_grams 分析器。

search_grams 分析器会生成以下词项:

Pos 1: the_quick
Pos 2: quick_and
Pos 3: and_brown
Pos 4: brown
Pos 5: fox

分析器剔除了所有常用词的 unigrams,只留下常用词的 bigrams 以及低频的 unigrams。如 the_quick 这样的 bigrams 比单个词项 the 更为少见,这样有两个好处:

  • the_quick 的位置信息要比 the 的小得多,所以它读取磁盘更快,对系统缓存的影响也更小。

  • 词项 the_quick 没有 the 那么常见,所以它可以大量减少需要计算的文档。

两词短语(Two-Word Phrases)

There is one further optimization. By far the majority of phrase queries consist of only two words. If one of those words happens to be a common word, such as

我们的优化可以更进一步,因为大多数的短语查询只由两个词组成,如果其中一个恰好又是常用词,例如:

GET /my_index/_search
{
"query": {
"match_phrase": {
"text": {
"query": "The quick",
"analyzer": "search_grams"
}
}
}
}

那么 search_grams 分析器会输出单个标记:the_quick。这将原来昂贵的查询(查询 thequick)转换成了对单个词项的高效查找。

停用词与相关性(Stopwords and Relevance)

在离开停用词相关内容之前,最后一个话题是关于相关性的。在索引中保留停用词会降低相关度计算的准确性,特别是当我们的文档非常长时。

正如我们已经在 词频饱和度(Term-frequency saturation) 中讨论过的,原因在于 TF/IDF 没有为词频的影响设置上限,非常常用的词可能由于文档的长度很长,而降低它们的重要性,停用词如果在单个文档中的出现频率陡增,也可能导致他们的权重值被人为刺激而变大。

可能也会考虑对较长字段使用 Okapi BM25 相似度算法,并且包括使用停用词,而不是默认的 Lucene 相似度。

参考

elastic.co: Stopwords: Performance Versus Precision