BM25 算法索引建立流程和检索流程总结
BM25(Best Matching 25)是一种基于词频和逆文档频率的排名函数,用于衡量文档与查询之间的相关性。在全文检索系统中,BM25 被广泛应用于搜索引擎、信息检索等领域。
您提供的代码实现了一个简单的 BM25 模型,包括索引建立和检索过程。以下将基于该代码,详细总结 BM25 的索引建立流程和检索时的流程。
一、BM25 索引建立流程
BM25 的索引建立主要包括以下步骤:
1. 文本预处理
-
分词(Tokenization):将文档和查询文本分割成单独的词语或词项。代码中,
docs
是已经分词后的文档列表,每个文档是一个词汇列表。 -
可选的预处理步骤(代码中未体现):
- 去除停用词(Stop Words Removal):如 “的”、“是”、“了” 等常用但信息量低的词语。
- 词干提取或词形还原:将词语还原为基本形式,如将 “running” 还原为 “run”。
2. 统计词频和文档频率
-
词频(Term Frequency,TF):
- 文档词频:对于每个文档,统计其中每个词语出现的次数。
- 在代码中,通过
Counter(doc)
统计每个文档的词频,结果存储在self.doc_freqs
中。
-
文档频率(Document Frequency,DF):
- 词项文档频率:统计每个词语出现在多少个不同的文档中。
- 在代码中,通过遍历每个文档的词汇集合(
set(doc)
),更新每个词的文档频率df
。
3. 计算逆文档频率(IDF)
-
逆文档频率(Inverse Document Frequency,IDF):
- 公式:
idf
(
w
)
=
ln
(
N
−
df
(
w
)
+
0.5
df
(
w
)
+
0.5
+
1
)
\text{idf}(w) = \ln\left(\frac{N - \text{df}(w) + 0.5}{\text{df}(w) + 0.5} + 1\right)
idf(w)=ln(df(w)+0.5N−df(w)+0.5+1)
- N N N:文档总数。
- df ( w ) \text{df}(w) df(w):包含词语 w w w 的文档数量。
- IDF 反映了词语的辨识度,词频越低,IDF 值越高,说明该词对区分文档越有用。
- 公式:
idf
(
w
)
=
ln
(
N
−
df
(
w
)
+
0.5
df
(
w
)
+
0.5
+
1
)
\text{idf}(w) = \ln\left(\frac{N - \text{df}(w) + 0.5}{\text{df}(w) + 0.5} + 1\right)
idf(w)=ln(df(w)+0.5N−df(w)+0.5+1)
-
在代码中,计算每个词的 IDF 值并存储在
self.idf
中。
4. 计算文档长度和平均文档长度
-
文档长度(Document Length):
- 计算每个文档的长度,即词语的总数。
- 在代码中,
self.doc_len
存储了每个文档的长度。
-
平均文档长度(Average Document Length):
- 计算所有文档长度的平均值。
- 在代码中,
self.avgdl
表示平均文档长度。
5. 建立索引结构
-
存储必要的数据结构:
- 词典(Vocabulary):所有出现过的词语集合。
- 倒排索引(Inverted Index):在 BM25 的实现中,虽然未显式构建倒排索引,但通过词频和文档频率的统计,可以快速检索包含查询词语的文档及其相关统计信息。
-
在实际应用中,通常会建立倒排索引,以便高效地检索和计算相关性。
二、BM25 检索时的流程
在检索阶段,BM25 算法根据查询计算每个文档与查询的相关性得分,并排序返回相关文档。具体流程如下:
1. 查询预处理
-
分词:将用户的查询文本分割成词语列表。
- 在代码中,查询
query
是已经分词后的词语列表。
- 在代码中,查询
-
可选的预处理步骤(代码中未体现):
- 去除停用词:过滤掉无意义的常用词。
- 词干提取或词形还原:标准化词语形式。
2. 计算 BM25 相关性得分
对于每个文档,计算其与查询的 BM25 得分:
-
初始化得分:
score = 0.0
-
遍历查询词语:
-
对于查询中的每个词
word
,执行以下操作:-
检查词是否在文档中:
- 如果
word
在文档doc
中(即word in self.doc_freqs[doc]
),则继续计算。
- 如果
-
获取词频:
- 获取词
word
在文档doc
中的出现次数freq
。
- 获取词
-
获取词的 IDF 值:
- 从预先计算的
self.idf
中获取word
的 IDF 值。
- 从预先计算的
-
计算 BM25 部分得分:
- 使用 BM25 公式计算该词对文档得分的贡献:
[
\text{score} += \text{idf}[w] \times \frac{\text{freq} \times (k1 + 1)}{\text{freq} + k1 \times (1 - b + b \times \frac{\text{dl}}{\text{avgdl}})}
]-
freq
\text{freq}
freq:词
word
在文档中的频率。 - dl \text{dl} dl:文档长度(词语总数)。
- avgdl \text{avgdl} avgdl:平均文档长度。
- k 1 k1 k1 和 b b b:BM25 的调节参数。
-
freq
\text{freq}
freq:词
- 使用 BM25 公式计算该词对文档得分的贡献:
-
-
-
累加得分:将每个查询词的得分累加,得到文档与查询的总相关性得分。
3. 排序和返回结果
-
汇总得分:对所有文档计算与查询的 BM25 得分,形成一个文档得分列表。
-
排序:根据得分从高到低排序文档,得分越高,文档与查询的相关性越强。
-
返回结果:根据排序结果,返回最相关的文档列表给用户。
三、BM25 算法的核心公式
BM25 的核心计算公式如下:
[
\text{score}(D, Q) = \sum_{w \in Q} \text{idf}(w) \times \frac{\text{tf}(w, D) \times (k1 + 1)}{\text{tf}(w, D) + k1 \times \left(1 - b + b \times \frac{\text{dl}}{\text{avgdl}}\right)}
]
- score ( D , Q ) \text{score}(D, Q) score(D,Q):文档 D D D 与查询 Q Q Q 的相关性得分。
- idf ( w ) \text{idf}(w) idf(w):词语 w w w 的逆文档频率。
- tf ( w , D ) \text{tf}(w, D) tf(w,D):词语 w w w 在文档 D D D 中的词频。
- k 1 k1 k1、 b b b:调节参数,通常取 k 1 = 1.5 k1 = 1.5 k1=1.5, b = 0.75 b = 0.75 b=0.75。
- dl \text{dl} dl:文档 D D D 的长度。
- avgdl \text{avgdl} avgdl:所有文档的平均长度。
四、代码中的具体实现
1. 初始化和索引建立
def __init__(self, docs, k1=1.5, b=0.75):
self.docs = docs
self.k1 = k1
self.b = b
self.doc_len = [len(doc) for doc in docs] # 计算每个文档的长度
self.avgdl = sum(self.doc_len) / len(docs) # 计算平均文档长度
self.doc_freqs = [] # 存储每个文档的词频
self.idf = {} # 存储每个词的逆文档频率
self.initialize()
- 计算文档长度和平均文档长度。
- 初始化词频统计和 IDF 字典。
def initialize(self):
df = {} # 词项文档频率
for doc in self.docs:
# 统计每个文档的词频
self.doc_freqs.append(Counter(doc))
# 更新词项的文档频率
for word in set(doc):
df[word] = df.get(word, 0) + 1
# 计算每个词的 IDF 值
for word, freq in df.items():
self.idf[word] = math.log((len(self.docs) - freq + 0.5) / (freq + 0.5) + 1)
- 遍历文档,统计词频和文档频率。
- 计算并存储每个词的 IDF 值。
2. 计算 BM25 得分
def score(self, doc, query):
score = 0.0
for word in query:
if word in self.doc_freqs[doc]:
freq = self.doc_freqs[doc][word] # 词在文档中的频率
# 应用 BM25 公式计算得分
score += (self.idf[word] * freq * (self.k1 + 1)) / (
freq + self.k1 * (1 - self.b + self.b * self.doc_len[doc] / self.avgdl)
)
return score
- 遍历查询词语,计算每个词对文档得分的贡献。
- 累加得分,得到文档与查询的总相关性得分。
五、示例说明
1. 示例文档集
docs = [
["the", "quick", "brown", "fox"],
["the", "lazy", "dog"],
["the", "quick", "dog"],
["the", "quick", "brown", "brown", "fox"]
]
- 四个文档,已经分词。
2. 查询
query = ["quick", "brown"]
- 查询包含两个词:“quick” 和 “brown”。
3. 计算得分
# 初始化 BM25 模型
bm25 = BM25(docs)
# 计算每个文档与查询的得分
scores = [bm25.score(i, query) for i in range(len(docs))]
4. 得分结果
scores = [1.0192447810666774, 0.0, 0.3919504878447609, 1.2045355839511414]
- 解释:
- 第 0 个文档得分约为 1.0192
- 第 1 个文档得分为 0.0(因为不包含查询词语)
- 第 2 个文档得分约为 0.3920
- 第 3 个文档得分约为 1.2045(得分最高,最相关)
六、BM25 的优势与注意事项
优势
- 考虑了词频和文档长度:BM25 平衡了词频和文档长度的影响,避免了长文档得分过高或过低的问题。
- 简单高效:计算公式相对简单,易于实现和优化。
- 适用性广泛:在各种信息检索任务中表现良好。
注意事项
- 参数调节: k 1 k1 k1 和 b b b 参数的选择会影响模型性能,通常需要根据实际数据进行调优。
- 预处理质量:文本预处理(如分词、去除停用词)对结果有较大影响,需确保预处理的合理性。
- 索引结构:在大型数据集上,需构建高效的索引结构(如倒排索引)以支持快速检索。
七、总结
-
索引建立流程:
- 文本预处理:分词、去除停用词等。
- 统计词频和文档频率:计算每个词在文档中的词频和在所有文档中的文档频率。
- 计算 IDF 值:根据文档频率计算每个词的逆文档频率。
- 计算文档长度和平均文档长度。
- 构建索引结构:存储词频、文档频率、IDF 等必要信息,方便检索时快速访问。
-
检索时的流程:
- 查询预处理:对查询进行分词等预处理。
- 计算 BM25 得分:对于每个文档,计算其与查询的相关性得分。
- 排序:根据得分对文档进行排序,得分越高,相关性越强。
- 返回结果:向用户展示排序后的文档列表。
通过上述流程,BM25 可以有效地衡量文档与查询之间的相关性,为信息检索提供可靠的排序机制。
参考:https://blog.csdn.net/weixin_40959890/article/details/138121459