搜索排序算法之BM25

        BM25属于bag-of-word(词袋)模型, 是用来计算某一个目标文档(Document)相对于一个查询关键字(Query)的“相关性”(Relevance)的流程。BM25认为:词频和相关性之间的关系是非线性的,具体来说,每一个词对于文档相关性的分数不会超过一个特定的阈值,当词出现的次数达到一个阈值后,其影响不再线性增长,而这个阈值会跟文档本身有关。达到的效果是,某一个单词对最后分数的贡献不会随着词频的增加而无限增加。

算法按照以下3部分来构建

  • 单词 q i q_i qi与文档 D D D之间的相关性
  • 单词 q i q_i qi与query之间的相关性
  • 每个单词 q i q_i qi的权重

单词和目标文档的相关性
        tf-idf中,单词和文档的相关性用“词频”表示(关于 t f tf tf i d f idf idf的计算可以参考文章文本特征提取之TF-IDF),BM25对词频进行了标准化处理,公式如下:

s c o r e ( t f q i , D ) = t f t d ∗ ( k 1 + 1 ) t f t d + k 1 ∗ ( 1 − b + b ∗ ∣ D ∣ a v g d l ) score(tf_{q_i, D})=\frac{tf_{td}*(k_1+1)}{tf_{td}+k_1*(1-b+b*\frac{|D|}{avgdl})} score(tfqi,D)=tftd+k1(1b+bavgdlD)tftd(k1+1)
其中, q i q_i qi为query中的第 i i i个词, t f ( q i , D ) tf(q_i, D) tf(qi,D)为目标文档中 q i q_i qi的词频, ∣ D ∣ |D| D是文档D的长度, a v g d l avgdl avgdl是语料库全部文档的平均长度, k 1 k_1 k1 b b b是参数。

单词和query的相关性

s c o r e ( t f q i , Q ) = ( k 2 + 1 ) ∗ t f t q k 2 + t f t q score(tf_{q_i, Q}) = \frac{(k_2+1)*tf_{tq}}{k_2+tf_{tq}} score(tfqi,Q)=k2+tftq(k2+1)tftq
其中, t f t q tf_{tq} tftq是词项t在查询q中的权重,从上面的公式中可以看到,当词频无限增大时, s c o r e ( t f q i , D ) = k 2 + 1 score(tf_{q_i, D})=k_2+1 score(tfqi,D)=k2+1

单词权重
        单词的权重最简单的就是用idf值或是其变种,我们用 i d f ( q i ) idf(q_i) idf(qi)表示单词 q i q_i qi的权重。

合起来公式就是

s c o r e ( D , Q ) = ∑ i = 1 n i d f ( q i ) ∗ t f t d ∗ ( k 1 + 1 ) t f t d + k 1 ∗ ( 1 − b + b ∗ ∣ D ∣ a v g d l ) ∗ ( k 2 + 1 ) ∗ t f t q k 2 + t f t q score(D,Q)=\sum_{i=1}^nidf(q_i)*\frac{tf_{td}*(k_1+1)}{tf_{td}+k_1*(1-b+b*\frac{|D|}{avgdl})} * \frac{(k_2+1)*tf_{tq}}{k_2+tf_{tq}} score(D,Q)=i=1nidf(qi)tftd+k1(1b+bavgdlD)tftd(k1+1)k2+tftq(k2+1)tftq

BM25 在 TF-IDF 的基础上增加了两个可调参数: k 1 k_1 k1 b b b,分别代表 “词语频率饱和度(term frequency saturation)” 和 “字段长度规约(Field-length normalization)

  • 词语频率饱和度(term frequency saturation): k 1 k_1 k1参数的值一般介于 1.2 到 2.0 之间。数值越低则饱和的过程越快速。如果 k 1 k_1 k1 取 0,则对应 BIM 模型,document term frequency完全没有影响,如果 k 1 k_1 k1 取较大值,对应使用原始的 term frequency
  • 字段长度归约(Field-length normalization): 将文档的长度归约化到全部文档的平均长度上。b=1 表示基于文档长度对 term frequency 进行完全的缩放,b=0 表示归一化时不考虑文档长度因素,长文档更有可能排在前面如果查询很长,
  • 对于 query term 也可以采用类似的权重计算方法。对查询长度没有进行归一化(相当于b=0)。 k 2 = 0 k_2=0 k2=0 表示 term frequency in query 并没有影响,(apple apple pie) 和 (apple pie) 完全一样。

参考文档

可插拔的相似度计算
经典检索算法
搜索中的权重度量利器: TF-IDF和BM25

要使用 Java 实现多字段 BM25F 排序算法,可以按照以下步骤进行,首先需要了解 BM25F 是对 BM25 算法的扩展,用于处理具有结构化字段的文档,它会单独计算每个字段的 BM25 分数,然后使用加权和等方法合并分数得到文档的最终相关性分数[^1]。 以下是一个简单的 Java 示例实现: ```java import java.util.HashMap; import java.util.List; import java.util.Map; // 文档类 class Document { private String id; private Map<String, String> fields; public Document(String id, Map<String, String> fields) { this.id = id; this.fields = fields; } public String getId() { return id; } public Map<String, String> getFields() { return fields; } } // BM25F 算法实现类 class BM25F { private static final double k1 = 1.2; private static final double b = 0.75; private Map<String, Double> fieldWeights; public BM25F(Map<String, Double> fieldWeights) { this.fieldWeights = fieldWeights; } // 计算单个字段的 BM25 分数 private double bm25Score(String fieldText, String queryTerm, double avgFieldLength) { int termFrequency = countTermFrequency(fieldText, queryTerm); double fieldLength = fieldText.length(); double numerator = termFrequency * (k1 + 1); double denominator = termFrequency + k1 * (1 - b + b * (fieldLength / avgFieldLength)); return numerator / denominator; } // 计算文档的 BM25F 分数 public double bm25fScore(Document doc, String queryTerm, Map<String, Double> avgFieldLengths) { double score = 0; Map<String, String> fields = doc.getFields(); for (Map.Entry<String, String> fieldEntry : fields.entrySet()) { String fieldName = fieldEntry.getKey(); String fieldText = fieldEntry.getValue(); double fieldWeight = fieldWeights.getOrDefault(fieldName, 1.0); double avgLength = avgFieldLengths.getOrDefault(fieldName, 1.0); score += fieldWeight * bm25Score(fieldText, queryTerm, avgLength); } return score; } // 统计术语在文本中出现的频率 private int countTermFrequency(String text, String term) { int count = 0; int index = 0; while ((index = text.indexOf(term, index)) != -1) { count++; index += term.length(); } return count; } } // 主类,用于测试 public class BM25FExample { public static void main(String[] args) { // 初始化文档 Map<String, String> fields1 = new HashMap<>(); fields1.put("title", "Java Programming"); fields1.put("content", "Java is a popular programming language."); Document doc1 = new Document("1", fields1); Map<String, String> fields2 = new HashMap<>(); fields2.put("title", "Python Programming"); fields2.put("content", "Python is also a great programming language."); Document doc2 = new Document("2", fields2); // 初始化字段权重 Map<String, Double> fieldWeights = new HashMap<>(); fieldWeights.put("title", 2.0); fieldWeights.put("content", 1.0); // 初始化平均字段长度 Map<String, Double> avgFieldLengths = new HashMap<>(); avgFieldLengths.put("title", 10.0); avgFieldLengths.put("content", 20.0); // 创建 BM25F 实例 BM25F bm25f = new BM25F(fieldWeights); // 计算文档的 BM25F 分数 String queryTerm = "programming"; double score1 = bm25f.bm25fScore(doc1, queryTerm, avgFieldLengths); double score2 = bm25f.bm25fScore(doc2, queryTerm, avgFieldLengths); System.out.println("Document 1 score: " + score1); System.out.println("Document 2 score: " + score2); } } ``` 在这个示例中,`Document` 类表示一个文档,包含文档的 ID 和各个字段的内容。`BM25F` 类实现了 BM25F 算法,其中 `bm25Score` 方法计算单个字段的 BM25 分数,`bm25fScore` 方法计算文档的 BM25F 分数。`countTermFrequency` 方法用于统计术语在文本中出现的频率。在 `main` 方法中,我们初始化了两个文档,设置了字段权重和平均字段长度,创建了 `BM25F` 实例,并计算了文档的 BM25F 分数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值