BM25算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BM25算法相关的知识,希望对你有一定的参考价值。

参考技术A

bm25 是一种用来评价搜索词和文档之间相关性的算法,它是一种基于 概率检索模型 提出的算法,再用简单的话来描述下bm25算法:我们有一个query和一批文档Ds,现在要计算query和每篇文档D之间的相关性分数,我们的做法是,先对query进行切分,得到单词,然后单词的分数由3部分组成:

最后对于每个单词的分数我们做一个求和,就得到了query和文档之间的分数。

讲bm25之前,我们要先介绍一些概念。

BIM(binary independence model)是为了对文档和query相关性评价而提出的算法,BIM为了计算,引入了两个基本假设:

假设一:二元假设

所谓二元假设,类似于布尔模型的表示方法,一篇文章在由特征表示的时候,以特征“出现”和“不出现”两种情况来表示,也可以理解为相关不相关。

假设二:词汇独立性假设

所谓独立性假设,是指文档里出现的单词之间没有任何关联,任一个单词在文章中的分布率不依赖于另一个单词是否出现,这个假设明显与事实不符,但是为了简化计算,很多地方需要做出独立性假设,这种假设是普遍的。

在以上两个假设的前提下,二元独立模型即可以对两个因子P(D|R)和P(D|NR)进行估算(条件概率),举个简单的例子,文档D中五个单词的出现情况如下:1,0,1,0,1 0表示不出现,1表示出现。用Pi表示第i个单词在相关文档中出现的概率,在已知相关文档集合的情况下,观察到文档D的概率为:

对于因子P(D|NR),我们假设用Si表示第i个单词在在不相关文档集合中出现的概率,于是在已知不相关文档集合的情况下,观察到文档D的概率为:

于是我们可以得到下面的估算

可以将各个因子规划为两个部分,一部分是在文档D中出现的各个单词的概率乘积,另一部分是没在文档D中出现的各个单词的概率乘积,于是公式可以理解为下面的形式

对公式进行一下等价的变换,可得:

为了方便计算,对上述公式两边取log,得到:

那么如何估算概率Si和Pi呢,如果给定用户查询,我们能确定哪些文档集合构成了相关文档集合,哪些文档构成了不相关文档集合,那么就可以用如下的数据对概率进行估算:

根据上表可以计算出Pi和Si的概率估值,为了避免log(0),对估值公式进行平滑操作,分子+0.5,分母+1.0

代入估值公式得到:

这个公式代表的含义就是,对于同时出现在查询Q和文档D中的单词,累加每个单词的估值结果就是文档D和查询Q的相关性度量,在预先不知道哪些文档相关哪些文档不相关的情况下,可以使用固定值代替,这种情况下该公式等价于向量空间模型(VSM)中的IDF因子,实际证明该模型的实际使用结果不好,但是它是BM25模型的基础。
后来人们发现应该讲BIM中没有考虑到的词频和文档长度等因素都考虑进来,就有了后面的BM25算法。

BIM(二元假设模型)对于 单词特征 ,只考虑单词是否在 doc中出现过 ,并没有考虑 单词本身的相关特征 ,BM25在BIM的基础上引入单词在查询中的权值,单词在doc中的权值,以及一些经验参数,所以BM25在实际应用中效果要远远好于BIM模型。

BM25由3部分组成:

在第二部分中K因子代表了文档长度的考虑,dl是文档的长度,avdl是文档的平均长度,k1和b是调整参数,b为0时即不考虑文档长度的影响,经验表明b=0.75左右效果比较好。但是也要根据相应的场景进行调整。b越大对文档长度的惩罚越大,k1因子用于调整词频,极限情况下k1=0,则第二部分退化成1,及词频特征失效,可以证明k1越大词频的作用越大。

在我们不知道哪些文档相关,哪些文档不相关的情况下,将相关文档数R及包含查询词相关文档数r设为0,那么第一部分的BIM公式退化成:

就是IDF因子的定义,N是总文档数,n是查询词的tf信息,0.5是平滑因子。

BM算法详解

BM算法详解

在用于查找子字符串的算法当中,BM(Boyer-Moore)算法匹配还是非常高效的,一般情况下,比KMP算法快好几倍。

注意: BM算法在移动模式串的时候是从左到右,而进行比较的时候是从右到左的。

BM算法实际上包含两个并行的算法,坏字符算法和好后缀算法。这两种算法的目的就是让模式串每次向右移动尽可能大的距离(j+=x,x尽可能的大)。
BM其实包含两种算法:

  • 坏字符算法

  • 好后缀算法

坏字符算法

有下面两种情况

  • 坏字符没出现在模式串中,这时把模式串移动到坏字符的下一个字符,继续比较,如下图,字符’c’和字符’a’没有匹配上,字符’c’就坏字符,但坏字符’c’根本没有在模式串中,所以把模式串移到’c’字符后面继续匹配:

  • 坏字符在模式串中, BM算法向右移动模式串, 让模式串中最靠右的对应字符与坏字符相对,然后继续匹配,如下图:

计算坏字符的方法

为了用代码来描述上述的两种情况,设计一个数组bmBc[‘k’],表示坏字符‘k’在模式串中出现的位置距离模式串末尾的最大长度,那么当遇到坏字符的时候,模式串可以移动距离为: shift(坏字符) = bmBc[T[i]]-(m-1-i)。shift(坏字符) 有可能是负数,这时候模式串可能会出现回退的情况,如下图:


计算坏字符的代码实现

#define ASIZE 256
void preBmBc(char *x, int m, int bmBc[]) 

     int i;
     for (i = 0; i < ASIZE; ++i)
         bmBc[i] = m;
     for (i = 0; i < m - 1; ++i)
       bmBc[x[i]] = m - i - 1;

对上面的代码简单的说明一下,ASIZE位256,表示了所有的字符,然后初始化所有所有字符在模式串总的未知位模式串的长度,最后从特征串后面往前遍历,计算特征串所有包含的字符里特征串尾部的长度。

好后缀算法

为了实现好后缀算法,需要定义一个数组suffix[],其中suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度,如下图所示,用公式可以描述:满足P[i-s, i] == P[m-s, m]的最大长度s,m为特征串的长度。

好后缀代码实现:

void suffixes(char *pattern, int pattern_len, int suffix[])

    int j;
    suffix[m-1] = m;
    for (i = pattern_len-2; i>=0; --i)
    
        j = i;
        while(j >= 0&&pattern[j] == p[m-1 - (i - j)])
        
            --j;
        
        suffix[i] = i - j;
    


void preBmGs(char *pattern, int pattern_len, int bmGs[]) 
     int i, j, suffix[XSIZE];
     suffixes(pattern, pattern_len, suffix);
     //都初始化为找不子串匹配到好后缀的
     for (i = 0; i < pattern_len; ++i)
        bmGs[i] = pattern_len; 
     j = 0;
     //最大前缀
     for (i = pattern_len - 1; i >= 0; --i)
        if (suff[i] == i + 1)
           for (; j < pattern_len - 1 - i; ++j)
             if (bmGs[j] == pattern_len)
                bmGs[j] = pattern_len - 1 - i;
    //模式串中有子串匹配上好后缀
    for (i = 0; i <= pattern_len - 2; ++i)
       bmGs[pattern_len - 1 - suff[i]] = pattern_len - 1 - i;

通过坏字符和好后缀算法,计算出了bmBc[]和bmGs[],查找匹配的时候取两着中大的跳转值,移动模式串。

以上是关于BM25算法的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch实用BM25 -第2部分:BM25算法及其变量

BM25算法解析

BM25算法语义相似度计算

文本相似度-bm25算法原理及实现

BM25算法研究和实例

bm25