BM25 文本相似度算法_Little Coder的博客-程序员秘密_bm25算法

技术标签: NLP  

BM25, 下一代的TF-IDF

新版的lucence不再把TF-IDF作为默认的相关性算法,而是采用了BM25(BM是Best Matching的意思)。BM25是基于TF-IDF并做了改进的算法。

BM25算法,通常用来作搜索相关性评分。

一句话概况其主要思想:对Query进行语素解析,生成语素qi;然后,对于每个搜索结果D,计算每个语素qi与D的相关性得分,最后,将qi相对于D的相关性得分进行加权求和,从而得到Query与D的相关性得分。

传统TF-IDF vs. BM25

传统的TF-IDF是自然语言搜索的一个基础理论,它符合信息论中的熵的计算原理,虽然作者在刚提出它时并不知道与信息熵有什么关系,但你观察IDF公式会发现,它与熵的公式是类似的。实际上IDF就是一个特定条件下关键词概率分布的交叉熵。

BM25在传统TF-IDF的基础上增加了几个可调节的参数,使得它在应用上更佳灵活和强大,具有较高的实用性。

BM25同样使用词频,逆文档频率以及字段长度归一化,但是每个因子的定义都有细微差别

  • TF-IDF没有考虑词频上限的问题,因为高频停用词已经被移除了)

  • BM25 有一个上限,文档里出现5-10次的词会比那些只出现一两次的对相关度有显著影响),参见词频饱和度图:
    在这里插入图片描述

BM25算法的一般性公式如下:
在这里插入图片描述

  • Q表示Query
  • qi表示Q解析之后的一个语素(对中文而言,我们可以把对Query的分词作为语素分析,每个词看成语素qi。)
  • d表示一个搜索结果文档;
  • Wi表示语素qi的权重;
  • R(qi,d)表示语素qi与文档d的相关性得分。
Wi

下面我们来看如何定义Wi。判断一个词与一个文档的相关性的权重,方法有多种,较常用的是IDF。这里以IDF为例,公式如下:
在这里插入图片描述

  • N为索引中的全部文档数(可以理解为一篇文章的所有句子数)
  • n(qi)为包含了qi的文档数(可以理解为word出现在了几个句子中的句子数量)
    hanlp中代码实现:
Map<String, Double> idf = new TreeMap<String, Double>();
idf.put(word, Math.log(D - freq + 0.5) - Math.log(freq + 0.5));

根据IDF的定义可以看出,对于给定的文档集合,包含了qi的文档数越多,qi的权重则越低。也就是说,当很多文档都包含了qi时,qi的区分度就不高,因此使用qi来判断相关性时的重要度就较低。

R(qi,d)

语素qi与文档d的相关性得分R(qi,d)。首先来看BM25中相关性得分的一般形式:
在这里插入图片描述

  • k1,k2,b为调节因子,通常根据经验设置,一般k1=2,b=0.75;
  • fi为qi在d中的出现频率;(可以理解为每个word在文章中的词频)
  • qfi为qi在Query中的出现频率;(可以理解为每个word在那句话中的词频)
  • dl为文档d的长度;
  • avgdl为所有文档的平均长度。

不像 TF/IDF ,BM25 有一个比较好的特性就是它提供了两个可调参数:

  • k1
    这个参数控制着词频结果在词频饱和度中的上升速度。默认值为 1.2 。值越小饱和度变化越快,值越大饱和度变化越慢。
  • b
    这个参数控制着字段长归一值所起的作用, 0.0 会禁用归一化, 1.0 会启用完全归一化。默认值为 0.75 。

由于绝大部分情况下,qi在Query中只会出现一次,即qfi=1,因此公式可以简化为:
在这里插入图片描述
从K的定义中可以看到,参数b的作用是调整文档长度对相关性影响的大小。b越大,K值越小,文档长度的对相关性得分的影响越大,反之越小。而文档的相对长度越长,K值将越大,则相关性得分会越小。这可以理解为,当文档较长时,包含qi的机会越大,因此,同等fi的情况下,长文档与qi的相关性应该比短文档与qi的相关性弱。

综上,BM25算法的相关性得分公式可总结为:
在这里插入图片描述
hanlp中的计算代码:

score =  (idf.get(word) * tf * (k1 + 1)  / (tf + k1 * (1 - b + b * d  / avgdl)))

附录HanLP的BM25 算法的java实现代码 :

public class BM25
{
    /**
     * 文档句子的个数
     */
    int D;

    /**
     * 文档句子的平均长度
     */
    double avgdl;

    /**
     * 拆分为[句子[单词]]形式的文档
     */
    List<List<String>> docs;

    /**
     * 文档中每个句子中的每个词与词频
     */
    Map<String, Integer>[] f;

    /**
     * 文档中全部词语与出现在几个句子中
     */
    Map<String, Integer> df;

    /**
     * IDF
     */
    Map<String, Double> idf;

    /**
     * 调节因子
     */
    final static float k1 = 1.5f;

    /**
     * 调节因子
     */
    final static float b = 0.75f;

    public BM25(List<List<String>> docs)
    {
        this.docs = docs;
        D = docs.size();
        for (List<String> sentence : docs)
        {
            avgdl += sentence.size();
        }
        avgdl /= D;
        f = new Map[D];
        df = new TreeMap<String, Integer>();
        idf = new TreeMap<String, Double>();
        init();
    }

    /**
     * 在构造时初始化自己的所有参数
     */
    private void init()
    {
        int index = 0;
        for (List<String> sentence : docs)
        {
            Map<String, Integer> tf = new TreeMap<String, Integer>();
            for (String word : sentence)
            {
                Integer freq = tf.get(word);
                freq = (freq == null ? 0 : freq) + 1;
                tf.put(word, freq);
            }
            f[index] = tf;
            for (Map.Entry<String, Integer> entry : tf.entrySet())
            {
                String word = entry.getKey();
                Integer freq = df.get(word);
                freq = (freq == null ? 0 : freq) + 1;
                df.put(word, freq);
            }
            ++index;
        }
        for (Map.Entry<String, Integer> entry : df.entrySet())
        {
            String word = entry.getKey();
            Integer freq = entry.getValue();
            idf.put(word, Math.log(D - freq + 0.5) - Math.log(freq + 0.5));
        }
    }

    /**
     * 计算一个句子与一个文档的BM25相似度
     *
     * @param sentence 句子(查询语句)
     * @param index    文档(用语料库中的下标表示)
     * @return BM25 score
     */
    public double sim(List<String> sentence, int index)
    {
        double score = 0;
        for (String word : sentence)
        {
            if (!f[index].containsKey(word)) continue;
            int d = docs.get(index).size();
            Integer tf = f[index].get(word);
            score += (idf.get(word) * tf * (k1 + 1)
                    / (tf + k1 * (1 - b + b * d
                                                / avgdl)));
        }

        return score;
    }

    public double[] simAll(List<String> sentence)
    {
        double[] scores = new double[D];
        for (int i = 0; i < D; ++i)
        {
            scores[i] = sim(sentence, i);
        }
        return scores;
    }
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_20989105/article/details/95460003

智能推荐

混凝土弹塑性损伤本构模型在Abaqus中vumat子程序的实现_CAE320的博客-程序员秘密

一。混凝土弹塑性本构混凝土的受力非线性行为同时包含微裂缝(微缺陷)和塑性流动这两种微观机制的影响,导致混凝土材料具有以如下显著特征:1)峰值应力后存在不稳定区域并伴随明显的刚度退化和强度软化;2)加卸载时的滞回特性:变形超过定的阀值后,混凝土完全卸载后存在着不可恢复变形;3)有侧限(如双轴受压应力状态)时材料的强度和延性明显增大;4)由于拉应力的影响,二维拉压应力下混凝...

Android和PHP开发学习笔记3.1_叔叔有糖吃的博客-程序员秘密

1.PHP实际项目中的应用主要有:a) 用于后台脚本编程,即以命令行(CLI)的方式进行。b) 用于网络应用编程,即以mod_php或者fastCGI的方式执行。(LAMP框架) 2.PHP是一门“弱类型”语言,null,‘’,0,false还有空数组,在php中都是可以直接相等的。当时用全等号可以区分他们:echo "null==0:";var_dump(null==

JS强制刷新页面、清除缓存刷新_weixin_30808693的博客-程序员秘密

清理网站缓存的几种方法meta方法&lt;meta http-equiv="pragma" content="no-cache"&gt;&lt;meta http-equiv="Cache-Control" content="no-cache, must-revalidate"&gt;&lt;meta http-equiv="expires" content="0"&...

IMX6Q GPIO定义_weixin_34211761的博客-程序员秘密

ret = gpio_request_array(mx6q_sabresd_flexcan_gpios,                        ARRAY_SIZE(mx6q_sabresd_flexcan_gpios));static struct gpio mx6q_sabresd_flexcan_gpios[] = {        { SABRESD_CAN1_STBY, ...

.Net MVC中log4net 使用入门(一)_weixin_30814223的博客-程序员秘密

  一.介绍    log4net库是Apache log4j框架在Microsoft .NET平台的实现,是一个帮助程序员将日志信息输出到各种目标(控制台、文件、数据库等)的工具。  二.寻找log4net    1.百度搜索log4net    2.点击进入        3.点击Download    4.点击log4net-2.0.8-bin-newke...

随便推点

Java对redis的五种values的操作API_七妹要奈斯的博客-程序员秘密

五种value类型:String,List,Hash,Set,ZSet(有序集)package com.realrainy.oa;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Set;import redis.clients.jedis.Jedis;impo...

用Python可以解决的数学问题,探究代数、统计、几何、概率等_人邮异步社区的博客-程序员秘密

我们将编写程序,把数字和公式作为输入,进行一些计算,然后得到解或绘制出图形。其中一些程序能提供强大的计算功能来解决一些数学问题。这些程序能求出方程的解,计算数据集之间的相关性,确定函数的最大值,等等。在其他程序中,我们将模拟现实生活中的事件,如抛物运动、掷硬币或掷骰子。使用程序来模拟这样的事件,让我们可以用一个简单的方法来更好地分析和了解事情本身。也许你会发现一些不借助计算机程序会非常难于探索...

Android应用逆向——分析反编译代码之大神器_安卓小小鸟的博客-程序员秘密

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/CharlesSimonyi/article/details/52027563  如果说使用dex2jar和JD-GUI获得了一个APP反编译后的JAVA代码,再结合smali代码调试器来进行调试还不够爽,不够畅快的话,下面将介绍一个帮助分析代码执行流程的大神器。这个神器优点很多,不过遗憾的是它有一个致命...

gulp打包时报错 Error: ENOENT: no such file or directory, chmod 'E:\Practice\myWorld\build\img\bg'_gulp no such file_宋大王的博客-程序员秘密

gulp打包时报错 Error: ENOENT: no such file or directory, chmod 'E:\Practice\myWorld\build\img\bg'gulp任务顺序文件目录gulpfile.jsconsolegulp任务顺序task clean 在打包之前,先清空build文件夹gulp.task('clean', d =&gt; { if (chec...

用AudioBuffer里的数据得到音量_Glassy_sky1207的博客-程序员秘密

使用AudioQueue录音时,回调方法中可以得到一个AudioBufferList类型的数据,每个frame里有inNumberFrames个数据,有inNumberChannels个channel。inNumberFrames*inNumberChannels的结果,就是AudioBufferList其中一个buffer里的数据量。AudioBufferList里的buffer里面的数据,...

Yum安装LAMP(Centos7.2+Apache2.4+Mariadb5.5.56+PHP7.0.24)_weixin_34044273的博客-程序员秘密

一、简介最近客户提出需要使用PHP7的需求,第一次是给客户安装的是LNMP-full的集成环境,但是后面不便于添加扩展模块,以及本人对Nginx不是很了解,经协商后改用LAMP,以下内容为真实环境搭建完成后为了方便记忆在虚拟环境中的配置,和真是环境基本一样二、准备环境操作系统IP地址相关软件包Centos7.210.0.0.11php70w-common php70w...