Elasticsearch全文搜索与TF/IDF

  • 2019 年 10 月 6 日
  • 筆記

一、TF/IDF

1. TF

TF:Term Frequency,即词频。它表示一个词在内容(如某文章)中出现的次数。为了消除文档本身大小的影响,通常,它的定义是:

TF = 某个词在文档中出现的次数 / 文档的总词数

也有其他表示方法,在Elasticsearch (lucene)中的使用的方法是

tf(t in d) = √frequency , 即 (某个词t在文档d中出现的次数) 的 平方跟

某个词出现越多,表示它约重要。比如某篇新闻中,“剑术”出现了5次,“电视”出现了1次,很可能这是一个剑术赛事报道。

如果这篇新闻中,“中国”和“剑术”出现的次数一样多,是不是表示两者同等重要呢?答案是否定的,因为中国这个词很常见,它难以表达文档的特性。而剑术很少见,更能表达文章的特性。

某个词越少见,就越能表达一篇文章的特性,反之则越不能。像“的”、“了”这些词,在所有文档中出现的频率都特别高,以至于失去了表达文章特性的意义。人们干脆称它们为“停用词”,直接从统计中忽略掉。

2. IDF

IDF(Inverse Document Frequency),即逆文档频率,它是一个表达词语重要性的指标。通常,它的计算方法是:

IDF=log(语料库中的文档数/(包含该词的文档数+1))

如果所有文章都包涵某个词,那个词的IDF=log(1)=0, 即重要性为零。停用词的IDF约等于0。

如果某个词只在很少的文章中出现,则IDF很大,其重要性也越高。

为了避免分母为0,所以+1.

在Elasticsearch (lucene)中的计算方法是

idf(t) = 1 + log ( numDocs / (docFreq + 1)) , 即 1 + log ( 索引中的文档总数 / (包含该词的文档数 + 1))

上述公式是文档中给的,但实际中用的是 log(1 + (docCount – docFreq + 0.5) / (docFreq + 0.5))

TF-IDF值

TF-IDF = TF X IDF

在Elasticsearch中,还有一个概念叫 字段长度的归一化,Field-Length Norm.

字段内容越短,权重越大。如果一个关键词出现在较短的字段中,比如title,就比它出现在长字段(如简介)中更能表达文章的特性。

norm(d) = 1 / √numTerms 即: 1 / 词出现次数的平方根

二、elasticsearch的全文搜索

elasticsearh的全文搜索涉及到两个重要的方面:相关性(Relevance)和分析(Analysis)

相关性(Relevance)

它是评价查询与其结果间的相关程度,并根据这种相关程度对结果排名的一种能力,这种计算方式可以是 TF/IDF 方法(参见 相关性的介绍)、地理位置邻近、模糊相似,或其他的某些算法。本文只介绍TF/IDF方法。

TF/IDF 相关性方法分析

做一次搜索,带explain,elasticsearch会返回如何匹配。比如在title字段中进行全文搜索,关键词为'python'

GET course/_search?explain  {    "query": {      "multi_match" : {        "query":    "python",        "fields": [ "title" ]      }    },"size":10  }

返回内容中,第一条匹配的结果如下

{          "_shard": "[course][0]",          ...          "_score": 6.1884723,          "_source": {            "title": "Python 语句",            ...          },          "_explanation": {            "value": 6.1884723,            "description": "weight(title:python in 1363) [PerFieldSimilarity], result of:",            "details": [              {                "value": 6.1884723,                "description": "score(doc=1363,freq=1.0 = termFreq=1.0n), product of:",                "details": [                  {                    "value": 4.4812255,                    "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",                    "details": [                      {                        "value": 17,                        "description": "docFreq",                        "details": []                      },                      {                        "value": 1545,                        "description": "docCount",                        "details": []                      }                    ]                  },                  {                    "value": 1.3809776,                    "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",                    "details": [                      {                        "value": 1,                        "description": "termFreq=1.0",                        "details": []                      },                      {                        "value": 1.2,                        "description": "parameter k1",                        "details": []                      },                      {                        "value": 0.75,                        "description": "parameter b",                        "details": []                      },                      {                        "value": 7.861489,                        "description": "avgFieldLength",                        "details": []                      },                      {                        "value": 2.56,                        "description": "fieldLength",                        "details": []                      }                    ]                  }                ]              }            ]          }        }

解释

"value": 4.4812255,  "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",

上面是求idf值的值。idf=ln(1 + (docCount – docFreq + 0.5) / (docFreq + 0.5))=ln(1+(1545-17+0.5)/(17+0.5)) = ln(88.34)=4.4812255

//返回内容里用了log(以10为底的对数), 实际是ln (以e为底的对数)

"value": 1.3809776,  "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",

上面是求tfNorm(归一化后的TF)的值。根据描述tfNorm = (freq * (k1 + 1)) / (freq + k1 * (1 – b + b * fieldLength / avgFieldLength))

其中termFreq=1,k1=1.2, b=0.75, avgFieldLength=7.861489, fieldLength=2.56。

为什么要用这样的公式,以及k1和b的值是怎么来的,我也不清楚。

计算最终结果,tfNorm=1.38.

"value": 6.1884723,  "description": "score(doc=1363,freq=1.0 = termFreq=1.0n), product of:",

最终得分 TF-IDF = TF * IDF =4.4812255 * 1.3809776 = 6.1884723