[关闭]
@evilking 2017-10-15T10:20:38.000000Z 字数 3246 阅读 2233

NLP

BM25算法

BM25算法,通常用作搜索相关性评分,计算某个 Query 与某篇文档的相似度。

我们在提取自动摘要时,需要计算句子与文本的相似度,从而能得出一个重要度的排名,排名靠前的即为摘要语句;其中计算句子与文本的相似度,就可以用BM25算法来进行处理.

BM25算法原理

核心思想 : 对 Query 进行语素解析,生成语素 ;然后对于每个搜索结果 ,计算每个语素 的相关性得分,最后将 相对于 的相关性得分进行加权求和,从而得到 Query 与 D 的相关性得分.

Query 为一次查询,比如用户在浏览器上输入的一句查询语句.

语素是最小的语音、语义结体,是最小的有意义的语言单位;在中文处理中,通常对 Query 的分词作为语素分析,分词后的每个词即表示一个语素.


BM25算法的一般性公式为 :

其中 :


下面考察如何来定义 。判断一个词与一个文档的相关度的权重,方法有很多,较常用的是 IDF,所以下面以 IDF 为例来说明 :

其中, 为索引中的全部文档数, 为包含了 的文档数.

于是根据 IDF 的定义可知,对于给定的文档集合,包含了 的文档数越多, 的权重则越低。

也即是说,当很多文档都包含 时, 的区分度就不高,因此使用 来判断相关性的重要度就不高.


再来考察语素 与文档 的相关性得分 :

其中 :

由于绝大部分情况下, 在 Query 中只会出现一次,即 ,所以公式可以简化为 :


的定义可以看出,参数 的作用是调整文档长度对相关性影响的大小。 越大,文档长度的相关性得分的影响越大,反之越小。

而文档的相对长度越长, 值将越大,则相关性得分会越小。这可以理解为,当文档较长时,包含 的机会越大,因此,同等 的情况下,长文档与 的相关性应该比短文档与 的相关性弱。

综上,BM25算法的相关性得分公式总结为 :

从BM25的公式可以看出,通过使用不同的语素分析方法、语素权重判定方法,以及语素与文档的相关性判定方法,我们可以衍生出不同的搜索相关性得分计算方法,这就为我们设计算法提供了较大的灵活性.


举例说明

假设现在有文档集为 :

  1. 我爱吃苹果
  2. 苹果是我最爱吃的水果
  3. 香蕉我也爱吃

查询语句为 :

  1. 香蕉和苹果

分别对文档集合查询语句做分词,得到语素单元 :

  1. 苹果
  2. 苹果 水果
  3. 香蕉 爱吃
  4. 香蕉 苹果

根据 IDF 的计算公式,则有 :

则,
其中,


java 源码分析

这里的BM25实现是用的是上篇自动摘要的工程代码,下载地址为 : https://github.com/hankcs/TextRank

我们之间从代码开始分析:

  1. //传入文档句子列表,每个句子又是单词列表
  2. public BM25(List<List<String>> docs){
  3. this.docs = docs;
  4. //计算句子总数
  5. D = docs.size();
  6. for (List<String> sentence : docs){
  7. avgdl += sentence.size();
  8. }
  9. //计算所有文档的平均句子长度
  10. avgdl /= D;
  11. f = new Map[D];
  12. df = new TreeMap<String, Integer>();
  13. idf = new TreeMap<String, Double>();
  14. init();
  15. }
  16. //计算单词的词频、IDF值
  17. private void init(){
  18. int index = 0;
  19. for (List<String> sentence : docs){
  20. Map<String, Integer> tf = new TreeMap<String, Integer>();
  21. for (String word : sentence){
  22. Integer freq = tf.get(word);
  23. freq = (freq == null ? 0 : freq) + 1;
  24. tf.put(word, freq);
  25. }
  26. f[index] = tf;
  27. for (Map.Entry<String, Integer> entry : tf.entrySet()){
  28. String word = entry.getKey();
  29. Integer freq = df.get(word);
  30. //使用平滑处理
  31. freq = (freq == null ? 0 : freq) + 1;
  32. df.put(word, freq);
  33. }
  34. ++index;
  35. }
  36. for (Map.Entry<String, Integer> entry : df.entrySet()){
  37. String word = entry.getKey();
  38. Integer freq = entry.getValue();
  39. //计算idf,对数线性处理
  40. idf.put(word, Math.log(D - freq + 0.5) - Math.log(freq + 0.5));
  41. }
  42. }

初始化做了几步,计算了句子长度,平均句子长度,以及各个单词的词频、 idf值.

  1. public double sim(List<String> sentence, int index){
  2. double score = 0;
  3. for (String word : sentence){
  4. if (!f[index].containsKey(word)) continue;
  5. int d = docs.get(index).size();
  6. Integer wf = f[index].get(word);
  7. //此即为上述最后的BM25算法计算公式
  8. score += (idf.get(word) * wf * (k1 + 1)
  9. / (wf + k1 * (1 - b + b * d/ avgdl)));
  10. }
  11. return score;
  12. }

此算法相对还比较简单,读者可以自己仿照实现 R版本的BM25算法.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注